8

Lets say I have:

{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TupleSections #-}
import Control.Lens

data T a b = T { _foo :: a
               , _bar :: a -> b
               }

makeLenses ''T

a appears in both foo and bar, so updates have to be "simulatenous", as it were. This is possible without lenses:

eg1 :: T a (b -> c) -> b -> T (a, b) c
eg1 (T foo bar) b = T (foo, b) (uncurry bar)

But how can I do this with lenses? The following doesn't work with an occurs check:

eg :: T a (b -> c) -> b -> T (a, b) c
eg t b = t & foo %~ (, b) & bar %~ uncurry
dfeuer
  • 48,079
  • 5
  • 63
  • 167
user2141650
  • 2,827
  • 1
  • 15
  • 23

1 Answers1

6

You can't do this using the automatically generated lenses for T. If you want to stretch things a bit, you can first define

data T' a b c = T' { _foo' :: c, _bar' :: a -> b}

tt :: Iso (T a b) (T a' b') (T' a b a) (T' a' b' a')
tt = dimap (\(T x g) -> T' x g) (fmap (\(T' x g) -> T x g))

Then you can (automatically) build type-changing lenses for T' and use tt to use them to modify values of type T a b through the isomorphism.

For example, rearranging the arguments a bit, you can write

eg :: b -> T a (b -> c) -> T (a, b) c
eg b = over tt $ (foo' %~ (,b)) . (bar' %~ uncurry)

Another approach that will likely be better if you don't need to fuss around with T too much is to define it as a newtype around T':

newtype T a b = T { getT :: T' a b a }

Then you can skip the Iso and just compose things. Rearranging the arguments the same way,

eg' :: b -> T a (b -> c) -> T (a, b) c
eg' b = T . (foo' %~ (,b)) . (bar' %~ uncurry) . getT
dfeuer
  • 48,079
  • 5
  • 63
  • 167
  • 1
    `over` can be used directly in lieu of `from` bracketing: `supply y = over tt (\(T' x f) -> T' (x, y) (uncurry f))` – duplode Sep 27 '15 at 18:24
  • @duplode, actually, I can't seem to make that work at all. I've backed off to the bracketing approach, but if you can show how to do it better, I'm all ears. – dfeuer Sep 28 '15 at 05:09
  • 1
    It seems the only issue is that `tt` should be `Iso (T a b) (T a' b') (T' a b a) (T' a' b' a')`; that is, `a` should be allowed to change as well. That should be enough to allow you to define `eg` with a single `over` then. – duplode Sep 28 '15 at 11:30
  • @duplode, at last! I also worked in the lensy modification to `T'`, which was the actual purpose of setting up that `Iso`. – dfeuer Sep 28 '15 at 14:48