Then xs*.y would be defined as
F.map(X.y)(xs)
Where xs is an instance of F<X> for some type constructor F with the
metatype Functor. So this has got me paying proper attention to the
problem of how to represent Functors in Ceylon, i.e. and how to define
a polymorphic map() function that works for optional types, sequences,
collections, trees, etc. (The purpose of this would be to be able to
abstract over all type constructors which can be map()ed.)
There are a couple of issues with trying to do this generalization in
the current language:
0) We don't have metatypes yet.
1) We can't define metatypes for a union type, so the type
constructors ? and [] can't be functors.
2) Without type constructor parametrization, I don't think it's
possible to represent Functor as a metatype.
Actually, if I'm understanding my Haskell correctly - and I might not
be - a Functor is represented in Haskell as a type class that applies
to a higher kind. (i.e. in our terminology, a metatype of a type
constructor.)
Now, I believe I can encode the notion of a functor into our type
system like this:
Functor<F<T>X> {
F<Y> map<Y>(Y f(X x))(F<X> x);
}
(Assuming support for type constructor parametrization.)
Then a List type would be a functor as follows:
interface List<X> is Functor<List,X> ....
The difference between this and the Haskell definition is that in
Haskell the type parameter X would be a parameter of map(), not of
Functor. (I haven't yet thought this through far enough to decide how
serious a limitation of our type system this is.)
So the point of all this is that without the introduction of type
constructor parametrization and a metatype system somewhat more
flexible than what I have written down today, we would not be able to
define the *. operator without resort to overloading, nor would we be
able to define a single polymorphic map() operation, and therefore we
would not be able to abstract over types that can be map()ed. The FP
folks won't love that.
--
Gavin King
gavin...@gmail.com
http://in.relation.to/Bloggers/Gavin
http://hibernate.org
http://seamframework.org
//a higher-kinded interface defining the metatype
interface Functor<F<T>,X> {
shared F<Y> map<Y>(Y f(X x))(F<X> fx);
}
//a type alias for T?
shared type Optional<T> = Nothing|T; //i.e. T?
//an interface that introduces a metatype
//to Type<T?>
shared interface OptionalFunctor<T>
adapts Type<Optional<T>>
satisfies Functor<Optional,T> {
shared actual Y? map(Y f(X x))(X? x) {
if (exists x) {
return f(x);
}
else {
return null;
}
}
}
//a type alias for T[]
shared type Seq<T> = Empty|Sequence<T>; //i.e. T[]
//an interface that introduces a metatype
//to Type<T[]>
shared interface SeqFunctor<T>
adapts Type<Seq<T>>
satisfies Functor<Seq,T> {
shared actual Y[] map(Y f(X x))(X[] xs) {
variable Y[] result := {};
for (x in xs) {
result = result.append(f(x));
}
return result;
}
}
So the "total" list of languages features that I need is:
1) type constructor parametrization
2) type aliases for union types
3) the ability to (like in a generalized algebraic type) parametrize
such a type by a parameter that does not appear in all cases of the
union
4) metatypes for these union types aliases
5) the ability to introduce a metatype
That's a reasonable slab of functionality, but notice that it's all
pretty consistent with stuff we've already speculated that we might
need.
1/ I don't get the feeling that "map" should work on optional types, I
understand it's similar in concept to a sequence of 0 or 1 size, but
"map" is about taking lists and returning lists. I'm not sure other FP
languages will have "map" be defined as returning something else than
a sequence (not in Scheme in any case).
2/ If we can have "Nothing|Empty|Sequence<X> foo;" then (whether now
with ?. and *. or with your proposal) how do we "map" over it?
"foo?*.x" ?
3/ Enabling overriding of those operators means that we'll lose the
ability to optimise them properly in the compiler because they are
defined on types that can be subclassed (Object? or Sequence)
4/ Do we really want to allow overriding of those operators?
5/ "?." is a pretty common operator nowadays, "*." less so I think and
starts looking a bit complex (and reeks of multiplication). "[]." was
a bit more intuirive (if more verbose) in my opinion.
6/ A Real Man's "map" is defined as such
(http://www.cs.bham.ac.uk/research/projects/poplog/paradigms_lectures/lecture5.html#map):
(map f e1 ... en)
where:
- f is a function of n arguments,
- ei to en are lists of the same length,
- the result is a list each of whose members is obtained by applying f
successively to the members of the lists ei to en.
So for example:
(map + (list 1 2 3) (list 4 5 6)) == (list (+ 1 4) (+ 2 5) (+ 3 6)) ==
(list 5 7 9)
So FP guys will mock us anyways because this can't be mapped easily to
a Sequence type ;)
7/ The compiler can implement the current version of "?." and "[]." as
it is defined right now, for M1, without all this.
> 1/ I don't get the feeling that "map" should work on optional types, I
> understand it's similar in concept to a sequence of 0 or 1 size, but
> "map" is about taking lists and returning lists. I'm not sure other FP
> languages will have "map" be defined as returning something else than
> a sequence (not in Scheme in any case).
In Haskell, Maybe is not only a Functor but even a Monad. You can fmap() it.
> 2/ If we can have "Nothing|Empty|Sequence<X> foo;" then (whether now
> with ?. and *. or with your proposal) how do we "map" over it?
> "foo?*.x" ?
Good question.
> 3/ Enabling overriding of those operators means that we'll lose the
> ability to optimise them properly in the compiler because they are
> defined on types that can be subclassed (Object? or Sequence)
Does it? I don't think so.
> 4/ Do we really want to allow overriding of those operators?
That's an interesting question, but even if we don't, we still might
want to be able to abstract over map().
> 5/ "?." is a pretty common operator nowadays, "*." less so I think and
> starts looking a bit complex (and reeks of multiplication). "[]." was
> a bit more intuirive (if more verbose) in my opinion.
Agreed.
> 7/ The compiler can implement the current version of "?." and "[]." as
> it is defined right now, for M1, without all this.
Sure, but the question isn't really about M1, it's about what is the
future direction.