How to shoot yourself in the foot with lenses and state

The type of van Laarhoven lenses forall f. Functor f => (i -> f i) -> o -> f o seems to be made for effectful updates i -> f i. At least so I believed. And I had to pay dearly because of that.

Read the full story in my blogpost at: How to shoot yourself in the foot with lenses and state | shoot-yourself-in-the-foot-with-lenses

Source: GitHub - andreasabel/shoot-yourself-in-the-foot-with-lenses: Blog post on van Laarhoven lenses in Haskell

(Apologies for using github as minimalist platform for blog posts…)

17 Likes

Wow that is good to know, thanks for sharing!

A while back there was some discussion of a combinator like modifyM in a lens issue.

1 Like

Even if you didn’t use any lenses, modifyM on its own would already be incorrect though, wouldn’t it?

You’re reading the state, updating it in an effectful way that can include state modifications and then overriding those state modifications by writing the result.

8 Likes

^ this. I wonder if you could do something like

modifyM :: MonadState o m => Lens' o i -> (i -> m i) -> m ()
modifyM l f = do
  o <- get
  i' <- get_ l <$> l f o
  modify $ \o' -> set l o' i'
2 Likes

@brandonchinn178 What if the effects of f included also changes to the i part of o? Those would get deleted by the set operation.

I now see that my modifyMSafe version has the same problem.

Then why use lenses in the first place?? :stuck_out_tongue: You’re using a lens to focus on a specific subpart, but then you’re modifying the another subpart too. It kind of feels like a mapM except you’re allowed to modify the list in the callback. It’s a red flag; you might be using the wrong abstraction.

When I first saw your blog post, I assumed modifyM would be used for stuff like “modify a field based on the contents of a file”, which makes a bit more sense for a lens operation. You could still shoot yourself in the foot, but at least it has some usefulness.

3 Likes

Of all the mysterious things about this, this one seems least mysterious. The point is the the i -> m i function gives you the new value of i. If m has also modified in the meantime I wouldn’t be surprised if that modification was superseded by the returned i.