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

269 lines
7.9 KiB
Plaintext
Raw Normal View History

2023-06-04 15:18:40 +02:00
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