>
> Having read the reddit thread about just starting big and breaking things 
> down, it reminds of arguments I've heard for years about how monolithic 
> programs are easier to work with.

 
This Reddit post 
<https://www.reddit.com/r/elm/comments/5jd2xn/how_to_structure_elm_with_multiple_models/dbuu0m4/>
 
is about *how to break up a monolith*. It doesn't talk about anything else 
except how to break up a monolith. :)

Abstraction barriers are what make big programs possible. I don't think 
> there is any magic in Elm that changes this fundamental lesson of 60 years 
> of software development.


I couldn't agree more! Glad we're on the same page about that.
 

> The rest of the world has looked at TEA and seen value in it and is 
> looking to extend it.


If people look at it, see value in it, and then think "okay this works 
great, but how can we take it in a different direction?" - that sounds like 
putting the cart before the horse to me.

I think a lot of people would be happier if they gave the "use the simplest 
API that works" approach a shot. :)
 

> The remark that composing TEA-shaped units isn't what Elm is designed for 
> raises the question of whether we should then be expecting Cmd.map and 
> friends to be going away since that would seem to be exactly what they are 
> designed for.
>

That's a great question! They are designed to make it easier to work with 
Html and Cmd alongside several message constructors.

Example

Pretend there is no Html.map and I'm writing a reusable view whose entire 
API consists of one function:

checkbox : (Bool -> msg) -> Bool -> Html msg

The only state I care about is whether the checkbox is checked (the Bool 
argument), and the only user interaction is checking or unchecking the box 
(the (Bool -> msg) argument; the caller is free to use whatever Msg type 
they are already working with, no conversion necessary).

Using a higher-order function to manage messages is a great lightweight 
default choice, so I don't miss Html.map at all here. I wouldn't use it 
regardless!

Now let's say what I'm building is not a reusable checkbox, but something 
more complex: a reusable *signup form* that expands in-place when the user 
clicks it.

This will involve a lot more Msg constructors - for recording text entry in 
the username/password fields, HTTP responses for username availability 
checks, expanding and collapsing it, submitting it...overall, a lot more 
going on. Let's say it needs 10 message constructors to work properly.

Using the same API design as we did for checkbox above, we'd write 
something like this:

signupForm : (SignupForm.State -> GiganticRecordOfTenMsgConstructors msg) 
-> SignupForm.State -> Html msg

With this API, reusing this signup form entails spelling out all 10 Msg 
constructors as the first argument to this function. That's a lot more work 
than the single Bool argument needed by checkbox! This is where a more 
heavyweight approach can pay off: creating a new SignupForm.Msg and having 
signupForm return Html SignupForm.Msg instead of Html msg.

Here's how that API would look:

signupForm : SignupForm.State -> Html SignupForm.Msg

type SignupForm.Msg

We had to do more work to set this up, of course (the checkbox function 
didn't need the Msg implementation this signupForm function requires), but 
we successfully eliminated the need for the gigantic record argument. Great!

In exchange, though, not only have we needed a new Msg type on the 
SignupForm side, but now we also need our caller to convert from 
SignupForm.Msg to whatever their local flavor of Msg is. (Hence why this is 
a more heavyweight approach.)

Since our application uses FooApp.Msg, but signupForm returns Html 
parameterized 
on a SignupForm.Msg, we'll have a type mismatch if our application's view 
tries to call signupForm directly. In the absence of Html.map, we're stuck 
- this thing is no longer reusable, it's become a nonfunctional art piece 
that we can't actually plug in anywhere.

If we have Html.map, problem solved! We can add this approach to our 
toolbox, right alongside the simpler "one higher-order function and you're 
done" API we used for checkbox.

Overengineering

Now granted, Html.map does introduce the potential for overengineering. We 
wrote this earlier:

checkbox : (Bool -> msg) -> Bool -> Html msg

This was an unremarkably simple API, but Html.map gives us the ability to 
replace this single function with a full-blown Checkbox module, complete 
with:

   - type alias Model = { checked : Bool }
   - type Msg = SetChecked Bool
   - update : Msg -> Model -> ( Model, Cmd Msg) *[gotta return Cmd Msg in 
   addition to Model, because what if someday our checkbox wants to send HTTP 
   requests?!]*
   - view : Model -> Html Msg
   - a chauffeured Rolls-Royce to deliver the single Boolean worth of state 
   and interaction from one function to another.

To implement this massively overengineered API (remember, we achieved the 
same functionality using *one small function* before) in a world without 
Html.map and Cmd.map, we'd have to pass those hulking 10-field records 
between even more functions. In such a world, this API would be so 
outrageously cumbersome to use that I doubt anyone would attempt it. The 
fact that Html.map makes this appear deceptively okay as an API choice is 
an unfortunate downside of Html.map.

And sure, given that Html.map and Cmd.map exist, it's a reasonable question 
to ponder - "if I was correct to give my Reusable Signup Form this API, 
shouldn't One Size Fit All, meaning it would also be correct to choose that 
exact same API for every other use case I can think of?" - but there is an 
answer to this question, and it is "no; my code will be simpler if I fit 
the API to the use case."

Despite the fact that Html.map introduces this potential overengineering 
pitfall, I think it's clearly useful enough for the Reusable Signup Forms 
of the world to be worth including. I wouldn't (and don't) worry about it 
going anywhere! :)

-- 
You received this message because you are subscribed to the Google Groups "Elm 
Discuss" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to [email protected].
For more options, visit https://groups.google.com/d/optout.

Reply via email to