some feedback on crystal after first learning it

237 views
Skip to first unread message

Roger Pack

unread,
Oct 19, 2016, 9:18:09 PM10/19/16
to Crystal
After using crystal for my first project (worked about flawlessly), I did have some feedbacks "from your end users" as it were, sorry to post them all together, but here they are (just for discussion):


It seems odd you can't do assignment into block vars, like:

[['a', 'b'], ['c', 'd']].each{|first, second|
 ...
}


next:

It's also almost odd that "casting' ints is done with

0.to_int64

versus
0.as(Int64)

(when the latter is used for everything else).

next:

It would be nice if crystal had a "-E" option (like gcc) to show what all the macros expand to (the "full output" as it were).

next:

It would be nice to have some tracer option that output each method call with its arguments so you could see what's going on.

next:

It seems a bit odd to have the nil class named Nil (it can confuse newbies like myself, who accidentally use it in place of nil when first starting to learn the language, since some other languages use Nil as nil).


next:

Strings: the default "#{object}" is similar to java's, like this: #<Edl:0x111fcd7e0>
this is almost useless.  Suggest default be the .inspect output?


next:
also it's frustrating you can't do this:

" abc" + obj
so then you try
"abc" + obj.to_s
and that fails *too* by default
but this works:
"abc#{obj}"
the multiple to_s methods causes confusion here, it would be nice for .to_s to behave similarly to "#{obj}" since it feels to me like both should use some default to_s method.

That's it for me, awesome language (possibly best I've run into so far), and I plan on continuing to use it.

Thanks!
-roger-

carlosgajardo

unread,
Oct 20, 2016, 10:42:10 AM10/20/16
to Crystal
It seems odd you can't do assignment into block vars, like:
[['a', 'b'], ['c', 'd']].each { |first, second| ... }

With Ruby you can do

[['a', 'b'], ['c', 'd'], ['e']].each do |first, second|
  puts first.ord, second.ord
end

but that fails

97
98
99
100
tmp/repl.cr:6:in `block in <main>': undefined method `ord' for nil:NilClass (NoMethodError)
from tmp/repl.cr:5:in `each'
from tmp/repl.cr:5:in `<main>'

Crystal allows you to check in compile-time those errors. And if you are sure all elements will be a pair of chars you can use a Tuple like this:

[{'a', 'b'}, {'c', 'd'], {'e', 'f'}].each do |first, second|
  puts first.ord, second.ord
end

That will compile, and **work without errors** with Crystal

It's also almost odd that "casting' ints is done with

0.to_int64

versus
  
0.as(Int64)

(when the latter is used for everything else).

You have some missconceptions here:
  1.  The as pseudo-method doesn't transform an object; is just a promise you give to the compiler with something it can't know, but you, as human, can.
  2. Number literals, like `0`, are initialized as Int32, so if you "promise" a Int32 will be a Int64, as you do with `0.as(Int64)`, it will fail, because you are basically lying. If you know in advance you want a Int64 you can use the "typed number literals" like `0_i64`, `1324_u64`, etc.
  3. When you want a type to be re-instantiated as other type you need a `to_something` method, that will create a whole new object. So doing `0.to_i64` is the same of doing `Int64.new(0)` it is just a new object, there is no cast involved.

It would be nice if crystal had a "-E" option (like gcc) to show what all the macros expand to (the "full output" as it were).
It would be nice to have some tracer option that output each method call with its arguments so you could see what's going on.

Yes, +1
Meanwhile, for -E, you can use `{% debug() %}` inside you macros and it will print the expanded macro to the console.


It seems a bit odd to have the nil class named Nil 

It may be, but the compiler with catch that, and UpperCamelCase naming for classes/structs/modules/etc. is already explicit enough for me...
I wouldn't like using `String | NilClass` instead of `String | Nil`


also it's frustrating you can't do this:

" abc" + obj
so then you try
"abc" + obj.to_s
and that fails *too* by default
but this works:
"abc#{obj}"

I don't know what you mean, `"abc" + obj.to_s` works when I try it, I think you are overriding `to_s` instead of `to_s(io)` read the section "Don't create intermediate strings when writing to an IO" of this guide for farther information


That's it for me, awesome language (possibly best I've run into so far), and I plan on continuing to use it.
Thanks you for your good attitude! Please continue making Crystal's community nice and awesome!

TR NS

unread,
Oct 20, 2016, 4:14:06 PM10/20/16
to Crystal


On Thursday, October 20, 2016 at 10:42:10 AM UTC-4, carlosgajardo wrote:

It's also almost odd that "casting' ints is done with

0.to_int64

versus
  
0.as(Int64)

(when the latter is used for everything else).

You have some missconceptions here:
  1.  The as pseudo-method doesn't transform an object; is just a promise you give to the compiler with something it can't know, but you, as human, can.
  2. Number literals, like `0`, are initialized as Int32, so if you "promise" a Int32 will be a Int64, as you do with `0.as(Int64)`, it will fail, because you are basically lying. If you know in advance you want a Int64 you can use the "typed number literals" like `0_i64`, `1324_u64`, etc.
  3. When you want a type to be re-instantiated as other type you need a `to_something` method, that will create a whole new object. So doing `0.to_i64` is the same of doing `Int64.new(0)` it is just a new object, there is no cast involved.
Still it would be nice if casting were `x.to(Int64)`

Chris Hobbs

unread,
Oct 20, 2016, 4:29:29 PM10/20/16
to crysta...@googlegroups.com

That would actually be quite doable I think. Might be worth experimenting with and discussing.

--
You received this message because you are subscribed to the Google Groups "Crystal" group.
To unsubscribe from this group and stop receiving emails from it, send an email to crystal-lang...@googlegroups.com.
To post to this group, send email to crysta...@googlegroups.com.
Visit this group at https://groups.google.com/group/crystal-lang.
To view this discussion on the web visit https://groups.google.com/d/msgid/crystal-lang/5d83fa06-6d06-4c84-b6d0-8e79d33d87e5%40googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Doug Everly

unread,
Oct 20, 2016, 4:54:29 PM10/20/16
to Crystal
Somethign like this:

struct Int32
    def to(t : Int32.class)
    self
  end
  
  def to(t : Int64.class)
    self.to_i64
  end

  def to(t : String.class)
    self.to_s
  end
end


a = 13_i32

a.to(Int64)
a.to(String)
a.to(Int32)


Luis Lavena

unread,
Oct 20, 2016, 5:00:43 PM10/20/16
to Crystal
Hello,

Not native english speaker here, but for me "to" implies some sort of conversion/transformation while "as" implies treatment of that instance (aka: casting)

"0".to_u64 # Converts "0" to 64bits unsigned integer
dog.as(Animal) # Treat Dog instance as it were an Animal instance (which will not allow me to call #bark method, given me a compiler error)

--
Luis Lavena

Chris Hobbs

unread,
Oct 20, 2016, 5:16:31 PM10/20/16
to crysta...@googlegroups.com

I think we need to clear up some terminology here. Confusingly, #as is a “cast” operator, as well is Int32#to_i64. I was thinking about the possibility that #to_s and #to_i32 could be replaced with #to(String) and #to(Int32), not modifying #as at all.

--
You received this message because you are subscribed to the Google Groups "Crystal" group.
To unsubscribe from this group and stop receiving emails from it, send an email to crystal-lang...@googlegroups.com.
To post to this group, send email to crysta...@googlegroups.com.
Visit this group at https://groups.google.com/group/crystal-lang.

Tim Uckun

unread,
Oct 20, 2016, 5:45:49 PM10/20/16
to crysta...@googlegroups.com
On the one hand the method call seem more natural 1.to(String) or even 1.as(String) both seem like they belong in the language and are easy to understand. On the other hand it's more typing and as a general rule the more verbose the language gets the less I enjoy using it. 

I guess it's a tradeoff between readability and writabitlity (is that a word?)

On Fri, Oct 21, 2016 at 10:16 AM, Chris Hobbs <ch...@rx14.co.uk> wrote:

I think we need to clear up some terminology here. Confusingly, #as is a “cast” operator, as well is Int32#to_i64. I was thinking about the possibility that #to_s and #to_i32 could be replaced with #to(String) and #to(Int32), not modifying #as at all.

On 20/10/16 22:00, Luis Lavena wrote:

Hello,

On Thursday, October 20, 2016 at 5:14:06 PM UTC-3, TR NS wrote:


On Thursday, October 20, 2016 at 10:42:10 AM UTC-4, carlosgajardo wrote:

It's also almost odd that "casting' ints is done with

0.to_int64

versus
  
0.as(Int64)

(when the latter is used for everything else).

You have some missconceptions here:
  1.  The as pseudo-method doesn't transform an object; is just a promise you give to the compiler with something it can't know, but you, as human, can.
  2. Number literals, like `0`, are initialized as Int32, so if you "promise" a Int32 will be a Int64, as you do with `0.as(Int64)`, it will fail, because you are basically lying. If you know in advance you want a Int64 you can use the "typed number literals" like `0_i64`, `1324_u64`, etc.
  3. When you want a type to be re-instantiated as other type you need a `to_something` method, that will create a whole new object. So doing `0.to_i64` is the same of doing `Int64.new(0)` it is just a new object, there is no cast involved.
Still it would be nice if casting were `x.to(Int64)`


Not native english speaker here, but for me "to" implies some sort of conversion/transformation while "as" implies treatment of that instance (aka: casting)

"0".to_u64 # Converts "0" to 64bits unsigned integer
dog.as(Animal) # Treat Dog instance as it were an Animal instance (which will not allow me to call #bark method, given me a compiler error)

--
Luis Lavena
--
You received this message because you are subscribed to the Google Groups "Crystal" group.
To unsubscribe from this group and stop receiving emails from it, send an email to crystal-lang+unsubscribe@googlegroups.com.

--
You received this message because you are subscribed to the Google Groups "Crystal" group.
To unsubscribe from this group and stop receiving emails from it, send an email to crystal-lang+unsubscribe@googlegroups.com.

To post to this group, send email to crysta...@googlegroups.com.
Visit this group at https://groups.google.com/group/crystal-lang.

Chris Hobbs

unread,
Oct 20, 2016, 5:57:04 PM10/20/16
to crysta...@googlegroups.com

The word you’re looking for is writability.

I think that specifying the full type to convert to makes a lot of sense. I don’t find myself using to_foo often enough to be worried about a small amount of extra typing such as that, and I think the readability gains are wroth it. I’ve said before that I’m not a fan of the “more typing” complaint in general because I spend much more time thinking about and reading code than writing it.

It would be nice to try it out on a large project to see how it feels.

To unsubscribe from this group and stop receiving emails from it, send an email to crystal-lang...@googlegroups.com.

To post to this group, send email to crysta...@googlegroups.com.
Visit this group at https://groups.google.com/group/crystal-lang.

Doug Everly

unread,
Oct 20, 2016, 5:58:42 PM10/20/16
to Crystal
I initially chose to use #to instead of #as to avoid this error: 'as' is a pseudo-method and can't be redefined

That said, there actually room for both. There is a notable difference between a cast and conversion. Casting will modify how a set chunk of memory will be interpreted, whereas a conversion may return a completely new object of perhaps an unrelated type.

# cast
class Dog < Animal; end
d = Dog.new
d.as(Animal)

vs

# conversion
32.to_s # => "32" # unrelated type
#or
32.to(String) # using above proposed => "32"


Luis Lavena

unread,
Oct 20, 2016, 6:01:59 PM10/20/16
to Crystal
On Thursday, October 20, 2016 at 6:16:31 PM UTC-3, RX14 wrote:

I think we need to clear up some terminology here. Confusingly, #as is a “cast” operator, as well is Int32#to_i64. I was thinking about the possibility that #to_s and #to_i32 could be replaced with #to(String) and #to(Int32), not modifying #as at all.


Are you sure about that statement? to_i64 is not casting but actually doing type conversion. For what is worth, neither "as" is a cast operator:

https://crystal-lang.org/docs/syntax_and_semantics/as.html

Adding "to" methods with all the possible overloads might be a bit verbose, specially when doing things like JSON or MsgPack and output to an specific IO:

io = MemoryIO.new
dog.to(JSON, io)        # vs. dog.to_json(io)
dog.to(MessagePack, io) # vs. dog.to_msgpack(io)

--
Luis Lavena

Doug Everly

unread,
Oct 20, 2016, 6:07:48 PM10/20/16
to Crystal
Having

dog.to(JSON, io)
dog.to(MessagePack, io)

does enable a nice "hash-like" behavior enabling consistent method call and eliminates if/else statements, which is kinda nice:

def foo(t : (JSON.class | MessagePack.class))
  dog.to(t, io)
end

Roger Pack

unread,
Oct 20, 2016, 6:12:22 PM10/20/16
to crysta...@googlegroups.com
On Thu, Oct 20, 2016 at 8:42 AM, carlosgajardo <cjga...@gmail.com> wrote:
>> It seems odd you can't do assignment into block vars, like:
>> [['a', 'b'], ['c', 'd']].each { |first, second| ... }
>
>
> With Ruby you can do
>
> [['a', 'b'], ['c', 'd'], ['e']].each do |first, second|
> puts first.ord, second.ord
> end
>
> but that fails
>
> 97
> 98
> 99
> 100
> tmp/repl.cr:6:in `block in <main>': undefined method `ord' for nil:NilClass
> (NoMethodError)
> from tmp/repl.cr:5:in `each'
> from tmp/repl.cr:5:in `<main>'
>
> Crystal allows you to check in compile-time those errors. And if you are
> sure all elements will be a pair of chars you can use a Tuple like this:
>
> [{'a', 'b'}, {'c', 'd'], {'e', 'f'}].each do |first, second|
> puts first.ord, second.ord
> end
>
> That will compile, and **work without errors** with Crystal

Interesting. OK I like tuples, my road to reach this was:

[['a', 'b'], ['c', 'd'], ['e']].each do |args| first, seconds = args
# still throws index out of bounds exception...

but I'll use tuples now :)

>> It's also almost odd that "casting' ints is done with
>>
>> 0.to_int64
>>
>> versus
>>
>>
>>
>> 0.as(Int64)
>>
>> (when the latter is used for everything else).
>
>
> You have some missconceptions here:
>
> The as pseudo-method doesn't transform an object; is just a promise you
> give to the compiler with something it can't know, but you, as human, can.
> Number literals, like `0`, are initialized as Int32, so if you "promise" a
> Int32 will be a Int64, as you do with `0.as(Int64)`, it will fail, because
> you are basically lying. If you know in advance you want a Int64 you can use
> the "typed number literals" like `0_i64`, `1324_u64`, etc.
> When you want a type to be re-instantiated as other type you need a
> `to_something` method, that will create a whole new object. So doing
> `0.to_i64` is the same of doing `Int64.new(0)` it is just a new object,
> there is no cast involved.

I guess it just confused me because "as" doesn't seem to *not* imply
conversion in my head somehow...can't think of anything better
unfortunately, because "as" kind of implies a non conversion.
Tricky.
Clearer might be dog.is(Animal) but also more confusing somehow LOL.

I kind of like Doug Everly's proposed 0.to(Int64) to keep parity with
the "as" being a method with parameters, as it were.

>
>> It would be nice if crystal had a "-E" option (like gcc) to show what all
>> the macros expand to (the "full output" as it were).
>> It would be nice to have some tracer option that output each method call
>> with its arguments so you could see what's going on.
>
>
> Yes, +1
> Meanwhile, for -E, you can use `{% debug() %}` inside you macros and it will
> print the expanded macro to the console.

good to know thank you.

>
>> It seems a bit odd to have the nil class named Nil
>
>
> It may be, but the compiler with catch that, and UpperCamelCase naming for
> classes/structs/modules/etc. is already explicit enough for me...
> I wouldn't like using `String | NilClass` instead of `String | Nil`

unfortunately the compiler wasn't smart enough to catch people like me
who do this:

def go()
return Nil
end

if go()
... expect to never get here...but get here every time.
end

unfortunately you're right about it being more verbose as well...I
guess it may remain as a wart for new users to bump into

>
>
>> also it's frustrating you can't do this:
>>
>> " abc" + obj
>> so then you try
>> "abc" + obj.to_s
>> and that fails *too* by default
>> but this works:
>> "abc#{obj}"
>
>
> I don't know what you mean, `"abc" + obj.to_s` works when I try it, I think
> you are overriding `to_s` instead of `to_s(io)` read the section "Don't
> create intermediate strings when writing to an IO" of this guide for farther
> information

I haven't overridden any "to_s" this is just a "new class" as it were
(I would expect crystal to generate some kind of default to_s
anyway...).

$ cat test.cr
class Url
end

puts "" + Url.new
$ crystal test.cr
Error in ./test.cr:6: no overload matches 'String#+' with type Url
Overloads are:
- String#+(other : self)
- String#+(char : Char)

Thank you!
-roger-

Roger Pack

unread,
Oct 20, 2016, 6:20:40 PM10/20/16
to crysta...@googlegroups.com
> On 20/10/16 22:45, Tim Uckun wrote:
>
> On the one hand the method call seem more natural 1.to(String) or even
> 1.as(String) both seem like they belong in the language and are easy to
> understand. On the other hand it's more typing and as a general rule the
> more verbose the language gets the less I enjoy using it.

I agree it "feels" more crystal-zy (and less like "magic") than
.to_int64 etc. somehow...FWIW.


> I guess it's a tradeoff between readability and writabitlity (is that a
> word?)

Typically you'd want to err toward readability IMO :)
-roger-

>
> On Fri, Oct 21, 2016 at 10:16 AM, Chris Hobbs <ch...@rx14.co.uk> wrote:
>>
>> I think we need to clear up some terminology here. Confusingly, #as is a
>> “cast” operator, as well is Int32#to_i64. I was thinking about the
>> possibility that #to_s and #to_i32 could be replaced with #to(String) and
>> #to(Int32), not modifying #as at all.

Maybe could rename that operator to a "convert" operator or something... :)

Luis Lavena

unread,
Oct 20, 2016, 6:47:03 PM10/20/16
to Crystal

To add a bit more about this, it might be a good interface also with mappings that instantiate objects:

cat = Animal.from(JSON, %({"type":"cat"})) # vs. Animal.from_json(...)

But it might get *waaaay* too verbose when dealing with nested classes like DB (DB::ResultSet):

cats = Cat.from(DB::ResultSet, rs) # vs. Cat.from_rs(rs)

--
Luis Lavena

carlosgajardo

unread,
Oct 20, 2016, 8:53:00 PM10/20/16
to Crystal
`as` is not a "cast", at least as known in other languages...
class Animal
  def species
    "undefined"
  end
end

class Dog < Animal
  def species
    "Canis lupus"
  end
end

puts Dog.new.as(Animal).species

What do you expect that to return? `undefined` or `Canis lupus` ?? 
It prints "Canis lupus"

Objects won't be directly casted with `as`. (It can be "casted" with pointers, but that's obscure, don't do that, don't be bad people xD)

Use `as` just as a compile-time assertion. (unless you are reaaaally sure 
of what you are doing)

carlosgajardo

unread,
Oct 20, 2016, 9:11:06 PM10/20/16
to Crystal
As for to(Foo), even it is pretty, I don't trust that people won't implement a generic transformation
def to(t : Object.class)
  t.new self
end
which is worse than awful

So, finally, to_foo is more than enough to me. Less "Ruby and Crystal differences" to learn and safer

TR NS

unread,
Oct 20, 2016, 9:59:44 PM10/20/16
to Crystal


On Thursday, October 20, 2016 at 9:11:06 PM UTC-4, carlosgajardo wrote:
As for to(Foo), even it is pretty, I don't trust that people won't implement a generic transformation
def to(t : Object.class)
  t.new self
end
which is worse than awful


They won't. That's the kind of thing someone thinks once, "Oh, cool I can do..." then ten minutes later forever more thinks, "well, actually that's kind of stupid."

TR NS

unread,
Oct 20, 2016, 10:08:29 PM10/20/16
to Crystal


On Thursday, October 20, 2016 at 6:47:03 PM UTC-4, Luis Lavena wrote:

To add a bit more about this, it might be a good interface also with mappings that instantiate objects:

cat = Animal.from(JSON, %({"type":"cat"})) # vs. Animal.from_json(...)

But it might get *waaaay* too verbose when dealing with nested classes like DB (DB::ResultSet):

cats = Cat.from(DB::ResultSet, rs) # vs. Cat.from_rs(rs)


 Personally that doesn't look too bad to me, maybe if it were

cats = Cat.from(PelicanReefService::Database::ResultSet, rs)

But nothing is stopping people from creating there own shortcuts if the feel they need to tighten up their code. They could define their own #from_rs method if they wanted, or even just `RS = DB::ResultSet`. And really that's better because do we really prefer for APIs to heavily pollute the method name space?

Tim Uckun

unread,
Oct 20, 2016, 10:11:06 PM10/20/16
to crysta...@googlegroups.com
dog.to(JSON, io)        # vs. dog.to_json(io)

I don't see anything wrong with having both. Either way it's clear it's a method call.

But how about this.

JSON(io) or Integer(10) or  Float(x)  that seems OK too.

Luis Lavena

unread,
Oct 21, 2016, 12:01:30 AM10/21/16
to Crystal

If I'm not mistaken is one of the things Crystal creators want to avoid: having class-like methods names. Similar to one of the goals of not having aliases (applies to this and the above comment).

Cheers,
--
Luis Lavena
 

Tim Uckun

unread,
Oct 21, 2016, 1:06:09 AM10/21/16
to crysta...@googlegroups.com

>As for to(Foo), even it is pretty, I don't trust that people won't implement a generic transformation

This doesn't bother me at all. In fact suppose there was a  magic class method called initialize. This would be like the normal initialize but called when Class(blah) was called.

This could be used to do conversions or casting so for example Integer("5")  would translate call the initialize class method with the string signature.  This would allow you to create classes which could be cast to other classes just by overloading that function.

Of course it doesn't need to be called initialize, I am using that as an example.

Roger Pack

unread,
Oct 21, 2016, 5:01:33 PM10/21/16
to crysta...@googlegroups.com
On Thu, Oct 20, 2016 at 7:11 PM, carlosgajardo <cjga...@gmail.com> wrote:
> As for to(Foo), even it is pretty, I don't trust that people won't implement
> a generic transformation
>
> def to(t : Object.class)
> t.new self
> end
>
> which is worse than awful

Does the compiler even allow that?

This fails anyway:


class A

def to(t : Object.class)
t.new self
end

end

puts A.new.to(Int32)


in /usr/local/Cellar/crystal-lang/0.19.2/src/int.cr:614: undefined
method 'to_i32' for A

value.to_i32

But I'm not sure where it's getting the to_i32 from some magic
conversion rule or something here?

>
> So, finally, to_foo is more than enough to me. Less "Ruby and Crystal
> differences" to learn and safer
>
> --
> You received this message because you are subscribed to a topic in the
> Google Groups "Crystal" group.
> To unsubscribe from this topic, visit
> https://groups.google.com/d/topic/crystal-lang/kkbE8jfBldM/unsubscribe.
> To unsubscribe from this group and all its topics, send an email to
> crystal-lang...@googlegroups.com.
> To post to this group, send email to crysta...@googlegroups.com.
> Visit this group at https://groups.google.com/group/crystal-lang.
> To view this discussion on the web visit
> https://groups.google.com/d/msgid/crystal-lang/166ecab7-6add-49d8-9b97-96db69f5841c%40googlegroups.com.

carlosgajardo

unread,
Oct 21, 2016, 5:33:28 PM10/21/16
to Crystal
Roger,

Does the compiler even allow that? This fails anyway

that's the problem, it does not always fails, and the result will be ambiguous. Look at this

class Object
  def to(t)
    t.new self
  end
end

arr = [65_u8, 66_u8, 67_u8]

puts arr.to_s                   #=> [65, 66, 67]
puts arr.to_unsafe.to_s         #=> Pointer(UInt8)@0x2592fd0
puts arr.to_unsafe.to(String)   #=> ABC


But I'm not sure where it's getting the to_i32 from some magic conversion rule or something here? 


# Returns an `Int32` by invoking `to_i32` on *value*.
def self.new(value)
  value.to_i32
end

  Greets

TR NS

unread,
Oct 21, 2016, 5:39:53 PM10/21/16
to Crystal


On Friday, October 21, 2016 at 5:01:33 PM UTC-4, Roger Pack wrote:
Does the compiler even allow that?

This fails anyway:


class A

def to(t : Object.class)
  t.new self
end

end

puts A.new.to(Int32)

 I imagine, Int32.new, seeing that an instance of A is not anything it knows how to convert, must try to see if it has an to_i32 method.

Message has been deleted
Message has been deleted

Roger Pack

unread,
Oct 25, 2016, 1:34:58 PM10/25/16
to Crystal


On Friday, October 21, 2016 at 3:33:28 PM UTC-6, carlosgajardo wrote:
Roger,

Does the compiler even allow that? This fails anyway

that's the problem, it does not always fails, and the result will be ambiguous. Look at this

class Object
  def to(t)
    t.new self
  end
end

arr = [65_u8, 66_u8, 67_u8]

puts arr.to_s                   #=> [65, 66, 67]
puts arr.to_unsafe.to_s         #=> Pointer(UInt8)@0x2592fd0
puts arr.to_unsafe.to(String)   #=> ABC


Oh OK you're saying that because to(String) calls String.new on the input, the usage of ".to" is unstable, because who knows what #new will do, right...
However, it is guaranteed to return the right type, I don't see too much problem there...it seems the problem here is mostly because of the use of to_unsafe, and possibly expected (what would you expected .to_unsafe.to(String) to do?)
 

But I'm not sure where it's getting the to_i32 from some magic conversion rule or something here? 

It is the definition of Int32.new itself, no magic

https://github.com/crystal-lang/crystal/blob/7f82f79bd1e3a7a1a73607e35c0662906736f1f8/src/int.cr#L607

# Returns an `Int32` by invoking `to_i32` on *value*.
def self.new(value)
  value.to_i32
end


The "to_i32" still feels a bit magic here.
I agree we should still have to_i and to_f for ruby compatibility though (thankfully they're waaay more sane than their ruby equivalents and you can't do 'a'.to_i).
maybe it could be to_i
to_i
to_f(Int64)
to_f
to_f(Float64)

Though I'm not in love with it somehow.  I suppose there could be a #to_n or #to_num

to_n(Uint8)

For instance, just today I learned there are
to_u
and
to_u8
methods...it's like...somehow as a developer I must have memorized these shortcuts instead of just looking up the class names like UInt8 and knowing I can use those.
Anyway just some food for thought, just trying to decrease the learning curve for people just learning the language.  The current way works OK once you learn it, you just "have to learn it" as it were :|
-roger-

Roger Pack

unread,
Oct 25, 2016, 1:41:51 PM10/25/16
to Crystal
In retrospect, it seems like my kind of gripe is there are these methods

#to_f64

and then these classes Float64

somehow those two don't map to each other in my head, it's like "wait what?" I know it's trying to keep ruby-like symantics of to_i and to_f ...
but it's like "I have to memorize internally how to convert from Float64 to a method named to_f64" it's not a big deal, but still it's more to memorize/learn.

maybe if there is a failure message of "cannot assign Float64 to value x" the error message could say "have you considered #to_f64"

or perhaps if there is a generic error message "wrong class" it could also spit out all methods on that object "would give you" (return) a Float64 (thus generating a better error message for *all* cases).

Cheers!

TR NS

unread,
Oct 25, 2016, 3:36:02 PM10/25/16
to Crystal


On Tuesday, October 25, 2016 at 1:34:58 PM UTC-4, Roger Pack wrote:


The "to_i32" still feels a bit magic here.
I agree we should still have to_i and to_f for ruby compatibility though

 
Why? Crystal differs from Ruby in other ways. Don't see why this i special.

I think the rest of reasoning is spot on. Having to know both `i32` and `Int32` means there are two things we have to learn for the same idea. It's anti-DRY. I want the language to be "smooth", such that, from the perspective of the programmer as least, it all follows the same patterns of construction.

    10.of(Int32)
    10.as(Int32)
    10.to(Int32)

The concision of _i32 is much less important than this. (And if that were really an issue, how about renaming Int32 to I32, and saving us the chars everywhere else too).



Roger Pack

unread,
Oct 27, 2016, 1:49:46 PM10/27/16
to Crystal

It would be nice if crystal had a "-E" option (like gcc) to show what all the macros expand to (the "full output" as it were).
Yes, +1
Meanwhile, for -E, you can use `{% debug() %}` inside you macros and it will print the expanded macro to the console.

 
Nice.
Also it would be nice if the mythical "-E" option printed out what the inferred types of the various objects were.
If it could pretty print the output (so you can figure out where that missing brace is) that would be welcome as well :)
Cheers!
Reply all
Reply to author
Forward
0 new messages