-
-
Save jbrains/1a4e2f4c22705e6d5cac6652db607bed to your computer and use it in GitHub Desktop.
changePlayerName : PlayerModel -> String -> ScorekeeperModel -> ScorekeeperModel | |
changePlayerName playerToChange newName scorekeeperModel = | |
let | |
{ players } = | |
scorekeeperModel | |
in | |
{ scorekeeperModel | |
| players = | |
Exts.List.mergeBy | |
(.id) | |
players | |
[ { playerToChange | name = newName } ] | |
} | |
leaveEditingMode : ScorekeeperModel -> ScorekeeperModel | |
leaveEditingMode scorekeeperModel = | |
{ scorekeeperModel | |
| playerToEdit = Nothing | |
, newNameOfPlayer = "" | |
} | |
-- This is the part that feels weird. How do I inline this into the pipeline below? | |
changePlayerNameThenLeaveEditingMode : PlayerModel -> String -> ScorekeeperModel -> ScorekeeperModel | |
changePlayerNameThenLeaveEditingMode playerToChange newName = | |
changePlayerName playerToChange newName | |
>> leaveEditingMode | |
-- deep inside an update function | |
-- newNameOfPlayer is a String | |
-- playerToChange is a PlayerModel | |
-- scorekeeperModel is a ScorekeeperModel | |
-- this expression should return a ScorekeeperModel | |
scorekeeperModel | |
|> (newNameOfPlayer | |
|> Exts.Maybe.validate (not << String.isEmpty) | |
|> Maybe.map (changePlayerNameThenLeaveEditingMode playerToChange) | |
|> Maybe.withDefault identity | |
) |
You can just go with the simple approach. I think that's easier to understand.
if String.isEmpty newNameOfPlayer then
scorekeeperModel
else
changePlayerNameThenLeaveEditingMode playerToChange newNameOfPlayer scorekeeperModel
Your types are not lining up, as far as I can tell.
One thing that I find helpful here is that the order of applying Maybe.map
and function composition shouldn't matter.
That is, Maybe.map (f >> g) == (Maybe.map f) >> (Maybe.map g)
(and that will hold true for <<
as well).
So looking at Maybe.map (changePlayerName playerToChange >> leaveEditingMode)
, I think of it as first using Maybe.map on each of the functions being composed, then composing the resulting Maybe functions. So for the whole thing to typecheck, each of the components has to typecheck.
The type of changePlayerName playerToChange
is String -> ScorekeeperModel -> ScorekeeperModel
- it takes a String and a ScorekeeperModel, then returns an updated ScorekeeperModel. We could also say "it takes a String, and returns a function that takes a ScorekeeperModel and returns a ScorekeeperModel". This latter phrasing seems odd, but it's the more appropriate one when we think of the Maybe type we have:
Exts.Maybe.validate (not << String.isEmpty) : String -> Maybe String
Because Maybe.map
takes a function on base types, a -> b
, and returns a function that turns a Maybe
of type a
into a Maybe
of type b
.
So Maybe.map (changePlayerName playerToChange)
takes a Maybe String
, and returns a Maybe (ScorekeeperModel -> ScorekeeperModel)
. (This is, in fact, just the type of changePlayerNameThenLeaveEditingMode playerToChange
- which is why the code above works).
But it breaks down when you try compose that with
Maybe.map leaveEditingMode
Because the type of that must necessarily be
Maybe.map leaveEditingMode : Maybe ScorekeeperModel -> Maybe ScorekeeperModel
whereas for the composition to work we would need the type to be (ScorekeeperModel -> ScorekeeperModel) -> (ScorekeeperModel -> ScorekeeperModel)
.
What that type looks like to me is a function which takes a "ScorekeeperModel updater", and returns a different one. This is what @SamirTalwar's suggestion does - it gives to the second Maybe.map
a function that says "compose something with leaveEditingMode". That something is also a "ScorekeeperModel updater", and they compose just in the way we need. (I think possibly you might want the arrow in the opposite direction, to apply leaveEditingMode last.)
HTH
I would probably have done something similar. Another option is to let the functions used operate on
Maybe ScorekeeperModel
instead ofScorekeeperModel
. Your pipe will be neater but your function interfaces more bloated. In my experience it is hard to get neat code all over the place so you will have to decide what parts of the code that benefits most from readability.