The Entropic Thoughts blog has a new post: “Non-Obvious Haskell Idiom: ViewPattern Argument Transform” regarding something I didn’t know existed in Haskell, and it touches upon something I’ve written about before so I’d like to say how they are related, and wonder whether this is the correct solution, I don’t have a better one.
So first the new feature in Haskell is a “ViewPattern argument transform”. What is one of those? Well suppose you have a function such as:
someFunction param =
let
usefulValue = transform param
in
... in which usefulValue is useed
The issue here is that param
is only used once to transform it into something that is more useful throughtout the rest of the scope of the function.
It would be better if we could somehow remove param
from scope, so that we do not unnecessarily use it. In Haskell, there is a way to do that, using a “ViewPattern argument transform” the function can instead be written as:
someFunction (transform -> usefulValue) =
... in which usefulValue is useed
Now, as I said before, I’ve written about about the basic problem that this is solving before. To recap the example from that post:
update : Msg -> Model -> ( Model, Cmd Msg)
update message model =
...
RollDice ->
let
newModel : Model
newModel =
rollDice model
in
( case newModel.dice == 6 of
True ->
{ newModel | score = newModel.score * 2 }
False ->
newModel
, Cmd.none
)
...
Now notice that newModel
is used 4 times in the body of the let-in
. Also note that it is the same type as model
and hence an easy bug to make is to misuse model
in place of newModel
. I chose a rollDice
example, because that likely uses elm/random
and the new Model
that it returns has the updated seed for the random number generator. It’s a common bug in functional languages to forget to store the new seed. It’s also likely a pernicious bug because basically everything works more or less as expected. You have to notice that, on certain paths, the subsequent numbers generated are not entirely ‘random’. If the seed is sometimes used in some other way as well, then this can be pretty hard to spot. Anyway, this is the problem that “ViewPattern Argument Transform” solve. In this case, we would have to factor out the RollDice
case into a function of its own:
updateRollDice : Model -> (Model, Cmd Msg)
updateRollDice (rollDice -> newModel) =
( case newModel.dice == 6 of
True ->
{ newModel | score = newModel.score * 2 }
False ->
newModel
, Cmd.none
)
update : Msg -> Model -> ( Model, Cmd Msg)
update message model =
...
RollDice ->
updateRollDice model
...
Note that we don’t even need the name newModel
now, we can just call this model
, so we could re-write updateRollDice
as:
updateRollDice : Model -> (Model, Cmd Msg)
updateRollDice (rollDice -> model) =
( case model.dice == 6 of
True ->
{ model | score = model.score * 2 }
False ->
model
, Cmd.none
)
Since the name newModel
was only ever there to distinguish it from model
.
So I think this would mostly solve the problem that I had written about. There are two things I do dislike about this syntax:
- It sometimes, as in this example, forces you to factor out code you may not have otherwise chosen to.
- It feels a little ‘magic’, until someone explains what happens and why I feel like reading this code in the wild would seem quite opaque.
In my previous blog post I noted that Python does have syntax or this:
>>> x = [1,2,3]
>>> y = x
>>> del x
>>> x
Traceback ...
NameError: name 'x' is not defined
>>> y
[1, 2, 3]
This is far more intuitive, but I also noted that in functional languages typically the order of definitions doesn’t matter, and that this feels more functional and less imperative. So having a del <name>
command in Elm (or any other functional language) would feel a bit imperative.
I don’t have a better solution than either del
or “ViewPattern Argument Transform”, but if I were making a functional language I would consider either feature not quite perfect enough to include.