-
-
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 | |
) |
On a tablet so I can't test, but this feels right:
|> Maybe.map (changePlayerName playerToChange)
|> Maybe.map ((<<) leaveEditingMode)
I would probably have done something similar. Another option is to let the functions used operate on Maybe ScorekeeperModel
instead of ScorekeeperModel
. 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.
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 want to be able to inline
changePlayerNameThenLeaveEditingMode
in this pipeline, but I can't figure out how to interleave the function composition with theMaybe
ness. I have the feeling that either (1) I'm looking for something that isn't there or (2) I'm missing something altogether different that I should be doing instead.My overall strategy was to do this: parse the player name, Maybe.map (changePlayerName >> leaveEditingMode), and then apply this to the scorekeeper model, with
identity
as a default function to apply to the model. I'd like to do this as pointlessly (haha) as I can.I looked into Applicatives, but I don't think I understood it. I was thinking that perhaps I could create a Maybe (changePlayerName >> leaveEditingMode) to apply to scorekeeperModel, and if function were Nothing, then I might just get back scorekeeperModel. I couldn't figure out how to do that. I didn't go far down this road; maybe I should have?
What would you do?