[MLton-commit] r5924

Vesa Karvonen vesak at mlton.org
Wed Aug 22 05:44:32 PDT 2007


Added experimental IO / State Monad base module IOSMonad : IOS_MONAD.

----------------------------------------------------------------------

A   mltonlib/trunk/com/ssh/extended-basis/unstable/detail/io/ios-monad.sml
U   mltonlib/trunk/com/ssh/extended-basis/unstable/detail/ml/smlnj/sigs.cm
U   mltonlib/trunk/com/ssh/extended-basis/unstable/detail/ml/smlnj/unsealed.cm
U   mltonlib/trunk/com/ssh/extended-basis/unstable/extensions.mlb
U   mltonlib/trunk/com/ssh/extended-basis/unstable/public/export/common.sml
A   mltonlib/trunk/com/ssh/extended-basis/unstable/public/io/ios-monad.sig

----------------------------------------------------------------------

Added: mltonlib/trunk/com/ssh/extended-basis/unstable/detail/io/ios-monad.sml
===================================================================
--- mltonlib/trunk/com/ssh/extended-basis/unstable/detail/io/ios-monad.sml	2007-08-22 04:52:17 UTC (rev 5923)
+++ mltonlib/trunk/com/ssh/extended-basis/unstable/detail/io/ios-monad.sml	2007-08-22 12:44:31 UTC (rev 5924)
@@ -0,0 +1,24 @@
+(* Copyright (C) 2007 SSH Communications Security, Helsinki, Finland
+ *
+ * This code is released under the MLton license, a BSD-style license.
+ * See the LICENSE file or http://mlton.org/License for details.
+ *)
+
+structure IOSMonad :> IOS_MONAD = struct
+   datatype sum = datatype Sum.sum
+
+   type ('a, 's) t = 's -> 'a * 's
+
+   exception EOS
+
+   fun fromReader rA s = case rA s of NONE => raise EOS | SOME ? => ?
+   fun fromWriter wV v s = ((), wV (v, s))
+   fun fromPutter pV v s = (pV (s, v), s)
+
+   fun mapState (s2t, t2s) tT = Pair.map (Fn.id, t2s) o tT o s2t
+
+   fun map a2b aT = Pair.map (a2b, Fn.id) o aT
+
+   fun return v s = (v, s)
+   fun op >>= (aT, a2bT) = Fn.uncurry a2bT o aT
+end


Property changes on: mltonlib/trunk/com/ssh/extended-basis/unstable/detail/io/ios-monad.sml
___________________________________________________________________
Name: svn:eol-style
   + native

Modified: mltonlib/trunk/com/ssh/extended-basis/unstable/detail/ml/smlnj/sigs.cm
===================================================================
--- mltonlib/trunk/com/ssh/extended-basis/unstable/detail/ml/smlnj/sigs.cm	2007-08-22 04:52:17 UTC (rev 5923)
+++ mltonlib/trunk/com/ssh/extended-basis/unstable/detail/ml/smlnj/sigs.cm	2007-08-22 12:44:31 UTC (rev 5924)
@@ -57,6 +57,7 @@
    ../../../public/generic/fix.sig
    ../../../public/generic/iso.sig
    ../../../public/generic/tie.sig
+   ../../../public/io/ios-monad.sig
    ../../../public/io/reader.sig
    ../../../public/io/text-io.sig
    ../../../public/io/writer.sig

Modified: mltonlib/trunk/com/ssh/extended-basis/unstable/detail/ml/smlnj/unsealed.cm
===================================================================
--- mltonlib/trunk/com/ssh/extended-basis/unstable/detail/ml/smlnj/unsealed.cm	2007-08-22 04:52:17 UTC (rev 5923)
+++ mltonlib/trunk/com/ssh/extended-basis/unstable/detail/ml/smlnj/unsealed.cm	2007-08-22 12:44:31 UTC (rev 5924)
@@ -43,6 +43,7 @@
    ../../../detail/generic/fix.sml
    ../../../detail/generic/iso.sml
    ../../../detail/generic/tie.sml
+   ../../../detail/io/ios-monad.sml
    ../../../detail/io/reader.sml
    ../../../detail/io/text-io.sml
    ../../../detail/io/writer.sml

Modified: mltonlib/trunk/com/ssh/extended-basis/unstable/extensions.mlb
===================================================================
--- mltonlib/trunk/com/ssh/extended-basis/unstable/extensions.mlb	2007-08-22 04:52:17 UTC (rev 5923)
+++ mltonlib/trunk/com/ssh/extended-basis/unstable/extensions.mlb	2007-08-22 12:44:31 UTC (rev 5924)
@@ -248,6 +248,10 @@
          public/io/writer.sig
          detail/io/writer.sml
 
+         (* IOS Monad *)
+         public/io/ios-monad.sig
+         detail/io/ios-monad.sml
+
          (* Exit *)
          public/control/exit.sig
          detail/control/exit.sml

Modified: mltonlib/trunk/com/ssh/extended-basis/unstable/public/export/common.sml
===================================================================
--- mltonlib/trunk/com/ssh/extended-basis/unstable/public/export/common.sml	2007-08-22 04:52:17 UTC (rev 5923)
+++ mltonlib/trunk/com/ssh/extended-basis/unstable/public/export/common.sml	2007-08-22 12:44:31 UTC (rev 5924)
@@ -73,6 +73,7 @@
 signature FOLD = FOLD
 signature INTEGER = INTEGER
 signature INT_INF = INT_INF
+signature IOS_MONAD = IOS_MONAD
 signature ISO = ISO
 signature LAZY = LAZY
 signature LIST = LIST
@@ -148,6 +149,7 @@
 structure FixedInt : INTEGER = FixedInt
 structure Fn : FN = Fn
 structure Fold : FOLD = Fold
+structure IOSMonad : IOS_MONAD = IOSMonad
 structure Int : INTEGER = Int
 structure Iso : ISO = Iso
 structure Iso : ISO = Iso

Added: mltonlib/trunk/com/ssh/extended-basis/unstable/public/io/ios-monad.sig
===================================================================
--- mltonlib/trunk/com/ssh/extended-basis/unstable/public/io/ios-monad.sig	2007-08-22 04:52:17 UTC (rev 5923)
+++ mltonlib/trunk/com/ssh/extended-basis/unstable/public/io/ios-monad.sig	2007-08-22 12:44:31 UTC (rev 5924)
@@ -0,0 +1,213 @@
+(* Copyright (C) 2007 SSH Communications Security, Helsinki, Finland
+ *
+ * This code is released under the MLton license, a BSD-style license.
+ * See the LICENSE file or http://mlton.org/License for details.
+ *)
+
+(**
+ * Signature for an experimental, deterministic, unified IO stream/state
+ * monad base module {IOSMonad}.
+ *
+ * == About the Design ==
+ *
+ * The goal of this module is to provide a unified basis for
+ * deterministic, monadic IO action combinators.  By deterministic it is
+ * meant that the design does not explicitly support non-deterministic IO
+ * such as backtracking parser combinators.  By unified it is meant that
+ * the design is supposed to support both input and output actions equally
+ * well.  One reason behind using monadic combinators is the implicit
+ * threading of state, which is often convenient.
+ *
+ * The Basis Library provides the {('answer, 'state) StringCvt.reader}
+ * type
+ *
+ *> type ('answer, 'state) reader = 'state -> ('answer * 'state) Option.t
+ *
+ * for functional stream readers.  It can be augmented to a monad by
+ * providing implementations of {return} and {>>=}:
+ *
+ *> val return : 'a -> ('a, 's) reader =
+ *>  fn a => fn s => SOME (a, s)
+ *
+ *> val op >>= : ('a, 's) reader * ('a -> ('b, 's) reader) -> ('b, 's) reader =
+ *>  fn (aM, a2bM) =>
+ *>     fn s =>
+ *>        case aM s
+ *>         of NONE        => NONE
+ *>          | SOME (a, s) => a2bM a s
+ *
+ * While the reader type is technically flexible enough for almost
+ * anything, there are some warts.
+ *
+ * Returning an option gives special support for a single special
+ * condition --- typically an end-of-stream condition.  Other special
+ * conditions or errors still need to be handled in some other way.  For
+ * example, scanning functions, such as {Int.scan}, raise exceptions under
+ * other error conditions.  A wart with returning an option is that
+ * whether or not an end-of-stream condition is a special condition to be
+ * expected or an unexpected error depends on the situation.  Many parsers
+ * are deterministic in the sense that they know when to stop reading the
+ * input.  If the input ends prematurely, it is a non-recoverable error.
+ *
+ * The Basis Library does not provide a type for functional writers.
+ * Handling writers through the {reader} type would be possible.  A writer
+ * would have a type of the form {'a -> (Unit.t, 's) reader}.  IOW, a
+ * writer is a function from values to readers (or IO actions).  This
+ * would allow unified handling of writers (and readers) using the same
+ * monadic combinators.  However, there is no single special condition
+ * that would nicely translate to returning {NONE}.  In other words, for
+ * writers, returning an option is an unnecessary hurdle.  All writers
+ * would just raise exceptions on error conditions.
+ *
+ * In a purely functional language, that does not provide core language
+ * support for exceptions, it would be natural for IO actions to return
+ * either an error value or an answer and the resulting state.  While such
+ * a type would work in Standard ML, which does provide exceptions, it
+ * would not rule out the possibility of raising exceptions in IO actions.
+ * IO combinators would have to deal with two kinds of errors.  Therefore
+ * it seems that handling special conditions or errors solely through core
+ * language exceptions would be simpler, both conceptually and
+ * implementation wise.
+ *
+ * Thus we arrive at the traditional IO monad approximation or the state
+ * monad defined as follows:
+ *
+ *> type ('answer, 'state) t = 'state -> 'answer * 'state
+ *
+ *> val return : 'a -> ('a, 's) t =
+ *>  fn a => fn s => (a, s)
+ *
+ *> val op >>= : ('a, 's) t * ('a -> ('b, 's) t) -> ('b, 's) t =
+ *>  fn (aM, a2bM) =>
+ *>     fn s => let
+ *>        val (a, s) = aM s
+ *>     in
+ *>        a2bM a s
+ *>     end
+ *
+ * The question is then how do core language exceptions interact with the
+ * above monad.
+ *
+ * It turns out that adding core language exceptions to the above monad
+ * essentially gives us a monad with zero(es).  A primitive zero could
+ * be implemented as
+ *
+ *> val zero : ('a, 's) t =
+ *>  fn _ => raise exn
+ *
+ * where {exn} is some exception.  It is easy to verify that {zero} is a
+ * left zero for {>>=}:
+ *
+ *> zero >>= k = zero
+ *
+ * Interestingly, with core language exceptions, it is not necessary to
+ * introduce a primitive zero.  Zeroes can be introduced with the
+ * following combinator:
+ *
+ *> val fail : Exn.t -> ('a, 's) t =
+ *>  fn exn = return () >>= raising exn
+ *
+ * It is further possible to extend from monad with zero to a monad with
+ * plus by adding a primitive alternation combinator:
+ *
+ *> val op <|> : ('a, 's) t BinOp.t =
+ *>  fn (lM, rM) =>
+ *>     fn s =>
+ *>        lM s handle _ => rM s
+ *
+ * It is easy to verify that {<|>} and {zero} form a monoid:
+ *
+ *> zero <|> a = a
+ *> a <|> zero = a
+ *> (a <|> b) <|> c = a <|> (b <|> c)
+ *
+ * The "Left Catch" law also holds trivially:
+ *
+ *> return a <|> b = return a
+ *
+ * The "Left Distribution" law
+ *
+ *> (a <|> b) >>= k = (a >>= k) <|> (b >>= k)
+ *
+ * does not hold, however.  If {a <|> b} succeeds with {a}, then {b} will
+ * not be tried.
+ *
+ * Unfortunately, the alternation combinator is less useful than it might
+ * first appear, because it typically leads to space leaks by holding on
+ * to the entire input or state {s} until the computation finishes.  To
+ * fix space-leaks would require a considerably more complicated monad and
+ * the design space is much larger.
+ *
+ * == Idioms ==
+ *
+ * The reader type of the Basis Library treats the end-of-stream condition
+ * as a special case.  Parsers that do not necessarily fail at
+ * end-of-stream, can be written in several ways.  Perhaps the most
+ * obvious way is to use an input operation that returns an option:
+ *
+ *> val read : (Char.t Option.t, 's) IOSMonad.t -> ('a, 's) IOSMonad.t
+ *
+ * Another way is to parameterize the parser with both an end-of-stream
+ * predicate {eos} and an input operation {input} as follows:
+ *
+ *> val read : {eos : (Bool.t, 's) IOSMonad.t,
+ *>             input : (Char.t, 's) IOSMonad.t} -> ('a, 's) IOSMonad.t
+ *
+ * It is also possible to convert between the two.
+ *
+ * When using monads in a strict language, it is sometimes necessary to
+ * introduce additional laziness to avoid too eager evaluation.  Consider
+ * the following function for creating an IO action that writes a list of
+ * values:
+ *
+ *> fun writeList []      = return ()
+ *>   | writeList (x::xs) = write x >> writeList xs
+ *
+ * The problem is that applying {writeList aList} immediately loops over
+ * the entire list without performing any actions.  This is typically not
+ * desired.  Fortunately, adding the desired laziness is easy.  One can
+ * just use {>>=} as follows:
+ *
+ *> fun writeList []      = return ()
+ *>   | writeList (x::xs) = write x >>= (fn () => writeList xs)
+ *
+ * Now, applying {writeList aList} merely examines the first cons of the
+ * list.
+ *)
+signature IOS_MONAD = sig
+   type ('answer, 'state) t = 'state -> 'answer * 'state
+   (**
+    * The IO / State Monad base type.  In each particular instance of the
+    * monad the {'state} type parameter is fixed.
+    *)
+
+   (** == Monad Base Operations == *)
+
+   val return : 'a -> ('a, 's) t
+   val >>= : ('a, 's) t * ('a -> ('b, 's) t) -> ('b, 's) t
+
+   (** == Lifting == *)
+
+   exception EOS
+   (** Exception for signaling end-of-stream. *)
+
+   val fromReader : ('a, 's) Reader.t -> ('a, 's) t
+   (**
+    * Lifts a reader to the monad.  Unlike a reader, the returned action
+    * raises {EOS} at end-of-stream.
+    *)
+
+   val fromWriter : ('a, 's) Writer.t -> 'a -> (Unit.t, 's) t
+   (** Lifts a functional writer to the monad. *)
+
+   val fromPutter : ('s * 'a) Effect.t -> 'a -> (Unit.t, 's) t
+   (** Lifts an imperative put effect to the monad. *)
+
+   (** == Additional Combinators == *)
+
+   val mapState : ('s, 't) Iso.t -> ('a, 't) t -> ('a, 's) t
+   (** Functionally updates the state/stream of the given action. *)
+
+   val map : ('a -> 'b) -> ('a, 's) t -> ('b, 's) t
+   (** {map a2b aT} is equivalent to {aT >>= return o a2b}. *)
+end


Property changes on: mltonlib/trunk/com/ssh/extended-basis/unstable/public/io/ios-monad.sig
___________________________________________________________________
Name: svn:eol-style
   + native




More information about the MLton-commit mailing list