For the life of me, I cannot figure out how to use higher order functors in SML/NJ to any practical end.
According to the
SML/NJ docs on the implementation's special features,
it should be possible to specify one functor as an argument to another by use of
the funsig
keyword. Thus, given a signature
signature SIG = sig ... end
we should be able to specify a functor that will produce a module satisfying
SIG
, when applied to a structure satisfying some signature SIG'
. E.g.,
funsig Fn (S:SIG') = SIG
With Fn
declared in this way, we should then (be able to define another
functor that takes this functor as an argument. I.e., we can define a module
that is parameterized over another parameterized module, and presumably use the
latter within the former; thus:
functor Fn' (functor Fn:SIG) =
struct
...
structure S' = Fn (S:SIG')
...
end
It all looks good in theory, but I can't figure out how to actually make use of this pattern.
Here are two instances where I've tried to use this pattern, only to find it impracticable:
For my first attempt, just playing around, I tried to make a functor that would take a functor implementing an ordered set, and produce a module for dealing with sets of integers (not really useful, but it would let you parameterize sets of a given type over different set implementations). I can define the following structures, and they will compile (using Standard ML of New Jersey v110.7):
structure IntOrdKey : ORD_KEY
= struct
type ord_key = int
val compare = Int.compare
end
funsig SET_FN (KEY:ORD_KEY) = ORD_SET
functor IntSetFn (functor SetFn:SET_FN) =
struct
structure Set = SetFn (IntOrdKey)
end
But when I actually try to apply IntSetFn
to a functor that should satisfy the
SET_FN
funsig, it just doesn't parse:
- structure IntSet = IntSetFn (functor ListSetFn);
= ;
= ;;
stdIn:18.1-24.2 Error: syntax error: deleting RPAREN SEMICOLON SEMICOLON
- structure IntSet = IntSetFn (functor BinarySetFn) ;
= ;
= ;
stdIn:19.1-26.2 Error: syntax error: deleting RPAREN SEMICOLON SEMICOLON
My second attempt fails in two ways.
I have defined a structure of nested modules implementing polymorphic and monomorphic stacks (the source file, for the curious). To implement a monomorphic stack, you do
- structure IntStack = Collect.Stack.Mono (type elem = int);
structure IntStack : MONO_STACK?
- IntStack.push(1, IntStack.empty);
val it = - : IntStack.t
and so forth. It seems to work fine so far. Now, I want to define a module that
parameterizes over this functor. So I have defined a funsig for the
Collect.Stack.Mono
functor (which can be seen in my repo). Then, following the
pattern indicated above, I tried to define the following test module:
(* load my little utility library *)
CM.autoload("../../../utils/sources.cm");
functor T (functor StackFn:MONO_STACK) =
struct
structure S = StackFn (type elem = int)
val x = S.push (1, S.empty)
end
But this won't compile! I get a type error:
Error: operator and operand don't agree [overload conflict]
operator domain: S.elem * S.t
operand: [int ty] * S.t
in expression:
S.push (1,S.empty)
uncaught exception Error
raised at: ../compiler/TopLevel/interact/evalloop.sml:66.19-66.27
../compiler/TopLevel/interact/evalloop.sml:44.55
../compiler/TopLevel/interact/evalloop.sml:292.17-292.20
Yet, inside functor T
, I appear to be using the exact same instantiation
pattern that works perfectly at the top level. What am I missing?
Unfortunately, that's not the end of my mishaps. Now, I remove the line causing the type error, leaving,
functor T (functor StackFn:MONO_STACK) =
struct
structure S = StackFn (type elem = int)
end
This compiles fine:
[scanning ../../../utils/sources.cm]
val it = true : bool
[autoloading]
[autoloading done]
functor T(<param>: sig functor StackFn : <fctsig> end) :
sig
structure S : <sig>
end
val it = () : unit
But I cannot actually instantiate the module! Apparently the path access syntax is unsupported for higher order functors?
- structure Test = T (functor Collect.Stack.Mono);
stdIn:43.36-43.43 Error: syntax error: deleting DOT ID DOT
I am at a lost.
I have three related questions:
Many thanks in advance for any answers, hints, or followup questions!
Regarding your first attempt, the right syntax to apply your IntSetFn
functor is:
structure IntSet = IntSetFn (functor SetFn = ListSetFn)
The same applies to your application of the Test
functor in the second attempt:
structure Test = T (functor StackFn = Collect.Stack.Mono)
That should fix the syntax errors.
The type error you get when trying to use your stack structure S
inside functor T
has to do with the way you defined the MONO_STACK
funsig:
funsig MONO_STACK (E:ELEM) = MONO_STACK
This just says that it returns a MONO_STACK
structure, with a fully abstract elem
type. It does not say that its elem
type is gonna be the same as E.elem
. According to that, I would able to pass in a functor like
functor F (E : ELEM) = struct type elem = unit ... end
to your functor T
. Hence, inside T
, the type system is not allowed to assume that type S.elem
= int
, and consequently you get a type error.
To fix this, you need to refine the MONO_STACK
funsig as follows:
funsig MONO_STACK (E:ELEM) = MONO_STACK where type elem = E.elem
That should eliminate the type error.
[Edit]
As for your questions:
Higher-order functors are a little awkward syntactically in SML/NJ because it tries to stay 100% compatible with plain SML, which separates the namespace of functors from that for structures. If that wasn't the case then there wouldn't be the need for funsigs as a separate namespace either (and other syntactic baroqueness), and the language of signatures could simply be extended to include functor types.
Moscow ML is another SML dialect with a higher-order module extension that resolves the compatibility issue somewhat more elegantly (and is more expressive). There also was (now mostly dead) ALice ML, yet another SML dialect with higher-order functors that simply dropped the awkward namespace separation. OCaml of course did not have this constraint in the first place, so its higher-order modules are also more regular syntactically.
The approach seems fine.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With