I'm writing a library to handle simple images in Standard ML. It should support different types used as colour for each pixel, e.g. bool, Word8.word, etc.
I've got a abstype 'a image
with all common functions defined independent of representation ('a
is the colour representation) , but the output formats differ, so I'd like to have different structures.
Is there a way to "open" an abstype
inside a structure? I can only get this working in a very ugly way:
abstype 'clr absimage = Image of {width : int, height : int, data : 'clr array array}
with
fun createFunc (w, h) f = Image {width = w, height = h, data = ...}
fun createBlank (w, h) clr = createFunc (w, h) (fn _ => clr)
...
end
signature IMAGE = sig
type colour
type image
val createFunc : (int * int) -> (int * int -> colour) -> image
val createBlank : (int * int) -> colour -> image
...
val toBinPPM : image -> string -> unit
end
functor ImageFn(C : sig type colour end) = struct
open C
type image = colour absimage
val createFunc = createFunc
val createBlank = createBlank
...
end
structure Image8 :> IMAGE = struct
structure T = ImageFn(struct type colour = Word8.word end)
open T
fun toBinPPM img filename = ...
end
In particular, the definition of the functor requires to write statements like val name = name
for all functions defined in the with ... end
part of abstype
.
Or is my approach completely wrong?
This combination of abstype
and signature
is my attempt to recreate OOP's abstract class with common methods from abstype
and require implementation of other methods in all structures using the signature
P.S. Why does SML disallow statements like open (ImageFn(struct ... end))
and forces to use a temporary structure (T
in the above code)?
There is no reason to use abstype
in today's SML. Consider it deprecated. It is a relict of pre-module times. You can achieve the same effect of hiding the constructors of a type with structures, signatures and sealing (the :>
operator), but in a more flexible and consistent manner. That also explains why it does not integrate nicely with modules -- it predates them and was essentially replaced by them.
In your concrete example, instead of using abstype
, simply define image
as a datatype
directly in the body of the ImageFn
functor, and hide its constructors with a signature annotation, like so:
signature IMAGE =
sig
type colour
type image
val createFunc : int * int -> (int * int -> colour) -> image
val createBlank : int * int -> colour -> image
...
end
signature IMAGE8 =
sig
include IMAGE
val toBinPPM : image -> string -> unit
end
functor ImageFn(type colour) :> IMAGE =
struct
datatype image = Image of {width : int, height : int, data : colour array array}
fun createFunc (w, h) f = Image {width = w, height = h, data = ...}
fun createBlank (w, h) clr = createFunc (w, h) (fn _ => clr)
...
end
structure Image8 :> IMAGE8 =
struct
structure T = ImageFn(type colour = Word8.word)
open T
fun toBinPPM img filename = ...
end
Edit: In fact, it isn't even necessary in this case to define image
as a datatype. A plain type would do just as well and makes the code slightly simpler:
type image = {width : int, height : int, data : colour array array}
As for your PS question: yeah, I don't know either. There is no particular reason. Some SML dialects implement it as an extension.