-
-
Notifications
You must be signed in to change notification settings - Fork 419
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
Functions with constraints on type parameters of containing type. #683
Comments
This is something I've been thinking about for a while. I think adding constraints is definitely interesting. I think it's also possible we could have a "type language" for computing a type for some instantiation, for example: type Map[K, V, H: HashFunction[K] val = F] is HashMap[K, V, H] where
F = match K
| (Hashable #read & Equatable[K] #read) => HashEq[K]
else
HashIs[K]
end I think in the specific example of turning an fun string[A: Stringable](): String iso^ =>
"""
Use a type constraint at compile time.
"""
let s = recover String end
s.push("[")
for v in values() do
let s' = v.string()
s.push(s')
end
s.push("]")
s fun string(): String iso^ =>
"""
Use a type constraint at runtime.
"""
let s = recover String end
s.push("[")
if A <: Stringable then // requires new syntax here
for v in values() do
let s' = v.string()
s.push(s')
end
else
s.push("unknown")
end
s.push("]")
s fun string(f: {(this->A!): String} box): String iso^ =>
"""
Use a lambda.
"""
let s = recover String end
s.push("[")
for v in values() do
let s' = f(v)
s.push(s')
end
s.push("]")
s |
Is this anything like the fun contains[B: (A & HasEq[A!] #read) = A](a: box->B): Bool =>
"""
Returns true if the list contains the provided element, false otherwise.
"""
try
_contains[B](head(), a)
else
false
end |
As discussed in a call today, we need some syntax to use in conditionals for constraining that One obvious approach might be to use if A <: B then
// ...
else
// ...
end However, due to Pony's LL1 parser, we have to know when we hit the So, we need a syntax with a token that precedes if |- A <: B then
// ...
else
// ...
end but such an approach might be too cryptic. Does anyone have any other syntax ideas that meet these needs? |
I, for one, would like to go on record as supporting the turnstile syntax option, but only if you require the unicode RIGHT TACK character (⊢). Every language should have at least one un-typable, impossible to find operator glyph. And it would put Pony ahead of Perl. (I think.) |
going with the pony style of keywords that are words smushed together we could try something like this: if checktype A <: B then
// ...
else
// ...
end |
@jemc Why is interface SomeContainer[A]
new create() => //... Should we then take any instances of the string I think that it's a lot less ambiguous to just have the typename represent the type, and it's the way most languages handle it. If that becomes the case, does that remove the need for a preceding token? As much as I like lambdas, I think there definitely needs to be a simple syntax for the vast majority of use cases, // the lambda w/ value type solution:
fun my_fun[conv: {(A): String}](): String // the functional notion of providing a 'proof' of convertibility |
It's all about context. The Pony parser is a LL(1) parser that consumes a stream of lexeme tokens from a lexer. So, expanding on your example to include some more examples of type name interpretation, the tokens would look something like this: interface SomeContainer[A]
new create(): Bar =>
let a: Bar = Bar
a [TK_INTERFACE, TK_ID("SomeContainer"), TK_LSQUARE, TK_ID("A"), TK_RSQUARE,
TK_NEW, TK_ID("create"), TK_LPAREN, TK_RPAREN, TK_COLON, TK_ID("Bar"), TK_DBLARROW,
TK_LET, TK_ID("a"), TK_COLON, TK_ID("Bar"), TK_EQ, TK_ID("Bar"),
TK_ID("a"), ...] The LL(1) part means that it only has 1 token of lookahead. So, when it sees the In this case, the preceding So your suggestion of requiring all |
Makes sense, thanks. Can we get around the parsing issue by turning Otherwise, I think method_with_constraint[A: TypeConstraint]() => // where A defined in surrounding scope How far do you think this should go in terms of constraining non-concrete types? interface SomeContainer[A: Any val]
fun box sort[A: Comparable[A](): SomeContainer[A]
fun box clone(): SomeContainer[A]
class Impl1[A]
fun box clone(): Impl1[A] //... implementation
class Impl2[A]
fun box clone(): Impl2[A] //... implementation
fun box sort[A: Comparable[A]](): Impl2[A]
// I think I handled the contra/co-variance here such that the following makes sense: Then: I'd argue that we should be able to constrain types in interfaces as well - if my interface exposes certain methods when the typearg obeys certain constraints, classes trying to implement it should be fine without those methods when the typearg doesn't obey those constraints, but should need to expose those methods to properly be a subtype of the interface when the typearg does obey the constraints. I'd think it could be very useful. How about conditional compilation based on value type? Does this start to become Macros-lite if you've got a bunch of if's conditionally inserting code all over the place, and therefore does it make sense to consider this in the complex case (beyond simply constraining a simple method) as a part of a broader macro system? |
I think there are some good questions here, but I'll leave them for @sylvanc to ponder because he has a clearer vision of where he wants the type system to go. |
@sylvanc any further thoughts on this? |
@jemc are you planning on turning this into a RFC? if not, I'll add a request for RFC. I'm trying to close out all the "turn into rfc" tagged items. |
Closed in favor of ponylang/rfcs#60 |
We currently have type parameters on types (
class
,actor
, etc), which allow passing in type parameters when referring to that type (for example,Array[String]
). We also have type parameters on functions, which allow passing in type parameters when calling the function (for example,h.assert_equal[String]("foo", "foo")
). Both of these involve the compiler generating separate LLVM functions for each type combination encountered at call sites.Type parameters can also be constrained. For example,
h.assert_equal
constrains its type parameter so that the given type must be bothEquatable
for checking equality, andStringable
for printing. If the given type doesn't meet the constraints, it will generate a compiler error.When working on generics, I've often wished for another related feature: making the function conditional on type parameters of the containing type.
For example, we don't have an
Array.string
function because not everything you might want to put in anArray
isStringable
- that is, we might be able to implementArray[U8].string()
orArray[String].string()
, but we can't implementArray[MyClass]
ifMyClass
is notStringable
. Currently, to accomplish this we would need to do one of two things:class Array[A]
toclass Array[A: Stringable]
. However, this would mean we could no longer even create anArray[MyClass]
ifMyClass
is notStringable
. So this doesn't really work for us.Array.string()
method do a type conversion withas
, anderror
if the typeA
is notStringable
. However, this is still a bit silly - our compiler can know at compile time whetherA
isStringable
, so why defer this to runtime? Why even allow us to callArray[MyClass].string()
if it will never work, and why make us surroundArray[String].string()
with atry ... end
block if it will always work?My proposal would be:
Allow functions that will compile only when additional constraints on the outer type is met. For example, when compiling
Array[A]
, thestring
method would be just ignored and left out ifA
was notStringable
.I'm open to ideas about syntax, but my current thought is this - since we already can't shadow a type parameter name (for example, inside
class Array[A]
we cannot currently definefun string[A]()
), we can do this by "overloading" the existing constraint syntax, referencing the outer existing type parameter name, and constraining it further (for example, insideclass Array[A]
we might definefun string[A: Stringable]()
).The text was updated successfully, but these errors were encountered: