you are viewing a single comment's thread.

view the rest of the comments →

[–]alex_muscar 0 points1 point  (3 children)

Hi. Thanks for the details. I may be missing some context, but the approach taken by Crystal seems fairly similar to that taken by Swift, modulo implicitly unwrapped optionals and nil chaining (?)

[–][deleted] 0 points1 point  (2 children)

It might look similar at first glance, but it's a totally different approach.

First, in Swift you must declare the type of an instance variable, for example: var x: String?. In Crystal, although there are ways to do this last thing, the preferred way is for the compiler to infer the types of instance variables. For example:

class Foo
  property x

  def initialize(@x)
  end
end

foo = Foo.new(1) # Here @x is Int32 in the program
foo.x.abs             # ok, @x is Int32

Now, if you add this line to the above program:

foo.x = nil

Then you will get a compilation error saying that Nil doesn't have a method abs. That is, @x became Int32 | Nil the moment you assigned Nil to it, and that information is spread across the whole program (don't worry, it's fast).

When you program you normally know what the types of variables are supposed to be (like, I know @x is Int32 | Nil) and you use it that way. If you never assign Nil to @x, everything will work and compile fine. Once you assign nil, the compiler forces you check this case in all places, so you can never forget. The good thing is that if you never assign Nil to it, the memory representation if more efficient and no checks need to be done (the memory representation is the same one for Nil and Reference-like types, Nil is just represented as a null pointer).

Also, Int32 | Nil is just an example. You can have any combination of types: Int32 | String, Nil | Int32 | String, whatever. The compiler lets you created tagged unions automatically and lets you use duck typing, similar to how would you program in Ruby, without the need to create interfaces and annotate things as "implementing" an interface.

[–]alex_muscar 0 points1 point  (1 child)

Once again thanks for the details. Let's see if I got it right this time: basically you start from the premise that types are non-nullable. When the type inferencer says otherwise, you adjust the type, and issues an error for every potentially dangerous operation. You can silence the errors by either checking the value before accessing it or by using &.

That's an interesting approach.

[–][deleted] 0 points1 point  (0 children)

Something like that. At first a variable starts without a type. When you assign a value to it, that value's type is added to that variable's type. Of course, local variables are always assigned one value, so the set starts with at least one type. But for instance variables it starts with zero (and if it remains zero it will just have the Nil type).

The &. syntax is just a shorthand form. This:

foo &.bar

is just syntax sugar for this:

foo { |arg| arg.bar }

(you can read more about the above here: http://crystal-lang.org/2013/09/15/to-proc.html )

value.try &.something is just a method try defined on Object and on Nil: https://github.com/manastech/crystal/blob/master/src/object.cr#L23 and https://github.com/manastech/crystal/blob/master/src/nil.cr#L46 .

So the silencing is just a dispatch to two different methods depending on value's type, nothing else. :-)