Last active
August 29, 2015 14:27
-
-
Save TheSeamau5/c034acc34b7cd5fa0064 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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