[Grace-core] typeof/decltype/getType as alternaties to generic methods?
James Noble
kjx at ecs.vuw.ac.nz
Wed Nov 19 03:11:47 PST 2014
Hi all
continuing some thoughts on generic methods, particular Marco's question
(do we need them?) and discussions with Tim about other ways to get some
of the same effects.
Kim mentioned the "map" method. In Java8, we have
class Stream<T> {
<R> Stream<R> map(Function<? super T,? extends R> mapper)
//...
}
in square bracket Grace, without generic<> methods, we could write this something like:
class Stream[T].new {
method map(mapper : type { apply(T) -> Object) -> Stream[Object]
}
(alternatively we could replace Object with Unknown for more dynamicity)
The problem is that we need to talk about the return type of the block,
which would be captured automatically by the Java generic method type parameter <R>.
I wonder if we can use some variant of a "typeof" (or "decltype") operator
that returns the type of an expression. We'd have to write a mouthful like
class Stream[T].new {
method map(mapper : type { apply(T) -> Object) } )
-> Stream[ typeof(mapper.apply(_)) ]
with the intention that this means the return type of the method is
a stream of elements of the type returned by that apply method.
The catch seems to be to define the "right" semantics for typeof.
There are two obvious semantics:
- the declared type (C++'s decltype) that would return "Object"
- the dynamic type (roughly C#s e.GetType) --- although that's closer to Smalltalks "class" really
but of course it wouldn't make much sense to put this in a type declaration!
I fear what we'd want is rather more subtle / twisted semantics for typeof
- statically inside the method: the declared type
- dynamically inside the method: the dynamic type?
- statically outside, in the caller's context: the static type of the expression with types bound.
So e.g. were I to write something like
def inputStream : Stream[Number] = ...
def outputStream = inputStream.map { each : Number -> each.asString }
then outputStream will be statically declared as a Stream[String]
(each has type Number, Number.asString -> String, so we infer the type of the block as
type { apply( _ :Number) -> String) }
we can pass this to map because type { apply( _ :Number) -> String) <: type { apply(Number) -> Object) } )
but (in the requesting context) we statically compute the return type of map as Stream(Number)
because "typeof(mapper.apply(_))" is String.
I hope this doesn't have the full power of wildcards, but I don't know for sure, of course.
I also hope this (along with Grace's type system without variance annotations) would be sound,
but we don't quite know that either (and Jonathan Aldrich suggested in places it may not be)
james
footnote:
in Grace with generic<> methods, the example is:
class Stream.new<T> {
method map<R>(mapper : type { apply(T) -> Object) -> Stream[R]
}
but if we write
def inputStream : Stream<Number> = ...
def outputStream = inputStream.map { each : Number -> each.asString }
then outputStream will be a Stream<Unknown> because the <R> method argument defaults to Unknown.
to get the desired result, we'd have to write
def outputStream = inputStream.map<String> { each : Number -> each.asString }
but even then, the result of the block inside map isn't statically checked to be String.
More information about the Grace-core
mailing list