-
Notifications
You must be signed in to change notification settings - Fork 34
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
type classes #899
Comments
Sounds like you want
I know you wanted to get rid of statics, but that's what |
I don't want
Remember: No, much better to keep your "static"s in a toplevel |
Ah, I hadn't thought about initialization. |
Another problem is that you may want to change the monoid being used from the default one. My student is working on this via our shapes stuff. So with that (and feel free to change the syntax):
|
Interestingly, the following point of view perhaps makes more sense:
According to this syntax, you can apply the constraint when you define I'm perfectly happy with this syntax. The bit that has me stumped is how to define the implementation of
The idea is we're kinda splitting the definition of |
I know but I would prefer to not bite that one off, at least not now. Which is why I clarified from the start that this was not a proposal for introductions. Yesyes, I know perfectly well that there are actually two very important monoids for However, upon reflection, I'm inclined to go there, at least for the purposes of this discussion, since it might help us clarify what the syntax should be. So let's contemplate that we might possibly want to introduce this at some future point. Well, my proposed syntax which separates the definition of the type class from the definition of the type is actually very easily capable of representing that, and this is another argument in favor of my approach. We could easily add something like this in future:
To be clear, I'm definitely not advocating that we introduce this now. I'm just trying to use it as a strawman to help motivate the syntax. |
Yep, makes sense. The one thing I don't like about this syntax is having two separate declarations for
|
This did help me a little. By analogy with:
Where we have the pattern:
This, we would have:
This to me looks like a reasonable starting point for arguing over the syntax. WDYT? |
I would have the same concern, if it weren't for the fact that we have already established that pattern with getter/setter declarations:
And, hell, you could even argue that the syntax for enumerated types is similar:
So I think of this as just an extension of that pattern. |
If Ceylon gets extension methods, then "introductions" will never actually be needed. |
OK, after a couple of iterations I arrived at this: // a type class:
interface Monoid<Value> {
shared formal Value zero;
shared formal Value add(Value x, Value y);
}
// an interface constrained to types
// that satisfy the type class
interface Summable<Other> of Other
is Monoid<Other>
given Other satisfies Summable<Other> {
shared formal Other plus(Other other)
=> Other.add(this,other);
}
// a concrete class that satisfies both
// the type class and the interface
class String()
satisfies Summable<String>
is StringMonoid {
...
}
//an implementation of the type class
//for this concrete class
interface StringMonoid
satisfies Monoid<String> {
zero => "";
add(String x, String y) => String(x.chain(y));
} I'm actually pretty happy with this! It preserves the natural meanings of the keywords and avoids introducing any new kinds of declaration. The only really new syntax is the |
@pthariensflame: Extension methods and introductions or type classes are very different from each other. @gavinking: Ok, I see you're reasoning about the separation. Then the one thing I don't like is having the overridden implementation be a type. If it's a type, it can be used in all sorts of ways you don't expect. So I think it's better to view it as an object. Conceptually, |
I don't think that's right. Extension methods don't let you introduce a new type, they just let you call a function with a diffierent syntax. |
@RossTate I must be misunderstanding what "introductions" are, then. Haskell's type classes (exempting extensions) are exactly covered by this proposal. I thought an introduction was an injection of a method into a type by a type class, which doesn't exist in Haskell (because methods don't exist, at least not in that sense). Am I wrong here? If not, wouldn't an extension method trivially delegating to a type-classed function produce exactly the same effect? |
This is certainly the least-motivated thing in what I have right now. The thing that was pushing me in the direction of interfaces here is that if we have a type like Does that make sense? |
@gavinking You're not "mixing together objects", you just happen to have more than one instance of a type, which you definitely do already know how to deal with. |
An introduction (terminology I first learned in the context of AOP, but it might be older) means the ability to retrospectively add a whole supertype to a type. It sounds superficially like the sort of thing you can do with typeclasses in Haskell but actually it's broken in various ways. |
I mean if I have:
Then the metatype, that is, the type of the expression
I want to, from an implementation point of view, produce an actual value that implements this type, somewhere under the covers. |
Then you want (roughly) what this Haskell code gives you: {-# LANGUAGE ConstraintKinds, KindSignatures #-}
import Data.Constraint
type DecidableMonoidalRing (a :: *) = ((Monoid a, Ring a, Eq a) :: Constraint)
evidence :: Dict (DecidableMonoidalRing Float)
evidence = Dict This is, in the proposed Ceylon syntax, just: alias DecidableMonoidalRing<A> = Monoid<A> & Ring<A> & Eq<A>;
value DecidableMonoidalRing<Float> evidence = Float; That is, you're just letting the metatype implement all the (meta)interfaces at once. This is just an instance (heh) of the existing confusion with the intersection-like syntax in |
@pthariensflame: Extension methods are purely syntactic sugar for calling top-level/static functions/methods. @gavinking: Okay, I think I see what you're doing. You're taking advantage of the fact that your |
@RossTate Yes, but there's nothing stopping an extension method from delegating to a type class method on the metatype. For example: public interface Monoid<Value> {
Value zero;
Value plus(Value x, Value y);
}
Value plusExtension<Value>(Value this, Value other) given Value is Monoid<Value>
=> Value.plus(this, other); This gets you pretty much all of the (additional-to-type-classes) benefits of introductions, without basically any of the faults. |
Huh? You seem to be assuming a way to make a type's metaobject implement an interface, which is what we're trying to figure out here. |
Please keep in mind that I have the following ulterior goals here:
This needs to be a bit more than syntax sugar, but I want it to be not much more. |
@RossTate So you just declare that that's what happens. You can take a (very minor) leaf from universe-polymorphic type theory here: The metatype of a type is simply a type-level code for a kind. That is, it is a reified kind, in the same sense in which Ceylon already features reified types. Subkinding becomes involved, but that is both conceptually and implementationally no different to the subtyping that Ceylon already knows how to handle. An |
Rust, one of the up'n'coming languages for systems programming, has a nice system of type classes (called "traits"). http://pcwalton.github.io/blog/2012/08/08/a-gentle-introduction-to-traits-in-rust/ |
From what's in that article, traits are just interfaces. If David Herman's at POPL next week, I'll double-check with him. |
Ah ... I'm assuming that the reason they have |
Oh. Looks like they plan it but don't have it yet. Well, it will fit really nicely in with the rest of what they have, when they add it. |
@gavinking Sorry, but I'm slightly confused by your example... What does |
Yes, thanks, I fixed it.
Yes of course. Also fixed.
Excellent :-) |
OK, after reflecting on this issue this morning, finally taking into account the changes to the language that resulted from the addition of constructors, along with what is proposed in #1129, I have a completely different proposal. The code would look like this: shared interface Summable<T> of T
given T satisfies Summable<T> {
shared formal static T zero;
shared formal T plus(T t);
}
shared class Num
satisfies Summable<T> {
shared actual static Num zero => Num(0);
Integer int;
shared new (Integer int) {
this.int = int;
}
shared actual Num plus(Num num)
=> Num(int+num.int);
}
shared T sum<T>({T*} ts)
given T satisfies Summable<T> {
value result = T.zero;
for (t in ts) {
result += t;
}
return result;
} Some notes on this:
What's great about this proposal is:
I think this would be great, and really put the icing on the cake of our type system. |
Yeah, so I think there's several ways that this could potentially work:
@RossTate any thoughts? |
@gavinking Hrm. I'm not sure if I like that so much... Personally, I still prefer using the |
Once you add In fact, treating this differently from subtyping solves the constraint problem you're talking about. Suppose So the more I think about this (I hadn't actually thought of the problems you mentioned before), the more I think we should have a separate construct for this pattern. |
I don't think the keyword 'static' is a very good one. It is being used quite differently than Java's static. Perhaps 'Instance' might be a better word? - This has the advantage that it subtly implies points 3 and 4 above. |
The current naming seems very awkard for monoids in general (monoid being a perfect example of where type classes can be useful) and restricting this to Just think about it (in some made up syntax): typeclass BooleanAndMonoid<Boolean> {
zero => true; // WTF
plus => Boolean.and;
}
typeclass SetUnionMonoid<Set<Anything>> {
zero => set {};
plus => Set.union; // not really +
}
typeclass OrderingMonoid<Ordering> {
zero => equals; // WTF
plus(Ordering other) => switch ( other )
case smaller: smaller;
case equal: other;
else greater; // how is this +
}
typeclass MultiplicationMonoid<Integer> {
zero => 1; // oh no!
plus(Integer other) => this * other; // this is certainly not +
} The Hope you guys manage to make it possible to write proper monoids with type classes easily. And I can see a whole bunch of things that people use in the Haskell world (simple things that even us, Java developer, can get!) with type classes being possible in Ceylon as well... Obs: perhaps extension methods that can provide some default value would be enough to implement the above? Example: // for a type to be a monoid, an `append` function must be implemented for it which provides an identity:
typeclass Monoid<T> => append(T)(T other) given T identity;
// add the append extension method to String with some weird syntax, making String addition a Monoid:
String append(String)(String other) given Identity = "" => this + other; Just throwing ideas around, hope some of them help. |
@renatoathaydes wrong repository… |
@lucaswerkmeister thanks :) I will reformulate my suggestion and post it in the right place... Got to this issue from a link in a comment in http://ceylon-lang.org/blog/2014/05/10/about-plus/ (pretty old). |
Scope
The purpose of this proposal is to allow the definition of operations which:
The terminology is not intended to imply that this feature is exactly the same as Haskell's type classes. In particular, this is not a proposal for introductions. A type class in this proposal is specified when the type is defined, not where it is used.
Concept
I would like to be able define a generic
sum()
function that accepts{Element*}
, rather than{Element+}
as it does at present. This means we would need to makeSummable
into a true monoid rather than just a semigroup. We would like to be able to write:Ceylon is a perfect language for implementing this kind of thing, since we already have reified generics, so we already pass an object to the type parameter
Value
when we callsum()
.So, conceptually,
zero
is a member of the metatype ofValue
, i.e. of the objectValue
. A very strawman syntax for defining such a setup would be:Now, the constraint
given Value satisfies Summable<Value>
would additionally imply thatValue is Monoid<Value>
.Alternatively, we could move the type class constraint
is Monoid<Other>
from the definition ofSummable
to the definition ofsum()
, i.e.but that seems a bit less convenient and would result in more repetition. Whatever.
Anyway, so for a concrete class that actually implements
Summable
/Monoid
, we would wind up with, approximately:Not sure what to do to clean up the syntax here. It could also, potentially be a separate
object
declaration.The text was updated successfully, but these errors were encountered: