Crocodile & Alligator

In my previous post I talked about using Lens es as a way to clean up some of the destructuring boilerplate in Elm’s update function, but we can use these to do some other nifty tricks.

Recently at work I had a problem involving some nested data types in an Either . So one thing you’ll notice about Elm is that you have to push in very concrete types through the port system—and you definitely can’t have polymorphism. There’s nothing wrong with that, and it can give you some nice guarantees. So here’s the problem I ran into: I have two record types that are similar, but different enough that I needed to have different types; let’s assume these as type Foo and Bar that correspond to a field on our model. At the end of the port sending in List Foo s (and similarly with Bar s), I marshaled List Foo with:

import Monocle.Lens exposing (Lens) type alias Model = { foobar : List (Either Foo Bar) } foobar : Lens Model (Either Foo Bar) foobar = Lens .foobar (\fb m -> { model | foobar = fb })

import Either exposing (Either(Left, Right)) import Return exposing (ReturnF) update : Route -> Msg -> ReturnF Msg Model update route msg = case msg of ... FoosReceived foos -> List.map (marshalFoo >> Left) foos |> .set Model.foobar |> Return.map BarsReceived bars -> List.map (marshalBar >> Right) bars |> .set Model.foobar |> Return.map ...

This makes a lot of sense since I can define what’s going to be shoved into the port for both types, do the modifications I need—like turning Int s to their representative ADT s—and then tagging it Left or Right to distiguish them. So where this starts to get funky though is at the view layer where now you’re going to be pulling apart this structure a lot which is going to involve a lot of repetitious Either.unpack s that get ugly. This is what we’re going to solve with our buddy the Lens .

Toy Time with the Order Crocodilia

So let’s talk about alligators and crocodiles (that’s why you’re here, right?). Unless you’re a biologist, nerd, or pedantic asshole (like me), these two aquatic reptiles are essentially the same thing. Alas, they are not, for instance: alligators are normally grey, shorter, U-snouted, freshwatery, and only fans of living in the US and China—my educated guess is because they are also fans of the military-industrial complex and imperialism (mandible-fest destiny??).

Martial alligator

…But you know what they really don’t have in common? The data structure for their names in my toy example.

import Either exposing (Either) type alias Crocodile = { firstName : String , lastName : String } type alias Alligator = { fullName : String } type alias Model = { poppycroc = List (Either Crocodile Alligator) }

These guys always gotta be similar but just a little bit different. For the sake of simplicity for this toy concept, a full name looks like “Bob Saget”—no mononyms—where “Bob” is the first name and “Saget” is the last name.

So what happen at the view layer?

import Either exposing (Either) import Function.Extra as Fn import Html exposing (..) import List.Extra as List view : Model -> Html Msg view { poppycroc } = div [] [ h1 [] [ text "Our Reptile Names" ] , ul [] <| List.map (Either.unpack (Fn.map2 (++) .firstName .lastName) .fullNa|e >>

-> li [] [ text n ] ) poppycroc ]

So what we have here is some point-free goodness in that unpack that on the Left gets the firstName and lastName fields and lifts them across the (++) , the append infix, to make a full name, and on the Right we just access the fullName . Those unpack to String s, and are then shoved into a text node, then into a list singleton , and finally into an li . Neat, yes, but what what are we going to do if we need to use that full name all over the app as you normally do? Well, we could store that unpackery to a function or we could use a Lens .

module CMT exposing (..) import Either exposing (Either) import Function.Extra as Fn import String type alias ChevroletMovieTheater = Either Crocodile Alligator fullName : Lens ChevroletMovieTheater String fullName = let setc : String -> Crocodile -> Crocodile setc n c = case String.split " " n of [ fn, ln ] -> { c | firstName = fn, lastName = ln } -- I thought we said no mononyms... you're killing this -- example. IRL, I'd have a validation step first for -- setting the name. _ -> c seta : String -> Alligator -> Alligator seta n a = { a | fullName = n } in Lens -- getter (Either.unpack (Fn.map2 (++) .firstName .lastName) .fullName ) -- setter (

ame -> Either.mapBoth (setc name) (seta name))

This is Lens that traverses the Either . It’s reusable and composable (we could even use Lens es with with subtypes as well!). So let’s look at that new view:

import Html exposing (..) import List.Extra as List import CMT view : Model -> Html Msg view { poppycroc } = let names : List (Html Msg) names = List.map (.get CMT.fullName >>

-> li [] [ text n ]) poppycroc in div [] [ h1 [] [ text "Our Reptile Names" ] , ul [] names ]

Takeaway

That Lens function is so short in the view, we can actually one-line this list item. We have a way to peek at our Either Crocodile Alligator through a Lens that let’s us act as though these two similar-but-different types were actually the same type.

At my job, I used this across an Either and called head on an inner list containing records that all had a field of the same value (like a primary key) which saved me a lot of steps and really cleaned up my code. It also afforded me the ability to update all the records in the list with the new field too!

Hopefully this excites you to think of other clever, useful ways to use to access more complex data structures. If you need some inspiration: