Why would my datatype need to an instance of Monoid to use this lens?
Asked Answered
D

2

10

I'm using the code below on a record that has a field '_scene' of type SceneGraph. I've created lenses for it using makeLenses.

inputGame :: Input -> Game -> Game
inputGame i g = flip execState g $ do
    let es = g ^. userInput . events
        sg = g ^. scene
    userInput .= i
    scene .= foldl (flip inputEvent) sg es

inputEvent :: InputEvent -> SceneGraph -> SceneGraph
inputEvent (WindowSizeChangedTo (w,h)) (SceneRoot _ _ sg) = SceneRoot w h sg
inputEvent _ sg = sg

I'm getting the error:

No instance for (Monoid SceneGraph) arising from a use of `scene'
Possible fix: add an instance declaration for (Monoid SceneGraph)
In the second argument of `(^.)', namely `scene'
In the expression: g ^. scene
In an equation for `sg': sg = g ^. scene

But I don't understand why SceneGraph has to be an instance of Monoid in order to use this lens.

Dogvane answered 9/7, 2013 at 1:47 Comment(0)
B
17

You probably want either (^?), or maybe (^..) (non-operator names: preview, toListOf).

When you have a Lens (or a Getter, Iso, Equality, etc.), it always refers to exactly one item. So you can use plain old (^.) (non-operator name: view). When you have have a Traversal (or a Fold, Prism, etc.), it can refer to 0-or-more items.

Therefore there must be a way to combine them if there's more than one, or a default value to give if there are none. This is done with the Monoid constraint. toListOf gives you a list of all the values; preview gives you either Nothing or Just the first value.

You didn't give the types for any of the functions you're using, so I can't really tell what you intended. My guess would be that maybe scene can fail because you used makeLenses with a sum type that doesn't define scene in every summand. In this case you'd probably want to use (^?) and handle the Nothing case. But it might be something else.

See also my answer to this question (and this question from yesterday! This seems to be a popular topic).

Bechtel answered 9/7, 2013 at 3:50 Comment(5)
Ah! That's why makeLenses would create a prism.Carilla
Okay, I think I get it. It's because my datatype looks like data Game = GameLoad | Game { _scene :: SceneGraph } | GameOver so it's possible that scene could fail due to game being one of the other two constructors...Dogvane
Aha. Yes. You need to handle the GameLoad/GameOver cases.Bechtel
In this case it's not a prism, just an (affine) traversal. Though it could be a prism, since Game has exactly one argument (but I'd consider generating a prism to be somewhat unintuitive behavior here).Bechtel
I've read this answer 3 times today, and schellsan's intuition in the comment above finally helped me understand it. Thanks!Holmen
R
2

I'm assuming you are using Ed Kmett's lens library, it would be helpful if you could also post the version you are using and the imports. It also looks like there are two versions of (^.) supported in that lens library, one with a Getter and one with a Fold, the fold version requiring an instance of Monoid: (^.) :: Monoid r => s -> Fold s r -> r

Edit: I'm betting that you accidentally imported Control.Lens.Fold

Renter answered 9/7, 2013 at 2:13 Comment(3)
No, there's only one (^.). When used with a fold/traversal the Monoid constraint gets added.Bechtel
What is the advantage of requiring that the result be a Monoid?Dogvane
It allows a single result to returned even when the lens is actually focused on 0 (mempty) or multiple (mconcat) values.Carilla

© 2022 - 2024 — McMap. All rights reserved.