Search code examples
functional-programmingsmlfunctorabstract-data-type

SML use abstype with signatures


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)?


Solution

  • 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.