'How to use lens to access a record field behind a sum type

I am trying to access a nested record using lenses and prisms in Haskell:

import Data.Text (Text)
import Control.Lens.TH

data State = State
    { _stDone :: Bool
    , _stStep :: StateStep
    }

data StateStep
    = StatePause
    | StateRun
        { _stCounter  :: Int
        , _stMMistake :: Maybe Text
        }

makeLenses ''State
makeLenses ''StateStep
makePrisms ''StateStep

main :: IO ()
main = do
    let st = State False $ StateRun 0 Nothing

    -- works, but the `_2` seems weird
        mMistake = st ^? stStep . _StateStepRun . _2 . _Just

    -- why not something like (the following does not compile)
        mMistake = st ^. stStep . _StateStepRun . _Just . stMMistake

The line that works leaves some questions open. I am unsure whether or not the type match by coincidence. The field _stMMistake has type Maybe Text, but what about

let st = State False StatePause

? I am missing the explicit join.

And I am clueless about how prisms work. While it seems logical for the prism to give me a tuple, at the same time I expected something composable in the sense that I can go deeper into my nested structure, using lenses. Do I have to derive my instances manually for this, maybe?



Solution 1:[1]

Optics generally work out more cleanly when sum type constructors each have at most one field. In your case, you could write something like

data StateStep
    = StatePause
    | StateRun {-# UNPACK #-} !Runny

data Runny = Runny
  { _ryCounter :: Int
  , _ryNoMistake :: Maybe Text
  }

Using a strict field and (since that field is not "small" in the sense of -funpack-small-strict-fields) an {-# UNPACK #-} pragma, you can ensure that StateStep has the same runtime representation as in your code. But now you can get nice field lenses into Runny and everything will work out nicely—no magicked-up tuples.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 dfeuer