Dear David,
I'm trying to understand the IR of functors in Poly/ML, and I got a few questions here:
1. I tried the following example:
signature A = sig type element end
signature B = sig type element end
functor myfctor(s: A) :> B = struct type element = s.element end
which corresponds to the following code tree:
BLOCK(DECL #1{0 uses} = LAMBDAONLYINLINE( myfctor CL=false CR=0 LEV=0 LOCALS=0 ARGS=G ARGLIVES= RES=G CLOS=() BLOCK(DECL #1{0 uses} = BLOCK(DECL #2{0 uses} = INDIRECT(0, PARAM(0,1)); RECCONSTR(INDIRECT(0, LOCAL(0,2)), POLY_SYS_alloc_store G $(LIT1 G, LIT40 G, POLY_SYS_load_word G $(INDIRECT(1, LOCAL(0,2)) G, LIT0 G) G ), INDIRECT(2, LOCAL(0,2)), INDIRECT(3, LOCAL(0,2)) ) ); BLOCK(RECCONSTR(LOCAL(0,1))) )){LAMBDA}; RECCONSTR(LOCAL(0,1)) )
From above, I understand that functor is compiled to a lambda
expression. But I don't quite see what the body of the lambda expression does. From my understanding, here PARAM(0, 1) refers to the input structure s which is a tuple. However, what does the values of
INDIRECT(0, LOCAL(0,2)) INDIRECT(1, LOCAL(0,2)) INDIRECT(2, LOCAL(0,2)) INDIRECT(3, LOCAL(0,2))
represent? For example, from the printing of the IR of a structure whose type is A is the following:
structure foo : A =
# struct # type element = int # end; BLOCK(DECL #1{0 uses} = LIT0; RECCONSTR(LOCAL(0,1))) structure foo : A
Since the tuple returned in the last statement only contains only one local variable whose value is LIT0, why we could use index 1, 2, 3 above to access other elements in the tuple?
And may I also ask you what does the following code in the above codetree do, and why it does that?
POLY_SYS_alloc_store G $(LIT1 G, LIT40 G, POLY_SYS_load_word G $(INDIRECT(1, LOCAL(0,2)) G, LIT0 G) G )
2. Later I tried to add more content which is a type implemented by a list:
signature A = sig type element end
signature B = sig type element type set end
functor myfctor(s: A) :> B = struct type element = s.element type set = element list end
The printed codetree looks similar to the one given in 1, except the corresponding IR for type set = element list, which is:
DECL #3{0 uses} = BLOCK(RECCONSTR(LIT0, POLY_SYS_alloc_store G $(LIT1 G, LIT40 G, LAMBDA( print-list CL=false CR=0 LEV=0 LOCALS=0 ARGS=G ARGLIVES= RES=G CLOS=() POLY_SYS_load_word G $(GLOBAL (LIT <long>, NIL ) (*GLOBAL*) G, LIT0 G) G $(RECCONSTR(LIT0, LAMBDA( print-element CL=false CR=0 LEV=0 LOCALS=0 ARGS=G ARGLIVES= RES=G CLOS=() POLY_SYS_load_word G $(INDIRECT(1, INDIRECT(0, PARAM(2,1))) G, LIT0 G ) G $(PARAM(0,1) G)){LAMBDA}, INDIRECT(2, INDIRECT(0, PARAM(1,1))), INDIRECT(3, INDIRECT(0, PARAM(1,1))) ) G ) G $(PARAM(0,1) G)){LAMBDA} G ), GLOBAL (FUN "boxed-list", LAMBDAINLINE( boxed-list CL=false CR=0 LEV=0 LOCALS=0 ARGS=G ARGLIVES= RES=G CLOS=() LIT3){LAMBDA} ) (*GLOBAL*){early} G $(RECCONSTR(LIT0, LIT0, INDIRECT(2, INDIRECT(0, PARAM(0,1))), LIT1) G ), GLOBAL (FUN "size-list", LAMBDAINLINE( size-list CL=false CR=0 LEV=0 LOCALS=0 ARGS=G ARGLIVES= RES=G CLOS=() LIT1){LAMBDA} ) (*GLOBAL*){early} G $(RECCONSTR(LIT0, LIT0, LIT0, INDIRECT(3, INDIRECT(0, PARAM(0,1)))) G ) ) );
Here why there are two more functions "size-list" and "boxed-list" appended to the end of code tree?
Sorry for making this email too long, and thank you very much for your help!
-- Yue
Yue Li wrote:
functor myfctor(s: A) :> B = struct type element = s.element end
From above, I understand that functor is compiled to a lambda
expression. But I don't quite see what the body of the lambda expression does.
functor myfctor(s: A) :> B = struct type element = s.element type set = element list end
Here why there are two more functions "size-list" and "boxed-list" appended to the end of code tree?
Functors are implemented as functions, actually normally inline functions. I think, though, that looking into the details of what's going on here is going to be distracting and probably not relevant to what you're trying to do. It's all to do with the implementation of type identifiers at run-time. At one time there was no run-time data associated with a type but from version 5.3 types have print and equality functions that have to be passed into and out of functors. In 5.4 some extra functions, "box" and "size" were added. These functions are created when a type is declared and packaged up into a tuple.
It's complicated in your example because you have an opaque sharing constraint on the result signature. That means that a distinct "print reference" has to be created so that if necessary a pretty printer can be installed on the result type without affecting the implementation type. The code: POLY_SYS_alloc_store G $(LIT1 G, LIT40 G, POLY_SYS_load_word G $(INDIRECT(1, LOCAL(0,2)) G, LIT0 G) G ) creates a new reference using the current value of a reference as its initial value. It's essentially "val r = ref(!x)". You can think of your first functor as roughly equivalent to val funct = fn (eq, pr, box, size) => (eq, ref(!pr), box, size) so that the print reference of the result type is initialised to the currently installed print function for the implementing type but the equality, size and box functions of the implementing type are inherited directly.
You'd probably find it easier to look at the core language rather than the modules language but even there you may find things that are confusing. For example, functions that take tuples or are curried are generated as pairs of mutually recursive functions.
Regards, David