Crystal: Type restriction should upcast

Created on 3 Jun 2016  路  10Comments  路  Source: crystal-lang/crystal

example

class A
end

class B < A
  getter :v

  def initialize(@v : A)
  end
end

class C < A
  getter :v

  def initialize(@v : A)
  end
end

def foo(x : A)
  arr = [x]
 #if replace it with arr = [x.as(A)], it compiles.
  while arr.size > 0
    a = arr.pop
    case a
    when B
      arr << a.v
    when C
      arr << a.v
    end
  end
end
a = A.new
b = B.new a
c = C.new b
foo(c)

no overload matches 'Array(C)#<<' with type A+
Overloads are:

  • Array(T)#<<(value : T)
    Couldn't find overloads for these types:
  • Array(C)#<<(value : A)
draft compiler

Most helpful comment

Personally I like the current concept of restriction.

Maybe it should be more clearly explained in docs, with increased clearer knowledge among crystal users (many probably assume semantics from syntactic similarity to other typed languages), and then perhaps some one comes up with a better/refined idea, otherwise I think it's neat.

All 10 comments

smaller example https://carc.in/#/r/10la

class A
end

class B < A
end

def foo(a : A)
  p typeof(a)
end

foo B.new # => B, expected A

@jhass the right behavior should be return A and if you want to access something particular to B, should call inside an if is_a?(B), right?

I would say so, yes.

Crystal only uses the type restriction in methods to choose. It does not restrict or use the type information during body method.

For example the following compiles and runs:

https://play.crystal-lang.org/#/r/10nc

def change(a : Enumerable)
  a[0] = 3
end

b = [1,2,3]
pp b # => b = [1, 2, 3]
change b
pp b # => b = [3, 2, 3]

The motivation is that it is the nearest behavior to an unrestricted method:

def change(a)
  a[0] = 3
end

I did raise this to @asterite in the past and what I exhibit here is more or less the sample he use to explain why he thinks it should work as it is.

And we can't solve the issue @chenkovsky / @jhass shows without loosing the working samples posted in this comment.

I guess I simply disagree that they should work and that they do work is expected.

Also:

def foo(x : Int)
  # I don't want x to be of type Int, just restrict it's type
  y = x.to_i32
end

And many, many more cases like that. That's why they are called "type restrictions" and not just "argument types". I'd leave this as an RFC or close it. It's a fundamental part of the language that, if changed, will break everything. So it basically needs to be reconsidered once the language evolves a bit more.

That makes the initial issue a duplicate of #1297 I guess.

@jhass I still have mixed feelings too :-). The surprise factor is as your minimal example, that the static typeof of the argument does not match the type restriction.

It feels too different from what it is used, even not formal, but is because we are narrow to the classic formalism or way to read the functions declarations.

I don't see it a duplicate of #1297. There, the question is how generic arguments should participate in the type restrictions or eventually in #is_a?, etc.

Personally I like the current concept of restriction.

Maybe it should be more clearly explained in docs, with increased clearer knowledge among crystal users (many probably assume semantics from syntactic similarity to other typed languages), and then perhaps some one comes up with a better/refined idea, otherwise I think it's neat.

I'm closing this. A type restriction is just that: a restriction. The examples for Enumerable and Int are good enough for not considering doing this. You wouldn't like this to happen:

def foo(x : Enumerable)
  x.first
end

typeof(foo([1,2, 3])) # => Object

It's Object, according to this proposal, because x is now an Enumerable of any possible type, thus Object.

Given that you are using a type restriction A, you can use it to type the array:

def foo(x : A)
  arr = [x] of A
end

and problem solved.

Was this page helpful?
0 / 5 - 0 ratings