From eaf3d06f4c7538359081da7e8e589ca5ef38053a Mon Sep 17 00:00:00 2001 From: Nathan Faubion Date: Thu, 3 May 2018 21:04:53 -0700 Subject: [PATCH 1/2] 0.12 Updates (#147) * Migrate Eff to Effect * Fix tests * Remove monoid import * Update package.json * Move namespace to Effect.Aff * Move console dependency to dev --- README.md | 77 +---- bower.json | 27 +- package.json | 4 +- src/Control/Monad/Aff.purs | 404 --------------------------- src/Control/Monad/Aff/AVar.purs | 92 ------ src/Control/Monad/Aff/Class.purs | 45 --- src/Control/Monad/Aff/Compat.purs | 53 ---- src/Control/Monad/Aff/Console.purs | 53 ---- src/Control/Monad/Aff/Unsafe.purs | 9 - src/{Control/Monad => Effect}/Aff.js | 2 +- src/Effect/Aff.purs | 397 ++++++++++++++++++++++++++ src/Effect/Aff/Class.purs | 44 +++ src/Effect/Aff/Compat.purs | 53 ++++ test/Test/Bench.purs | 22 +- test/Test/Main.purs | 220 +++++++-------- 15 files changed, 628 insertions(+), 874 deletions(-) delete mode 100644 src/Control/Monad/Aff.purs delete mode 100644 src/Control/Monad/Aff/AVar.purs delete mode 100644 src/Control/Monad/Aff/Class.purs delete mode 100644 src/Control/Monad/Aff/Compat.purs delete mode 100644 src/Control/Monad/Aff/Console.purs delete mode 100644 src/Control/Monad/Aff/Unsafe.purs rename src/{Control/Monad => Effect}/Aff.js (99%) create mode 100644 src/Effect/Aff.purs create mode 100644 src/Effect/Aff/Class.purs create mode 100644 src/Effect/Aff/Compat.purs diff --git a/README.md b/README.md index 0f524f9..ec4ebb3 100644 --- a/README.md +++ b/README.md @@ -37,9 +37,9 @@ asynchronously without any callbacks. Error handling is baked in so you only deal with it when you want to. The library contains instances for `Semigroup`, `Monoid`, `Apply`, -`Applicative`, `Bind`, `Monad`, `Alt`, `Plus`, `MonadEff`, `MonadError`, and +`Applicative`, `Bind`, `Monad`, `Alt`, `Plus`, `MonadEffect`, `MonadError`, and `Parallel`. These instances allow you to compose asynchronous code as easily -as `Eff`, as well as interop with existing `Eff` code. +as `Effect`, as well as interop with existing `Effect` code. ## Escaping Callback Hell @@ -47,10 +47,10 @@ Hopefully, you're using libraries that already use the `Aff` type, so you don't even have to think about callbacks! If you're building your own library, then you can make an `Aff` from -low-level `Eff` callbacks with `makeAff`. +low-level `Effect` callbacks with `makeAff`. ```purescript -makeAff :: forall eff a. ((Either Error a -> Eff eff Unit) -> Eff eff (Canceler eff)) -> Aff eff a +makeAff :: forall a. ((Either Error a -> Effect Unit) -> Effect Canceler) -> Aff a ``` This function expects you to provide a handler, which should call the @@ -60,7 +60,7 @@ You should also return `Canceler`, which is just a cleanup effect. Since `Aff` threads may be killed, all asynchronous operations should provide a mechanism for unscheduling it. -`Control.Monad.Aff.Compat` provides functions for easily binding FFI +`Effect.Aff.Compat` provides functions for easily binding FFI definitions: ```javascript @@ -84,14 +84,14 @@ exports._ajaxGet = function (request) { // accepts a request ``` ```purescript -foreign import _ajaxGet :: forall eff. Request -> EffFnAff (ajax :: AJAX | eff) Response +foreign import _ajaxGet :: Request -> EffectFnAff Response ``` We can wrap this into an asynchronous computation like so: ```purescript -ajaxGet :: forall eff. Request -> Aff (ajax :: AJAX | eff) Response -ajaxGet = fromEffFnAff <<< _ajaxGet +ajaxGet :: Request -> Aff Response +ajaxGet = fromEffectFnAff <<< _ajaxGet ``` This eliminates callback hell and allows us to write code simply using `do` @@ -103,22 +103,18 @@ example = do log response.body ``` -## Eff +## Effect -All purely synchronous computations (`Eff`) can be lifted to asynchronous -computations with `liftEff` defined in `Control.Monad.Eff.Class`. +All purely synchronous computations (`Effect`) can be lifted to asynchronous +computations with `liftEffect` defined in `Effect.Class`. ```purescript -liftEff $ log "Hello world!" +liftEffect $ log "Hello world!" ``` This lets you write your whole program in `Aff`, and still call out to synchronous code. -If your `Eff` code throws exceptions (`exception :: EXCEPTION`), you can -remove the exception label using `liftEff'`. Exceptions are part of `Aff`s -built-in semantics, so they will always be caught and propagated anyway. - ## Dealing with Failure `Aff` has error handling baked in, so ordinarily you don't have to worry @@ -188,7 +184,7 @@ Because Javascript is single-threaded, forking does not actually cause the computation to be run in a separate thread. Forking just allows the subsequent actions to execute without waiting for the forked computation to complete. -Forking returns a `Fiber eff a`, representing the deferred computation. You can +Forking returns a `Fiber a`, representing the deferred computation. You can kill a `Fiber` with `killFiber`, which will run any cancelers and cleanup, and you can observe a `Fiber`'s final value with `joinFiber`. If a `Fiber` threw an exception, it will be rethrown upon joining. @@ -203,53 +199,6 @@ example = do else (log "Not Canceled") ``` - -## AVars - -The `Control.Monad.Aff.AVar` module contains asynchronous variables, which -are very similar to Haskell's `MVar`. - -`AVar`s represent a value that is either full or empty. Calling `takeVar` on -an empty `AVar` will queue until it is filled by a `putVar`. - -```purescript -example = do - var <- makeEmptyVar - _ <- forkAff do - value <- takeVar var - log $ "Got a value: " <> value - _ <- forkAff do - delay (Milliseconds 100.0) - putVar var "hello" - pure unit -``` -``` -(Waits 100ms) -> Got a value: hello -``` - -Likewise, calling `putVar` on a filled `AVar` will queue until it is emptied by -a `takeVar`. - -```purescript -example = do - var <- makeVar "hello" - _ <- forkAff do - delay (Milliseconds 100.0) - value <- takeVar var - log $ "Got a value: " <> value - putVar var "next" - log "Value put" -``` -``` -(Waits 100ms) -> Got a value: hello -> Value put -``` - -These combinators (and a few more) can be used as the building blocks for -complex asynchronous coordination. - ## Parallel Execution The `Parallel` instance for `Aff` makes writing parallel computations a breeze. diff --git a/bower.json b/bower.json index 52c076a..e4a3b3a 100644 --- a/bower.json +++ b/bower.json @@ -17,22 +17,19 @@ "package.json" ], "dependencies": { - "purescript-console": "^3.0.0", - "purescript-exceptions": "^3.0.0", - "purescript-functions": "^3.0.0", - "purescript-parallel": "^3.0.0", - "purescript-transformers": "^3.0.0", - "purescript-unsafe-coerce": "^3.0.0", - "purescript-datetime": "^3.0.0", - "purescript-free": "^4.0.1", - "purescript-st": "^3.0.0", - "purescript-type-equality": "^2.1.0", - "purescript-avar": "^2.0.0" + "purescript-exceptions": "#compiler/0.12", + "purescript-functions": "#compiler/0.12", + "purescript-parallel": "#compiler/0.12", + "purescript-transformers": "#compiler/0.12", + "purescript-unsafe-coerce": "#compiler/0.12", + "purescript-datetime": "#compiler/0.12", + "purescript-effect": "#compiler/0.12" }, "devDependencies": { - "purescript-partial": "^1.2.0", - "purescript-minibench": "^1.0.0", - "purescript-assert": "^3.0.0", - "purescript-js-timers": "^3.0.0" + "purescript-console": "#compiler/0.12", + "purescript-partial": "#compiler/0.12", + "purescript-minibench": "#compiler/0.12", + "purescript-assert": "#compiler/0.12", + "purescript-free": "#compiler/0.12" } } diff --git a/package.json b/package.json index 3c9f7e7..078eb5c 100644 --- a/package.json +++ b/package.json @@ -8,9 +8,9 @@ "devDependencies": { "jscs": "^3.0.7", "jshint": "^2.9.4", - "pulp": "^11.0.0", + "pulp": "^12.0.0", "purescript-psa": "^0.5.0", - "purescript": "^0.11.0", + "purescript": "^0.12.0-rc1", "rimraf": "^2.5.4" }, "jscsConfig": { diff --git a/src/Control/Monad/Aff.purs b/src/Control/Monad/Aff.purs deleted file mode 100644 index f6a8d06..0000000 --- a/src/Control/Monad/Aff.purs +++ /dev/null @@ -1,404 +0,0 @@ -module Control.Monad.Aff - ( Aff - , Fiber - , ParAff(..) - , Canceler(..) - , makeAff - , launchAff - , launchAff_ - , launchSuspendedAff - , runAff - , runAff_ - , runSuspendedAff - , forkAff - , suspendAff - , liftEff' - , supervise - , attempt - , apathize - , delay - , never - , finally - , invincible - , killFiber - , joinFiber - , cancelWith - , bracket - , BracketConditions - , generalBracket - , nonCanceler - , effCanceler - , module Exports - ) where - -import Prelude - -import Control.Alt (class Alt) -import Control.Alternative (class Alternative) -import Control.Apply (lift2) -import Control.Lazy (class Lazy) -import Control.Monad.Eff (Eff, kind Effect) -import Control.Monad.Eff.Class (class MonadEff, liftEff) -import Control.Monad.Eff.Exception (Error, EXCEPTION, error) -import Control.Monad.Eff.Exception (Error, error, message) as Exports -import Control.Monad.Eff.Unsafe (unsafeCoerceEff, unsafePerformEff) -import Control.Monad.Error.Class (class MonadError, class MonadThrow, throwError, catchError, try) -import Control.Monad.Error.Class (try, throwError, catchError) as Exports -import Control.Monad.Rec.Class (class MonadRec, Step(..)) -import Control.Parallel (parSequence_, parallel) -import Control.Parallel.Class (class Parallel) -import Control.Parallel.Class (sequential, parallel) as Exports -import Control.Plus (class Plus, empty) -import Data.Either (Either(..)) -import Data.Function.Uncurried as Fn -import Data.Monoid (class Monoid, mempty) -import Data.Newtype (class Newtype) -import Data.Time.Duration (Milliseconds(..)) -import Data.Time.Duration (Milliseconds(..)) as Exports -import Partial.Unsafe (unsafeCrashWith) -import Unsafe.Coerce (unsafeCoerce) - --- | An `Aff eff a` is an asynchronous computation with effects `eff`. The --- | computation may either error with an exception, or produce a result of --- | type `a`. `Aff` effects are assembled from primitive `Eff` effects using --- | `makeAff` or `liftEff`. -foreign import data Aff ∷ # Effect → Type → Type - -instance functorAff ∷ Functor (Aff eff) where - map = _map - -instance applyAff ∷ Apply (Aff eff) where - apply = ap - -instance applicativeAff ∷ Applicative (Aff eff) where - pure = _pure - -instance bindAff ∷ Bind (Aff eff) where - bind = _bind - -instance monadAff ∷ Monad (Aff eff) - -instance semigroupAff ∷ Semigroup a ⇒ Semigroup (Aff eff a) where - append = lift2 append - -instance monoidAff ∷ Monoid a ⇒ Monoid (Aff eff a) where - mempty = pure mempty - -instance altAff ∷ Alt (Aff eff) where - alt a1 a2 = catchError a1 (const a2) - -instance plusAff ∷ Plus (Aff eff) where - empty = throwError (error "Always fails") - --- | This instance is provided for compatibility. `Aff` is always stack-safe --- | within a given fiber. This instance will just result in unnecessary --- | bind overhead. -instance monadRecAff ∷ MonadRec (Aff eff) where - tailRecM k = go - where - go a = do - res ← k a - case res of - Done r → pure r - Loop b → go b - -instance monadThrowAff ∷ MonadThrow Error (Aff eff) where - throwError = _throwError - -instance monadErrorAff ∷ MonadError Error (Aff eff) where - catchError = _catchError - -instance monadEffAff ∷ MonadEff eff (Aff eff) where - liftEff = _liftEff - -instance lazyAff ∷ Lazy (Aff eff a) where - defer f = pure unit >>= f - --- | Applicative for running parallel effects. Any `Aff` can be coerced to a --- | `ParAff` and back using the `Parallel` class. -foreign import data ParAff ∷ # Effect → Type → Type - -instance functorParAff ∷ Functor (ParAff eff) where - map = _parAffMap - --- | Runs effects in parallel, combining their results. -instance applyParAff ∷ Apply (ParAff eff) where - apply = _parAffApply - -instance applicativeParAff ∷ Applicative (ParAff eff) where - pure = parallel <<< pure - -instance semigroupParAff ∷ Semigroup a ⇒ Semigroup (ParAff eff a) where - append = lift2 append - -instance monoidParAff ∷ Monoid a ⇒ Monoid (ParAff eff a) where - mempty = pure mempty - --- | Races effects in parallel. Returns the first successful result or the --- | first error if all fail with an exception. Losing branches will be --- | cancelled. -instance altParAff ∷ Alt (ParAff eff) where - alt = _parAffAlt - -instance plusParAff ∷ Plus (ParAff e) where - empty = parallel empty - -instance alternativeParAff ∷ Alternative (ParAff e) - -instance parallelAff ∷ Parallel (ParAff eff) (Aff eff) where - parallel = (unsafeCoerce ∷ ∀ a. Aff eff a → ParAff eff a) - sequential = _sequential - -type OnComplete eff a = - { rethrow ∷ Boolean - , handler ∷ (Either Error a → Eff eff Unit) → Eff eff Unit - } - --- | Represents a forked computation by way of `forkAff`. `Fiber`s are --- | memoized, so their results are only computed once. -newtype Fiber eff a = Fiber - { run ∷ Eff eff Unit - , kill ∷ Fn.Fn2 Error (Either Error Unit → Eff eff Unit) (Eff eff (Eff eff Unit)) - , join ∷ (Either Error a → Eff eff Unit) → Eff eff (Eff eff Unit) - , onComplete ∷ OnComplete eff a → Eff eff (Eff eff Unit) - , isSuspended ∷ Eff eff Boolean - } - -instance functorFiber ∷ Functor (Fiber eff) where - map f t = unsafePerformEff (makeFiber (f <$> joinFiber t)) - -instance applyFiber ∷ Apply (Fiber eff) where - apply t1 t2 = unsafePerformEff (makeFiber (joinFiber t1 <*> joinFiber t2)) - -instance applicativeFiber ∷ Applicative (Fiber eff) where - pure a = unsafePerformEff (makeFiber (pure a)) - --- | Invokes pending cancelers in a fiber and runs cleanup effects. Blocks --- | until the fiber has fully exited. -killFiber ∷ ∀ eff a. Error → Fiber eff a → Aff eff Unit -killFiber e (Fiber t) = liftEff t.isSuspended >>= if _ - then liftEff $ void $ Fn.runFn2 t.kill e (const (pure unit)) - else makeAff \k → effCanceler <$> Fn.runFn2 t.kill e k - --- | Blocks until the fiber completes, yielding the result. If the fiber --- | throws an exception, it is rethrown in the current fiber. -joinFiber ∷ ∀ eff a. Fiber eff a → Aff eff a -joinFiber (Fiber t) = makeAff \k → effCanceler <$> t.join k - --- | A cancellation effect for actions run via `makeAff`. If a `Fiber` is --- | killed, and an async action is pending, the canceler will be called to --- | clean it up. -newtype Canceler eff = Canceler (Error → Aff eff Unit) - -derive instance newtypeCanceler ∷ Newtype (Canceler eff) _ - -instance semigroupCanceler ∷ Semigroup (Canceler eff) where - append (Canceler c1) (Canceler c2) = - Canceler \err → parSequence_ [ c1 err, c2 err ] - --- | A no-op `Canceler` can be constructed with `mempty`. -instance monoidCanceler ∷ Monoid (Canceler eff) where - mempty = nonCanceler - --- | A canceler which does not cancel anything. -nonCanceler ∷ ∀ eff. Canceler eff -nonCanceler = Canceler (const (pure unit)) - --- | A canceler from an Eff action. -effCanceler ∷ ∀ eff. Eff eff Unit → Canceler eff -effCanceler = Canceler <<< const <<< liftEff - --- | Forks an `Aff` from an `Eff` context, returning the `Fiber`. -launchAff ∷ ∀ eff a. Aff eff a → Eff eff (Fiber eff a) -launchAff aff = do - fiber ← makeFiber aff - case fiber of Fiber f → f.run - pure fiber - --- | Forks an `Aff` from an `Eff` context, discarding the `Fiber`. -launchAff_ ∷ ∀ eff a. Aff eff a → Eff eff Unit -launchAff_ = void <<< launchAff - --- | Suspends an `Aff` from an `Eff` context, returning the `Fiber`. -launchSuspendedAff ∷ ∀ eff a. Aff eff a → Eff eff (Fiber eff a) -launchSuspendedAff = makeFiber - --- | Forks an `Aff` from an `Eff` context and also takes a callback to run when --- | it completes. Returns the pending `Fiber`. -runAff ∷ ∀ eff a. (Either Error a → Eff eff Unit) → Aff eff a → Eff eff (Fiber eff Unit) -runAff k aff = launchAff $ liftEff <<< k =<< try aff - --- | Forks an `Aff` from an `Eff` context and also takes a callback to run when --- | it completes, discarding the `Fiber`. -runAff_ ∷ ∀ eff a. (Either Error a → Eff eff Unit) → Aff eff a → Eff eff Unit -runAff_ k aff = void $ runAff k aff - --- | Suspends an `Aff` from an `Eff` context and also takes a callback to run --- | when it completes. Returns the suspended `Fiber`. -runSuspendedAff ∷ ∀ eff a. (Either Error a → Eff eff Unit) → Aff eff a → Eff eff (Fiber eff Unit) -runSuspendedAff k aff = launchSuspendedAff $ liftEff <<< k =<< try aff - --- | Forks am `Aff` from within a parent `Aff` context, returning the `Fiber`. -forkAff ∷ ∀ eff a. Aff eff a → Aff eff (Fiber eff a) -forkAff = _fork true - --- | Suspends an `Aff` from within a parent `Aff` context, returning the `Fiber`. --- | A suspended `Aff` is not executed until a consumer observes the result --- | with `joinFiber`. -suspendAff ∷ ∀ eff a. Aff eff a → Aff eff (Fiber eff a) -suspendAff = _fork false - --- | Pauses the running fiber. -delay ∷ ∀ eff. Milliseconds → Aff eff Unit -delay (Milliseconds n) = Fn.runFn2 _delay Right n - --- | An async computation which does not resolve. -never ∷ ∀ eff a. Aff eff a -never = makeAff \_ → pure mempty - --- | All `Eff` exceptions are implicitly caught within an `Aff` context, but --- | standard `liftEff` won't remove the effect label. -liftEff' ∷ ∀ eff a. Eff (exception ∷ EXCEPTION | eff) a → Aff eff a -liftEff' = liftEff <<< unsafeCoerceEff - --- | A monomorphic version of `try`. Catches thrown errors and lifts them --- | into an `Either`. -attempt ∷ ∀ eff a. Aff eff a → Aff eff (Either Error a) -attempt = try - --- | Ignores any errors. -apathize ∷ ∀ eff a. Aff eff a → Aff eff Unit -apathize = attempt >>> map (const unit) - --- | Runs the first effect after the second, regardless of whether it completed --- | successfully or the fiber was cancelled. -finally ∷ ∀ eff a. Aff eff Unit → Aff eff a → Aff eff a -finally fin a = bracket (pure unit) (const fin) (const a) - --- | Runs an effect such that it cannot be killed. -invincible ∷ ∀ eff a. Aff eff a → Aff eff a -invincible a = bracket a (const (pure unit)) pure - --- | Attaches a custom `Canceler` to an action. If the computation is canceled, --- | then the custom `Canceler` will be run afterwards. -cancelWith ∷ ∀ eff a. Aff eff a → Canceler eff → Aff eff a -cancelWith aff (Canceler cancel) = - generalBracket (pure unit) - { killed: \e _ → cancel e - , failed: const pure - , completed: const pure - } - (const aff) - --- | Guarantees resource acquisition and cleanup. The first effect may acquire --- | some resource, while the second will dispose of it. The third effect makes --- | use of the resource. Disposal is always run last, regardless. Neither --- | acquisition nor disposal may be cancelled and are guaranteed to run until --- | they complete. -bracket ∷ ∀ eff a b. Aff eff a → (a → Aff eff Unit) → (a → Aff eff b) → Aff eff b -bracket acquire completed = - generalBracket acquire - { killed: const completed - , failed: const completed - , completed: const completed - } - -type Supervised eff a = - { fiber ∷ Fiber eff a - , supervisor ∷ Supervisor eff - } - --- | Creates a new supervision context for some `Aff`, guaranteeing fiber --- | cleanup when the parent completes. Any pending fibers forked within --- | the context will be killed and have their cancelers run. -supervise ∷ ∀ eff a. Aff eff a → Aff eff a -supervise aff = - generalBracket (liftEff acquire) - { killed: \err sup → parSequence_ [ killFiber err sup.fiber, killAll err sup ] - , failed: const (killAll killError) - , completed: const (killAll killError) - } - (joinFiber <<< _.fiber) - where - killError ∷ Error - killError = - error "[Aff] Child fiber outlived parent" - - killAll ∷ Error → Supervised eff a → Aff eff Unit - killAll err sup = makeAff \k → - Fn.runFn3 _killAll err sup.supervisor (k (pure unit)) - - acquire ∷ Eff eff (Supervised eff a) - acquire = do - sup ← Fn.runFn2 _makeSupervisedFiber ffiUtil aff - case sup.fiber of Fiber f → f.run - pure sup - -foreign import data Supervisor ∷ # Effect → Type -foreign import _pure ∷ ∀ eff a. a → Aff eff a -foreign import _throwError ∷ ∀ eff a. Error → Aff eff a -foreign import _catchError ∷ ∀ eff a. Aff eff a → (Error → Aff eff a) → Aff eff a -foreign import _fork ∷ ∀ eff a. Boolean → Aff eff a → Aff eff (Fiber eff a) -foreign import _map ∷ ∀ eff a b. (a → b) → Aff eff a → Aff eff b -foreign import _bind ∷ ∀ eff a b. Aff eff a → (a → Aff eff b) → Aff eff b -foreign import _delay ∷ ∀ a eff. Fn.Fn2 (Unit → Either a Unit) Number (Aff eff Unit) -foreign import _liftEff ∷ ∀ eff a. Eff eff a → Aff eff a -foreign import _parAffMap ∷ ∀ eff a b. (a → b) → ParAff eff a → ParAff eff b -foreign import _parAffApply ∷ ∀ eff a b. ParAff eff (a → b) → ParAff eff a → ParAff eff b -foreign import _parAffAlt ∷ ∀ eff a. ParAff eff a → ParAff eff a → ParAff eff a -foreign import _makeFiber ∷ ∀ eff a. Fn.Fn2 FFIUtil (Aff eff a) (Eff eff (Fiber eff a)) -foreign import _makeSupervisedFiber ∷ ∀ eff a. Fn.Fn2 FFIUtil (Aff eff a) (Eff eff (Supervised eff a)) -foreign import _killAll ∷ ∀ eff. Fn.Fn3 Error (Supervisor eff) (Eff eff Unit) (Eff eff (Canceler eff)) -foreign import _sequential ∷ ∀ eff a. ParAff eff a → Aff eff a - -type BracketConditions eff a b = - { killed ∷ Error → a → Aff eff Unit - , failed ∷ Error → a → Aff eff Unit - , completed ∷ b → a → Aff eff Unit - } - --- | A general purpose bracket which lets you observe the status of the --- | bracketed action. The bracketed action may have been killed with an --- | exception, thrown an exception, or completed successfully. -foreign import generalBracket ∷ ∀ eff a b. Aff eff a → BracketConditions eff a b → (a → Aff eff b) → Aff eff b - --- | Constructs an `Aff` from low-level `Eff` effects using a callback. A --- | `Canceler` effect should be returned to cancel the pending action. The --- | supplied callback may be invoked only once. Subsequent invocation are --- | ignored. -foreign import makeAff ∷ ∀ eff a. ((Either Error a → Eff eff Unit) → Eff eff (Canceler eff)) → Aff eff a - -makeFiber ∷ ∀ eff a. Aff eff a → Eff eff (Fiber eff a) -makeFiber aff = Fn.runFn2 _makeFiber ffiUtil aff - -newtype FFIUtil = FFIUtil - { isLeft ∷ ∀ a b. Either a b → Boolean - , fromLeft ∷ ∀ a b. Either a b → a - , fromRight ∷ ∀ a b. Either a b → b - , left ∷ ∀ a b. a → Either a b - , right ∷ ∀ a b. b → Either a b - } - -ffiUtil ∷ FFIUtil -ffiUtil = FFIUtil - { isLeft - , fromLeft: unsafeFromLeft - , fromRight: unsafeFromRight - , left: Left - , right: Right - } - where - isLeft ∷ ∀ a b. Either a b → Boolean - isLeft = case _ of - Left _ -> true - Right _ → false - - unsafeFromLeft ∷ ∀ a b. Either a b → a - unsafeFromLeft = case _ of - Left a → a - Right _ → unsafeCrashWith "unsafeFromLeft: Right" - - unsafeFromRight ∷ ∀ a b. Either a b → b - unsafeFromRight = case _ of - Right a → a - Left _ → unsafeCrashWith "unsafeFromRight: Left" diff --git a/src/Control/Monad/Aff/AVar.purs b/src/Control/Monad/Aff/AVar.purs deleted file mode 100644 index 36d05e5..0000000 --- a/src/Control/Monad/Aff/AVar.purs +++ /dev/null @@ -1,92 +0,0 @@ -module Control.Monad.Aff.AVar - ( module Control.Monad.Eff.AVar - , makeVar - , makeEmptyVar - , status - , isEmptyVar - , isFilledVar - , isKilledVar - , takeVar - , tryTakeVar - , putVar - , tryPutVar - , readVar - , tryReadVar - , killVar - ) where - -import Prelude -import Control.Monad.Aff (Aff, makeAff, effCanceler) -import Control.Monad.Eff.AVar (AVar, AVAR, AVarStatus(..), isEmpty, isFilled, isKilled) -import Control.Monad.Eff.AVar as AVar -import Control.Monad.Eff.Class (liftEff) -import Control.Monad.Eff.Exception (Error) -import Data.Maybe (Maybe) - --- | Creates a fresh AVar with an initial value. -makeVar ∷ ∀ eff a. a → Aff (avar ∷ AVAR | eff) (AVar a) -makeVar = liftEff <<< AVar.makeVar - --- | Creates a fresh AVar. -makeEmptyVar ∷ ∀ eff a. Aff (avar ∷ AVAR | eff) (AVar a) -makeEmptyVar = liftEff AVar.makeEmptyVar - --- | Synchronously checks the status of an AVar. -status ∷ ∀ eff a. AVar a → Aff (avar ∷ AVAR | eff) (AVar.AVarStatus a) -status = liftEff <<< AVar.status - --- | Synchronously checks whether an AVar currently is empty. -isEmptyVar ∷ ∀ eff a. AVar a → Aff (avar ∷ AVAR | eff) Boolean -isEmptyVar = liftEff <<< AVar.isEmptyVar - --- | Synchronously checks whether an AVar currently has a value. -isFilledVar ∷ ∀ eff a. AVar a → Aff (avar ∷ AVAR | eff) Boolean -isFilledVar = liftEff <<< AVar.isFilledVar - --- | Synchronously checks whether an AVar has been killed. -isKilledVar ∷ ∀ eff a. AVar a → Aff (avar ∷ AVAR | eff) Boolean -isKilledVar = liftEff <<< AVar.isKilledVar - --- | Takes the AVar value, leaving it empty. If the AVar is already empty, --- | the callback will be queued until the AVar is filled. Multiple takes will --- | resolve in order as the AVar fills. -takeVar ∷ ∀ eff a. AVar a → Aff (avar ∷ AVAR | eff) a -takeVar avar = makeAff \k → do - c ← AVar.takeVar avar k - pure (effCanceler c) - --- | Attempts to synchronously take an AVar value, leaving it empty. If the --- | AVar is empty, this will return `Nothing`. -tryTakeVar ∷ ∀ eff a. AVar a → Aff (avar ∷ AVAR | eff) (Maybe a) -tryTakeVar = liftEff <<< AVar.tryTakeVar - --- | Sets the value of the AVar. If the AVar is already filled, it will be --- | queued until the value is emptied. Multiple puts will resolve in order as --- | the AVar becomes available. -putVar ∷ ∀ eff a. a → AVar a → Aff (avar ∷ AVAR | eff) Unit -putVar value avar = makeAff \k → do - c ← AVar.putVar value avar k - pure (effCanceler c) - --- | Attempts to synchronously fill an AVar. If the AVar is already filled, --- | this will do nothing. Returns true or false depending on if it succeeded. -tryPutVar ∷ ∀ eff a. a → AVar a → Aff (avar ∷ AVAR | eff) Boolean -tryPutVar value = liftEff <<< AVar.tryPutVar value - --- | Reads the AVar value. Unlike `takeVar`, this will not leave the AVar empty. --- | If the AVar is empty, this will queue until it is filled. Multiple reads --- | will resolve at the same time, as soon as possible. -readVar ∷ ∀ eff a. AVar a → Aff (avar ∷ AVAR | eff) a -readVar avar = makeAff \k → do - c ← AVar.readVar avar k - pure (effCanceler c) - --- | Attempts to synchronously read an AVar. If the AVar is empty, this will --- | return `Nothing`. -tryReadVar ∷ ∀ eff a. AVar a → Aff (avar ∷ AVAR | eff) (Maybe a) -tryReadVar = liftEff <<< AVar.tryReadVar - --- | Kills the AVar with an exception. All pending and future actions will --- | resolve immediately with the provided exception. -killVar ∷ ∀ eff a. Error → AVar a → Aff (avar ∷ AVAR | eff) Unit -killVar error = liftEff <<< AVar.killVar error diff --git a/src/Control/Monad/Aff/Class.purs b/src/Control/Monad/Aff/Class.purs deleted file mode 100644 index dd6c0c5..0000000 --- a/src/Control/Monad/Aff/Class.purs +++ /dev/null @@ -1,45 +0,0 @@ -module Control.Monad.Aff.Class where - -import Prelude -import Control.Monad.Aff (Aff) -import Control.Monad.Cont.Trans (ContT) -import Control.Monad.Eff.Class (class MonadEff) -import Control.Monad.Except.Trans (ExceptT) -import Control.Monad.List.Trans (ListT) -import Control.Monad.Maybe.Trans (MaybeT) -import Control.Monad.Reader.Trans (ReaderT) -import Control.Monad.RWS.Trans (RWST) -import Control.Monad.State.Trans (StateT) -import Control.Monad.Trans.Class (lift) -import Control.Monad.Writer.Trans (WriterT) -import Data.Monoid (class Monoid) - -class MonadEff eff m ⇐ MonadAff eff m | m → eff where - liftAff ∷ ∀ a. Aff eff a → m a - -instance monadAffAff ∷ MonadAff e (Aff e) where - liftAff = id - -instance monadAffContT ∷ MonadAff eff m ⇒ MonadAff eff (ContT r m) where - liftAff = lift <<< liftAff - -instance monadAffExceptT ∷ MonadAff eff m ⇒ MonadAff eff (ExceptT e m) where - liftAff = lift <<< liftAff - -instance monadAffListT ∷ MonadAff eff m ⇒ MonadAff eff (ListT m) where - liftAff = lift <<< liftAff - -instance monadAffMaybe ∷ MonadAff eff m ⇒ MonadAff eff (MaybeT m) where - liftAff = lift <<< liftAff - -instance monadAffReader ∷ MonadAff eff m ⇒ MonadAff eff (ReaderT r m) where - liftAff = lift <<< liftAff - -instance monadAffRWS ∷ (MonadAff eff m, Monoid w) ⇒ MonadAff eff (RWST r w s m) where - liftAff = lift <<< liftAff - -instance monadAffState ∷ MonadAff eff m ⇒ MonadAff eff (StateT s m) where - liftAff = lift <<< liftAff - -instance monadAffWriter ∷ (MonadAff eff m, Monoid w) ⇒ MonadAff eff (WriterT w m) where - liftAff = lift <<< liftAff diff --git a/src/Control/Monad/Aff/Compat.purs b/src/Control/Monad/Aff/Compat.purs deleted file mode 100644 index b1fafc4..0000000 --- a/src/Control/Monad/Aff/Compat.purs +++ /dev/null @@ -1,53 +0,0 @@ --- | This module provides compatability functions for constructing `Aff`s which --- | are defined via the FFI. -module Control.Monad.Aff.Compat - ( EffFnAff(..) - , EffFnCanceler(..) - , EffFnCb - , fromEffFnAff - , module Control.Monad.Eff.Uncurried - ) where - -import Prelude -import Control.Monad.Aff (Aff, Canceler(..), makeAff, nonCanceler) -import Control.Monad.Eff.Exception (Error) -import Control.Monad.Eff.Uncurried (EffFn1, EffFn2, EffFn3, mkEffFn1, mkEffFn2, mkEffFn3, runEffFn1, runEffFn2, runEffFn3) -import Data.Either (Either(..)) - -type EffFnCb eff a = EffFn1 eff a Unit - -newtype EffFnAff eff a = EffFnAff (EffFn2 eff (EffFnCb eff Error) (EffFnCb eff a) (EffFnCanceler eff)) - -newtype EffFnCanceler eff = EffFnCanceler (EffFn3 eff Error (EffFnCb eff Error) (EffFnCb eff Unit) Unit) - --- | Lift a FFI definition into an `Aff`. `EffFnAff` makes use of `EffFn` so --- | `Eff` thunks are unnecessary. A definition might follow this example: --- | --- | ```javascript --- | exports._myAff = function (onError, onSuccess) { --- | var cancel = doSomethingAsync(function (err, res) { --- | if (err) { --- | onError(err); --- | } else { --- | onSuccess(res); --- | } --- | }); --- | return function (cancelError, onCancelerError, onCancelerSuccess) { --- | cancel(); --- | onCancelerSuccess(); --- | }; --- | }; --- | ``` --- | --- | ```purescript --- | foreign import _myAff :: forall eff. EffFnAff (myeffect :: MYEFFECT | eff) String --- | --- | myAff :: forall eff. Aff (myeffect :: MYEFFECT | eff) String --- | myAff = fromEffFnAff _myAff --- | ```` -fromEffFnAff ∷ ∀ eff a. EffFnAff eff a → Aff eff a -fromEffFnAff (EffFnAff eff) = makeAff \k → do - EffFnCanceler canceler ← runEffFn2 eff (mkEffFn1 (k <<< Left)) (mkEffFn1 (k <<< Right)) - pure $ Canceler \e → makeAff \k2 → do - runEffFn3 canceler e (mkEffFn1 (k2 <<< Left)) (mkEffFn1 (k2 <<< Right)) - pure nonCanceler diff --git a/src/Control/Monad/Aff/Console.purs b/src/Control/Monad/Aff/Console.purs deleted file mode 100644 index 2f40551..0000000 --- a/src/Control/Monad/Aff/Console.purs +++ /dev/null @@ -1,53 +0,0 @@ -module Control.Monad.Aff.Console - ( module Exports - , log - , logShow - , warn - , warnShow - , error - , errorShow - , info - , infoShow - ) where - -import Prelude -import Control.Monad.Aff (Aff) -import Control.Monad.Eff.Class (liftEff) -import Control.Monad.Eff.Console (CONSOLE) as Exports -import Control.Monad.Eff.Console as C - --- | Write a message to the console. Shorthand for `liftEff $ log x`. -log ∷ ∀ eff. String → Aff (console ∷ C.CONSOLE | eff) Unit -log = liftEff <<< C.log - --- | Write a value to the console, using its `Show` instance to produce a --- | `String`. Shorthand for `liftEff $ logShow x`. -logShow ∷ ∀ a eff. Show a ⇒ a → Aff (console ∷ C.CONSOLE | eff) Unit -logShow = liftEff <<< C.logShow - --- | Write a warning to the console. Shorthand for `liftEff $ warn x`. -warn ∷ ∀ eff. String → Aff (console ∷ C.CONSOLE | eff) Unit -warn = liftEff <<< C.warn - --- | Write a warning value to the console, using its `Show` instance to produce --- | a `String`. Shorthand for `liftEff $ warnShow x`. -warnShow ∷ ∀ a eff. Show a ⇒ a → Aff (console ∷ C.CONSOLE | eff) Unit -warnShow = liftEff <<< C.warnShow - --- | Write an error to the console. Shorthand for `liftEff $ error x`. -error ∷ ∀ eff. String → Aff (console ∷ C.CONSOLE | eff) Unit -error = liftEff <<< C.error - --- | Write an error value to the console, using its `Show` instance to produce a --- | `String`. Shorthand for `liftEff $ errorShow x`. -errorShow ∷ ∀ a eff. Show a ⇒ a → Aff (console ∷ C.CONSOLE | eff) Unit -errorShow = liftEff <<< C.errorShow - --- | Write an info message to the console. Shorthand for `liftEff $ info x`. -info ∷ ∀ eff. String → Aff (console ∷ C.CONSOLE | eff) Unit -info = liftEff <<< C.info - --- | Write an info value to the console, using its `Show` instance to produce a --- | `String`. Shorthand for `liftEff $ infoShow x`. -infoShow ∷ ∀ a eff. Show a ⇒ a → Aff (console ∷ C.CONSOLE | eff) Unit -infoShow = liftEff <<< C.infoShow diff --git a/src/Control/Monad/Aff/Unsafe.purs b/src/Control/Monad/Aff/Unsafe.purs deleted file mode 100644 index 0d05e0d..0000000 --- a/src/Control/Monad/Aff/Unsafe.purs +++ /dev/null @@ -1,9 +0,0 @@ -module Control.Monad.Aff.Unsafe - ( unsafeCoerceAff - ) where - -import Control.Monad.Aff (Aff) -import Unsafe.Coerce (unsafeCoerce) - -unsafeCoerceAff ∷ ∀ eff1 eff2 a. Aff eff1 a -> Aff eff2 a -unsafeCoerceAff = unsafeCoerce diff --git a/src/Control/Monad/Aff.js b/src/Effect/Aff.js similarity index 99% rename from src/Control/Monad/Aff.js rename to src/Effect/Aff.js index 1689250..e9a066d 100644 --- a/src/Control/Monad/Aff.js +++ b/src/Effect/Aff.js @@ -1054,7 +1054,7 @@ exports._fork = function (immediate) { }; }; -exports._liftEff = Aff.Sync; +exports._liftEffect = Aff.Sync; exports._parAffMap = function (f) { return function (aff) { diff --git a/src/Effect/Aff.purs b/src/Effect/Aff.purs new file mode 100644 index 0000000..0249e6e --- /dev/null +++ b/src/Effect/Aff.purs @@ -0,0 +1,397 @@ +module Effect.Aff + ( Aff + , Fiber + , ParAff(..) + , Canceler(..) + , makeAff + , launchAff + , launchAff_ + , launchSuspendedAff + , runAff + , runAff_ + , runSuspendedAff + , forkAff + , suspendAff + , supervise + , attempt + , apathize + , delay + , never + , finally + , invincible + , killFiber + , joinFiber + , cancelWith + , bracket + , BracketConditions + , generalBracket + , nonCanceler + , effectCanceler + , module Exports + ) where + +import Prelude + +import Control.Alt (class Alt) +import Control.Alternative (class Alternative) +import Control.Apply (lift2) +import Control.Lazy (class Lazy) +import Control.Monad.Error.Class (class MonadError, class MonadThrow, throwError, catchError, try) +import Control.Monad.Error.Class (try, throwError, catchError) as Exports +import Control.Monad.Rec.Class (class MonadRec, Step(..)) +import Control.Parallel (parSequence_, parallel) +import Control.Parallel.Class (class Parallel) +import Control.Parallel.Class (sequential, parallel) as Exports +import Control.Plus (class Plus, empty) +import Data.Either (Either(..)) +import Data.Function.Uncurried as Fn +import Data.Newtype (class Newtype) +import Data.Time.Duration (Milliseconds(..)) +import Data.Time.Duration (Milliseconds(..)) as Exports +import Effect (Effect) +import Effect.Class (class MonadEffect, liftEffect) +import Effect.Exception (Error, error) +import Effect.Exception (Error, error, message) as Exports +import Effect.Unsafe (unsafePerformEffect) +import Partial.Unsafe (unsafeCrashWith) +import Unsafe.Coerce (unsafeCoerce) + +-- | An `Aff a` is an asynchronous computation with effects. The +-- | computation may either error with an exception, or produce a result of +-- | type `a`. `Aff` effects are assembled from primitive `Effect` effects using +-- | `makeAff` or `liftEffect`. +foreign import data Aff ∷ Type → Type + +instance functorAff ∷ Functor Aff where + map = _map + +instance applyAff ∷ Apply Aff where + apply = ap + +instance applicativeAff ∷ Applicative Aff where + pure = _pure + +instance bindAff ∷ Bind Aff where + bind = _bind + +instance monadAff ∷ Monad Aff + +instance semigroupAff ∷ Semigroup a ⇒ Semigroup (Aff a) where + append = lift2 append + +instance monoidAff ∷ Monoid a ⇒ Monoid (Aff a) where + mempty = pure mempty + +instance altAff ∷ Alt Aff where + alt a1 a2 = catchError a1 (const a2) + +instance plusAff ∷ Plus Aff where + empty = throwError (error "Always fails") + +-- | This instance is provided for compatibility. `Aff` is always stack-safe +-- | within a given fiber. This instance will just result in unnecessary +-- | bind overhead. +instance monadRecAff ∷ MonadRec Aff where + tailRecM k = go + where + go a = do + res ← k a + case res of + Done r → pure r + Loop b → go b + +instance monadThrowAff ∷ MonadThrow Error Aff where + throwError = _throwError + +instance monadErrorAff ∷ MonadError Error Aff where + catchError = _catchError + +instance monadEffectAff ∷ MonadEffect Aff where + liftEffect = _liftEffect + +instance lazyAff ∷ Lazy (Aff a) where + defer f = pure unit >>= f + +-- | Applicative for running parallel effects. Any `Aff` can be coerced to a +-- | `ParAff` and back using the `Parallel` class. +foreign import data ParAff ∷ Type → Type + +instance functorParAff ∷ Functor ParAff where + map = _parAffMap + +-- | Runs effects in parallel, combining their results. +instance applyParAff ∷ Apply ParAff where + apply = _parAffApply + +instance applicativeParAff ∷ Applicative ParAff where + pure = parallel <<< pure + +instance semigroupParAff ∷ Semigroup a ⇒ Semigroup (ParAff a) where + append = lift2 append + +instance monoidParAff ∷ Monoid a ⇒ Monoid (ParAff a) where + mempty = pure mempty + +-- | Races effects in parallel. Returns the first successful result or the +-- | first error if all fail with an exception. Losing branches will be +-- | cancelled. +instance altParAff ∷ Alt ParAff where + alt = _parAffAlt + +instance plusParAff ∷ Plus ParAff where + empty = parallel empty + +instance alternativeParAff ∷ Alternative ParAff + +instance parallelAff ∷ Parallel ParAff Aff where + parallel = (unsafeCoerce ∷ ∀ a. Aff a → ParAff a) + sequential = _sequential + +type OnComplete a = + { rethrow ∷ Boolean + , handler ∷ (Either Error a → Effect Unit) → Effect Unit + } + +-- | Represents a forked computation by way of `forkAff`. `Fiber`s are +-- | memoized, so their results are only computed once. +newtype Fiber a = Fiber + { run ∷ Effect Unit + , kill ∷ Fn.Fn2 Error (Either Error Unit → Effect Unit) (Effect (Effect Unit)) + , join ∷ (Either Error a → Effect Unit) → Effect (Effect Unit) + , onComplete ∷ OnComplete a → Effect (Effect Unit) + , isSuspended ∷ Effect Boolean + } + +instance functorFiber ∷ Functor Fiber where + map f t = unsafePerformEffect (makeFiber (f <$> joinFiber t)) + +instance applyFiber ∷ Apply Fiber where + apply t1 t2 = unsafePerformEffect (makeFiber (joinFiber t1 <*> joinFiber t2)) + +instance applicativeFiber ∷ Applicative Fiber where + pure a = unsafePerformEffect (makeFiber (pure a)) + +-- | Invokes pending cancelers in a fiber and runs cleanup effects. Blocks +-- | until the fiber has fully exited. +killFiber ∷ ∀ a. Error → Fiber a → Aff Unit +killFiber e (Fiber t) = liftEffect t.isSuspended >>= if _ + then liftEffect $ void $ Fn.runFn2 t.kill e (const (pure unit)) + else makeAff \k → effectCanceler <$> Fn.runFn2 t.kill e k + +-- | Blocks until the fiber completes, yielding the result. If the fiber +-- | throws an exception, it is rethrown in the current fiber. +joinFiber ∷ Fiber ~> Aff +joinFiber (Fiber t) = makeAff \k → effectCanceler <$> t.join k + +-- | A cancellation effect for actions run via `makeAff`. If a `Fiber` is +-- | killed, and an async action is pending, the canceler will be called to +-- | clean it up. +newtype Canceler = Canceler (Error → Aff Unit) + +derive instance newtypeCanceler ∷ Newtype Canceler _ + +instance semigroupCanceler ∷ Semigroup Canceler where + append (Canceler c1) (Canceler c2) = + Canceler \err → parSequence_ [ c1 err, c2 err ] + +-- | A no-op `Canceler` can be constructed with `mempty`. +instance monoidCanceler ∷ Monoid Canceler where + mempty = nonCanceler + +-- | A canceler which does not cancel anything. +nonCanceler ∷ Canceler +nonCanceler = Canceler (const (pure unit)) + +-- | A canceler from an Effect action. +effectCanceler ∷ Effect Unit → Canceler +effectCanceler = Canceler <<< const <<< liftEffect + +-- | Forks an `Aff` from an `Effect` context, returning the `Fiber`. +launchAff ∷ ∀ a. Aff a → Effect (Fiber a) +launchAff aff = do + fiber ← makeFiber aff + case fiber of Fiber f → f.run + pure fiber + +-- | Forks an `Aff` from an `Effect` context, discarding the `Fiber`. +launchAff_ ∷ ∀ a. Aff a → Effect Unit +launchAff_ = void <<< launchAff + +-- | Suspends an `Aff` from an `Effect` context, returning the `Fiber`. +launchSuspendedAff ∷ ∀ a. Aff a → Effect (Fiber a) +launchSuspendedAff = makeFiber + +-- | Forks an `Aff` from an `Effect` context and also takes a callback to run when +-- | it completes. Returns the pending `Fiber`. +runAff ∷ ∀ a. (Either Error a → Effect Unit) → Aff a → Effect (Fiber Unit) +runAff k aff = launchAff $ liftEffect <<< k =<< try aff + +-- | Forks an `Aff` from an `Effect` context and also takes a callback to run when +-- | it completes, discarding the `Fiber`. +runAff_ ∷ ∀ a. (Either Error a → Effect Unit) → Aff a → Effect Unit +runAff_ k aff = void $ runAff k aff + +-- | Suspends an `Aff` from an `Effect` context and also takes a callback to run +-- | when it completes. Returns the suspended `Fiber`. +runSuspendedAff ∷ ∀ a. (Either Error a → Effect Unit) → Aff a → Effect (Fiber Unit) +runSuspendedAff k aff = launchSuspendedAff $ liftEffect <<< k =<< try aff + +-- | Forks am `Aff` from within a parent `Aff` context, returning the `Fiber`. +forkAff ∷ ∀ a. Aff a → Aff (Fiber a) +forkAff = _fork true + +-- | Suspends an `Aff` from within a parent `Aff` context, returning the `Fiber`. +-- | A suspended `Aff` is not executed until a consumer observes the result +-- | with `joinFiber`. +suspendAff ∷ ∀ a. Aff a → Aff (Fiber a) +suspendAff = _fork false + +-- | Pauses the running fiber. +delay ∷ Milliseconds → Aff Unit +delay (Milliseconds n) = Fn.runFn2 _delay Right n + +-- | An async computation which does not resolve. +never ∷ ∀ a. Aff a +never = makeAff \_ → pure mempty + +-- | A monomorphic version of `try`. Catches thrown errors and lifts them +-- | into an `Either`. +attempt ∷ ∀ a. Aff a → Aff (Either Error a) +attempt = try + +-- | Ignores any errors. +apathize ∷ ∀ a. Aff a → Aff Unit +apathize = attempt >>> map (const unit) + +-- | Runs the first effect after the second, regardless of whether it completed +-- | successfully or the fiber was cancelled. +finally ∷ ∀ a. Aff Unit → Aff a → Aff a +finally fin a = bracket (pure unit) (const fin) (const a) + +-- | Runs an effect such that it cannot be killed. +invincible ∷ ∀ a. Aff a → Aff a +invincible a = bracket a (const (pure unit)) pure + +-- | Attaches a custom `Canceler` to an action. If the computation is canceled, +-- | then the custom `Canceler` will be run afterwards. +cancelWith ∷ ∀ a. Aff a → Canceler → Aff a +cancelWith aff (Canceler cancel) = + generalBracket (pure unit) + { killed: \e _ → cancel e + , failed: const pure + , completed: const pure + } + (const aff) + +-- | Guarantees resource acquisition and cleanup. The first effect may acquire +-- | some resource, while the second will dispose of it. The third effect makes +-- | use of the resource. Disposal is always run last, regardless. Neither +-- | acquisition nor disposal may be cancelled and are guaranteed to run until +-- | they complete. +bracket ∷ ∀ a b. Aff a → (a → Aff Unit) → (a → Aff b) → Aff b +bracket acquire completed = + generalBracket acquire + { killed: const completed + , failed: const completed + , completed: const completed + } + +type Supervised a = + { fiber ∷ Fiber a + , supervisor ∷ Supervisor + } + +-- | Creates a new supervision context for some `Aff`, guaranteeing fiber +-- | cleanup when the parent completes. Any pending fibers forked within +-- | the context will be killed and have their cancelers run. +supervise ∷ ∀ a. Aff a → Aff a +supervise aff = + generalBracket (liftEffect acquire) + { killed: \err sup → parSequence_ [ killFiber err sup.fiber, killAll err sup ] + , failed: const (killAll killError) + , completed: const (killAll killError) + } + (joinFiber <<< _.fiber) + where + killError ∷ Error + killError = + error "[Aff] Child fiber outlived parent" + + killAll ∷ Error → Supervised a → Aff Unit + killAll err sup = makeAff \k → + Fn.runFn3 _killAll err sup.supervisor (k (pure unit)) + + acquire ∷ Effect (Supervised a) + acquire = do + sup ← Fn.runFn2 _makeSupervisedFiber ffiUtil aff + case sup.fiber of Fiber f → f.run + pure sup + +foreign import data Supervisor ∷ Type +foreign import _pure ∷ ∀ a. a → Aff a +foreign import _throwError ∷ ∀ a. Error → Aff a +foreign import _catchError ∷ ∀ a. Aff a → (Error → Aff a) → Aff a +foreign import _fork ∷ ∀ a. Boolean → Aff a → Aff (Fiber a) +foreign import _map ∷ ∀ a b. (a → b) → Aff a → Aff b +foreign import _bind ∷ ∀ a b. Aff a → (a → Aff b) → Aff b +foreign import _delay ∷ ∀ a. Fn.Fn2 (Unit → Either a Unit) Number (Aff Unit) +foreign import _liftEffect ∷ ∀ a. Effect a → Aff a +foreign import _parAffMap ∷ ∀ a b. (a → b) → ParAff a → ParAff b +foreign import _parAffApply ∷ ∀ a b. ParAff (a → b) → ParAff a → ParAff b +foreign import _parAffAlt ∷ ∀ a. ParAff a → ParAff a → ParAff a +foreign import _makeFiber ∷ ∀ a. Fn.Fn2 FFIUtil (Aff a) (Effect (Fiber a)) +foreign import _makeSupervisedFiber ∷ ∀ a. Fn.Fn2 FFIUtil (Aff a) (Effect (Supervised a)) +foreign import _killAll ∷ Fn.Fn3 Error Supervisor (Effect Unit) (Effect Canceler) +foreign import _sequential ∷ ParAff ~> Aff + +type BracketConditions a b = + { killed ∷ Error → a → Aff Unit + , failed ∷ Error → a → Aff Unit + , completed ∷ b → a → Aff Unit + } + +-- | A general purpose bracket which lets you observe the status of the +-- | bracketed action. The bracketed action may have been killed with an +-- | exception, thrown an exception, or completed successfully. +foreign import generalBracket ∷ ∀ a b. Aff a → BracketConditions a b → (a → Aff b) → Aff b + +-- | Constructs an `Aff` from low-level `Effect` effects using a callback. A +-- | `Canceler` effect should be returned to cancel the pending action. The +-- | supplied callback may be invoked only once. Subsequent invocation are +-- | ignored. +foreign import makeAff ∷ ∀ a. ((Either Error a → Effect Unit) → Effect Canceler) → Aff a + +makeFiber ∷ ∀ a. Aff a → Effect (Fiber a) +makeFiber aff = Fn.runFn2 _makeFiber ffiUtil aff + +newtype FFIUtil = FFIUtil + { isLeft ∷ ∀ a b. Either a b → Boolean + , fromLeft ∷ ∀ a b. Either a b → a + , fromRight ∷ ∀ a b. Either a b → b + , left ∷ ∀ a b. a → Either a b + , right ∷ ∀ a b. b → Either a b + } + +ffiUtil ∷ FFIUtil +ffiUtil = FFIUtil + { isLeft + , fromLeft: unsafeFromLeft + , fromRight: unsafeFromRight + , left: Left + , right: Right + } + where + isLeft ∷ ∀ a b. Either a b → Boolean + isLeft = case _ of + Left _ -> true + Right _ → false + + unsafeFromLeft ∷ ∀ a b. Either a b → a + unsafeFromLeft = case _ of + Left a → a + Right _ → unsafeCrashWith "unsafeFromLeft: Right" + + unsafeFromRight ∷ ∀ a b. Either a b → b + unsafeFromRight = case _ of + Right a → a + Left _ → unsafeCrashWith "unsafeFromRight: Left" diff --git a/src/Effect/Aff/Class.purs b/src/Effect/Aff/Class.purs new file mode 100644 index 0000000..2e3ef5f --- /dev/null +++ b/src/Effect/Aff/Class.purs @@ -0,0 +1,44 @@ +module Effect.Aff.Class where + +import Prelude +import Control.Monad.Cont.Trans (ContT) +import Control.Monad.Except.Trans (ExceptT) +import Control.Monad.List.Trans (ListT) +import Control.Monad.Maybe.Trans (MaybeT) +import Control.Monad.Reader.Trans (ReaderT) +import Control.Monad.RWS.Trans (RWST) +import Control.Monad.State.Trans (StateT) +import Control.Monad.Trans.Class (lift) +import Control.Monad.Writer.Trans (WriterT) +import Effect.Aff (Aff) +import Effect.Class (class MonadEffect) + +class MonadEffect m ⇐ MonadAff m where + liftAff ∷ Aff ~> m + +instance monadAffAff ∷ MonadAff Aff where + liftAff = identity + +instance monadAffContT ∷ MonadAff m ⇒ MonadAff (ContT r m) where + liftAff = lift <<< liftAff + +instance monadAffExceptT ∷ MonadAff m ⇒ MonadAff (ExceptT e m) where + liftAff = lift <<< liftAff + +instance monadAffListT ∷ MonadAff m ⇒ MonadAff (ListT m) where + liftAff = lift <<< liftAff + +instance monadAffMaybe ∷ MonadAff m ⇒ MonadAff (MaybeT m) where + liftAff = lift <<< liftAff + +instance monadAffReader ∷ MonadAff m ⇒ MonadAff (ReaderT r m) where + liftAff = lift <<< liftAff + +instance monadAffRWS ∷ (MonadAff m, Monoid w) ⇒ MonadAff (RWST r w s m) where + liftAff = lift <<< liftAff + +instance monadAffState ∷ MonadAff m ⇒ MonadAff (StateT s m) where + liftAff = lift <<< liftAff + +instance monadAffWriter ∷ (MonadAff m, Monoid w) ⇒ MonadAff (WriterT w m) where + liftAff = lift <<< liftAff diff --git a/src/Effect/Aff/Compat.purs b/src/Effect/Aff/Compat.purs new file mode 100644 index 0000000..541d1ef --- /dev/null +++ b/src/Effect/Aff/Compat.purs @@ -0,0 +1,53 @@ +-- | This module provides compatability functions for constructing `Aff`s which +-- | are defined via the FFI. +module Effect.Aff.Compat + ( EffectFnAff(..) + , EffectFnCanceler(..) + , EffectFnCb + , fromEffectFnAff + , module Effect.Uncurried + ) where + +import Prelude +import Data.Either (Either(..)) +import Effect.Aff (Aff, Canceler(..), makeAff, nonCanceler) +import Effect.Exception (Error) +import Effect.Uncurried (EffectFn1, EffectFn2, EffectFn3, mkEffectFn1, mkEffectFn2, mkEffectFn3, runEffectFn1, runEffectFn2, runEffectFn3) + +type EffectFnCb a = EffectFn1 a Unit + +newtype EffectFnAff a = EffectFnAff (EffectFn2 (EffectFnCb Error) (EffectFnCb a) EffectFnCanceler) + +newtype EffectFnCanceler = EffectFnCanceler (EffectFn3 Error (EffectFnCb Error) (EffectFnCb Unit) Unit) + +-- | Lift a FFI definition into an `Aff`. `EffectFnAff` makes use of `EffectFn` so +-- | `Effect` thunks are unnecessary. A definition might follow this example: +-- | +-- | ```javascript +-- | exports._myAff = function (onError, onSuccess) { +-- | var cancel = doSomethingAsync(function (err, res) { +-- | if (err) { +-- | onError(err); +-- | } else { +-- | onSuccess(res); +-- | } +-- | }); +-- | return function (cancelError, onCancelerError, onCancelerSuccess) { +-- | cancel(); +-- | onCancelerSuccess(); +-- | }; +-- | }; +-- | ``` +-- | +-- | ```purescript +-- | foreign import _myAff :: EffectFnAff String +-- | +-- | myAff :: Aff String +-- | myAff = fromEffectFnAff _myAff +-- | ```` +fromEffectFnAff ∷ EffectFnAff ~> Aff +fromEffectFnAff (EffectFnAff eff) = makeAff \k → do + EffectFnCanceler canceler ← runEffectFn2 eff (mkEffectFn1 (k <<< Left)) (mkEffectFn1 (k <<< Right)) + pure $ Canceler \e → makeAff \k2 → do + runEffectFn3 canceler e (mkEffectFn1 (k2 <<< Left)) (mkEffectFn1 (k2 <<< Right)) + pure nonCanceler diff --git a/test/Test/Bench.purs b/test/Test/Bench.purs index 1bf49bb..1b8862e 100644 --- a/test/Test/Bench.purs +++ b/test/Test/Bench.purs @@ -1,14 +1,14 @@ module Test.Bench where import Prelude -import Control.Monad.Aff as Aff -import Control.Monad.Eff (Eff, runPure) -import Control.Monad.Eff.Unsafe (unsafeCoerceEff) -import Control.Monad.Eff.Console as Console import Control.Monad.Rec.Class (Step(..), tailRecM) import Performance.Minibench (bench) +import Effect (Effect) +import Effect.Aff as Aff +import Effect.Unsafe (unsafePerformEffect) +import Effect.Console as Console -loop1 ∷ ∀ eff. Int → Aff.Aff eff Int +loop1 ∷ Int → Aff.Aff Int loop1 = tailRecM go where go n @@ -26,7 +26,7 @@ loop1 = tailRecM go pure n pure $ Loop (n - 1) -loop2 ∷ ∀ eff. Int → Aff.Aff eff Int +loop2 ∷ Int → Aff.Aff Int loop2 = go where go n @@ -44,20 +44,20 @@ loop2 = go pure n loop2 (n - 1) -fib1 ∷ ∀ e. Int → Aff.Aff e Int +fib1 ∷ Int → Aff.Aff Int fib1 n = if n <= 1 then pure n else do a ← fib1 (n - 1) b ← fib1 (n - 2) pure (a + b) -main ∷ Eff (console ∷ Console.CONSOLE) Unit +main ∷ Effect Unit main = do Console.log "\nAff tailRecM:" - bench \_ → runPure $ unsafeCoerceEff $ void $ Aff.launchAff $ loop1 10000 + bench \_ → unsafePerformEffect $ void $ Aff.launchAff $ loop1 10000 Console.log "\nAff loop:" - bench \_ → runPure $ unsafeCoerceEff $ void $ Aff.launchAff $ loop2 10000 + bench \_ → unsafePerformEffect $ void $ Aff.launchAff $ loop2 10000 Console.log "\nAff fib:" - bench \_ → runPure $ unsafeCoerceEff $ void $ Aff.launchAff $ fib1 20 + bench \_ → unsafePerformEffect $ void $ Aff.launchAff $ fib1 20 diff --git a/test/Test/Main.purs b/test/Test/Main.purs index 223113e..a09de19 100644 --- a/test/Test/Main.purs +++ b/test/Test/Main.purs @@ -4,18 +4,6 @@ import Prelude import Control.Alt ((<|>)) import Control.Lazy (fix) -import Control.Monad.Aff (Aff, Canceler(..), runAff_, launchAff, makeAff, try, bracket, generalBracket, delay, forkAff, suspendAff, joinFiber, killFiber, never, supervise, Error, error, message) -import Control.Monad.Aff.AVar (AVAR, makeEmptyVar, takeVar, putVar) -import Control.Monad.Aff.Compat as AC -import Control.Monad.Eff (Eff, runPure) -import Control.Monad.Eff.Class (class MonadEff, liftEff) -import Control.Monad.Eff.Console (CONSOLE) -import Control.Monad.Eff.Console as Console -import Control.Monad.Eff.Exception (throwException, EXCEPTION) -import Control.Monad.Eff.Ref (REF, Ref) -import Control.Monad.Eff.Ref as Ref -import Control.Monad.Eff.Ref.Unsafe (unsafeRunRef) -import Control.Monad.Eff.Timer (TIMER, setTimeout, clearTimeout) import Control.Monad.Error.Class (throwError, catchError) import Control.Parallel (parallel, sequential, parTraverse_) import Data.Array as Array @@ -23,28 +11,32 @@ import Data.Bifunctor (lmap) import Data.Either (Either(..), either, isLeft, isRight) import Data.Foldable (sum) import Data.Maybe (Maybe(..)) -import Data.Monoid (mempty) import Data.Time.Duration (Milliseconds(..)) import Data.Traversable (traverse) -import Test.Assert (assert', ASSERT) - -type TestEffects eff = (assert ∷ ASSERT, console ∷ CONSOLE, ref ∷ REF, exception ∷ EXCEPTION, avar ∷ AVAR, timer ∷ TIMER | eff) -type TestEff eff = Eff (TestEffects eff) -type TestAff eff = Aff (TestEffects eff) - -newRef ∷ ∀ m eff a. MonadEff (ref ∷ REF | eff) m ⇒ a → m (Ref a) -newRef = liftEff <<< Ref.newRef - -readRef ∷ ∀ m eff a. MonadEff (ref ∷ REF | eff) m ⇒ Ref a → m a -readRef = liftEff <<< Ref.readRef - -writeRef ∷ ∀ m eff a. MonadEff (ref ∷ REF | eff) m ⇒ Ref a → a → m Unit -writeRef r = liftEff <<< Ref.writeRef r - -modifyRef ∷ ∀ m eff a. MonadEff (ref ∷ REF | eff) m ⇒ Ref a → (a → a) → m Unit -modifyRef r = liftEff <<< Ref.modifyRef r - -assertEff ∷ ∀ eff. String → Either Error Boolean → Eff (TestEffects eff) Unit +import Effect (Effect) +import Effect.Aff (Aff, Canceler(..), runAff, runAff_, launchAff, makeAff, try, bracket, generalBracket, delay, forkAff, suspendAff, joinFiber, killFiber, never, supervise, Error, error, message) +import Effect.Aff.Compat as AC +import Effect.Class (class MonadEffect, liftEffect) +import Effect.Console as Console +import Effect.Exception (throwException) +import Effect.Ref (Ref) +import Effect.Ref as Ref +import Effect.Unsafe (unsafePerformEffect) +import Test.Assert (assert') + +newRef ∷ ∀ m a. MonadEffect m ⇒ a → m (Ref a) +newRef = liftEffect <<< Ref.new + +readRef ∷ ∀ m a. MonadEffect m ⇒ Ref a → m a +readRef = liftEffect <<< Ref.read + +writeRef ∷ ∀ m a. MonadEffect m ⇒ Ref a → a → m Unit +writeRef r = liftEffect <<< flip Ref.write r + +modifyRef ∷ ∀ m a. MonadEffect m ⇒ Ref a → (a → a) → m Unit +modifyRef r = liftEffect <<< flip Ref.modify r + +assertEff ∷ String → Either Error Boolean → Effect Unit assertEff s = case _ of Left err → do Console.log ("[Error] " <> s) @@ -53,58 +45,58 @@ assertEff s = case _ of assert' ("Assertion failure " <> s) r Console.log ("[OK] " <> s) -runAssert ∷ ∀ eff. String → TestAff eff Boolean → TestEff eff Unit +runAssert ∷ String → Aff Boolean → Effect Unit runAssert s = runAff_ (assertEff s) -runAssertEq ∷ ∀ eff a. Eq a ⇒ String → a → TestAff eff a → TestEff eff Unit +runAssertEq ∷ ∀ a. Eq a ⇒ String → a → Aff a → Effect Unit runAssertEq s a = runAff_ (assertEff s <<< map (eq a)) -assertEq ∷ ∀ eff a. Eq a ⇒ String → a → TestAff eff a → TestAff eff Unit -assertEq s a aff = liftEff <<< assertEff s <<< map (eq a) =<< try aff +assertEq ∷ ∀ a. Eq a ⇒ String → a → Aff a → Aff Unit +assertEq s a aff = liftEffect <<< assertEff s <<< map (eq a) =<< try aff -assert ∷ ∀ eff. String → TestAff eff Boolean → TestAff eff Unit -assert s aff = liftEff <<< assertEff s =<< try aff +assert ∷ String → Aff Boolean → Aff Unit +assert s aff = liftEffect <<< assertEff s =<< try aff -withTimeout ∷ ∀ eff a. Milliseconds → TestAff eff a → TestAff eff a +withTimeout ∷ ∀ a. Milliseconds → Aff a → Aff a withTimeout ms aff = either throwError pure =<< sequential do parallel (try aff) <|> parallel (delay ms $> Left (error "Timed out")) -test_pure ∷ ∀ eff. TestEff eff Unit +test_pure ∷ Effect Unit test_pure = runAssertEq "pure" 42 (pure 42) -test_bind ∷ ∀ eff. TestEff eff Unit +test_bind ∷ Effect Unit test_bind = runAssertEq "bind" 44 do n1 ← pure 42 n2 ← pure (n1 + 1) n3 ← pure (n2 + 1) pure n3 -test_try ∷ ∀ eff. TestEff eff Unit +test_try ∷ Effect Unit test_try = runAssert "try" do n ← try (pure 42) case n of Right 42 → pure true _ → pure false -test_throw ∷ ∀ eff. TestEff eff Unit +test_throw ∷ Effect Unit test_throw = runAssert "try/throw" do n ← try (throwError (error "Nope.")) pure (isLeft n) -test_liftEff ∷ ∀ eff. TestEff eff Unit -test_liftEff = runAssertEq "liftEff" 42 do +test_liftEffect ∷ Effect Unit +test_liftEffect = runAssertEq "liftEffect" 42 do ref ← newRef 0 - liftEff do + liftEffect do writeRef ref 42 readRef ref -test_delay ∷ ∀ eff. TestAff eff Unit +test_delay ∷ Aff Unit test_delay = assert "delay" do delay (Milliseconds 1000.0) pure true -test_fork ∷ ∀ eff. TestAff eff Unit +test_fork ∷ Aff Unit test_fork = assert "fork" do ref ← newRef "" fiber ← forkAff do @@ -115,7 +107,7 @@ test_fork = assert "fork" do modifyRef ref (_ <> "parent") eq "gochildparent" <$> readRef ref -test_join ∷ ∀ eff. TestAff eff Unit +test_join ∷ Aff Unit test_join = assert "join" do ref ← newRef "" fiber ← forkAff do @@ -125,19 +117,19 @@ test_join = assert "join" do modifyRef ref (_ <> "parent") eq "parentchild" <$> joinFiber fiber -test_join_throw ∷ ∀ eff. TestAff eff Unit +test_join_throw ∷ Aff Unit test_join_throw = assert "join/throw" do fiber ← forkAff do delay (Milliseconds 10.0) throwError (error "Nope.") isLeft <$> try (joinFiber fiber) -test_join_throw_sync ∷ ∀ eff. TestAff eff Unit +test_join_throw_sync ∷ Aff Unit test_join_throw_sync = assert "join/throw/sync" do fiber ← forkAff (throwError (error "Nope.")) isLeft <$> try (joinFiber fiber) -test_multi_join ∷ ∀ eff. TestAff eff Unit +test_multi_join ∷ Aff Unit test_multi_join = assert "join/multi" do ref ← newRef 1 f1 ← forkAff do @@ -157,7 +149,7 @@ test_multi_join = assert "join/multi" do n2 ← readRef ref pure (sum n1 == 50 && n2 == 3) -test_suspend ∷ ∀ eff. TestAff eff Unit +test_suspend ∷ Aff Unit test_suspend = assert "suspend" do ref ← newRef "" fiber ← suspendAff do @@ -169,7 +161,7 @@ test_suspend = assert "suspend" do _ ← joinFiber fiber eq "goparentchild" <$> readRef ref -test_makeAff ∷ ∀ eff. TestAff eff Unit +test_makeAff ∷ Aff Unit test_makeAff = assert "makeAff" do ref1 ← newRef Nothing ref2 ← newRef 0 @@ -182,12 +174,12 @@ test_makeAff = assert "makeAff" do cb ← readRef ref1 case cb of Just k → do - liftEff $ k (Right 42) + liftEffect $ k (Right 42) _ ← joinFiber fiber eq 42 <$> readRef ref2 Nothing → pure false -test_bracket ∷ ∀ eff. TestAff eff Unit +test_bracket ∷ Aff Unit test_bracket = assert "bracket" do ref ← newRef [] let @@ -208,7 +200,7 @@ test_bracket = assert "bracket" do , "foo/release" ] -test_bracket_nested ∷ ∀ eff. TestAff eff Unit +test_bracket_nested ∷ Aff Unit test_bracket_nested = assert "bracket/nested" do ref ← newRef [] let @@ -237,7 +229,7 @@ test_bracket_nested = assert "bracket/nested" do , "foo/bar/run/release/bar/release" ] -test_general_bracket ∷ ∀ eff. TestAff eff Unit +test_general_bracket ∷ Aff Unit test_general_bracket = assert "bracket/general" do ref ← newRef "" let @@ -266,7 +258,7 @@ test_general_bracket = assert "bracket/general" do r4 ← readRef ref pure (isLeft r1 && isLeft r2 && isRight r3 && r4 == "foofoo/kill/zbarbar/throw/bbazcbaz/release/c") -test_supervise ∷ ∀ eff. TestAff eff Unit +test_supervise ∷ Aff Unit test_supervise = assert "supervise" do ref ← newRef "" r1 ← supervise do @@ -285,19 +277,19 @@ test_supervise = assert "supervise" do r2 ← readRef ref pure (r1 == "done" && r2 == "acquiredonerelease") -test_kill ∷ ∀ eff. TestAff eff Unit +test_kill ∷ Aff Unit test_kill = assert "kill" do fiber ← forkAff never killFiber (error "Nope") fiber isLeft <$> try (joinFiber fiber) -test_kill_canceler ∷ ∀ eff. TestAff eff Unit +test_kill_canceler ∷ Aff Unit test_kill_canceler = assert "kill/canceler" do ref ← newRef "" fiber ← forkAff do n ← makeAff \_ → pure $ Canceler \_ → do delay (Milliseconds 20.0) - liftEff (writeRef ref "cancel") + liftEffect (writeRef ref "cancel") writeRef ref "done" delay (Milliseconds 10.0) killFiber (error "Nope") fiber @@ -305,7 +297,7 @@ test_kill_canceler = assert "kill/canceler" do n ← readRef ref pure (n == "cancel" && (lmap message res) == Left "Nope") -test_kill_bracket ∷ ∀ eff. TestAff eff Unit +test_kill_bracket ∷ Aff Unit test_kill_bracket = assert "kill/bracket" do ref ← newRef "" let @@ -322,7 +314,7 @@ test_kill_bracket = assert "kill/bracket" do _ ← try (joinFiber fiber) eq "ab" <$> readRef ref -test_kill_bracket_nested ∷ ∀ eff. TestAff eff Unit +test_kill_bracket_nested ∷ Aff Unit test_kill_bracket_nested = assert "kill/bracket/nested" do ref ← newRef [] let @@ -352,7 +344,7 @@ test_kill_bracket_nested = assert "kill/bracket/nested" do , "foo/bar/run/release/bar/release" ] -test_kill_supervise ∷ ∀ eff. TestAff eff Unit +test_kill_supervise ∷ Aff Unit test_kill_supervise = assert "kill/supervise" do ref ← newRef "" let @@ -375,7 +367,7 @@ test_kill_supervise = assert "kill/supervise" do delay (Milliseconds 20.0) eq "acquirefooacquirebarkillfookillbar" <$> readRef ref -test_kill_finalizer_catch ∷ ∀ eff. TestAff eff Unit +test_kill_finalizer_catch ∷ Aff Unit test_kill_finalizer_catch = assert "kill/finalizer/catch" do ref ← newRef "" fiber ← forkAff $ bracket @@ -385,7 +377,7 @@ test_kill_finalizer_catch = assert "kill/finalizer/catch" do killFiber (error "Nope") fiber eq "caught" <$> readRef ref -test_kill_finalizer_bracket ∷ ∀ eff. TestAff eff Unit +test_kill_finalizer_bracket ∷ Aff Unit test_kill_finalizer_bracket = assert "kill/finalizer/bracket" do ref ← newRef "" fiber ← forkAff $ bracket @@ -400,7 +392,7 @@ test_kill_finalizer_bracket = assert "kill/finalizer/bracket" do killFiber (error "Nope") fiber eq "completed" <$> readRef ref -test_parallel ∷ ∀ eff. TestAff eff Unit +test_parallel ∷ Aff Unit test_parallel = assert "parallel" do ref ← newRef "" let @@ -417,7 +409,7 @@ test_parallel = assert "parallel" do r2 ← joinFiber f1 pure (r1 == "foobar" && r2.a == "foo" && r2.b == "bar") -test_parallel_throw ∷ ∀ eff. TestAff eff Unit +test_parallel_throw ∷ Aff Unit test_parallel_throw = assert "parallel/throw" $ withTimeout (Milliseconds 100.0) do ref ← newRef "" let @@ -432,7 +424,7 @@ test_parallel_throw = assert "parallel/throw" $ withTimeout (Milliseconds 100.0) r2 ← readRef ref pure (isLeft r1 && r2 == "foo") -test_kill_parallel ∷ ∀ eff. TestAff eff Unit +test_kill_parallel ∷ Aff Unit test_kill_parallel = assert "kill/parallel" do ref ← newRef "" let @@ -453,7 +445,7 @@ test_kill_parallel = assert "kill/parallel" do _ ← try $ joinFiber f2 eq "killedfookilledbardone" <$> readRef ref -test_parallel_alt ∷ ∀ eff. TestAff eff Unit +test_parallel_alt ∷ Aff Unit test_parallel_alt = assert "parallel/alt" do ref ← newRef "" let @@ -468,7 +460,7 @@ test_parallel_alt = assert "parallel/alt" do r2 ← joinFiber f1 pure (r1 == "bar" && r2 == "bar") -test_parallel_alt_throw ∷ ∀ eff. TestAff eff Unit +test_parallel_alt_throw ∷ Aff Unit test_parallel_alt_throw = assert "parallel/alt/throw" do r1 ← sequential $ parallel (delay (Milliseconds 10.0) *> throwError (error "Nope.")) @@ -476,7 +468,7 @@ test_parallel_alt_throw = assert "parallel/alt/throw" do <|> parallel (delay (Milliseconds 12.0) $> "bar") pure (r1 == "foo") -test_parallel_alt_sync ∷ ∀ eff. TestAff eff Unit +test_parallel_alt_sync ∷ Aff Unit test_parallel_alt_sync = assert "parallel/alt/sync" do ref ← newRef "" let @@ -492,7 +484,7 @@ test_parallel_alt_sync = assert "parallel/alt/sync" do r2 ← readRef ref pure (r1 == "foo" && r2 == "fookilledfoo") -test_parallel_mixed ∷ ∀ eff. TestAff eff Unit +test_parallel_mixed ∷ Aff Unit test_parallel_mixed = assert "parallel/mixed" do ref ← newRef "" let @@ -513,7 +505,7 @@ test_parallel_mixed = assert "parallel/mixed" do r4 ← readRef ref pure (r1 == "a" && r2 == "b" && r3 == "de" && r4 == "abde") -test_kill_parallel_alt ∷ ∀ eff. TestAff eff Unit +test_kill_parallel_alt ∷ Aff Unit test_kill_parallel_alt = assert "kill/parallel/alt" do ref ← newRef "" let @@ -534,7 +526,7 @@ test_kill_parallel_alt = assert "kill/parallel/alt" do _ ← try $ joinFiber f2 eq "killedfookilledbardone" <$> readRef ref -test_kill_parallel_alt_finalizer ∷ ∀ eff. TestAff eff Unit +test_kill_parallel_alt_finalizer ∷ Aff Unit test_kill_parallel_alt_finalizer = assert "kill/parallel/alt/finalizer" do ref ← newRef "" f1 ← forkAff $ sequential $ @@ -553,12 +545,12 @@ test_kill_parallel_alt_finalizer = assert "kill/parallel/alt/finalizer" do _ ← try $ joinFiber f2 eq "killeddone" <$> readRef ref -test_fiber_map ∷ ∀ eff. TestAff eff Unit +test_fiber_map ∷ Aff Unit test_fiber_map = assert "fiber/map" do ref ← newRef 0 let - mapFn a = runPure do - unsafeRunRef $ Ref.modifyRef ref (_ + 1) + mapFn a = unsafePerformEffect do + Ref.modify (_ + 1) ref pure (a + 1) f1 ← forkAff do delay (Milliseconds 10.0) @@ -570,12 +562,12 @@ test_fiber_map = assert "fiber/map" do n ← readRef ref pure (a == 11 && b == 11 && n == 1) -test_fiber_apply ∷ ∀ eff. TestAff eff Unit +test_fiber_apply ∷ Aff Unit test_fiber_apply = assert "fiber/apply" do ref ← newRef 0 let - applyFn a b = runPure do - unsafeRunRef $ Ref.modifyRef ref (_ + 1) + applyFn a b = unsafePerformEffect do + Ref.modify (_ + 1) ref pure (a + b) f1 ← forkAff do delay (Milliseconds 10.0) @@ -590,30 +582,16 @@ test_fiber_apply = assert "fiber/apply" do n ← readRef ref pure (a == 22 && b == 22 && n == 1) -test_avar_order ∷ ∀ eff. TestAff eff Unit -test_avar_order = assert "avar/order" do - ref ← newRef "" - var ← makeEmptyVar - f1 ← forkAff do - delay (Milliseconds 10.0) - value ← takeVar var - modifyRef ref (_ <> value) - putVar "foo" var - modifyRef ref (_ <> "taken") - joinFiber f1 - eq "takenfoo" <$> readRef ref - -test_efffn ∷ ∀ eff. TestAff eff Unit +test_efffn ∷ Aff Unit test_efffn = assert "efffn" do ref ← newRef "" let - jsDelay ms = AC.fromEffFnAff $ AC.EffFnAff $ AC.mkEffFn2 \ke kc → do - tid ← setTimeout ms (AC.runEffFn1 kc unit) - pure $ AC.EffFnCanceler $ AC.mkEffFn3 \e cke ckc → do - clearTimeout tid - AC.runEffFn1 ckc unit + effectDelay ms = AC.fromEffectFnAff $ AC.EffectFnAff $ AC.mkEffectFn2 \ke kc → do + fiber ← runAff (either (AC.runEffectFn1 ke) (AC.runEffectFn1 kc)) (delay ms) + pure $ AC.EffectFnCanceler $ AC.mkEffectFn3 \e cke ckc → do + runAff_ (either (AC.runEffectFn1 cke) (AC.runEffectFn1 ckc)) (killFiber e fiber) action = do - jsDelay 10 + effectDelay (Milliseconds 10.0) modifyRef ref (_ <> "done") f1 ← forkAff action f2 ← forkAff action @@ -621,52 +599,45 @@ test_efffn = assert "efffn" do delay (Milliseconds 20.0) eq "done" <$> readRef ref -test_parallel_stack ∷ ∀ eff. TestAff eff Unit +test_parallel_stack ∷ Aff Unit test_parallel_stack = assert "parallel/stack" do ref ← newRef 0 parTraverse_ (modifyRef ref <<< add) (Array.replicate 100000 1) eq 100000 <$> readRef ref -test_scheduler_size ∷ ∀ eff. TestAff eff Unit +test_scheduler_size ∷ Aff Unit test_scheduler_size = assert "scheduler" do ref ← newRef 0 _ ← traverse joinFiber =<< traverse forkAff (Array.replicate 100000 (modifyRef ref (add 1))) eq 100000 <$> readRef ref -test_lazy ∷ ∀ eff. TestAff eff Unit -test_lazy = assert "lazy" do - varA ← makeEmptyVar - varB ← makeEmptyVar - fiberA <- forkAff $ fix \loop -> do - a <- takeVar varA - putVar (a + 1) varB - loop - fiberB <- forkAff $ fix \loop -> do - b <- takeVar varB - if (b > 100) +test_lazy ∷ Aff Unit +test_lazy = assert "Lazy Aff" do + ref ← newRef 0 + fix \loop -> do + val ← readRef ref + if val < 10 then do - killFiber (error "finished") fiberA - pure "done" - else do - putVar (b + 1) varA + writeRef ref (val + 1) loop - putVar 0 varA - eq "done" <$> joinFiber fiberB + else + pure unit + eq 10 <$> readRef ref -test_regression_return_fork ∷ ∀ eff. TestAff eff Unit +test_regression_return_fork ∷ Aff Unit test_regression_return_fork = assert "regression/return-fork" do bracket (forkAff (pure unit)) (const (pure unit)) (const (pure true)) -main ∷ TestEff () Unit +main ∷ Effect Unit main = do test_pure test_bind test_try test_throw - test_liftEff + test_liftEffect void $ launchAff do test_delay @@ -697,7 +668,6 @@ main = do test_parallel_mixed test_kill_parallel_alt test_kill_parallel_alt_finalizer - test_avar_order test_lazy test_efffn test_fiber_map From a2309ada2ecfb6d2364957164db704af335c7534 Mon Sep 17 00:00:00 2001 From: Gary Burgess Date: Thu, 24 May 2018 22:41:36 +0100 Subject: [PATCH 2/2] Update dependencies and tests --- bower.json | 24 ++++++++-------- package.json | 10 +++---- test/Test/Main.purs | 68 ++++++++++++++++++++++----------------------- 3 files changed, 51 insertions(+), 51 deletions(-) diff --git a/bower.json b/bower.json index e4a3b3a..61e3ea9 100644 --- a/bower.json +++ b/bower.json @@ -17,19 +17,19 @@ "package.json" ], "dependencies": { - "purescript-exceptions": "#compiler/0.12", - "purescript-functions": "#compiler/0.12", - "purescript-parallel": "#compiler/0.12", - "purescript-transformers": "#compiler/0.12", - "purescript-unsafe-coerce": "#compiler/0.12", - "purescript-datetime": "#compiler/0.12", - "purescript-effect": "#compiler/0.12" + "purescript-exceptions": "^4.0.0", + "purescript-functions": "^4.0.0", + "purescript-parallel": "^4.0.0", + "purescript-transformers": "^4.0.0", + "purescript-unsafe-coerce": "^4.0.0", + "purescript-datetime": "^4.0.0", + "purescript-effect": "^2.0.0" }, "devDependencies": { - "purescript-console": "#compiler/0.12", - "purescript-partial": "#compiler/0.12", - "purescript-minibench": "#compiler/0.12", - "purescript-assert": "#compiler/0.12", - "purescript-free": "#compiler/0.12" + "purescript-console": "^4.1.0", + "purescript-partial": "^2.0.0", + "purescript-minibench": "^2.0.0", + "purescript-assert": "^4.0.0", + "purescript-free": "^5.0.0" } } diff --git a/package.json b/package.json index 078eb5c..e4a4920 100644 --- a/package.json +++ b/package.json @@ -7,11 +7,11 @@ }, "devDependencies": { "jscs": "^3.0.7", - "jshint": "^2.9.4", - "pulp": "^12.0.0", - "purescript-psa": "^0.5.0", - "purescript": "^0.12.0-rc1", - "rimraf": "^2.5.4" + "jshint": "^2.9.5", + "pulp": "^12.2.0", + "purescript-psa": "^0.6.0", + "purescript": "slamdata/node-purescript#0.12", + "rimraf": "^2.6.2" }, "jscsConfig": { "validateIndentation": false diff --git a/test/Test/Main.purs b/test/Test/Main.purs index a09de19..009e999 100644 --- a/test/Test/Main.purs +++ b/test/Test/Main.purs @@ -33,7 +33,7 @@ readRef = liftEffect <<< Ref.read writeRef ∷ ∀ m a. MonadEffect m ⇒ Ref a → a → m Unit writeRef r = liftEffect <<< flip Ref.write r -modifyRef ∷ ∀ m a. MonadEffect m ⇒ Ref a → (a → a) → m Unit +modifyRef ∷ ∀ m a. MonadEffect m ⇒ Ref a → (a → a) → m a modifyRef r = liftEffect <<< flip Ref.modify r assertEff ∷ String → Either Error Boolean → Effect Unit @@ -102,9 +102,9 @@ test_fork = assert "fork" do fiber ← forkAff do delay (Milliseconds 10.0) modifyRef ref (_ <> "child") - modifyRef ref (_ <> "go") + _ ← modifyRef ref (_ <> "go") delay (Milliseconds 20.0) - modifyRef ref (_ <> "parent") + _ ← modifyRef ref (_ <> "parent") eq "gochildparent" <$> readRef ref test_join ∷ Aff Unit @@ -112,9 +112,9 @@ test_join = assert "join" do ref ← newRef "" fiber ← forkAff do delay (Milliseconds 10.0) - modifyRef ref (_ <> "child") + _ ← modifyRef ref (_ <> "child") readRef ref - modifyRef ref (_ <> "parent") + _ ← modifyRef ref (_ <> "parent") eq "parentchild" <$> joinFiber fiber test_join_throw ∷ Aff Unit @@ -134,11 +134,11 @@ test_multi_join = assert "join/multi" do ref ← newRef 1 f1 ← forkAff do delay (Milliseconds 10.0) - modifyRef ref (_ + 1) + _ ← modifyRef ref (_ + 1) pure 10 f2 ← forkAff do delay (Milliseconds 20.0) - modifyRef ref (_ + 1) + _ ← modifyRef ref (_ + 1) pure 20 n1 ← traverse joinFiber [ f1 @@ -155,9 +155,9 @@ test_suspend = assert "suspend" do fiber ← suspendAff do delay (Milliseconds 10.0) modifyRef ref (_ <> "child") - modifyRef ref (_ <> "go") + _ ← modifyRef ref (_ <> "go") delay (Milliseconds 20.0) - modifyRef ref (_ <> "parent") + _ ← modifyRef ref (_ <> "parent") _ ← joinFiber fiber eq "goparentchild" <$> readRef ref @@ -185,7 +185,7 @@ test_bracket = assert "bracket" do let action s = do delay (Milliseconds 10.0) - modifyRef ref (_ <> [ s ]) + _ ← modifyRef ref (_ <> [ s ]) pure s fiber ← forkAff do delay (Milliseconds 40.0) @@ -206,7 +206,7 @@ test_bracket_nested = assert "bracket/nested" do let action s = do delay (Milliseconds 10.0) - modifyRef ref (_ <> [ s ]) + _ ← modifyRef ref (_ <> [ s ]) pure s bracketAction s = bracket @@ -235,7 +235,7 @@ test_general_bracket = assert "bracket/general" do let action s = do delay (Milliseconds 10.0) - modifyRef ref (_ <> s) + _ ← modifyRef ref (_ <> s) pure s bracketAction s = generalBracket (action s) @@ -265,13 +265,13 @@ test_supervise = assert "supervise" do _ ← forkAff do bracket (modifyRef ref (_ <> "acquire")) - (\_ → modifyRef ref (_ <> "release")) + (\_ → void $ modifyRef ref (_ <> "release")) (\_ → delay (Milliseconds 10.0)) _ ← forkAff do delay (Milliseconds 11.0) - modifyRef ref (_ <> "delay") + void $ modifyRef ref (_ <> "delay") delay (Milliseconds 5.0) - modifyRef ref (_ <> "done") + _ ← modifyRef ref (_ <> "done") pure "done" delay (Milliseconds 20.0) r2 ← readRef ref @@ -303,7 +303,7 @@ test_kill_bracket = assert "kill/bracket" do let action n = do delay (Milliseconds 10.0) - modifyRef ref (_ <> n) + void $ modifyRef ref (_ <> n) fiber ← forkAff $ bracket (action "a") @@ -320,7 +320,7 @@ test_kill_bracket_nested = assert "kill/bracket/nested" do let action s = do delay (Milliseconds 10.0) - modifyRef ref (_ <> [ s ]) + _ ← modifyRef ref (_ <> [ s ]) pure s bracketAction s = bracket @@ -350,13 +350,13 @@ test_kill_supervise = assert "kill/supervise" do let action s = generalBracket (modifyRef ref (_ <> "acquire" <> s)) - { failed: \_ _ → modifyRef ref (_ <> "throw" <> s) - , killed: \_ _ → modifyRef ref (_ <> "kill" <> s) - , completed: \_ _ → modifyRef ref (_ <> "complete" <> s) + { failed: \_ _ → void $ modifyRef ref (_ <> "throw" <> s) + , killed: \_ _ → void $ modifyRef ref (_ <> "kill" <> s) + , completed: \_ _ → void $ modifyRef ref (_ <> "complete" <> s) } (\_ -> do delay (Milliseconds 10.0) - modifyRef ref (_ <> "child" <> s)) + void $ modifyRef ref (_ <> "child" <> s)) fiber ← forkAff $ supervise do _ ← forkAff $ action "foo" _ ← forkAff $ action "bar" @@ -398,7 +398,7 @@ test_parallel = assert "parallel" do let action s = do delay (Milliseconds 10.0) - modifyRef ref (_ <> s) + _ ← modifyRef ref (_ <> s) pure s f1 ← forkAff $ sequential $ { a: _, b: _ } @@ -415,7 +415,7 @@ test_parallel_throw = assert "parallel/throw" $ withTimeout (Milliseconds 100.0) let action n s = do delay (Milliseconds n) - modifyRef ref (_ <> s) + _ ← modifyRef ref (_ <> s) pure s r1 ← try $ sequential $ { a: _, b: _ } @@ -431,10 +431,10 @@ test_kill_parallel = assert "kill/parallel" do action s = do bracket (pure unit) - (\_ → modifyRef ref (_ <> "killed" <> s)) + (\_ → void $ modifyRef ref (_ <> "killed" <> s)) (\_ → do delay (Milliseconds 10.0) - modifyRef ref (_ <> s)) + void $ modifyRef ref (_ <> s)) f1 ← forkAff $ sequential $ parallel (action "foo") *> parallel (action "bar") f2 ← forkAff do @@ -451,7 +451,7 @@ test_parallel_alt = assert "parallel/alt" do let action n s = do delay (Milliseconds n) - modifyRef ref (_ <> s) + _ ← modifyRef ref (_ <> s) pure s f1 ← forkAff $ sequential $ parallel (action 10.0 "foo") <|> parallel (action 5.0 "bar") @@ -475,7 +475,7 @@ test_parallel_alt_sync = assert "parallel/alt/sync" do action s = do bracket (pure unit) - (\_ → modifyRef ref (_ <> "killed" <> s)) + (\_ → void $ modifyRef ref (_ <> "killed" <> s)) (\_ → modifyRef ref (_ <> s) $> s) r1 ← sequential $ parallel (action "foo") @@ -490,7 +490,7 @@ test_parallel_mixed = assert "parallel/mixed" do let action n s = parallel do delay (Milliseconds n) - modifyRef ref (_ <> s) + _ ← modifyRef ref (_ <> s) pure s { r1, r2, r3 } ← sequential $ { r1: _, r2: _, r3: _ } @@ -512,10 +512,10 @@ test_kill_parallel_alt = assert "kill/parallel/alt" do action n s = do bracket (pure unit) - (\_ → modifyRef ref (_ <> "killed" <> s)) + (\_ → void $ modifyRef ref (_ <> "killed" <> s)) (\_ → do delay (Milliseconds n) - modifyRef ref (_ <> s)) + void $ modifyRef ref (_ <> s)) f1 ← forkAff $ sequential $ parallel (action 10.0 "foo") <|> parallel (action 20.0 "bar") f2 ← forkAff do @@ -535,7 +535,7 @@ test_kill_parallel_alt_finalizer = assert "kill/parallel/alt/finalizer" do (pure unit) (\_ → do delay (Milliseconds 10.0) - modifyRef ref (_ <> "killed")) + void $ modifyRef ref (_ <> "killed")) (\_ → delay (Milliseconds 20.0)) f2 ← forkAff do delay (Milliseconds 15.0) @@ -550,7 +550,7 @@ test_fiber_map = assert "fiber/map" do ref ← newRef 0 let mapFn a = unsafePerformEffect do - Ref.modify (_ + 1) ref + _ ← Ref.modify (_ + 1) ref pure (a + 1) f1 ← forkAff do delay (Milliseconds 10.0) @@ -567,7 +567,7 @@ test_fiber_apply = assert "fiber/apply" do ref ← newRef 0 let applyFn a b = unsafePerformEffect do - Ref.modify (_ + 1) ref + _ ← Ref.modify (_ + 1) ref pure (a + b) f1 ← forkAff do delay (Milliseconds 10.0) @@ -592,7 +592,7 @@ test_efffn = assert "efffn" do runAff_ (either (AC.runEffectFn1 cke) (AC.runEffectFn1 ckc)) (killFiber e fiber) action = do effectDelay (Milliseconds 10.0) - modifyRef ref (_ <> "done") + void $ modifyRef ref (_ <> "done") f1 ← forkAff action f2 ← forkAff action killFiber (error "Nope.") f2