Making Functional Machines with Elm

["He", "said", "," , "Hi", "there", "!"] 
> String.join " " ["He", "said", "," , "Hi", "there", "!"]
"He said , Hi there !"
"He said, Hi there!"
[("He", S), ("said", N), (",", S) , ("Hi", S), 
("there", N), ("!", P)]

A More General Problem

Step 0:  Input = [0, 1, 2, 3, 4]  Output = [ ]
Step 1: Input = [1, 2, 3, 4] Output = [1]
Step 2: Input = [2, 3, 4] Output = [1, 3]
Step 3: Input = [3, 4] Output = [1, 3, 6]
Step 4: Input = [4] Output = [1, 3, 6, 9]
Step 5: Input = [ ] Output = [1, 3, 6, 9, 7]

A List Processing Machine: Designing the Main Types

type alias InternalState a = 
{ before: Maybe a,
current: Maybe a,
after: Maybe a,
inputList: List a }
InternalState a -> b
run : (InternalState a -> b) -> List a -> List b
sumState : InternalState Int -> Int 
sumState internalState =
let
a = internalState.before |> Maybe.withDefault 0
b = internalState.current |> Maybe.withDefault 0
c = internalState.after |> Maybe.withDefault 0
in
a + b + c
ListMachine.run sumState [1,2,3,4]
==> [1, 3, 6, 9, 7]

Building the Machine

MachineState a b = 
{internalState: InternalState a, outputList: List b}
List.foldl : (u -> v -> v) -> v -> List u -> v
(a -> (Machine a b) -> (Machine a b)) -> (Machine a b) -> List a -> Machine a b
type alias Reducer a b = a -> MachineState a b -> MachineState a b
ApplyReducerToMachine : Reducer a b -> (Machine a b) -> List a -> Machine a b
applyReducerToMachineState : Reducer a b -> MachineState a b 
-> List a -> MachineState a b applyReducerToMachineState reducer initialState inputList =
List.foldl reducer initialState inputList

Computing the initial state

applyReducer : Reducer a b -> List a -> List b
applyReducer reducer inputList =
let
initialState = initialMachineState inputList
finalState = applyReducerToMachineState reducer initialState
inputList
in
List.reverse finalState.outputList
makeInitialState : List a -> InternalState a
makeInitialState inputList =
{before = Nothing
, current = List.head inputList
, after = List.head (List.drop 1 inputList)
, inputList = inputList
}
initialMachineState inputList = 
{internalState = makeInitialState inputList, outputList = []}

Computing the next internal state

nextInternalState : InternalState a -> InternalState a
nextInternalState internalState_ =
let
nextInputList_ = List.drop 1 internalState_.inputList
in
{
before = internalState_.current
, current = internalState_.after
, after = List.head (List.drop 1 nextInputList_)
, inputList = nextInputList_
}

Reducing the input

makeReducer : (InternalState a -> b) -> Reducer a b
makeReducer computeOutput input machineState =
let
nextInputList = List.drop 1 machineState.state.inputList
nextInternalState_ = nextInternalState machineState.state
newOutput = computeOutput machineState.state
outputList = newOutput::machineState.outputList
in
{internalState = nextInternalState_, outputList = outputList}
run : (InternalState a -> b) -> List a -> List b
run outputFunction inputList =
applyReducer (makeReducer outputFunction) inputList
run theOutputFunction theInputList

Example 1: the sumState machine

sumState : InternalState Int -> Int 
sumState internalState =
let
a = internalState.before |> Maybe.withDefault 0
b = internalState.current |> Maybe.withDefault 0
c = internalState.after |> Maybe.withDefault 0
in
a + b + c
ListMachine.run sumState [0, 1, 2, 3, 4]
=> [1, 3, 6, 9, 7].

Example 2: String processing

type SpacingSymbol = Space | NoSpace | EndParagraphstringJoiner : InternalState String -> (String, SpacingSymbol)
stringJoiner internalState =
let
b = internalState.current |> Maybe.withDefault ""
c = internalState.after |> Maybe.withDefault ""
symbol = if internalState.after == Nothing then
EndParagraph
else if List.member (String.left 1 c)
[",", ";", ".", ":", "?", "!"] then
NoSpace
else
Space
in
(b, symbol)
ListMachine.run stringJoiner ["He", "said", ",", "Wow", "!"]
==> [("He",Space),("said",NoSpace),(",",Space),("Wow",NoSpace),("!",EndParagraph)]

Example 3: a variant of String processing

stringJoiner2 : InternalState String -> String
stringJoiner2 internalState =
let
b = internalState.current |> Maybe.withDefault ""
c = internalState.after |> Maybe.withDefault ""
output = if internalState.after == Nothing then
b ++ "\n\n"
else if List.member (String.left 1 c)
[",", ";", ".", ":", "?", "!"] then
b
else
b ++ " "
in
output
ListMachine.run stringJoiner2b ["He", "said", ",", "Wow", "!"]
==> ["He ","said",", ","Wow","!\n\n"]

Conclusion

--

--

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store