Difference between revisions of "Trash/DDC/ClosureTyping"

From HaskellWiki
Jump to navigation Jump to search
Line 54: Line 54:
   
 
== Closure masking ==
 
== Closure masking ==
Because Disciple has type elaboration there is usually no need to write closures manually, but in the rare cases you might want to - maybe you're trying to import a foreign function that does something wierd which the regular FFI elaboration doesn't handle - closure masking can help (a little).
+
Because Disciple has type elaboration there is usually no need to write closures manually. However, in the rare cases you might want to - maybe you're trying to import a foreign function that does something wierd which the regular FFI elaboration doesn't handle - closure masking can help (a little).
   
 
For example:
 
For example:

Revision as of 05:14, 20 March 2008

Region sharing

Consider the following function:

f ()
 = do   x = 5
        g () = x
        g

Without closure information this function would have the following type:

f :: forall %r. () -> () -> Int %r

Remember that the forall %r at the front of the type is supposed to indicate that the return value is freshly allocated. This is certainly true if we apply both arguments:

twoSeparateInts :: Tuple2 %r1 (Int %r2, Int %r3)
twoSeparateInts = (f () (), f () ())

In twoSeparateInts there are different regions annotation on each of the Int constructors, which means they do not alias, and its safe to treat one as Const and the other as Mutable.

But what happens if we partially apply f? The standard type system will re-generalize the type for the new binding and we're left with:

f_unit :: forall %r1. () -> %r1
f_unit = f ()

We've now got a function which returns the same Int every time we call it, but the type says it's supposed to be fresh! The problem here is that x was free in our original definition of g so is shared between applications of it.

Closure typing for region sharing

Closure typing is used to track the sharing of regions between function calls like this one.

The type inferred for f is actually:

f   :: forall %r0
    .  () -> () -($c0)> Int %r0
    :- $c0        = x : %r0

The $c0 annotation tells us that this function has a object free in its closure, and that the object contains region %r0. The x is simply a name for the object which contains this region, and has no other special meaning.

If we use this new type and apply the first argument we have:

f_unit :: () -($c0)> Int %r0
       :- $c0   = x : %r0

This type says that f_unit is a function that takes a unit value and returns an Int, but the Int is free in its closure so is shared by all calls to it. The type system does not generalize regions which are free in these closures.

Closure masking

Because Disciple has type elaboration there is usually no need to write closures manually. However, in the rare cases you might want to - maybe you're trying to import a foreign function that does something wierd which the regular FFI elaboration doesn't handle - closure masking can help (a little).

For example:

addFour :: forall %r1 %r2 %r3 %r4 %r5
        .  Int %r1 -> Int %r2 -($c1)> Int %r3 -($c2)> Int %r4 -($c3 !e1)> Int %r5
        :- !e1 = !{ Read %r1; Read %r2; !Read %r3; !Read %r4 }
        ,  $c3 = ${ a : Int %r1; b : Int %r2; c : Int %r3; }
        ,  $c2 = ${ a : Int %r1; b : Int %r2; }
        ,  $c1 = ${ a : Int %r1; }

Starting from the right-most closure, each successive closure is simply the previous closure without the current argument.

A slightly nicer way of writing this is:

addFour :: forall %r1 %r2 %r3 %r4 %r5
        .  Int %r1 -> Int %r2 -($c1)> Int %r3 -($c2)> Int %r4 -($c3)> Int %r5
        :- !e1 = !{ Read %r1; Read %r2; !Read %r3; !Read %r4 }
        ,  $c3 = ${ a : Int %r1; b : Int %r2; c : Int %r3 }
        ,  $c2 = $c3 \ c
        ,  $c1 = $c2 \ b

In this version we use $c3 \ c to mean the closure $c3 without the regions from the object we've named c.