The “I’m stupid” Elm Language Nugget #18

Partial application under functors. I finally understand it.

I’ve known it was possible for a long time, but always felt weird about passing on a partly applied function in a functor such as the json decoder. I have a pretty big object I’m decoding though and finally decided to learn how threading the partly applied constructor of a record works with elm’s json decode.

In the near future, I’ll be adding a version of this to JsonCodec, assuming I can understand the needed signatures of the functions involved well enough, and removing the admittedly clumsy fold part. It’s a bit more complicated when it’s constructing an encoding and a decoder simultaneously, and this is a part of elm I’m still weak on.

For now, I’ve used this technique just for json decoding.

-- Construct a JD.Decoder that partially applies the constructor
-- to just the first parameter.
--
first : JD.Decoder a -> (a -> b) -> JD.Decoder b
first dec inp = JD.map inp dec
-- Take the JD.Decoder on the previously partially applied
-- constructor and yield a JD.Decoder of the same thing applied
-- to one more parameter.
--
-- A thing I found confusing was knowing whether x or y was
-- going to be greedy about matching the remaining function
-- arguments.
--
-- In my head, I'm thinking it could match as
-- (x=(a -> b -> c) -> y) or
-- (x -> y = (a -> b -> c))
--
-- At least here, it's matching as (x -> (a -> b -> c -> y))
--
rest : JD.Decoder x -> JD.Decoder (x -> y) -> JD.Decoder y
rest db da = JD.andThen (\sas -> JD.map sas db) da
type alias Project =
{ id : String
, name : String
...
, data : JD.Value
, time : Float
}
projectDecoder : JD.Decoder Project
projectDecoder =
Project
|> first (JD.field “id” JD.string)
-- Type: JD.Decoder
-- (x = String -> y = (... -> JD.Value -> Float -> Project))
|> rest (JD.field “name” JD.string)
-- Type: JD.Decoder (... -> JD.Value -> Float -> Project)
-- ...
-- Type: JD.Decoder (x = JD.Value -> y = (Float -> Project))
|> rest (JD.field “data” JD.value)
-- Type: JD.Decoder (x = Float -> y = Project)
|> rest (JD.field “time” JD.float)
-- Type: JD.Decoder Project -- JD.Decoder y -- :-)

What’s going on here is that functors can hold any type, so as long as you can figure out how to apply the value in the functor, you can decrease the number of remaining parameters by 1. As long as you can express the type of the resulting functor properly, then you can make a function that, given the right ingredients, makes a functor that adds one application to the function in the functor.

The <* and <~ operators on signals used to work this way before elm 0.17, and there are a few elm libraries that use this approach, but I’m writing this hopefully to help others build intuition about these things work. It took me about 2 years to really understand it.

Like what you read? Give art yerkes a round of applause.

From a quick cheer to a standing ovation, clap to show how much you enjoyed this story.