[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