├── .gitignore ├── .travis.yml ├── README.md ├── bower.json └── src └── Control └── Monad ├── IO.purs ├── IO ├── Class.purs └── Effect.purs ├── IOSync.purs └── IOSync └── Class.purs /.gitignore: -------------------------------------------------------------------------------- 1 | /bower_components/ 2 | /node_modules/ 3 | /.pulp-cache/ 4 | /output/ 5 | /.psci* 6 | /src/.webpack.js 7 | /test/ 8 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | dist: trusty 3 | sudo: required 4 | node_js: 6 5 | install: 6 | - npm install -g bower 7 | - npm install 8 | script: 9 | - bower install --production 10 | - npm run -s build 11 | - bower install 12 | - npm -s test 13 | after_success: 14 | - >- 15 | test $TRAVIS_TAG && 16 | echo $GITHUB_TOKEN | pulp login && 17 | echo y | pulp publish --no-push 18 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # purescript-io 2 | 3 | An IO monad for PureScript. 4 | 5 | Don't ask. Don't tell. Joyfully use in secrecy. 6 | 7 | **:warning: :rotating_light: In PureScript 0.12, you can use [Effect](https://github.com/purescript/purescript-effect) and [Aff](https://github.com/slamdata/purescript-aff) without effect rows, so you do not need this library.:warning: :rotating_light:** 8 | 9 | # Introduction 10 | 11 | PureScript's effect system is based on row types, and has no semantic or 12 | algebraic basis. Often, the effect labels have poorly-defined meanings, and 13 | different libraries use completely different labels to represent the same or 14 | overlapping effects. 15 | 16 | While the effect system is undoubtedly useful, there are cases where it's more 17 | of a hindrance — where it doesn't make reasoning about code easier, but instead, 18 | merely adds a ton of boilerplate, as semantically meaningless or overlapping 19 | labels get threaded through endless stacks of functions. 20 | 21 | In those cases, `IO` is here to the rescue! 22 | 23 | `IO a` represents a computation that may be synchronous or asynchronous, and 24 | which will either yield a value of type `a`, run forever, or halt with a 25 | catchable exception. 26 | 27 | Under the covers, `IO` is based on `Aff`, and there is no wrapper so there is 28 | no runtime overhead or performance penalty for `IO`. 29 | 30 | # Effects vs MTL 31 | 32 | In MTL, we would denote effects using type classes: 33 | 34 | ```haskell 35 | class (Monad m) <= MonadConfig m where 36 | readConfig :: m Config 37 | 38 | serverAddress :: forall m. (MonadConfig m) => m InetAddress 39 | ``` 40 | 41 | This serves a similar purpose to effect rows in PureScript, whereby every "label" corresponds to a set of functionality described by a type class. The advantage to using classes over labels is that the semantics can be well-described by the classes. 42 | 43 | Similarly, if we were using `Free` directly, instead of using type classes to abstract over a `Free` encoding, we would denote effects using functors: 44 | 45 | ```haskell 46 | data ConfigF a 47 | = ReadConfig (Config -> a) 48 | 49 | serverAddress :: ReaderT (PrismT' f ConfigF) (Free f) InetAddress 50 | ``` 51 | 52 | In this case, we have all the benefits of finely-grained effects, including semantic descriptions of those effects, but without needing to use labels in effect rows. 53 | 54 | In either MTL or direct-Free encodings, we have all the tools necessary to provide clear information to developers to help them reason about the code they are writing — all without using effect rows. 55 | 56 | Therefore, MTL and direct-Free approaches can be considered alternatives to PureScript's own effect system (which is implemented as a library, and not baked into PureScript). When using one of these alternatives, it can simplify code and improve type inference to use `IO` instead of trying to doubly-encode effects using two systems — one of which has no semantic or algebraic basis. 57 | 58 | # Usage 59 | 60 | `IO` is a newtype for `Aff`, which you can unwrap to be used in your `main`: 61 | 62 | ```haskell 63 | runIO :: forall a. IO a -> Aff (infinity :: INFINITY) a 64 | ``` 65 | 66 | This converts an `IO` into an `Aff`, which you can then "convert" into a 67 | runnable `Eff` using `launchAff` or `runAff`. 68 | 69 | The effect row is closed, which is intentional because `INFINITY` represents 70 | all possible effects. This will help ensure you only call `runIO` at the top 71 | level of your program. 72 | 73 | Besides this, `IO` has almost all the same instances as `Aff`, and may be used 74 | in the same way. In addition, a new `MonadIO` class has been introduced which 75 | allows you to lift `IO` computations into other monads that are as powerful. 76 | 77 | Similarly, `IOSync` exists as an alternative for `Eff`. 78 | 79 | Happy nuke launching! 80 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "purescript-io", 3 | "ignore": [ 4 | "**/.*", 5 | "node_modules", 6 | "bower_components", 7 | "output" 8 | ], 9 | "dependencies": { 10 | "purescript-aff": "^4.0.0", 11 | "purescript-control": "^3.3.0", 12 | "purescript-eff": "^3.1.0", 13 | "purescript-exceptions": "^3.1.0", 14 | "purescript-monoid": "^3.2.0", 15 | "purescript-newtype": "^2.0.0", 16 | "purescript-prelude": "^3.1.0", 17 | "purescript-tailrec": "^3.3.0", 18 | "purescript-transformers": "^3.4.0" 19 | }, 20 | "devDependencies": { 21 | "purescript-psci-support": "^3.0.0" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/Control/Monad/IO.purs: -------------------------------------------------------------------------------- 1 | module Control.Monad.IO 2 | ( module Control.Monad.IO.Effect 3 | , IO(..) 4 | , ParIO(..) 5 | , runIO 6 | , runIO' 7 | , launchIO 8 | ) where 9 | 10 | import Prelude 11 | 12 | import Control.Alt (class Alt) 13 | import Control.Alternative (class Alternative) 14 | import Control.Monad.Aff (Aff, ParAff, launchAff) 15 | import Control.Monad.Aff.Class (class MonadAff) 16 | import Control.Monad.Aff.Unsafe (unsafeCoerceAff) 17 | import Control.Monad.Eff.Class (class MonadEff, liftEff) 18 | import Control.Monad.Eff.Exception (Error) 19 | import Control.Monad.Eff.Unsafe (unsafeCoerceEff) 20 | import Control.Monad.Error.Class (class MonadError, class MonadThrow) 21 | import Control.Monad.IO.Effect (INFINITY) 22 | import Control.Monad.IOSync (IOSync) 23 | import Control.Monad.Rec.Class (class MonadRec) 24 | import Control.MonadZero (class MonadZero) 25 | import Control.Parallel (class Parallel, parallel, sequential) 26 | import Control.Plus (class Plus) 27 | import Data.Monoid (class Monoid) 28 | import Data.Newtype (class Newtype, unwrap, wrap) 29 | 30 | newtype IO a = IO (Aff (infinity :: INFINITY) a) 31 | 32 | newtype ParIO a = ParIO (ParAff (infinity :: INFINITY) a) 33 | 34 | runIO :: IO ~> Aff (infinity :: INFINITY) 35 | runIO = unwrap 36 | 37 | runIO' :: ∀ eff. IO ~> Aff (infinity :: INFINITY | eff) 38 | runIO' = unsafeCoerceAff <<< unwrap 39 | 40 | launchIO :: ∀ a. IO a -> IOSync Unit 41 | launchIO = void <<< liftEff <<< launchAff <<< unwrap 42 | 43 | derive instance newtypeIO :: Newtype (IO a) _ 44 | 45 | derive newtype instance functorIO :: Functor IO 46 | derive newtype instance applyIO :: Apply IO 47 | derive newtype instance applicativeIO :: Applicative IO 48 | derive newtype instance bindIO :: Bind IO 49 | derive newtype instance monadIO :: Monad IO 50 | derive newtype instance monadRecIO :: MonadRec IO 51 | derive newtype instance semigroupIO :: (Semigroup a) => Semigroup (IO a) 52 | derive newtype instance monoidIO :: (Monoid a) => Monoid (IO a) 53 | 54 | derive newtype instance functorParIO :: Functor ParIO 55 | derive newtype instance applyParIO :: Apply ParIO 56 | derive newtype instance applicativeParIO :: Applicative ParIO 57 | derive newtype instance semigroupParIO :: (Semigroup a) => Semigroup (ParIO a) 58 | derive newtype instance monoidParIO :: (Monoid a) => Monoid (ParIO a) 59 | 60 | instance monadAffIO :: MonadAff eff IO where 61 | liftAff = wrap <<< unsafeCoerceAff 62 | 63 | instance parallelParIO :: Parallel ParIO IO where 64 | parallel = ParIO <<< parallel <<< runIO 65 | sequential (ParIO ma) = IO $ sequential ma 66 | 67 | instance monadEffIO :: MonadEff eff IO where 68 | liftEff = wrap <<< liftEff <<< unsafeCoerceEff 69 | 70 | derive newtype instance monadThrowIO :: MonadThrow Error IO 71 | 72 | derive newtype instance monadErrorIO :: MonadError Error IO 73 | 74 | derive newtype instance altIO :: Alt IO 75 | 76 | derive newtype instance plusIO :: Plus IO 77 | 78 | instance alternativeIO :: Alternative IO 79 | 80 | instance monadZeroIO :: MonadZero IO 81 | -------------------------------------------------------------------------------- /src/Control/Monad/IO/Class.purs: -------------------------------------------------------------------------------- 1 | module Control.Monad.IO.Class where 2 | 3 | import Data.Monoid (class Monoid) 4 | import Control.Monad.IO (IO) 5 | import Control.Monad.Cont.Trans (ContT) 6 | import Control.Monad.Except.Trans (ExceptT) 7 | import Control.Monad.List.Trans (ListT) 8 | import Control.Monad.Maybe.Trans (MaybeT) 9 | import Control.Monad.Reader.Trans (ReaderT) 10 | import Control.Monad.RWS.Trans (RWST) 11 | import Control.Monad.State.Trans (StateT) 12 | import Control.Monad.Trans.Class (lift) 13 | import Control.Monad.Writer.Trans (WriterT) 14 | import Prelude 15 | 16 | class (Monad m) <= MonadIO m where 17 | liftIO :: IO ~> m 18 | 19 | instance monadIOIO :: MonadIO IO where 20 | liftIO = id 21 | 22 | instance monadIOContT :: MonadIO m => MonadIO (ContT r m) where 23 | liftIO = lift <<< liftIO 24 | 25 | instance monadIOExceptT :: MonadIO m => MonadIO (ExceptT e m) where 26 | liftIO = lift <<< liftIO 27 | 28 | instance monadIOListT :: MonadIO m => MonadIO (ListT m) where 29 | liftIO = lift <<< liftIO 30 | 31 | instance monadIOMaybe :: MonadIO m => MonadIO (MaybeT m) where 32 | liftIO = lift <<< liftIO 33 | 34 | instance monadIOReader :: MonadIO m => MonadIO (ReaderT r m) where 35 | liftIO = lift <<< liftIO 36 | 37 | instance monadIORWS :: (MonadIO m, Monoid w) => MonadIO (RWST r w s m) where 38 | liftIO = lift <<< liftIO 39 | 40 | instance monadIOState :: MonadIO m => MonadIO (StateT s m) where 41 | liftIO = lift <<< liftIO 42 | 43 | instance monadIOWriter :: (MonadIO m, Monoid w) => MonadIO (WriterT w m) where 44 | liftIO = lift <<< liftIO 45 | -------------------------------------------------------------------------------- /src/Control/Monad/IO/Effect.purs: -------------------------------------------------------------------------------- 1 | module Control.Monad.IO.Effect 2 | ( INFINITY 3 | ) where 4 | 5 | import Control.Monad.Eff (kind Effect) 6 | 7 | foreign import data INFINITY :: Effect 8 | -------------------------------------------------------------------------------- /src/Control/Monad/IOSync.purs: -------------------------------------------------------------------------------- 1 | module Control.Monad.IOSync 2 | ( module Control.Monad.IO.Effect 3 | , IOSync(..) 4 | , runIOSync 5 | , runIOSync' 6 | ) where 7 | 8 | import Control.Alt (class Alt) 9 | import Control.Alternative (class Alternative) 10 | import Control.Monad.Eff (Eff) 11 | import Control.Monad.Eff.Class (class MonadEff, liftEff) 12 | import Control.Monad.Eff.Exception (Error, catchException, error, throwException) 13 | import Control.Monad.Eff.Unsafe (unsafeCoerceEff) 14 | import Control.Monad.Error.Class (class MonadError, class MonadThrow, catchError, throwError) 15 | import Control.Monad.IO.Effect (INFINITY) 16 | import Control.Monad.Rec.Class (class MonadRec) 17 | import Control.MonadZero (class MonadZero) 18 | import Control.Plus (class Plus) 19 | import Data.Monoid (class Monoid, mempty) 20 | import Data.Newtype (class Newtype, unwrap, wrap) 21 | import Prelude 22 | 23 | newtype IOSync a = IOSync (Eff (infinity :: INFINITY) a) 24 | 25 | runIOSync :: IOSync ~> Eff (infinity :: INFINITY) 26 | runIOSync = unwrap 27 | 28 | runIOSync' :: ∀ eff. IOSync ~> Eff (infinity :: INFINITY | eff) 29 | runIOSync' = unsafeCoerceEff <<< unwrap 30 | 31 | derive instance newtypeIOSync :: Newtype (IOSync a) _ 32 | 33 | derive newtype instance functorIOSync :: Functor IOSync 34 | derive newtype instance applyIOSync :: Apply IOSync 35 | derive newtype instance applicativeIOSync :: Applicative IOSync 36 | derive newtype instance bindIOSync :: Bind IOSync 37 | derive newtype instance monadIOSync :: Monad IOSync 38 | 39 | derive newtype instance monadRecIOSync :: MonadRec IOSync 40 | 41 | instance semigroupIOSync :: (Semigroup a) => Semigroup (IOSync a) where 42 | append a b = append <$> a <*> b 43 | 44 | instance monoidIOSync :: (Monoid a) => Monoid (IOSync a) where 45 | mempty = pure mempty 46 | 47 | instance monadEffIOSync :: MonadEff eff IOSync where 48 | liftEff = wrap <<< unsafeCoerceEff 49 | 50 | instance monadErrorIOSync :: MonadError Error IOSync where 51 | catchError a k = liftEff $ 52 | catchException (\e -> unwrap $ k e) (unsafeCoerceEff $ unwrap a) 53 | 54 | instance monadThrowIOSync :: MonadThrow Error IOSync where 55 | throwError = liftEff <<< throwException 56 | 57 | instance altIOSync :: Alt IOSync where 58 | alt a b = a `catchError` const b 59 | 60 | instance plusIOSync :: Plus IOSync where 61 | empty = throwError $ error "plusIOSync.empty" 62 | 63 | instance alternativeIOSync :: Alternative IOSync 64 | 65 | instance monadZeroIOSync :: MonadZero IOSync 66 | -------------------------------------------------------------------------------- /src/Control/Monad/IOSync/Class.purs: -------------------------------------------------------------------------------- 1 | module Control.Monad.IOSync.Class where 2 | 3 | import Data.Monoid (class Monoid) 4 | import Data.Newtype (unwrap, wrap) 5 | import Control.Monad.Eff.Class (liftEff) 6 | import Control.Monad.IO (IO) 7 | import Control.Monad.IOSync (IOSync) 8 | import Control.Monad.Cont.Trans (ContT) 9 | import Control.Monad.Except.Trans (ExceptT) 10 | import Control.Monad.List.Trans (ListT) 11 | import Control.Monad.Maybe.Trans (MaybeT) 12 | import Control.Monad.Reader.Trans (ReaderT) 13 | import Control.Monad.RWS.Trans (RWST) 14 | import Control.Monad.State.Trans (StateT) 15 | import Control.Monad.Trans.Class (lift) 16 | import Control.Monad.Writer.Trans (WriterT) 17 | import Prelude 18 | 19 | class (Monad m) <= MonadIOSync m where 20 | liftIOSync :: IOSync ~> m 21 | 22 | instance monadIOSyncIOSync :: MonadIOSync IOSync where 23 | liftIOSync = id 24 | 25 | instance monadIOSyncIO :: MonadIOSync IO where 26 | liftIOSync = wrap <<< liftEff <<< unwrap 27 | 28 | instance monadIOSyncContT :: MonadIOSync m => MonadIOSync (ContT r m) where 29 | liftIOSync = lift <<< liftIOSync 30 | 31 | instance monadIOSyncExceptT :: MonadIOSync m => MonadIOSync (ExceptT e m) where 32 | liftIOSync = lift <<< liftIOSync 33 | 34 | instance monadIOSyncListT :: MonadIOSync m => MonadIOSync (ListT m) where 35 | liftIOSync = lift <<< liftIOSync 36 | 37 | instance monadIOSyncMaybe :: MonadIOSync m => MonadIOSync (MaybeT m) where 38 | liftIOSync = lift <<< liftIOSync 39 | 40 | instance monadIOSyncReader :: MonadIOSync m => MonadIOSync (ReaderT r m) where 41 | liftIOSync = lift <<< liftIOSync 42 | 43 | instance monadIOSyncRWS :: (MonadIOSync m, Monoid w) => MonadIOSync (RWST r w s m) where 44 | liftIOSync = lift <<< liftIOSync 45 | 46 | instance monadIOSyncState :: MonadIOSync m => MonadIOSync (StateT s m) where 47 | liftIOSync = lift <<< liftIOSync 48 | 49 | instance monadIOSyncWriter :: (MonadIOSync m, Monoid w) => MonadIOSync (WriterT w m) where 50 | liftIOSync = lift <<< liftIOSync 51 | --------------------------------------------------------------------------------