Module

Data.Veither

Package
purescript-veither
Repository
JordanMartinez/purescript-veither

#Veither Source

newtype Veither errorRows a

Veither is the same as Either except that the l type can be zero to many different types. Veither has all the instances that Either has, except for Eq1 and Ord1, which simply haven't been implemented yet. If you would use a function from Data.Either (e.g. hush) and you want to use the equivalent for Veither, add a v in front of it (e.g. vhush).

Conceptually, Veither has the following definition:

data Veither l1 l2 ... ln a
  = Right a
  | L1 l1
  | L2 l2
  | ...
  | LN ln

Veither is monadic via the a type parameter. For example, the Int type below represents the 'happy path' and any other errors will short-circuit the computation:

foo :: Variant (e1 :: Error1, e2 :: Error2) Int
foo = do
  i1 <- returnIntOrFailWithError1
  i2 <- returnIntOrFailWithError2
  pure $ i1 + i2

Creating a value of Veither can be done in one of two ways, depending on whether you want the resulting Veither to function like Either's Right constructor or like Either's Left constructor:

  • Either's Right constructor: use pure. For example, pure 4 :: forall errorRows. Veither errorRows Int
  • Either's Left constructor: use Data.Variant.inj. For example, Veither (inj (Proxy :: Proxy "foo") String)) :: forall a. Veither (foo :: String) a

One can also change an Either a b into a Veither (x :: a) b using vfromEither.

To consume a Veither value, use veither, vfromRight, vfromLeft, vnote, or vhush. For example, one might do the following using veither:

import Type.Proxy (Proxy(..))
import Data.Variant (case_, on, inj)

-- Given a variant value...
val :: Veither (a :: Int, b :: String, c :: Boolean) Number
val = pure 5

-- you consume it using the following pattern. You'll need to handle every possible error type
consume :: Veither (a :: Int, b :: String, c :: Boolean) Number -> String
consume v = veither handleError handleSuccess v
  where
  handleError :: Variant (a :: Int, b :: String, c :: Boolean)
  handleError =
    case_
      # on (Proxy :: Proxy "a") show
      # on (Proxy :: Proxy "b") show
      # on (Proxy :: Proxy "c") show

  handleSuccess :: Number -> String
  handleSuccess = show

Below are functions that exist in Veither but do not exist in Either:

  • vsafe (inspired by purescript-checked-exceptions's safe function)
  • vhandle
  • vhandleErrors (inspired by purescript-checked-exceptions's handleErrors function)
  • vfromEither
  • genVeitherUniform - same as genEither but with uniform probability
  • genVeitherFrequency - same as genEither but with user-specified probability

Constructors

Instances

#_veither Source

_veither :: Proxy "_"

Proxy type for Veither's happy path (e.g. Either's Right constructor).

Note: the label "_" intentionally doesn't match the name of this value (i.e. '_veither').

#veither Source

veither :: forall errorRows a b. (Variant errorRows -> b) -> (a -> b) -> Veither errorRows a -> b

Convert a Veither into a value by defining how to handle each possible value. Below is an example of the typical usage.

consume :: Veither (a :: Int, b :: String, c :: Boolean) Number -> String
consume v = veither handleError handleSuccess v
  where
  handleError :: Variant (a :: Int, b :: String, c :: Boolean)
  handleError =
    case_
      # on (Proxy :: Proxy "a") show
      # on (Proxy :: Proxy "b") show
      # on (Proxy :: Proxy "c") show

  handleSuccess :: Number -> String
  handleSuccess = show

#vsafe Source

vsafe :: forall a. Veither () a -> a

Extract the value out of the Veither when there are no other possible values

vsafe (pure x) == x

#vhandle Source

vhandle :: forall sym b otherErrorRows errorRows a. IsSymbol sym => Cons sym b otherErrorRows errorRows => Proxy sym -> (b -> a) -> Veither errorRows a -> Veither otherErrorRows a

Removes one of the possible error types in the Veither by converting its value to a value of type a, the 'happy path' type. This can be useful for gradually picking off some of the errors the Veither value could have by handling only some of them at a given point in your code.

If the number of errors in your Veither are small and can all be handled via vhandle, one can use vsafe to extract the value of the 'happy path' a type.

foo :: Veither (b :: Int) String
foo = pure "2"

_b :: Proxy "b"
_b = Proxy

bar :: Veither (b :: Int) String
bar = Veither (inj_ _b 3)

vhandle _b show bar == ((pure "3") :: Veither () String)
vhandle _b show foo == ((pure "2") :: Veither () String)

safe (vhandle _b show bar) == "3"
safe (vhandle _b show foo) == "2"

#vhandleErrors Source

vhandleErrors :: forall handlers rlHandlers handledRows remainingErrorRows allErrorRows a. RowToList handlers rlHandlers => VariantMatchCases rlHandlers handledRows a => Union handledRows (_ :: a | remainingErrorRows) (_ :: a | allErrorRows) => Record handlers -> Veither allErrorRows a -> Veither remainingErrorRows a

Removes one, some, or all of the possible error types in the Veither by converting its value to a value of type a, the 'happy path' type.

Note: you will get a compiler error unless you add annotations to the record argument. You can do this by defining defining the record using a let statement or by annotating it inline (e.g. { a: identity} :: { a :: Int -> Int }`).

If all errors are handled via vhandleErrors, one can use vsafe to extract the value of the 'happy path' a type.

Doing something like vhandleErrors {"_": \(i :: Int) -> i} v will fail to compile. If you want to handle all possible values in the Veither, use veither or Data.Variant.onMatch directly (e.g. onMatch record <<< un Veither) instead of this function.

Example usage:

_a :: Proxy "a"
_a = Proxy

_b :: Proxy "b"
_b = Proxy

va :: Veither (a :: Int, b :: Boolean, c :: List String) String
va = Veither $ inj _a 4

vb :: Veither (a :: Int, b :: Boolean, c :: List String) String
vb = Veither $ inj _b false

handlers :: Record (a :: Int -> String, b :: Boolean -> String)
handlers = { a: show, b: show }

vhandleErrors handlers va == ((pure "4") :: Veither (c :: List String) String)
vhandleErrors handlers vb == ((pure "false") :: Veither (c :: List String) String)

#vfromEither Source

vfromEither :: forall sym otherRows errorRows a b. IsSymbol sym => Cons sym a otherRows (_ :: b | errorRows) => Proxy sym -> Either a b -> Veither errorRows b

Convert an Either into a Veither.

p :: Proxy "foo"
p = Proxy

vfromEither p (Left Int)  :: forall a. Variant (foo :: Int) a
vfromEither p (Right Int) :: forall a. Variant (foo :: a  ) Int

#vfromRight Source

vfromRight :: forall errorRows a. a -> Veither errorRows a -> a

Extract the value from a Veither, using a default value in case the underlying Variant is storing one of the error rows' values.

vError :: Veither (foo :: Int) String
vError = Veither (inj (Proxy :: Proxy "foo") 4)

vSuccess :: Veither (foo :: Int) String
vSuccess = pure "yay"

vfromRight "" vError   == ""
vfromRight "" vSuccess == "yay"

#vfromRight' Source

vfromRight' :: forall errorRows a. (Unit -> a) -> Veither errorRows a -> a

Same as vfromRight but the default value is lazy.

#vfromLeft Source

vfromLeft :: forall errorRows a b. b -> (Variant errorRows -> b) -> Veither errorRows a -> b

Extract the error value from a Veither, using a default value in case the underlying Variant is storing the ("_" :: a) rows' values.

vError :: Veither (foo :: Int) String
vError = Veither (inj (Proxy :: Proxy "foo") 4)

vSuccess :: Veither (foo :: Int) String
vSuccess = pure "yay"

vfromLeft  8 (case_ # on (Proxy :: Proxy "foo") identity) vError   == 4
vfromRight 8 (case_ # on (Proxy :: Proxy "foo") identity) vSuccess == 8

#vfromLeft' Source

vfromLeft' :: forall errorRows a b. (Unit -> b) -> (Variant errorRows -> b) -> Veither errorRows a -> b

Same as vfromLeft but the default value is lazy.

#vnote Source

vnote :: forall otherErrorRows errorRows s a b. Cons s a otherErrorRows (_ :: b | errorRows) => IsSymbol s => Proxy s -> a -> Maybe b -> Veither errorRows b

Convert a Maybe value into a Veither value using a default value when the Maybe value is Nothing.

mJust :: Maybe String
mJust = Just "x"

mNothing :: Maybe String
mNothing = Nothing

_foo :: Proxy "foo"
_foo = Proxy

vnote _foo 2 mJust    == (pure "y")             :: Veither (foo :: Int) String
vnote _foo 2 mNothing == (Veither (inj _foo 2)) :: Veither (foo :: Int) String

#vnote' Source

vnote' :: forall otherErrorRows errorRows s a b. Cons s a otherErrorRows (_ :: b | errorRows) => IsSymbol s => Proxy s -> (Unit -> a) -> Maybe b -> Veither errorRows b

Same as vnote but the default value is lazy.

#vhush Source

vhush :: forall errorRows a. Veither errorRows a -> Maybe a

Convert a Veither value into a Maybe value.

#genVeitherUniform Source

genVeitherUniform :: forall a errorRows otherGenRows rowList. RowToList (_ :: Gen a | otherGenRows) rowList => GenVariantUniform (_ :: Gen a | otherGenRows) rowList (_ :: a | errorRows) => { _ :: Gen a | otherGenRows } -> Gen (Veither errorRows a)

Generate Veither with uniform probability given a record whose generators' labels correspond to the Veither's labels

-- Note: type annotations are needed! Otherwise, you'll get compiler errors.
quickCheckGen do
  v <- genVeitherUniform
     -- first approach: annotate inline
     { "_": genHappyPath :: Gen Int
     , x: genXValues :: Gen (Maybe String)
     , y: pure "foo" :: Gen String
     }
  -- rest of test...

quickCheckGen do
  let
    -- second approach: use a let with annotations before usage
    r :: { "_" :: Gen Int, x :: Gen (Maybe String), y :: Gen String }
    r = { "_": genHappyPath, x: genXValues, y: pure "foo" }
  v <- genVeitherUniform r
  -- rest of test...

#genVeitherFrequncy Source

genVeitherFrequncy :: forall a errorRows otherGenRows rowList. RowToList (_ :: Tuple Number (Gen a) | otherGenRows) rowList => GenVariantFrequency (_ :: Tuple Number (Gen a) | otherGenRows) Number rowList (_ :: a | errorRows) => { _ :: Tuple Number (Gen a) | otherGenRows } -> Gen (Veither errorRows a)

Generate Veither with user-specified probability given a record whose generators' labels correspond to the Veither's labels

-- Note: type annotations are needed! Otherwise, you'll get compiler errors.
quickCheckGen do
  v <- genVeitherFrequency
     -- first approach: annotate inline
     { "_": genHappyPath :: Gen Int
     , x: genXValues :: Gen (Maybe String)
     , y: pure "foo" :: Gen String
     }
  -- rest of test...

quickCheckGen do
  let
    -- second approach: use a let with annotations before usage
    r :: { "_" :: Gen Int, x :: Gen (Maybe String), y :: Gen String }
    r = { "_": genHappyPath, x: genXValues, y: pure "foo" }
  v <- genVeitherFrequency r
  -- rest of test...

#VariantArbitrarys Source

class VariantArbitrarys finalRow currentRL  where

Members

Instances

#UnknownVariantValue Source

#VariantCoarbitrarys Source

class VariantCoarbitrarys currentRL  where

Members

Instances

#GenVariantUniform Source

class GenVariantUniform recordRows rl variantRows | recordRows -> variantRows where

Members

Instances

#GenVariantFrequency Source

class GenVariantFrequency recordRows b rl variantRows | recordRows -> b variantRows where

Members

Instances

Modules
Data.Veither