Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Lift the restriction that type variables can only be single letter names #3294

Merged
merged 1 commit into from
Sep 13, 2016

Conversation

asterite
Copy link
Member

Conclusion of #3288

With this change, generic type variable can be named however we want. For example:

class ArrayWrapper(Type)
  def initialize(@array : Array(Type))
  end

  def <<(x : Type)
    @array << 1
  end
end

some = ArrayWrapper.new([1, 2, 3])
some << 1
some << "a" # Error

ArrayWrapper(String).new([1, 2, 3]) # Error

In the above example, when solving ArrayWrapper.new([1, 2, 3]), because the method is invoked on an uninstantiated type (ArrayWrapper(Type)), Type acts as a free variable that will take the type of the given Array, Int32, which makes the returned value be of type ArrayWrapper(Int32).

When solving some << 1, Type is already bound to Int32 so the call succeeds. This is why some << "a" is an error.

The last case where we invoke new on an instantiated type is similar and will give an error. So Type can only act as a free variable on uninstantiated generic types.

I believe this will make the language a bit better: using T, K and V for collections is OK, but for other non-collection types it might be good to use a more descriptive name instead of just a single letter.

This change still keeps the limitation that types T and U can't be declared at the top-level, though that might change soon.

@oprypin
Copy link
Member

oprypin commented Sep 12, 2016

Do I understand correctly that this allows type variables of classes to have arbitrary names, but no change for methods?

@asterite
Copy link
Member Author

@BlaXpirit Yes, this is for type variables of classes and modules, but what do you mean with no change to methods? I guess it means that we don't yet introduce either def (U) nor syntax for free variables, and that is correct.

@oprypin
Copy link
Member

oprypin commented Sep 12, 2016

def f(a : SomeType, b : SomeType)

It is possible to make any unexisting type become a type variable. This makes little sense, but looking at the title and with no evidence to the contrary, this pull request can be (mis)understood like this.

@asterite
Copy link
Member Author

Yes, it can be confusing. But no, your example will give an error if you invoke that method and SomeType doesn't exist. For free variables unrelated to type variables you still have to use T, T1, etc.

@bcardiff
Copy link
Member

We are going back and forth with this. I would say to first decide what to do about #3288

@oprypin
Copy link
Member

oprypin commented Sep 12, 2016

I consider this a better alternative to #3288. No ugly syntax is introduced and classes get to introduce long-lived class names, while methods can still easily get away with the short names just for that one method, and compensate with docs if absolutely necessary.

@ysbaddaden
Copy link
Contributor

I think it adds to the confusion.

  • single letter constant: generic or freevar.
  • longer constants: type.

With this convention I can NEVER confuse a generic type from a type, but if the restriction is lifted, well, there will be NO way to distinguish them without going back to the class definition.

@ozra
Copy link
Contributor

ozra commented Sep 13, 2016

@ysbaddaden has a very valid point.

Perhaps the better solution is simply to promote an idiomatic Crystal de-facto naming scheme of using (completely arbitrarily chosen suggestions here): class Foo(X, Y, Z) for type parametrizations, and def foo(x: T -> U, y: V) for method typevars - just to avoid accidental non intended effects. Or add one of the methods of specifying it explicitly when wanted as per #3288.

Or! Even enforce a naming scheme separating type-level and method-level generic symbols! However to do that cleanly, the only way is probably partitioning the letters as in above example - which might be a bit too extreme.

On the other hand, with this long-names-allowed syntax, a idiomatic naming convention will also solve the problem, with less restrictions. class Foo(ElementT, SomeOtherT), def foo(x: T -> U, y: V). That's probably the cleanest way to go about it.

I've grown very fond of the "duck-lexing" concept prevalent in Crystal. It makes it very easy for a human being to immediately grasp what the intentions and roles of a piece of code and it's identifiers means.

Limitations imposed in that regard are good constraints imo (increases clarity of intent).

@RX14
Copy link
Member

RX14 commented Sep 13, 2016

@ozra Can you expand on what you mean by "duck-lexing"?

@asterite asterite merged commit 5f372c1 into master Sep 13, 2016
@ozra
Copy link
Contributor

ozra commented Sep 14, 2016

@RX14 "if it looks like a duck, it probably is a..." B-)

@asterite asterite deleted the feature/relax_type_vars branch September 15, 2016 19:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

6 participants