I typed up this little example for a tutorial:

/////////////////
fun catmap[T,N with Str[T]] (sep:string) (f:T -> T) (x:array[T,N]) => 
  fold_left (fun (acc:string) (x:T) => acc+sep+str (f x)) "" x
;

var x = 1.0,2.0,2.5,3.0;

println$ "The squares of " + str x " are" + 
  catmap " " (fun (x:double)=> x * x) x
;
////////////

and was a bit annoyed when it didn't work. After some debugging I found the 
problem.
If I do this instead:

fun catmap[T,N with Str[T]] (sep:string) (f:T -> T) (x:array[T,N]) : string => 

it works fine. The problem is that when the return type of a function isn't 
known, it is initially set to TYP_none. When the function is added to the
symbol table, if the return type is TYP_none it is replaced by TYP_var i
where i is the table index of the function.

When we look at some type with type variables in it, we can tell if they're
bound variables (vars of the enclosing function) or these special unbound 
variables,
by simply looking up the index to see if it is a function.

Felix actually does genuine Hindley-Milner style type inference for these type
variables (its the only part of Felix that uses general inference: the rest of 
the
system only permits bottom-up inference, which I call deduction).

There is a special routine which stores discovered values of these variables
in a global cache. It only caches variables whose index is the same as a 
function.
It's basically a trick to save another term type or symbol table entry.

The problem is that when you write a function like catmap above,
it is desugared into a function which returns a function which 
returns a function, and they all get return type TYP_none and then
later their own type variables.

Now, when overload resolution sees

catmap: string -> Ti 

it can't unify that with the arguments in the program above which are

  string -> (double -> double) -> array[double,4]

to discover T=double and N=4. The type variable is always replaced
in the bound symbol table, but overloading uses unbound symbols.

What should happen is this:

fun catmap[T,N with Str[T]] (sep:string) (f:T -> T) (x:array[T,N]) : Ti =>

where i is the index of the inner most function being returned,
but the problem is the currying routine which deconstructs the multi-argument
function in to nested single argument functions is done at desugaring time
before there actually is an assigned index, so it just puts TYP_none
for all the nested functions. If it put TYP_none only on the inner
function and calculated the partially complete return types for the others,
there's be no way to tell where the nested TYP_none came from:

   string -> (T->T) -> TYP_none

has to be replaced later by

  string -> (T -> T) -> Ti

where i is the index of the inner function, but there's no way to know
this on seeing the first formula. If the return type is just plan TYP_none
we know it because we're examining the function going into slot i of the
symbol table "right now".

To fix this: if I put some type variable in right away instead of TYP_none,
during desugaring, I need to maintain a list of these type variables
so it is possible to tell if they're bound or not.

Another way is to use a new term like:

   TYP_unknown i

However this is not so simple, because now unification has to handle
two kinds of type variables instead of just one.

Keeping the old system, overloading might work if the type variables
were already calculated. For example if we have:

   T9 = string
   T8 = array[T,N] -> T8
   T7 = (T -> T) -> T8
   T6 = string -> T7

then we can resolve T9, and this actually happens now. But if T9 isn't
known yet, we'd just get:

  T6 = string -> (T -> T) -> array[T,N] T9

but that's quite good enough for overload resolution when it is checking
multiple arguments: it doesn't care about the final return type in the
chain, only the signature of the top level function, which is

  catmap of (string -> (T->T) -> array[T,N]

and now overload resolution would find T and N and be able to do
a match.

The reason it works when we specify the return type is "string" is that
then the desugaring routine specifies all the return types without any TYP_none.

A curious problem! 

--
john skaller
skal...@users.sourceforge.net





------------------------------------------------------------------------------
Learn how Oracle Real Application Clusters (RAC) One Node allows customers
to consolidate database storage, standardize their database environment, and, 
should the need arise, upgrade to a full multi-node Oracle RAC database 
without downtime or disruption
http://p.sf.net/sfu/oracle-sfdevnl
_______________________________________________
Felix-language mailing list
Felix-language@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/felix-language

Reply via email to