1
Fork 0
solar-conflux/purescript/lune/src/Handle.purs

269 lines
7.9 KiB
Plaintext

module Handle where
import Prelude
import Data.Function.Uncurried (Fn2, Fn3, Fn4, mkFn2, runFn3, runFn4)
import Data.Symbol (class IsSymbol, SProxy(..), reflectSymbol)
import Data.Tuple (Tuple)
import Data.Tuple.Nested (type (/\), (/\))
import Foreign (Foreign, unsafeToForeign)
import Lists (class MatchArrow, TCons, TNil, kind TList)
import Partial.Unsafe (unsafeCrashWith)
import Prim.Row as Row
import Prim.RowList as RowList
import Type.Data.Row (RProxy(..))
-- | Abilities for testing
type ReaderAbility e r = ( ask :: e | r )
type StreamAbility v r = ( emit :: v -> Unit | r )
type StoreAbility s r
= ( get :: s
, put :: s -> Unit | r )
-- | Transform a list of types into a chain of nested tuples.
class TListToTuples (i :: TList) o | i -> o
instance tlistTupleConsNil :: TListToTuples (TCons head TNil) head
else instance tlistTupleCons
:: TListToTuples tail outputTail
=> TListToTuples (TCons head tail) (head /\ outputTail)
else instance tlistTupleBase :: TListToTuples TNil Unit
-- | Transform a chain of nested tuples into a list of types
class TuplesToTList i (o :: TList) | i -> o
instance tupleToTListTuple
:: TuplesToTList b tail
=> TuplesToTList (a /\ b) (TCons a tail)
else instance tupleToTListUnit :: TuplesToTList Unit TNil
else instance tupleToTListGeneral :: TuplesToTList a (TCons a TNil)
-- | Measure the lenght of some nested tuples
class TupleLength t where
tupleLength :: Proxy t -> Int
instance tupleLengthTuple :: TupleLength b => TupleLength (a /\ b) where
tupleLength _ = tupleLength (Proxy :: _ b) + 1
else instance tupleLengthUnit :: TupleLength Unit where
tupleLength _ = 0
else instance tupleLengthGeneral :: TupleLength a where
tupleLength _ = 1
{-
get :: s
put :: s -> ()
...becomes
get :: (s -> Lune abilities a) -> Lune remaining result
put :: (unit -> Lune abilities a) -> state -> Lune remaining result
-}
class AbilityMatchers (all :: #Type) a result (abilities :: #Type) (matchers :: #Type)
| all a result abilities -> matchers
, all a result matchers -> abilities
instance abilityMatchersGeneral ::
( RowList.RowToList abilities abilities'
, AbilityMatchersRL (Lune all a) result abilities' matchers
) => AbilityMatchers all a result abilities ( pure :: a -> result | matchers )
-- | Internal version of abilityMatchers using rowlists
class AbilityMatchersRL next result (rowList :: RowList.RowList) (output :: #Type)
| next result rowList -> output
, next result output -> rowList
instance abilityMatchersRLNil :: AbilityMatchersRL next result RowList.Nil ()
else instance abilityMatchersRlCons ::
( AbilityMatchersRL continuationOutput result tail tail'
, MatchArrow focus parameters continuationInput
, MatchArrow return parameters result
, Row.Cons key ((continuationInput -> continuationOutput) -> return) tail' matchers
) => AbilityMatchersRL
continuationOutput
result
(RowList.Cons key focus tail)
matchers
-- | Type for lune requests
type Request abilities a = (forall t.
(forall key focus subrow arguments return tuples.
Row.Cons key focus subrow abilities =>
MatchArrow focus arguments return =>
TListToTuples arguments tuples =>
IsSymbol key =>
SProxy key ->
Int ->
tuples ->
(return -> Lune abilities a) ->
t
) -> t)
-- | The actual lune monad
data Lune (abilities :: #Type) a
= Pure a
| Request (Request abilities a)
-- TODO: ffi this
foreign import curryImpl ::
forall tuples arguments return t.
MatchArrow t arguments return =>
TListToTuples arguments tuples =>
Fn3
(forall a b. a -> b -> Tuple a b)
Int
(tuples -> return)
t
curryGeneralized ::
forall tuples arguments return t.
MatchArrow t arguments return =>
TListToTuples arguments tuples =>
TupleLength tuples =>
Proxy t ->
(tuples -> return) ->
t
curryGeneralized _ = runFn3 curryImpl (/\) (tupleLength (Proxy :: _ tuples))
request ::
forall abilities key focus subrow output arguments return tuples.
Row.Cons key focus subrow abilities =>
MatchArrow focus arguments return =>
TListToTuples arguments tuples =>
MatchArrow output arguments (Lune abilities return) =>
IsSymbol key =>
RProxy abilities ->
SProxy key ->
output
request r s = curryGeneralized (Proxy :: _ output) $ \t -> Request \f -> f s (tupleLength (Proxy :: _ tuples)) t Pure
type MatchLune abilities a t =
Fn2
(Lune abilities a)
{ pure :: a -> t
-- TODO: this is not that well typed, maybe try and fix it
, request ::
Fn4 Int String Foreign Foreign t
} t
matchLune :: forall abilities a t. MatchLune abilities a t
matchLune = mkFn2 \lune cases -> case lune of
Pure a -> cases.pure a
Request r -> r \sproxy argumentCount t a ->
runFn4 cases.request
argumentCount
(reflectSymbol sproxy)
(unsafeToForeign t)
(unsafeToForeign a)
foreign import handleImpl ::
forall abilities subrow remaining result a matchers.
Row.Union subrow remaining abilities =>
AbilityMatchers abilities a (Lune remaining result) subrow matchers =>
Fn4
(MatchLune abilities a (Lune abilities result))
(forall x y. Request x y -> Lune x y)
(Record matchers)
(Lune abilities a)
(Lune remaining result)
-- | Remove some effects.
handleWith ::
forall abilities subrow remaining result a matchers.
Row.Union subrow remaining abilities =>
AbilityMatchers abilities a (Lune remaining result) subrow matchers =>
RProxy subrow ->
Record matchers ->
Lune abilities a ->
Lune remaining result
handleWith _ matchers monad
= runFn4 handleImpl
matchLune
Request
matchers
monad
-- | Why is this not a thing already?
data Proxy t = Proxy
-- Tests
getState :: forall a. Lune (StoreAbility a ()) a
getState = request _store _get
where
_store :: RProxy (StoreAbility a ())
_store = RProxy
_get :: SProxy "get"
_get = SProxy
putState :: forall a. a -> Lune (StoreAbility a ()) Unit
putState s = request _store _put s
where
_store :: RProxy (StoreAbility a ())
_store = RProxy
_put :: SProxy "put"
_put = SProxy
-- Typeclass instances for Lune.
instance functorLune :: Functor (Lune abilities) where
map f = case _ of
Pure a -> Pure (f a)
Request existential ->
Request \runExistential ->
existential \key paramCount parameters continuation ->
runExistential key paramCount parameters (continuation >>> map f)
instance applyLune :: Apply (Lune abilities) where
apply = ap
instance applicativeLune :: Applicative (Lune abilities) where
pure = Pure
instance bindLune :: Bind (Lune abilities) where
bind m f = case m of
Pure a -> f a
Request existential ->
Request \runExistential ->
existential \key paramCount parameters continuation ->
runExistential key paramCount parameters (continuation >=> f)
instance monadLune :: Monad (Lune abilities)
-- | Extract the value from an empty lune monad.
extract :: forall a. Lune () a -> a
extract = case _ of
Pure a -> a
-- | This should never run
Request _ -> unsafeCrashWith "Cannot extract values from requests"
-- | Simple program for testing
myProgram :: Lune (StoreAbility Int ()) String
myProgram = do
state <- getState
putState (state + 3)
pure $ show $ state * 2
-- | Handler for stores
runStore :: forall state a r. state -> Lune (StoreAbility state r) a -> Lune r (state /\ a)
runStore initialState = handleWith _on (handler initialState)
where
handler state =
{ get: \continue -> handleWith _on (handler state) (continue state)
, put: \continue newState -> handleWith _on (handler newState) (continue unit)
, pure: \a -> pure (state /\ a)
}
_on :: RProxy (StoreAbility state ())
_on = RProxy
abilityMatchers ::
forall all a result abilities matchers.
AbilityMatchers all a result abilities matchers =>
Proxy (Lune all a) ->
Proxy result ->
RProxy abilities ->
RProxy matchers
abilityMatchers _ _ _ = RProxy