FP Concepts They Don't Want You to Know About (Which Is Why All the Fun Stuff is Hidden in *.Extra)
Special thanks to @fresheyeball for explaining this shit to me
npm install
npm start
So we know how Maybe
works--it's a Just a
or Nothing
.
a
in Just a
can be a function.
To go from a Just 1
to a Just 3
we'd use map
Maybe.map ((+) 2) (Just 1) == Just 3
--=> True
So what happens if we had a Just (+)
with the addition infix
operator... how do we use this to add in an applicative manner
to add Just 1 and Just 2?
Applicatives have 2 properties:
-
pure
,singleton
-
apply
,ap
,<*>
Pure lets us know how to create a singleton list of for the default case of some applicative. Examples:
-
Maybe
-Just
-
List
-flip (::) []
And the ability to apply values in.
So what is Just (+)
?
foo : Maybe (number -> number -> number)
foo =
Just (+)
So let's use apply
import Maybe.Extra as Maybe
bar : Maybe (number -> number)
bar =
foo `Maybe.andMap` Just 1
And let's apply the value to completely
baz : Maybe Int
baz =
bar `Maybe.andMap` Just 2
isJust3 : Bool
isJust3 =
baz == Just 3
--=> True
{-| Would be cool if we had Maybe.Extra.singleton -}
singleton : a -> Maybe a
singleton =
Just
infixl 2 =>
(<*>) : Maybe (a -> b) -> Maybe a -> Maybe b
(<*>) =
Maybe.andMap
isNothing : Bool
isNothing =
singleton (+) <*> Just 1 <*> Nothing == Nothing
--=> True
Look at the the demo MaybeApplicative.elm
.
So where have we seen a something like this?
foo' : number -> number -> number
foo' x y =
x * y
foo'' : number -> number
foo'' =
foo' 1
is function application ;)
In Json.Decode.Extra
:
apply : Decoder (a -> b) -> Decoder a -> Decoder b
(|:) : Decoder (a -> b) -> Decoder a -> Decoder b
Well that's obviously apply...
And in Json.Decode
:
succeed : a -> Decoder a
Looks pretty pure to me...
Create some dummy JSON string
coolJson : String
coolJson =
"""[
{
"foo": 0,
"bar": true
},
{
"foo": 1,
"bar": true
},
{
"foo": 2,
"bar": false
}
]
"""
Create some type alias to represent these cool data items
type alias CoolItem =
{ foo : Int
, bar : Bool
}
Let's create a decoder using applicative.
Reminder: the constructor for CoolItem
is (Int -> Bool -> CoolItem)
So, Decode.succed CoolItem
is Decoder (Int -> Bool -> CoolItem)
Also, lets forget Json.Decode.objectN
s don't exist because they don't
scale infinity whereas the applicatives do.
import Json.Decode as Decode exposing ((:=))
coolItemDecoder : Decoder CoolItem
coolItemDecoder =
Decode.succeed CoolItem
|: ("foo" := Decode.int)
|: ("bar" := Decode.bool)
(:=) : String -> Decoder a -> Decoder a
So this :=
infix operator is used to apply the given decoder given
a string for key JSON object (e.g. "foo" will be decoded as an integer).
So what happens when when apply in our foo decoder?
Well let's look at some types:
import Json.Decode as Decode exposing (Decoder, (:=))
import Json.Docode.Extra as Decode ((|:))
baz : Decoder (Int -> Bool -> CoolItem)
baz =
Decode.succeed CoolItem
qux : Decoder (Bool -> CoolItem)
qux =
Decode.succeed CoolItem
|: ("foo" := Decode.int)
-- And the full decoder
coolItemDecoder : Decoder CoolItem
coolItemDecoder =
Decode.succeed CoolItem
|: ("foo" := Decode.int)
|: ("bar" := Decode.bool)
So all we have to do now is set up our app to decode the JSON
import Html exposing (Html, text)
view : a -> Html String
view =
text << toString
main : Html String
main =
coolJson
|> Decode.decodeString (Decode.list coolListDecoder)
|> view
Decode.decodeString : Decoder a -> String -> Result String a
Decode.list : Decoder a -> Decoder (List a)
But, go look at the demo.
So how do we find Applicatives in a language like Elm without
higher-kinded types? Look for type signatures and certain names
or think about what the singleton
would be.
In Elm you'll see the term singleton
for pure
. ...And most
of the time you'll see andMap
, ap
, or apply
.
So let's see some in action -- see Pokémon example.