{-# LANGUAGE PatternGuards #-} -- | -- Extensible Denotational Semantics -- -- -- This work is a generalization of -- /Extensible Denotational Language Specifications -- Robert Cartwright, Matthias Felleisen -- Theor. Aspects of Computer Software, 1994/ -- -- -- to be referred to as EDLS. -- -- We implement the enhanced EDLS in Haskell and add delimited control. -- To be precise, we implement the Base interpreter (whose sole -- operations are Loop and Error) and the following extensions: CBV -- lambda-calculus, Arithmetic, Storage, Control. The extensions can be -- added to the Base interpreter in any order and in any combination. -- -- Our implementation has the following advantages over EDLS: -- -- * support for delimited control -- * support for a local storage (including `thread-local' storage and -- general delimited dynamic binding) -- * extensions do not have to be composed explicitly, and so the -- composition order truly does not matter. In the -- original EDLS, one -- had to write interpreter composition explicitly. In our approach, an -- extension is pulled in automatically if the program includes the -- corresponding syntactic forms. For example, if a program contains -- storage cell creation and dereference operations, the Storage extension -- will automatically be used to interpret the program. -- -- Our main departure from EDLS is is the removal of the `central -- authority'. There is no semantic `admin' function. Rather, admin is -- part of the source language and can be used at any place in the -- code. The `central authority' of EDLS must be an extensible function, -- requiring meta-language facilities to implement (such as quite -- non-standard Scheme modules). We do not have central -- authority. Rather, we have bureaucracy: each specific effect handler -- interprets its own effects as it can, throwing the rest `upstairs' for -- higher-level bureaucrats to deal with. Extensibility arises -- automatically. -- -- We take the meaning of a program to be the union of Values and -- (unfulfilled) Actions. If the meaning of the program is a (non-bottom) -- value, the program is terminating. If the meaning of the program is an -- Action -- the program finished with an error, such as an action to -- access a non-existing storage cell, or shift without reset, or a -- user-raised error. -- -- Incidentally, EDLS stresses on p. 2 (the last full paragraph) that -- their schema critically relies on the distinction between a complete -- program and a nested program phrase. Our contribution (which is the -- consequence of removing the central admin, making it first-class) is -- eliminating such a distinction! EDLS says, at the very top of p. 3, -- that the handle in the effect message ``is roughly a conventional -- continuation.'' Because the admin of EDLS is `outside' of the program -- (at the point of infinity, so to speak), its continuation indeed -- appears undelimited. By making our `admin' a part of the source -- language, we recognize the handle in the effect message for what it -- is: a _delimited_ continuation. -- -- As we see below, the Control aspect is orthogonal to the CBV -- aspect. So, we can just as easily replace the CBV extension with the -- CBN extension, which will give us the denotation of delimited control -- in CBN. -- -- -- We shall be using Haskell to express denotations, taking advantage of -- the fact that Haskell values are `lifted'. See also the remark on p21 -- of EDLS: someone (ref. [4]) showed that sets and partial functions can -- suffice... -- -- -- Our program denotations are stable: they have the same form regardless -- of a particular composition of modular interpreters. See Section 3.5 -- of EDLS for precise formulation (and the last paragraph of Section 3.4 -- for concise informal statement of stability). Also see the comparison -- with the monads on p. 20: different monads assign numeral radically -- different meanings, depending on the monad. But in EDLS, a numeral has -- exactly the same denotation. -- -- The meaning of an expression is also the same and stable, no matter -- what the interpreter is: it is a function from Env to Computations -- (the latter being the union of values and unfulfilled effects -- the -- same union mentioned above). -- -- However, by using handlers, some effect messages can be subtracted: -- so, strictly speaking, the denotation of a fragment is V + -- effect-message-issued - effect-messages handled. There seem to be a -- connection with effect systems. -- -- -- Additional notes on EDLS: -- -- The abstract and p. 2 (especially the last full paragraph) are -- so well-written! -- -- p. 5, bottom: ``It [ref \x.x, where ref means what it does in ML] -- is a closed expression that does not diverge, is not a value, and -- cannot be reduced to a value without affecting the context. We refer -- to such results as _effects_.'' -- -- Beginning of Section 3 and App A give a very good introduction -- to the denotational semantics. -- -- p. 6: ``Algebraically speaking, the interpreter is (roughly) a -- homomorphism from syntax to semantics'' -- -- ``A straightforward representation of an evaluation context is a -- function from values to computations, which directly corresponds to -- its operational usage as a function from syntactic values to -- expressions.'' (p. 8). EDLS then notices that the `handle' has -- the same type as the denotation of a context. Footnote 6 notes that -- handler is roughly the composition combinator for functions from values -- to computations [contexts!] and it superficially relates to [bind] -- but does not satisfy all monad laws. -- -- The last of EDLS Sec 3.5, p. 15: ``Informally speaking, this property -- [stable denotations] asserts that in the framework of extensible -- semantics, the addition of a programming construct corresponds to the -- addition of a new ``dimension'' to the space of meaning. [The -- equations, featuring injection and projection functions, make that -- precise and clear.] The reader may want to contrast this general -- statement with the numerous papers on the relationship between direct -- and continuation semantics.'' -- -- The practical consequence of the lack of stability of denotations for -- monads is that monadic transformers put layers upon layers of thunks, -- which have to be carried over _in full_ from one sub-expression into -- the other, even if no single subexpression uses all of the features. -- -- The present code is not written in idiomatic Haskell and does not take -- advantage of types at all. The ubiquitous projections from the -- universal domain tantamount to ``dynamic typing.'' The code is -- intentionally written to be close to the EDLS paper, emphasizing -- denotational semantics (whose domains are untyped). One can certainly -- do better, for example, employ user-defined datatypes for tagged -- values, avoiding the ugly string-tagged VT. -- module Control.ExtensibleDS where import Data.Maybe ------------------------------------------------------------------------ -- The base interpreter -- | The universal domain -- Yes, it is untyped... But Denot semantics is like this anyway... -- Bottom is the implicit member of this domain. data Value = VI Int -- integers | VS String -- strings | VP Value Value -- pairs | VF (Value -> Value) -- functions | VT Tag Value -- tagged value type Tag = String type Action = Value -- only differently tagged instance Show Value where show (VI x) = show x show (VS x) = show x show (VT tag v) = "{"++show tag++" "++show v++"}" show (VP x1 x2) = show (x1,x2) show (VF _) = "" -- | Computations: just like in EDLS -- The strictness of inV guarantees that inV bottom = bottom, as -- on Fig. 3 of EDLS -- Error is always a part of the computation -- data Comp = InV !Value | InFX (Value -> Comp) Action instance Show Comp where show (InV v) = show v show (InFX _ a) = "Effect: " ++ show a -- | Auxiliary functions newtype V = V String deriving (Eq, Show) -- Variables type Env v = V -> v empty_env x = error $ "impossible: an open program? var " ++ show x ext_env env x v y | x == y = v ext_env env x v y = env y -- | Note that inV is essentially the identity `partial' continuation inAC action = InFX InV action -- | The error `raise' action and its tag tagErr = "Err" raise err = inAC $ VT tagErr (VS err) -- | The universal handler of actions -- It propagates the action request InFX up. -- Since we do case-analysis on the first argument, v, the handler -- is strict in that argument. That is, handler bottom ==> bottom, -- as required by Fig. 3 of EDLS. -- The last clause is essentially the denotation of a `control channel' handler (InV v) k = k v handler (InFX ka a) k = InFX (\v -> handler (ka v) k) a -- | Expressions and their interpretations -- This is an open data type and an open function. Haskell typeclasses -- are ideal for that. -- -- This is the function \curlyM of EDLS class Interpretor exp where interpret :: exp -> Env Value -> Comp -- | The base language has only two expressions: omega and error -- See Fig. 3 (p. 7) of EDLS -- data BE_err = BE_err data BE_omega = BE_omega instance Interpretor BE_omega where interpret _ _ = InV undefined instance Interpretor BE_err where interpret _ _ = raise "error action" -- | The meaning of a program prog p = interpret p empty_env -- ---------------------------------------------------------------------- -- | Extension: Call-by-Value: see a part Fig. 4 of EDLS (or, Fig. 8) -- -- We add closures (procedures) to the Base type Proc = Value -> Comp class PV u where -- and the corresponding injection/ inP :: Proc -> u -- projection functions prP :: u -> Maybe (Proc) type Var = V data Lam e = Lam V e data App e1 e2 = e1 :@ e2 infixl 1 :@ instance Interpretor V where interpret v env = InV (env v) instance Interpretor e => Interpretor (Lam e) where interpret (Lam x e) env = InV (inP (\d -> interpret e (ext_env env x d))) instance (Interpretor e1, Interpretor e2) => Interpretor (App e1 e2) where interpret (e1 :@ e2) env = handler (interpret e1 env) $ \f -> handler (interpret e2 env) $ \a -> maybe (raise "InP expected") (\g -> g a) (prP f) -- | Create a few variables for use in examples vx, vy, vr, vf :: Var (vx,vy,vr,vf) = (V "x", V "y", V "r", V "f") -- Test of the extension -- Instantiate this extension on the top of the base language -- -- | Denotations of the new actions (inj/proj to/from the Universal Domain) tagProc = "Proc" tagCompV = "CompV" tagCompE = "CompE" -- | inject and project computations inComp :: Comp -> Value inComp (InV v) = VT tagCompV v inComp (InFX k a) = VT tagCompE (VP (VF (inComp . k)) a) prComp :: Value -> Maybe Comp prComp (VT tag v) | tag == tagCompV = Just (InV v) prComp (VT tag v) | tag == tagCompE = pr v where pr (VP (VF k) a) = Just $ InFX (fromJust . prComp . k) a pr _ = Nothing prComp _ = Nothing instance PV Value where inP f = VT tagProc (VF (inComp . f)) prP (VT tag (VF k)) | tag == tagProc = Just $ fromJust . prComp . k prP _ = Nothing test1 = Lam vx (vx :@ BE_err) test1e = prog test1 -- {"Proc" } ------------------------------------------------------------------------ -- | Extension: Arithmetic: see a part Fig. 4 of EDLS -- class PN u where -- inj/proj for numbers inN :: Int -> u prN :: u -> Maybe Int data IntC = IntC Int -- an integer constant data Add1 = Add1 instance Interpretor IntC where interpret (IntC v) _ = InV (inN v) instance Interpretor Add1 where interpret Add1 _ = InV (inP $ \v -> maybe (raise "InN expected") (InV . inN . succ) (prN v)) add1 x = Add1 :@ x -- | Test of the extension -- Instantiate this extension on the top of the CBV evaluator -- instance PN Value where inN n = VI n prN (VI n) = Just n prN _ = Nothing test2 = (Lam vx $ add1 vx) :@ (IntC 1) test2e = prog test2 -- 2 ------------------------------------------------------------------------ -- | Extension: State: see Fig. 5 of EDLS -- newtype Loc = Loc Int deriving (Eq, Show) -- locations class PL u where inL :: Loc -> u prL :: u -> Maybe Loc -- | a better idea is Loc -> Maybe Value, so that when lookup fails, -- we can re-throw that Deref or Set message. That would make the -- storage extensible... -- The first component of the pair is the allocation counter type Sto = (Int, Loc -> Value) tagSto = "Storage" inSto :: Sto -> Value inSto (cnt, st) = VT tagSto (VP (VI cnt) (VF (st . fromJust . prL))) prSto :: Value -> Maybe Sto prSto (VT tag (VP (VI cnt) (VF k))) | tag == tagSto = Just (cnt, k . inL) prSto _ = Nothing init_sto :: Sto init_sto = (0,\l->undefined) -- data StoAct v = InRef v | InDer Loc | InSet Loc v -- | new expressions data Ref e = Ref e data Deref e = Deref e data Set e1 e2 = Set e1 e2 -- | Denotations of the new actions (inj/proj to/from the Universal Domain) tagInRef = "Ref" tagInDer = "Deref" tagInSet = "Set" inRef v = VT tagInRef v prRef (VT tag v) | tag == tagInRef = Just v prRef _ = Nothing inDer v = VT tagInDer (inL v) prDer (VT tag v) | tag == tagInDer = prL v prDer _ = Nothing inSet l v = VT tagInSet (VP (inL l) v) prSet (VT tag (VP l v)) | tag == tagInSet = fmap (\l -> (l,v)) (prL l) prSet _ = Nothing instance Interpretor e => Interpretor (Ref e) where interpret (Ref e) env = handler (interpret e env) $ \v -> inAC(inRef v) instance Interpretor e => Interpretor (Deref e) where interpret (Deref e) env = handler (interpret e env) $ \v -> maybe (raise "InL expected") (inAC . inDer) (prL v) instance (Interpretor e1, Interpretor e2) => Interpretor (Set e1 e2) where interpret (Set e1 e2) env = handler (interpret e1 env) $ \l -> handler (interpret e2 env) $ \v -> maybe (raise "InL expected") (\l -> inAC (inSet l v)) (prL l) -- | Now we need a storage admin -- It is treated as an expression in the source language! -- data StoAdmin e = StoAdmin Sto e instance Interpretor e => Interpretor (StoAdmin e) where interpret (StoAdmin sto e) env = sto_handler sto (interpret e env) InV sto_handler (cnt,st) (InFX k a) f | Just v <- prRef a = let l = Loc cnt sto = (cnt+1, \l' -> if l' == l then v else st l') in sto_handler sto (k (inL l)) f sto_handler sto@(cnt,st) (InFX k a) f | Just l <- prDer a = sto_handler sto (k (st l)) f sto_handler (cnt,st) (InFX k a) f | Just (l,v) <- prSet a = sto_handler (cnt, \l' -> if l' == l then v else st l') (k (inL l)) f sto_handler sto (InV v) f = f (VP v (inSto sto)) -- normal return -- Propagate everything else sto_handler sto (InFX k a) f = InFX (\v -> sto_handler sto (k v) f) a tagLoc = "Loc" instance PL Value where inL (Loc l) = VT tagLoc (VI l) prL (VT tag (VI l)) | tag == tagLoc = Just (Loc l) prL _ = Nothing -- Test of the extension -- Note that we can use several local storages! test3 = (Lam vr $ Lam vf $ vf :@ (Deref vr)) :@ (Ref (IntC 1)) :@ (Lam vx $ add1 vx) -- we forgot the Admin..., so the program terminates with an effect test31 = prog test3 -- Effect: {"Ref" 1} test32 = prog (StoAdmin init_sto test3) -- (2,{"Storage" (1,)}) test33 = prog (StoAdmin init_sto ((Lam vr $ (Lam (V "_") (Deref vr)) :@ (Deref (Set vr (IntC 2)))) :@ (Ref (IntC 1)))) -- (2,{"Storage" (1,)}) ------------------------------------------------------------------------ -- Extension: Delimited continuation -- That is new compared to EDLS -- new effect message: Control ((Value->Comp)->Comp) tagControl = "Control" inControl :: (Value->Comp) -> Value inControl f = VT tagControl (VF (inComp . f)) prControl :: Value -> Maybe (Value->Comp) prControl (VT tag (VF f)) | tag == tagControl = Just (fromJust . prComp . f) prControl _ = Nothing data Control e = Control e instance Interpretor e => Interpretor (Control e) where interpret (Control e) env = handler (interpret e env) $ \a -> maybe (raise "InP expected") (\f -> inAC(inControl f)) (prP a) -- The administrator here is prompt, quite unsurprisingly data ControlAdmin e = ControlAdmin e instance Interpretor e => Interpretor (ControlAdmin e) where interpret (ControlAdmin e) env = ctl_handler (interpret e env) InV ctl_handler (InFX k a) f | Just e <- prControl a = ctl_handler (e (inP k)) f ctl_handler (InV v) f = f v -- normal return -- Propagate everything else ctl_handler (InFX k a) f = InFX (\v -> ctl_handler (k v) f) a -- Ken's tests control v e = Control (Lam (V v) e) prompt e = ControlAdmin e succ0 = (add1 (IntC 0)) -- if we forget Reset... test41 = prog $ add1 (Control (Lam vf succ0)) -- Effect: {"Control" } test42 = prog $ prompt (add1 (control "f" succ0)) -- 1 test43 = prog $ prompt (add1 (control "f" (vf :@ succ0))) --2 test44 = prog $ prompt (add1 (add1 (control "f" (vf :@ succ0)))) -- 3 test45 = prog $ prompt (add1 (add1 (control "f" (vf :@ (vf :@ succ0))))) -- 5 test46 = prog $ prompt (add1 ((Lam vx (add1 vx)) :@ (control "f" (vf :@ (vf :@ succ0))))) -- 5 test47 = prog $ prompt (add1 ((Lam vx (control "f" (IntC 0))) :@ (control "f" (vf :@ (vf :@ succ0))))) -- 0 test48 = prog $ add1 (prompt ((Lam vx (control "f" (IntC 0))) :@ (control "f" (vf :@ (vf :@ succ0))))) -- 1