[Prev][Next][Index][Thread]
Types & Strictness (forwarded)
Date: Thu, 16 Mar 89 12:23:18 GMT
---- TYPES & STRICTNESS ----
Gavin Wraith
15 Mar 89
Am I musing through my hat on the following ?
The functional programming community seem divided into two camps;
the major camp being proponents of normal order evaluation, a
minority arguing for applicative order. Everybody agrees that the
latter is more efficient, but the former is necessary for easy
manipulation of infinite data-objects.
Data-objects come in two sizes: small and large. Small data-objects
may as well be treated strictly. Why bother to delay updating the
components of the object, even if they will never be accessed, if there
are only a few of them? The updating might be quicker than forming the
closures necessary to do the delay.
Infinite data-objects arise as values of recursive product types.
To be precise about what I mean, recall that finite lists are
values of the recursive sum type
sum list(a) =
nil : unit -> list,
cons : a*list -> list ;
and that infinite lists are values of the recursive product type
product stream(a) =
hd : stream -> a,
tl : stream -> stream ;
and these two should not be confused. Lists in my sense are always
finite and streams are always infinite. You can define a third type
product seq(a) =
start : seq -> opt(a),
next : seq -> opt(seq) ;
where
sum opt(a) =
stop : unit -> opt,
go_on : a -> opt ;
whose values can be construed as either lists or streams via
def list_to_seq by
start (list_to_seq nil) = stop
next (list_to_seq nil) = stop
start (list_to_seq (cons (a,x))) = go_on a
next (list_to_seq (cons (a,x))) = go_on (list_to_seq x);
def stream_to_seq by
start (stream_to_seq x) = go_on (hd x)
next (stream_to_seq x) = go_on (stream_to_seq (tl x));
Note the type of take : NAT->stream(a)->list(a) defined by
def take by
take zero x = nil
take (succ n) x = cons(hd x,take n (tl x));
Now it may be that programmers would simply find it inconvenient to
be this nit-picking about distinctions between types, when a little
fudge with an exception here or there, or a domain theoretic semantics
with bottom elements allows you to avoid the distinctions. However,
>from the mathematical point of view I find it much clearer to retain
a sharp distinction.
I suggest that the only recursive values one ever wants are either
functions or values of recursive products (like stream(a), but I can
give you plenty of other examples). Would it not be feasible to
have data-objects treated strictly unless they were of the latter kind?
After all, you could always translate a finite object into a
potentially infinite one (e.g using an analog of list_to_seq)
when you wanted to handle it lazily, and then chop off a finite
portion (with an analog of take) afterwards.
I would be interested to hear from anyone who has comments, or who
can set down rigorously what I am groping for (if there is anything
worth groping for). I am trying to suggest that a categorical
approach to types should reveal strictness information.
Gavin Wraith
=========================================================================
RESPONSE by Andy Pitts
Translating your example type definitions into a language with unit
type, binary product type (X*Y), binary sum type (X+Y), initial recursive
types (muX.a), and final recursive types (nuX.a) we get
list(a) = muX.unit+(a*X)
stream(a) = nuX.a*X
seq(a) = nuX.(unit+a)*(unit+X)
(? So seq(a) = nuX.unit+a+X+(a*X), where `=' means isomorphic ...?)
The last one looks odd. What about the "lazy list" type most often
mentioned by functional people---i.e. the type of potentially infinite
lists of elements from a? In ML that's
datatype 'a llist = Nil | Cons of 'a * (unit -> 'a llist)
If we extend the type expression language to include explicit
computation types X~ (= "type of computations of type X"), isn't the
meaning of this exactly described by
llist(a) = muX.unit+(a*(X~))
(ML type (unit->X) simulates X~, with evaluation of a computation e:X~
to a value (if any) corresponding to function evaluation of e:unit->X
at the unique value ():unit.)
I am quite enthusiastic about the usefulness of explicit computation
types in a metalanguage for denotational semantics (with just initial
recursive types---but initial with respect to a notion of
approximation between types rather that with respect to all
functions). See Plotkin's notes on "partial domain theory", Constable
& Smith's article in LICS88 and Moggi's really nice (for us category
buffs) article in LICS89.
Andy Pitts