-
-
Notifications
You must be signed in to change notification settings - Fork 1.6k
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
Constrain type vars / free variables to specific types #934
Comments
I think code that involves generics is already pretty complex to want to make the type system more complex. But I think @waj likes these ideas so I'll leave this open as an enhancement. Personally, I feel code should be simpler, and generics should only be used for containers. |
So there are two concepts in the proposal:
If we think 1) alone, a usage I can think of is just to allow better errors to the user when using an invalid argument due to some interface assumtions ( Other usage of 1) is to express some dependency like Regarding 2) It might be enough at the beginning to allow class/code specialization at least on concrete types. |
Exactly. As you mention 1) is a great help to catch errors earlier and give more specific helpful error messages. When it comes to 2)
Each specific instantiation is compiled from class description, it simply will be based on the description matching the types better. So it "solves it self" as soon as the "overloading on typevar restrictions" matching is in place, so there's no special case for specialization requiring it to be limited to concrete types. And most of the logic for matching should be the same as for regular overloading, so a lot of the logic would already be available for revamping. Or am I missing something? |
+1 for these features |
+1 this also should work for modules, and abstract classes with abstract methods |
As mentioned in OP, I've edited it slightly, added #3298 issue for 2) to keep tracking separate. |
There's a workaround used in |
@RX14 thanks for linking the macro example. Macros solve most things (Crystal rocks), and still, as you say: nicer syntax would be nice :-) Also, for numbers as parametrization: If I recall correctly (I don't code C#), C# defines constraints suffixed to typedef head. Perhaps that's a style that would suit Crystal: Wonder if both constraints and specializations could be solved smoothly by a AST rewrite stage, making macro nodes from such statements, and then expand those as usual. In order to re-use and keep as much code as generic as possible in the compiler (mash all implementations of specialized defs into one def with macro conditionals to select actually used code, etc). |
Cross-posting and elaborating on my comment from #3298: I was hoping to do something like this (using the pseudo-syntax in this thread): class Hash(K, V = Array(K))
# ...
end The use-case in my case is for topological sorting of certain enumerable types. For instance, if we wanted to topologically sort keys in a class Hash(K, V = Array(K))
include TSort(K)
end
dag = {1=>[2, 3], 2=>[3], 3=>[] of Int32}
non_dag = {:x=>42, :y=>99}
dag.tsort #=> [3, 2, 1]
non_dag.tsort #!! wouldn't compile |
+1 would love to restrict generics based on type, for example, allowing only an inherited type. class ClassA
end
class ClassB < ClassA
end
class Example(T : ClassA) #would only allow those of type class A
end |
Would a syntax like alias Foo = Int32 | String
alias Bar = Float64 | Char
class FooArray(T) where T < Foo
end
class FooBarHash(K, V) where K < Foo, V < Bar
end This fits in with languages like Java and also conforms to the current I'm not sure if this is the right place or time to post this right now, but it's an idea to throw out in the open. |
It could just be |
@straight-shoota So basically: class FooArray(T) < Array(T) forall T < Foo Shouldn't be too hard to parse, then. |
@straight-shoota but Also, having the |
Also it would allow to do |
I wrote a lightweight (~100 LOC) implementation for this using the macro interpreter (see proposal: #3298 (comment)). It can handle (abstract) classes/structs and modules. Some examples: class Foo(T) where T < Number
end
Foo(Int32).new # OK
Foo(String).new # Error: type var String doesn't satisfy restriction 'T < Number' of generic class Foo struct MyTuple(*T) where T.size > 1
end
MyTuple(Bool, String, Int32).new # OK
MyTuple(Float64) # Error: type var Float64 doesn't satisfy restriction 'T.size > 1' of generic struct struct NumberTuple(*T) where T.type_vars.all? &.< Number
end
NumberTuple(Int32, Float64).new # OK
NumberTuple(Int32, Float64, String).new # Error: type vars Int32, Float64, String don't satisfy restriction 'T.type_vars.all?(&.<(Number))' of generic struct NumberTuple module Bar(T) where T < Int::Signed
end
class Foo(T)
include Bar(T)
end
Foo(Int16).new # OK
Foo(UInt64).new # Error: type var UInt64 doesn't satisfy restriction 'T < Int::Signed' of generic module Bar Changes: malte-v@4735140 Do you think this could be a viable solution? |
A while ago I was thinking that if constrained generic parameters use: # `<=` for subtypes, `<` for proper subtypes
# ditto for supertypes
class Foo(T) forall T <= Int
end then the unconstrained generics, which we already have now, would be written as: class Proc(*T, R) forall T, R
end Full specializations (#3298) would eventually not require any special syntax, as a lack of struct Slice(T) forall T
end
# okay, `UInt8` is not an unbound parameter
# requires `Slice` and `UInt8` to be defined prior to this point
# this is _not_ equivalent to `Slice(T) forall T <= UInt8`,
# because `Slice(NoReturn)#hexstring` is undefined here
# can perhaps be written as `Slice(T) forall T == UInt8` too
# (implies syntactic equality, not just type equivalence?)
struct Slice(UInt8)
def hexstring; end
end
struct StaticArray(T, N)
end
# partial specialization
struct StaticArray(UInt8, N) forall N
end
# not allowed; `Int32` is not an unbound parameter
# no prior definitions of `Bar` exist to provide a name
# for the corresponding formal parameter
class Bar(Int32)
end
# not allowed; `String` is not an unbound parameter
# no prior definitions of `Foo` allow `T = String`, as `T <= Int`
class Foo(String)
end The full macro language for the constraint is probably overkill, we should start with the subtyping operators first. The equivalent issue for free variables in defs is #11908 (it proposes only |
It would be good be able to constrain generic variables, just like common variables - exact same concept: (I'm skipping the meat of the classes just to show the concept.)
[ed: 2016-09-12, modified the example slightly]
Adding type intersections in addition to type unions could prove usable for constraints too:
ed: Below has its own issue now instead: #3298
Preferably, it should also be possible to "overload" the generic class matching to most specific type var constraint, thereby making specializations possible:
The text was updated successfully, but these errors were encountered: