This is more of a standard ML question, but I figure folks here will know. I've always found the situations in which poly/ML allows polymorphic values (versus forcing a monotype) a bit puzzling. Take this bit of code, for instance:
datatype 'a box = Empty | Box of 'a datatype 'a wrap = Wrap of 'a val box = Box val wrap = Wrap
val empty = Empty
(* first situation *) val x = Wrap empty (* polymorphic value : 'a box wrap *) val y = wrap empty (* monotype : _a box wrap *)
(* second situation *) fun wrapbox1 x = wrap (box x) (* : 'a -> 'a box wrap *) val wrapbox2 = wrap o box (* : _a -> _a box wrap *)
In the first situation, its clear that SML treats a type constructor differently than it's associated function, which makes sense. I've always treated the "val foo = Foo" syntax as syntactic sugar for "fun foo x = Foo x". Is this right, or only approximately right?
In the second situation, it seems like the expression "val wrapbox2 = wrap o box" is forcing "wrap o box" to be executed "too soon", so the compiler has no choice but to give it a monotype. For instance, changing the expression to "fn () => (wrap o box)" preserves the polymorphism.
So, my main question: Is there a reasonable/memorable set of rules for determining when an expression will produce a polymorphic value as opposed to a monotype? Is this written down somewhere (e.g. in the SML 97 spec/commentary)?
A second, more specific question: Is there a way for values resulting from a function call to maintain their polymorphism? That is, can I produce a polymorphic empty value of type " 'a box wrap " using just "wrap : 'a -> 'a wrap" and "empty : 'a box"?
Where everything is at the top-level, this doesn't seem necessary. However, with a bunch of structures and abstract types around, I've run into this dead end before. Is it possible to get something like "val empty = Foo.mk (Bar.empty, Baz.empty)", with "Bar.empty : 'a bar", and "Baz.empty : 'a baz" and "val empty : ('a bar, 'b baz) foo"?
The only solutions I've found are ditching the nice abstract type Foo.T by moving the datatype def to the signature (boo!) or replacing "val empty" with "fun empty () = ..." and needing to write "(empty ())" everywhere (double boo!). Does anyone know a nice way around this?