(*
 * Stream is a parameterized abstract type that defines a functional stream.
 * See 501 Lecture Notes for discussion.
 *)
abstype ('x,'xs) stream = 
    SNIL |
    Body of 'x * 'xs * ('xs->'x) * ('xs->'xs) * ('xs->bool)
with
    (*
     * Exceptions Shd, Stl, and Snth are raised in precisely analogous
     * circumstances as Hd, Tl, and Nth are raised for lists.
     *)
    exception Shd and Stl and Snth

    (*
     * newStream is a fundamentally different constructor than list cons.
     * For streams, the constructor makes an initial value that is then
     * accessed in toto.  There is incremental construction of a stream.
     *)
    fun newStream(x, xs, hdfun, tlfun, nilfun) =
	Body(x, xs, hdfun, tlfun, nilfun)

    (*
     * snil, snull, shd, stl, and snth are the precise analogs of their list
     * counterparts.
     *)
    val snil = SNIL

    fun snull(SNIL) = true |
	snull(x) = false

    fun shd(SNIL) = raise Shd |
	shd(Body(x, xs, hdfun, tlfun, nilfun)) = x:'a

    fun stl(SNIL) = raise Stl |
	stl(Body(x, xs, hdfun, tlfun, nilfun)) =
	    if nilfun(tlfun(xs)) then
		snil
	    else
		Body(hdfun(tlfun(xs)), tlfun(xs), hdfun, tlfun, nilfun)

    fun snth(s, n) =
	if snull(s) then
	    raise Snth
	else if n = 0 then
	    shd(s)
	else
	    snth(stl(s), n-1)

    (*
     * lstream streamifies a list.
     *)
    fun lstream(l) =
	newStream(hd(l), l, hd, tl, null);

    (*
     * sstream streamifies a string, via a list.
     *)
    fun sstream(s) =
	let
	    val es = explode(s)
	in
	    newStream(hd(es), es, hd, tl, null)
	end

    (*
     * fstream streamifies a file for input.  NOTE WELL: since ML's input
     * function violates referential transparency, so does stl on an fstream.
     * Specifically, an fstream is a call-by-var parameter to stl.  This could
     * be prevented if ML gave us some sort of reference equality op on
     * instreams, or access to the instream internals.
     *)
    fun fstream(filename) =
	stl(newStream("",openIn(filename),
	    fn(s)=>inputN(s,1),fn(s)=>s,endOfStream))

    (*
     * stdIn_stream streamifies stdIn.  NOTE: stl must be called first to get
     * it going.  I.e., shd will return null on stdIn_stream until the first
     * stl is called.  It's done this way to avoid a hang on the initial char.
     *)
    val stdIn_stream = newStream("",stdIn,fn(s)=>inputN(s,1),
	fn(s)=>s,endOfStream)

end