Skip to content

Instantly share code, notes, and snippets.

@TheSeamau5
Last active August 29, 2015 14:27
Show Gist options
  • Save TheSeamau5/c034acc34b7cd5fa0064 to your computer and use it in GitHub Desktop.
Save TheSeamau5/c034acc34b7cd5fa0064 to your computer and use it in GitHub Desktop.
import Task exposing (Task)
type Never = Never Never
-------------
-- ADDRESS --
-------------
type alias Address a = a -> Task Never ()
-- An empty address that discards its input
empty : Address a
empty _ =
Task.succeed ()
-- Create a forwarding address
forwardTo : (b -> a) -> Address a -> Address b
forwardTo f address b =
address (f b)
-- Gather two address into one from a splitting function
gather : (c -> (a, b)) -> Address a -> Address b -> Address c
gather f addressA addressB c =
case f c of
(a, b) ->
addressA a
`Task.andThen` \_ -> addressB b
-- Only send values to an address that
-- satisfy the given predicate
keepIf : (a -> Bool) -> Address a -> Address a
keepIf predicate address a =
if predicate a
then
address a
else
Task.succeed ()
-- Only send values to an address that
-- do not satisfy the given predicate
dropIf : (a -> Bool) -> Address a -> Address a
dropIf predicate =
keepIf (predicate >> not)
--------------
-- LOOPBACK --
--------------
-- A loopback is simply a function from address to address
-- The purpose of a loopback is to connect UI effects to UI messages/actions
--
-- Loopback action effect = Address action -> Address effect
--
-- In essence, the address part allows us to run tasks for the effect
-- and then send back the values to the action address
-- Put simply, we are really looking at the following type
--
-- Loopback action effect = Address action -> effect -> Task Never ()
--
-- This function can for example, take an effect, do some arbitrary HTTP
-- or database call or whatever, get some input, convert it to an action
-- and then send it to the action address
--
-- Note also that loopbacks capture the notion of forwarding
-- For example, look at the above definition of the forwardTo function
--
-- forwardTo : (b -> a) -> Address a -> Address b
--
-- This can also be rewritten as
--
-- forwardTo : (b -> a) -> Loopback a b
--
-- This should re-inforce the intuition that loopbacks seek to tie
-- effects to actions while allowing them an open door to perform whatever
-- tasks they need.
type alias Loopback a b = Address a -> Address b
-- A loopback that does nothing
noLoopback : Loopback a b
noLoopback _ _ =
Task.succeed ()
-- Create a simple loopback that just sends an action to an address.
constant : a -> Loopback a b
constant a address _ =
address a
-- Covariant map function on loopbacks
-- This applies the function on the action.
map : (a -> b) -> Loopback a x -> Loopback b x
map f loopback address =
loopback (forwardTo f address)
-- Contravariant map on loopbacks
-- This applies the function on the effect.
contramap : (b -> a) -> Loopback x a -> Loopback x b
contramap f loopback address b =
loopback address (f b)
-- Merge two loopbacks into one.
merge : Loopback a x -> Loopback a x -> Loopback a x
merge =
decompose (\a -> (a, a))
-- Merge many loopbacks into one.
mergeMany : List (Loopback a b) -> Loopback a b
mergeMany =
List.foldl merge noLoopback
-- Chain loopback computations.
-- Why is this useful? I don't know.
andThen : Loopback a x -> (a -> Loopback b x) -> Loopback b x
andThen loopback f address x =
loopback (\a -> f a address x) x
-- Loopback composition
compose : Loopback a b -> Loopback b c -> Loopback a c
compose =
(>>)
-- Create a loopback from two other loopbacks and a means of splitting
-- effects. This is very useful for when effects can be decomposed.
decompose : (c -> (a, b)) -> Loopback x a -> Loopback x b -> Loopback x c
decompose f loopA loopB address =
gather f (loopA address) (loopB address)
decompose3 : (d -> (a, b, c)) -> Loopback x a -> Loopback x b -> Loopback x c -> Loopback x d
decompose3 f loopA loopB loopC =
decompose (f >> (\(a,b,c) -> (a, (b,c)))) loopA (zip loopB loopC)
decomposeResult : (c -> Result a b) -> Loopback x a -> Loopback x b -> Loopback x c
decomposeResult f loopA loopB =
let
split c =
case f c of
Err err ->
(Just err, Nothing)
Ok ok ->
(Nothing, Just ok)
in
decompose split (maybe loopA) (maybe loopB)
-- Tuple up the effects of a loopback
zip : Loopback x a -> Loopback x b -> Loopback x (a, b)
zip =
decompose identity
result : Loopback x a -> Loopback x b -> Loopback x (Result a b)
result errLoopback okLoopback address r =
case r of
Err err ->
errLoopback address err
Ok ok ->
okLoopback address ok
list : Loopback x a -> Loopback x (List a)
list loopback address lst =
case lst of
[] ->
Task.succeed ()
x :: xs ->
loopback address x
`Task.andThen` \_ -> list loopback address xs
maybe : Loopback x a -> Loopback x (Maybe a)
maybe loopback address maybe =
case maybe of
Nothing ->
Task.succeed ()
Just a ->
loopback address a
-- The way decomposition works is as follows:
{- Decomposition example
-- Consider this union type representing all the types of effect that
-- a component may send
type Effect
= SendString String
| SendInt Int
| SendFloat Float
-- And consider the following loopbacks that send these effects
stringLoopback : Loopback a String
intLoopback : Loopback a Int
floatLoopback : Loopback a Float
-- We would like to make a loopback that encapsulates all three types of effects
-- into one. As you may imagine, this is useful in the case you have parent components
-- wishing to capture the effects of the child components
effectLoopback : Loopback a Effect
effectLoopback =
let
-- All you need to define is a split function
-- In this case, the split function will simply split an effect into
-- a three-tuple of maybes
-- split : Effect -> (Maybe String, Maybe Int, Maybe Float)
split effect =
case effect of
SendString str ->
(Just str, Nothing, Nothing)
SendInt int ->
(Nothing, Just int, Nothing)
SendFloat flt ->
(Nothing, Nothing, Just flt)
in
-- And then we simply call `decompose3` and we're good
decompose3 split (maybe stringLoopback) (maybe intLoopback) (maybe floatLoopback)
-- We can imagine that the case with all these maybes is so common
-- That we would make a higher order function to abstract this
-}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment