Standard ML functor examples

2020-02-09 04:54发布

Functors in Standard ML are related to the module system and can generate structures based on other structures. An example of a functor generating list combinators for various types of lists is given below, but this example has a problem:

The various types of lists all have advantages -- for example, lazy lists can be infinitely long, and concantenation lists have a O(1) concat operator. But when all of these list types conform to the same signature, the functor can only use their general properties.

My question is therefore: What is a good example of when functors are useful and the various generated structures don't lose their special abilities?

signature MYLIST =
sig
  type 'a t
  val null : 'a t -> bool
  val empty : 'a t
  val cons : 'a * 'a t -> 'a t
  val hd : 'a t -> 'a
  val tl : 'a t -> 'a t
end

structure RegularList : MYLIST =
struct
  type 'a t = 'a list
  val null = List.null
  val empty = []
  val cons = op::
  val hd = List.hd
  val tl = List.tl
end

structure LazyList : MYLIST =
struct
  datatype 'a t = Nil | Cons of 'a * (unit -> 'a t)
   val empty = Nil
   fun null Nil = true
    | null _ = false
   fun cons (x, xs) = Cons (x, fn () => xs)
   fun hd Nil = raise Empty
    | hd (Cons (x, _)) = x
   fun tl Nil = raise Empty
    | tl (Cons (_, f)) = f ()
end

structure ConcatList : MYLIST =
struct
  datatype 'a t = Nil | Singleton of 'a | Concat of 'a t * 'a t
  val empty = Nil
  fun null Nil = true
    | null (Singleton _) = false
    | null (Concat (xs, ys)) = null xs andalso null ys
  fun cons (x, xs) = Concat (Singleton x, xs)
  fun hd Nil = raise Empty
    | hd (Singleton x) = x
    | hd (Concat (xs, ys)) = hd xs
  fun tl Nil = raise Empty
    | tl (Singleton x) = Nil
    | tl (Concat (xs, ys)) = (* exercise *)
end

signature MYLISTCOMB =
sig
  type 'a t
  val length : 'a liste -> int
  val map : ('a -> 'b) -> 'a liste -> 'b liste
  val foldl : ('a * 'b -> 'b) -> 'b -> 'a liste -> 'b
  val append : 'a liste * 'a liste -> 'a liste
  val concat : 'a liste liste -> 'a liste
  val sort : ('a * 'a -> order) -> 'a t -> 'a t
end

functor ListComb (X : MYLIST) : MYLISTCOMB =
struct
  type 'a t = 'a X.t
  open X

  fun length xs =
      if null xs then 0
      else 1 + length (tl xs)

  fun map f xs =
      if null xs then empty
      else cons(f (hd xs), map f (tl xs))

  fun foldl f e xs =
      if null xs then e
      else foldl f (f (hd xs, e)) (tl xs)

  fun append (xs, ys) =
      if null xs then ys
      else cons (hd xs, append (tl xs, ys))

  fun concat xs =
      if null xs then empty
      else append (hd xs, concat (tl xs))

  fun sort cmp xs = (* exercise *)
end

structure RegularListComb = ListComb (RegularList)
structure LazyListComb = ListComb (LazyList)
structure ConcatListComb = ListComb (ConcatList)

3条回答
姐就是有狂的资本
2楼-- · 2020-02-09 05:06

Not sure I fully understand your question. Obviously, functors are useful for defining modular abstractions that (1) are polymorphic, (2) require a whole set of operations over their type parameters, and (3) provide types as part of their result (in particular, abstract types), and (4) provide an entire set of operations.

Note that your example doesn't make use of (3), which probably is the most interesting aspect of functors. Imagine, for example, implementing an abstract matrix type that you want to parameterise over the vector type it is based on.

One specific characteristic of ML functors -- as well as of core-language polymorphic functions -- is that they are parametric. Parametricity is a semantic property saying that evaluation (of polymorphic code) is oblivious to the concrete type(s) it is instantiated with. That is an important property, as it implies all kinds of semantic goodness. In particular, it provides very strong abstraction and reasoning principles (see e.g. Wadler's "Theorem's for free!", or the brief explanation I gave in reply to another question). It also is the basis for type-erasing compilation (i.e., no types are needed at runtime).

Parametricity implies that a single functor cannot have different implementations for different types -- which seems to be what you are asking about. But of course, you are free to write multiple functors that make different semantic/complexity assumptions about their parameters.

Hope that kind of answers your question.

查看更多
萌系小妹纸
3楼-- · 2020-02-09 05:17

Here is a number of useful examples of SML functors. They are made on the following premise: If you can do one set of things, this enables you to do another set of things.

A functor for sets: If you can compare elements, you can create sets using balanced data structures (e.g. binary search trees or other kinds of trees).

signature SET =
sig
    type elem
    type set
    val empty : set
    val singleton : elem -> set
    val union : set -> set -> set
    val intersect : set -> set -> set
end

signature ORD =
sig
    type t
    val compare : t * t -> order
end

functor BalancedSetFunctor(structure Cmp : ORD) :> SET =
struct
    type elem = Cmp.t
    type set = ...

    val empty = ...
    fun singleton x = ...
    fun union s1 s2 = ...
    fun intersect s1 s2 = ...
end

A functor for iteration: For any kind of collection of things (e.g. lists), if you can iterate them, you can automatically fold them. You can also create different structures for different ways to fold across the same datatype (e.g. pre-order, in-order and post-order traversal of trees).

signature ITERABLE =
sig
    type elem
    type collection
    val next : collection -> (elem * collection) option
end

signature FOLD =
sig
    type elem
    type collection
    val fold : (elem * 'b -> 'b) -> 'b -> collection -> 'b
end

functor FoldFunctor(Iter : ITERABLE) :> FOLD =
struct
    type elem = Iter.elem
    type collection = Iter.collection

    fun fold f e xs =
        case Iter.next xs of
            NONE => e
          | SOME (x, xs') => fold f (f (x, e)) xs'
end
查看更多
男人必须洒脱
4楼-- · 2020-02-09 05:19

Functors are "lifters" - they lift (this verb is standard FP terminology): for a given set of types and values, they let you create a new set of types and values on top of them. All the modules conforming to the required module interface can "benefit" from the functor, but they don't lose their special abilities, if by abilities you mean the implementation specific advantages.

Your very example, for instance, works well to demonstrate my point: concatenation lists have a very fast concat operator, as you wrote, and when lifted with the functor, this 'ability' doesn't vanish. It's still there and perhaps even used by the functor code. So in this example the functor code actually benefit from the list implementation, without knowing it. That's a very powerful concept.

On the other hand, since modules have to fit an interface when lifted by a functor, the superfluous values and types are lost in the process, which can be annoying. Still, depending on the ML dialect, this restriction might be somewhat relaxed.

查看更多
登录 后发表回答