st2 (empty) → 0.1.0.0
raw patch · 5 files changed
+488/−0 lines, 5 filesdep +basedep +gdpdep +ghc-primsetup-changed
Dependencies added: base, gdp, ghc-prim, primitive
Files
- ChangeLog.md +5/−0
- LICENSE +30/−0
- Setup.hs +2/−0
- src/Control/Monad/ST2.hs +418/−0
- st2.cabal +33/−0
+ ChangeLog.md view
@@ -0,0 +1,5 @@+# Revision history for st2++## 0.1.0.0 -- YYYY-mm-dd++* First version. Released on an unsuspecting world.
+ LICENSE view
@@ -0,0 +1,30 @@+Copyright (c) 2018, chessai++All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions are met:++ * Redistributions of source code must retain the above copyright+ notice, this list of conditions and the following disclaimer.++ * Redistributions in binary form must reproduce the above+ copyright notice, this list of conditions and the following+ disclaimer in the documentation and/or other materials provided+ with the distribution.++ * Neither the name of chessai nor the names of other+ contributors may be used to endorse or promote products derived+ from this software without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ src/Control/Monad/ST2.hs view
@@ -0,0 +1,418 @@+{-# LANGUAGE ConstraintKinds #-}+{-# LANGUAGE CPP #-}+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE KindSignatures #-}+{-# LANGUAGE MagicHash #-}+{-# LANGUAGE NoImplicitPrelude #-}+{-# LANGUAGE Rank2Types #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE TypeOperators #-}+{-# LANGUAGE UnboxedTuples #-}+{-# LANGUAGE UndecidableInstances #-}++{-# OPTIONS_GHC -Wall #-}++-- | This library implements the ST2 monad, a type using GDP (ghosts of departed proofs)+-- to define shared regions of memory between local mutable state threads. This allows+-- us to define a region of the heap in more minute contours, with each state thread+-- having explicit access to regions in memory. This is achieved using the function `runST2`,+-- which in effects lets the user run a computation that makes use of two partially-overlapping+-- memory regions. Within that computation, the user can run sub-computations bound to one or+-- the other memory region. Furthermore, a sub-computation can move any variable that it owns+-- into the common overlap via `share`.+--+-- An example is shown in below, where one sub-computation creates two cells: one+-- private, and the other shared. A second sub-computation has unconstrained access to the+-- shared cell. Yet even though the private reference is also in scope during the second+-- sub-computation, any attempts to access it there will fail to compile.+--+-- >>> :{+-- stSharingDemo :: Bool+-- stSharingDemo = runST2 $ do+-- -- In the "left" memory region, create and return+-- -- two references; one shared, and one not shared.+-- (secret, ref) <- liftL $ do+-- unshared <- newSTRef 42+-- shared <- share =<< newSTRef 17+-- return (unshared, shared)+-- -- In the "right" memory region, mutate the shared+-- -- reference. If we attempt to access the non-shared+-- -- reference here, the program will not compile.+-- liftR $ do+-- let mine = use (symm ref)+-- x <- readSTRef mine+-- writeSTRef mine (x + 1)+-- -- Back in the "left" memory region, verify that the+-- -- unshared reference still holds its original value.+-- liftL $ do+-- check <- readSTRef secret+-- return (check == 42) +-- :}+module Control.Monad.ST2+ ( -- * ST2 API+ ST(..), STRep+ , fixST+ , liftST++ , runST++ , STRef(..)+ , newSTRef+ , readSTRef+ , writeSTRef++ , type (∩)+ , Common+ , share+ , liftL+ , liftR+ , use+ , symm+ , runST2++ -- * Unsafe API+ , toBaseST+ , fromBaseST+ + , STret+ , unsafeInterleaveST, unsafeDupableInterleaveST+ , stToPrim+ , unsafePrimToST+ , unsafeSTToPrim+ , unsafeInlineST+ , stToIO+ , ioToST+ , RealWorld+ ) where++import Control.Applicative (Applicative(pure, (*>), (<*>), liftA2))+import Control.Exception.Base (catch, throwIO, NonTermination(..), BlockedIndefinitelyOnMVar(..))+import Control.Monad (Monad(return, (>>=), (>>)), ap, liftM2)+import Control.Monad.Primitive (PrimMonad(primitive, PrimState), PrimBase(internal), primToPrim, unsafePrimToPrim, unsafeInlinePrim)+import qualified Control.Monad.ST as BaseST+import Data.Eq (Eq((==)))+import Data.Function (($), (.))+import Data.Functor (Functor(fmap))+#if !(MIN_VERSION_base(4,11,0))+import Data.Monoid (Monoid(mempty, mappend))+#else+import Data.Monoid (Monoid(mempty))+#endif+import Data.Semigroup (Semigroup((<>)))+import GHC.IO (IO(IO),unsafeDupableInterleaveIO)+import GHC.MVar (readMVar, putMVar, newEmptyMVar)+import GHC.Prim (State#, realWorld#, unsafeCoerce#, MutVar#, newMutVar#, readMutVar#, writeMutVar#, sameMutVar#, RealWorld, noDuplicate#)+import GHC.Show (Show(showsPrec, showList), showString, showList__)+import GHC.Types (RuntimeRep, TYPE, Any, isTrue#)+import Theory.Named (type (~~))+import Unsafe.Coerce (unsafeCoerce)++-- | Convert an ST2 to an ST+toBaseST :: ST s a -> BaseST.ST s a+{-# INLINE toBaseST #-}+toBaseST = unsafeCoerce++-- | Convert an ST to an ST2+fromBaseST :: BaseST.ST s a -> ST s a+{-# INLINE fromBaseST #-}+fromBaseST = unsafeCoerce++-- The state-transformer monad proper. By default the monad is strict;+-- too many people got bitten by space leaks when it was lazy.++-- | The strict state-transformer monad.+-- A computation of type @'ST' s a@ transforms an internal state indexed+-- by @s@, and returns a value of type @a@.+-- The @s@ parameter is either+--+-- * an uninstantiated type variable (inside invocations of 'runST'), or+--+-- * 'RealWorld' (inside invocations of 'Control.Monad.ST.stToIO').+--+-- It serves to keep the internal states of different invocations+-- of 'runST' separate from each other and from invocations of+-- 'Control.Monad.ST.stToIO'.+--+-- The '>>=' and '>>' operations are strict in the state (though not in+-- values stored in the state). For example,+--+-- @'runST' (writeSTRef _|_ v >>= f) = _|_@+newtype ST s a = ST (STRep (Any ~~ s) a)++-- | Convenience type alias for expressing ST computations+-- more succintly.+type STRep s a = State# s -> (# State# s, a #)++instance PrimMonad (ST s) where+ type PrimState (ST s) = s+ primitive = ST . repToAny#+ {-# INLINE primitive #-}++instance PrimBase (ST s) where+ internal (ST p) = repFromAny# p+ {-# INLINE internal #-}++-- | A simple product type containing both the state thread and value inside of the 'ST'+data STret s a = STret (State# s) a++-- | 'liftST' is useful when we want a lifted result from an 'ST' computation. See+-- 'fixST' below.+liftST :: ST s a -> State# s -> STret s a+liftST (ST m) = \s -> case m (unsafeCoerce# s) of (# s', r #) -> STret (unsafeCoerce# s') r++noDuplicateST :: ST s ()+{-# INLINE noDuplicateST #-}+noDuplicateST = ST $ \s -> (# noDuplicate# s, () #)++-- | 'unsafeInterleaveST' allows an 'ST' computation to be deferred+-- lazily. When passed a value of type @ST a@, the 'ST' computation will+-- only be performed when the value of the @a@ is demanded.+{-# INLINE unsafeInterleaveST #-}+unsafeInterleaveST :: ST s a -> ST s a+unsafeInterleaveST m = unsafeDupableInterleaveST (noDuplicateST >> m)++-- | 'unsafeDupableInterleaveST' allows an 'ST' computation to be deferred+-- lazily. When passed a value of type @ST a@, the 'ST' computation will+-- only be performed when the value of the @a@ is demanded.+--+-- The computation may be performed multiple times by different threads,+-- possibly at the same time. To prevent this, use 'unsafeInterleaveST' instead.+{-# NOINLINE unsafeDupableInterleaveST #-}+-- See Note [unsafeDupableInterleaveIO should not be inlined]+-- in GHC.IO.Unsafe+unsafeDupableInterleaveST :: ST s a -> ST s a+unsafeDupableInterleaveST (ST m) = ST ( \ s ->+ let+ r = case m s of (# _, res #) -> res+ in+ (# s, r #)+ )++-- | Embed a strict state transformer in an 'IO'+-- action. The 'RealWorld' parameter indicates that the internal state+-- used by the 'ST' computation is a special one supplied by the 'IO'+-- monad, and thus distinct from those used by invocations of 'runST'.+stToIO :: ST RealWorld a -> IO a+stToIO (ST m) = IO (unsafeCoerce m)++-- | Convert an 'IO' action into an 'ST' action. The type of the result+-- is constrained to use a 'RealWorld' state, and therefore the result cannot+-- be passed to 'runST'.+ioToST :: IO a -> ST RealWorld a+ioToST (IO m) = ST (unsafeCoerce m)++-- | Convert an 'IO' action to an 'ST' action.+-- This relies on 'IO' and 'ST' having the same representation modulo the+-- constraint on the type of the state.+unsafeIOToST :: IO a -> ST s a+unsafeIOToST (IO io) = ST $ \ s -> (unsafeCoerce# io) s++-- | Convert an 'ST' action to an 'IO' action.+-- This relies on 'IO' and 'ST' having the same representation modulo the+-- constraint on the type of the state.+--+-- For an example demonstrating why this is unsafe, see+-- https://mail.haskell.org/pipermail/haskell-cafe/2009-April/060719.html+unsafeSTToIO :: ST s a -> IO a+unsafeSTToIO (ST m) = IO (unsafeCoerce# m)++-- | Convert an 'ST' action to a 'PrimMonad'.+stToPrim :: PrimMonad m => ST (PrimState m) a -> m a+{-# INLINE stToPrim #-}+stToPrim = primToPrim++-- | Convert any 'PrimBase' to 'ST' with an arbitrary state token. This operation is highly unsafe!+unsafePrimToST :: PrimBase m => m a -> ST s a+{-# INLINE unsafePrimToST #-}+unsafePrimToST = unsafePrimToPrim++-- | Convert an 'ST' action with an arbitrary state token to any 'PrimMonad'. This operation is highly unsafe!+unsafeSTToPrim :: PrimBase m => ST s a -> m a+{-# INLINE unsafeSTToPrim #-}+unsafeSTToPrim = unsafePrimToPrim++-- | Extract the value out of the 'ST' computation. Compare this to 'runST'; in this case, the rest of the program+-- is permitted to reference the state thread 's'. This operation is highly unsafe!+unsafeInlineST :: ST s a -> a+{-# INLINE unsafeInlineST #-}+unsafeInlineST = unsafeInlinePrim++-- | A value of type @STRef s a@ is a mutable variable in state thread @s@,+-- containing a value of type @a@+data STRef s a = STRef (MutVar# s a)++-- |Build a new 'STRef' in the current state thread+newSTRef :: a -> ST s (STRef s a)+newSTRef init = ST $ \s1# ->+ case newMutVar# init (rwFromAny# s1#) of { (# s2#, var# #) ->+ (# (unsafeCoerce# s2#), STRef var# #) }++-- |Read the value of an 'STRef'+readSTRef :: STRef s a -> ST s a+readSTRef (STRef var#) = ST $ \s1# -> rwTupleToAny# (readMutVar# var# (rwFromAny# s1#))++-- | Write a new value into an 'STRef'+writeSTRef :: STRef s a -> a -> ST s ()+writeSTRef (STRef var#) val = ST $ \s1# ->+ case writeMutVar# var# val (rwFromAny# s1#) of+ s2# -> (# (rwToAny# s2#), () #)++-- Just pointer equality on mutable references:+instance Eq (STRef s a) where+ STRef v1# == STRef v2# = isTrue# (sameMutVar# v1# v2#)++instance Functor (ST s) where+ fmap f (ST m) = ST $ \ s ->+ case (m s) of { (# new_s, r #) ->+ (# new_s, f r #) }++instance Applicative (ST s) where+ {-# INLINE pure #-}+ {-# INLINE (*>) #-}+ pure x = ST (\ s -> (# s, x #))+ m *> k = m >>= \ _ -> k+ (<*>) = ap+ liftA2 = liftM2++instance Monad (ST s) where+ {-# INLINE (>>=) #-}+ (>>) = (*>)+ (ST m) >>= k+ = ST (\ s ->+ case (m s) of { (# new_s, r #) ->+ case (k r) of { ST k2 ->+ (k2 new_s) }})++instance Semigroup a => Semigroup (ST s a) where+ (<>) = liftA2 (<>)++instance Monoid a => Monoid (ST s a) where+ mempty = pure mempty+#if !(MIN_VERSION_base(4,11,0))+ mappend = liftA2 mappend+#endif++instance Show (ST s a) where+ showsPrec _ _ = showString "<<ST action>>"+ showList = showList__ (showsPrec 0)++-- | A pretty type alias for 'Common'.+type s ∩ s' = Common s s'++-- | A type that shows that the state threads s and s' refer to a heap+-- region that overlaps in some way such that s and s' can both access+-- the overlap, while maintaining privacy in their own heap regions.+data Common s s'++-- | Move a variable that you own into a region with+-- common overlap.+share :: STRef s a -> ST s (STRef (Common s s') a)+{-# INLINE share #-}+share = return . unsafeCoerce++-- | Lift an 'ST' computation into a context with another heap region+liftL :: ST s a -> ST (Common s s') a+{-# INLINE liftL #-}+liftL = unsafeCoerce++-- | Lift an 'ST' computation into a context with another heap region+liftR :: ST s' a -> ST (Common s s') a+{-# INLINE liftR #-}+liftR = unsafeCoerce++-- | Given proof that one has access to the heap regions s and s',+-- yield an STRef to the region s.+use :: STRef (Common s s') a -> STRef s a+{-# INLINE use #-}+use = unsafeCoerce++-- | Given proof that one has access to the heap regions s and s',+-- yield an 'STRef' that swaps the order of the regions.+symm :: STRef (Common s s') a -> STRef (Common s' s) a+{-# INLINE symm #-}+symm = unsafeCoerce++-- | Return the value computed by a state transformer computation+-- over a shared heap. The @forall@ ensures that the internal state(s)+-- used by the 'ST' computation is inaccessible to the rest of the program.+runST2 :: (forall s s'. ST (Common s s') a) -> a+{-# INLINE runST2 #-}+runST2 (ST st_rep) = case runRegion# st_rep of (# _, a #) -> a++-- | Return the value computed by a state transformer computation.+-- The @forall@ ensures that the internal state used by the 'ST'+-- computation is inaccessible to the rest of the program.+runST :: (forall s. ST s a) -> a+{-# INLINE runST #-}+runST (ST st_rep) = case runRegion# st_rep of (# _, a #) -> a++-- | Allow the result of a state transformer computation to be used (lazily)+-- inside the computation.+--+-- Note that if @f@ is strict, @'fixST' f = _|_@.+fixST :: (a -> ST s a) -> ST s a+-- See Note [fixST]+fixST k = unsafeIOToST $ do+ m <- newEmptyMVar+ ans <- unsafeDupableInterleaveIO+ (readMVar m `catch` \BlockedIndefinitelyOnMVar ->+ throwIO NonTermination)+ result <- unsafeSTToIO (k ans)+ putMVar m result+ return result++{- Note [fixST]+ ~~~~~~~~~~~~+For many years, we implemented fixST much like a pure fixpoint,+using liftST:+ fixST :: (a -> ST s a) -> ST s a+ fixST k = ST $ \ s ->+ let ans = liftST (k r) s+ STret _ r = ans+ in+ case ans of STret s' x -> (# s', x #)+We knew that lazy blackholing could cause the computation to be re-run if the+result was demanded strictly, but we thought that would be okay in the case of+ST. However, that is not the case (see Trac #15349). Notably, the first time+the computation is executed, it may mutate variables that cause it to behave+*differently* the second time it's run. That may allow it to terminate when it+should not. More frighteningly, Arseniy Alekseyev produced a somewhat contrived+example ( https://mail.haskell.org/pipermail/libraries/2018-July/028889.html )+demonstrating that it can break reasonable assumptions in "trustworthy" code,+causing a memory safety violation. So now we implement fixST much like we do+fixIO. See also the implementation notes for fixIO. Simon Marlow wondered+whether we could get away with an IORef instead of an MVar. I believe we+cannot. The function passed to fixST may spark a parallel computation that+demands the final result. Such a computation should block until the final+result is available.+-}++runRegion# :: forall (r :: RuntimeRep) (o :: TYPE r) s.+ (State# (Any ~~ s) -> o) -> o+runRegion# m = m (rwToAny# realWorld#)+{-# INLINE runRegion# #-}++rwToAny# :: forall s s'. State# s' -> State# (Any ~~ s)+rwToAny# x# = unsafeCoerce# x#+{-# INLINE rwToAny# #-}++rwFromAny# :: forall s s'. State# (Any ~~ s) -> State# s'+rwFromAny# x# = unsafeCoerce# x#+{-# INLINE rwFromAny# #-}++--rwTupleFromAny# :: forall s a. (# State# (Any ~~ s), a #) -> (# State# s, a #)+--rwTupleFromAny# (# x, a #) = (# unsafeCoerce# x, a #)++rwTupleToAny# :: forall s a. (# State# s, a #) -> (# State# (Any ~~ s), a #)+rwTupleToAny# (# x, a #) = (# unsafeCoerce# x, a #)+{-# INLINE rwTupleToAny# #-}++repToAny# :: (State# s -> (# State# s, a #)) -> STRep (Any ~~ s) a+repToAny# = unsafeCoerce#+{-# INLINE repToAny# #-}++repFromAny# :: STRep (Any ~~ s) a -> (State# s -> (# State# s, a #))+repFromAny# = unsafeCoerce#+{-# INLINE repFromAny# #-}+
+ st2.cabal view
@@ -0,0 +1,33 @@+name: st2+version: 0.1.0.0+synopsis: shared heap regions between local mutable state threads+description:+ This library implements the ST2 monad, a type using GDP (ghosts of departed proofs)+ to define shared regions of memory between local mutable state threads. This allows+ us to define a region of the heap in more minute contours, with each state thread+ having explicit access to regions in memory. This is achieved using the function `runST2`,+ which in effects lets the user run a computation that makes use of two partially-overlapping+ memory regions. Within that computation, the user can run sub-computations bound to one or+ the other memory region. Furthermore, a sub-computation can move any variable that it owns+ into the common overlap via `share`.+ . + An example is shown in the documentation, where one sub-computation creates two cells: one+ private, and the other shared. A second sub-computation has unconstrained access to the+ shared cell. Yet even though the private reference is also in scope during the second+ sub-computation, any attempts to access it there will fail to compile.+homepage: https://github.com/chessai/st2.git+license: BSD3+license-file: LICENSE+author: chessai+maintainer: chessai1996@gmail.com+copyright: copyright (c) 2018 chessai, copyright (c) 2018 Matt Noonan+category: Data+build-type: Simple+extra-source-files: ChangeLog.md+cabal-version: >=1.10++library+ exposed-modules: Control.Monad.ST2+ build-depends: base >= 4.9 && < 4.13, gdp, ghc-prim, primitive+ hs-source-dirs: src+ default-language: Haskell2010