[MLton] sequences of products
Stephen Weeks
MLton@mlton.org
Sun, 18 Sep 2005 23:42:15 -0700
This note describes a new approach to arbitrary list products, along
with a new approach to products, different from infix & as a datatype
constructor.
The idea is to representat a product as its destructor function. That
is, represent the product of types t1, t2, ..., tn as the type
(t1 -> t2 -> ... -> tn -> 'a) -> 'a
We'd like to use
forall a'. (t1 -> t2 -> ... -> tn -> 'a) -> 'a
but since SML doesn't have first class polymorphism, we'll often have
to use an instantiation with 'a as some fixed type. Fortunately, for
many cases, a single fixed type suffices.
This representation of products has a couple of advantages over &.
First, it supports products of length 1. Second, there is a simple
associative operator for appending two products. If f represents t1,
t2, ..., tn and g represents u1, u2, ..., um, then g o f represents t1,
t2, ..., tn, u1, u2, ..., um. Furthermore, append has a natural unit,
namely, the identity function.
One drawback of this approach vs & is that destructuring requires a
curried function. That is, if p is a product of types t1, t2, ...,
tn, then the following expression destructures p.
p (fn x1 => fn x2 => ... fn xn => e)
The currying is not so concise in SML. One could use a function
declaration instead of an anonymous function, but for many situations
anonymous functions are handy. So, I think it's useful to have a
family of curry functions C<n> so that one can do
p (Cn (fn (x1, x2, ..., xn) => e))
In any case, once we have a representation of products, we can
represent a product of sequences as stream where each element is a
product of the corresponding element of each of the sequences. Then,
all the usual fold/forall/map functions work over streams, passing the
product for each element to the supplied function.
The code below implements this idea, with the added bonus that it
supports all kinds of sequences (arrays, lists, strings, vectors), not
just lists. The varargs stuff is used for constructing arbitrary
products, with directives for embedding each kind of sequence as a
stream.
--------------------------------------------------------------------------------
fun C2 z a b = z (a, b)
fun C3 z a b c = z (a, b, c)
fun C4 z a b c d = z (a, b, c, d)
fun C5 z a b c d e = z (a, b, c, d, e)
fun C6 z a b c d e f = z (a, b, c, d, e, f)
fun C7 z a b c d e f g = z (a, b, c, d, e, f, g)
fun C8 z a b c d e f g h = z (a, b, c, d, e, f, g, h)
fun C9 z a b c d e f g h i = z (a, b, c, d, e, f, g, h, i)
fun const c _ = c
val curry = C2
fun pass x f = f x
fun id x = x
fun $ (a, f) = f a
structure Fold =
struct
val fold = pass
fun step0 h (a1, f) = fold (h a1, f)
fun step1 h $ x = step0 (curry h x) $
fun step2 h $ a1 a2 = step0 (fn x => h (a1, a2, x)) $
end
structure Foldr =
struct
fun foldr (c, f) = Fold.fold (f, pass c)
fun step0 h = Fold.step0 (fn g => g o h)
fun step1 h = Fold.step1 (fn (z, g) => g o curry h z)
end
structure Prod =
struct
type ('a, 'b) t = 'a -> 'b
fun dest (f, g) = f g
val none = id
val one = pass
fun append (f, g) = g o f
end
structure Option =
struct
fun map (opt, f) =
case opt of
NONE => NONE
| SOME x => SOME (f x)
end
structure Stream =
struct
datatype 'a t = T of unit -> ('a * 'a t) option
val empty = T (fn () => NONE)
fun cons (x, s) = T (fn () => SOME (x, s))
fun dest (T f) = f ()
fun unfold (b, f) =
let
fun loop b = T (fn () => Option.map (f b, fn (a, b) => (a, loop b)))
in
loop b
end
fun forever x = unfold ((), fn () => SOME (x, ()))
fun fold (s, b, f) =
let
fun loop (s, b) =
case dest s of
NONE => b
| SOME (a, s) => loop (s, f (a, b))
in
loop (s, b)
end
fun toList s = List.rev (fold (s, [], op ::))
fun map (s, f) =
let
fun loop s =
T (fn () => Option.map (dest s, fn (a, s) => (f a, loop s)))
in
loop s
end
end
structure Prods =
struct
datatype ('a, 'b) t = T of {isNone: bool} * ('a, 'b) Prod.t Stream.t
fun none () = T ({isNone = true}, Stream.forever Prod.none)
fun stream s = T ({isNone = false}, Stream.map (s, Prod.one))
fun toStream (T (_, s)) = Stream.map (s, fn p => Prod.dest (p, id))
local
fun make (size, sub) s =
stream
(Stream.unfold
(0, fn i => if i = size s then NONE else SOME (sub (s, i), i + 1)))
in
fun array a = make (Array.length, Array.sub) a
fun string s = make (String.size, String.sub) s
fun vector v = make (Vector.length, Vector.sub) v
end
fun list l =
stream (Stream.unfold (l, fn [] => NONE | a :: l => SOME (a, l)))
fun toList ps = Stream.toList (toStream ps)
fun append (T ({isNone = i}, s), T ({isNone = i'}, s')) =
T ({isNone = i andalso i'},
Stream.unfold
((s, s'), fn (s, s') =>
let
fun empty i = if i then NONE else raise Fail "length mismatch"
in
case (Stream.dest s, Stream.dest s') of
(NONE, NONE) => NONE
| (NONE, SOME _) => empty i'
| (SOME (p, s), SOME (p', s')) =>
SOME (Prod.append (p, p'), (s, s'))
| (SOME _, NONE) => empty i
end))
fun P $ = Foldr.foldr (none (), fn p => p) $
local
fun make c = Foldr.step1 (fn (x, p) => append (c x, p))
in
val ` = fn $ => make id $
val A = fn $ => make array $
val L = fn $ => make list $
val S = fn $ => make string $
val V = fn $ => make vector $
end
local
fun make (T (is, s), b, f, step, done) =
let
fun loop (s, b) =
case Stream.dest s of
NONE => done b
| SOME (p, ls) => step (Prod.dest (p, f),
b, fn b => loop (ls, b))
in
loop (s, b)
end
in
fun fold (p, b, f) = make (p, b, f, fn (h, b, k) => k (h b), id)
fun forall (p, f) =
make (p, (), f, fn (h, _, k) => h andalso k (), const true)
fun foreach (p, f) =
make (p, (), f, fn (h, (), k) => k (), ignore)
fun map (p, f) =
make (p, (), f, fn (x, (), k) => Stream.cons (x, k ()),
const Stream.empty)
end
end
open Prods
val a = Array.fromList [1.0, 2.0, 3.0]
val l = [1, 2, 3]
val s = "abc"
val v = Vector.fromList ["foo", "bar", "baz"]
local
fun make ts x = (print (ts x); print "\n")
in
val pb = make Bool.toString
val pc = make Char.toString
val pi = make Int.toString
val pr = make Real.toString
end
val () = foreach (P L l $, pi)
val () =
foreach (P L l A a $,
C2 (fn (i, r) => (pi i; pr r)))
val _ : (real * int * char * string) Stream.t =
map (P A a L l S s V v $, C4 id)
val () =
print
(concat
(rev
("\n" :: (fold (P S s V v $,
[], C3 (fn (c, s, ac) => str c :: s :: ac))))))