A powerful general purpose type for writing applications.
For an extensive overview checkout the tests.
spago install yoga-om
Check out the minimal example!
An Om
consists of three parts.
A context, potential errors, and the value of a concurrent computation.
Conventionally in code, we abbreviate this as Om ctx errs a
.
Om
supports writing code that ask
s for dependencies that you only provide
once at the start of the application.
myOmThatNeedsDbName = do
{ dbName } <- ask
This is an Om
of the shape Om { dbName :: String } _ _
. We are only
concerned about the ctx
part of Om
in this section, hence the _
s.
Dealing with failure in Om
is just wonderful.
To throw an error, simply use the throwError
function with a record that
has the name of the error as its label and the error data as its value:
myOmThatThrows = Om.throw { myError: "This failed" }
This is an Om _ ( myError :: String ) _
To catch one or multiple errors use handleErrors
myOmHandled = handleErrors { myError: \text -> Console.warn text } myOmThatThrows
As stated earlier myOmThatThrows
has type: Om _ (myError :: String) _
.
By handling all potential myError
errors we've produced myOmHandled
which has
the type Om _ () _
.
A powerful feature of Om
is that you can easily combine different Om
computations that can throw different errors.
om1 :: forall otherErrors. Om _ ( ioError :: Int | otherErrors ) _
om1 = throw { ioError: -8 }
om2 :: forall otherErrors. Om _ ( userError :: String | otherErrors ) _
om2 = throw { userError: "Your last name can't be shorter than 0 characters" }
om3 :: forall errs. Om _ ( ioError :: Int, userError :: String | errs ) _
om3 = do
om1
om2
This means that you can and should only tag a function with the errors it can actually throw and not the complete set of errors that might happen anywhere in your program.
The compiler helps you out with this.
In order to transform any Aff a
into an Om _ _ a
you may use liftAff
, or fromAff
.
To do the same with Effect
you may use liftEffect
.
Let's bring it all back home. Eventually you want to actually run an Om
.
Most probably at the start of your application, in a main :: Effect Unit
function for
example.
That's the right time to supply the dependencies to your Om
and to handle any remaining possible errors:
module Main where
import Prelude
import Node.Process (lookupEnv)
import Effect.Class.Console as Console
import Effect (Effect)
import Data.Maybe (Maybe)
import Yoga.Om as Om
import Yoga.Om (Om)
main :: Effect Unit
main = do
envName <- lookupEnv "NAME"
greet
# Om.launchOm_
{ envName }
{ exception:
\e -> Console.error ("Unexpected exception: " <> show e)
, nameNotFound:
\_ -> Console.error "Make sure the $NAME env variable is set"
}
greet :: Om { envName :: Maybe String } (nameNotFound :: Unit) Unit
greet = do
{ envName } <- Om.ask
name <- envName # Om.note { nameNotFound: unit }
Console.log $ "Welcome " <> name
You can run different Om
s in parallel.
Either the fastest one that does not error out wins:
Om.race [ Om.delay (1.0 # Seconds) *> pure "slow" , pure "fast" ]
Or you look at all the results and get an Om _ _ (Array _)
:
Om.inParallel
[ Om.delay (9.0 # Milliseconds) *> pure "1"
, Om.delay (1.0 # Milliseconds) *> pure "2"
]
Since Om
combines the powers of Reader
and supports Effect
ful computations
it is less clutter (especially on the type signature) to bolt this functionality
on ad-hoc via Ref
s in the ctx
You will probably prefer to define type aliases:
module Main where
import DB as DB
import Cache as Cache
-- ...
import Type.Row (type (+))
myWholeApp :: Om (DB.Ctx + Cache.Ctx ()) (DB.Errs + Cache.Errs ()) Unit
myWholeApp = do
DB.writeToDB "'); DROP TABLE orders;--"
module DB where
writeToDB :: forall ctx errs. String -> Om (Ctx ctx) (Errs errs) Unit
writeToDB s = do
-- ...
pure unit
type Ctx r = (dbCtx :: { port :: Int, hostname :: String } | r)
type Errs r = IOErr + TimeoutErr + r
type IOErr r = ( ioError :: { message :: String, code :: Int, details :: String } | r )
type TimeoutErr r = ( timeout :: { query :: String } | r )
Om
is a monad transformer stack built from the stack safe continuation
passing RWSET
without State
or Writer
with errors specialised to
polymorphic Variant
s.
We would like to thank all the PureScript contributors, especially the ones whose libraries we depend on.
Special thanks to @reactormonk who has a very similar library (rave) which has the trick to inject variants as single field records instead of the cumbersome (Proxy :: Proxy "key") value
syntax.