0
votes

Let's say I have a Node and it can have children and siblings and I have traversal lens implementations for them. How do I compose them into a single lens that traverses both children and siblings?

_children :: Traversal' Node Node
_children = ...

_siblings :: Traversal' Node Node
_siblings = ...

_relatives :: Traversal' Node Node
_relatives = ???

Seems like a very basic question, but I could not find any info on how to "add" one traversal to another.

1
What does, for example, node # _relatives .~ anotherNode do? Does it set children? Or siblings? Or both? - Fyodor Soikin
I'm not used to the lens operators ... .~ is a set, right? So its set _relatives anotherNode node. Well, I think it must set both to satisfy the lens laws. I'm not sure though. - MnZrK
What I was getting to is: why do you need this "combined" lens? How are you going to use it? Do you really need it to be a lens, or would just a function be enough for your purposes? - Fyodor Soikin
Function is always enough :) But if you want more context, sure: I have a sum type, and for each constructor there is zero, one, or many values in it. I have a prism to focus on a constructor. And I have traversals to grab values from each of the constructors. But now I need to combine them together so I don't care which constructor I actually have. Does it make sense? - MnZrK
And I want it to be a lens, because such sum type is located inside another structure, so I want to compose such lens with some other lenses to get there. - MnZrK

1 Answers

0
votes

This is the closest I've got using the wander function:

_relatives :: Traversal' Node Node
_relatives = wander tra
  where
  tra1 :: forall f. Applicative f => (Node -> f Node) -> Node → f Node
  tra1 = traverseOf _children

  tra2 :: forall f. Applicative f => (Node -> f Node) -> Node → f Node
  tra2 = traverseOf _siblings

  tra :: forall f. Applicative f => (Node -> f Node) -> Node → f Node
  tra g w = lift2 combine fw' fw''
    where
    fw' = tra1 g w

    fw'' = tra2 g w

    combine (Node w') (Node w'') = Node (w' { siblings = w''.siblings })

If only I could get rid of this last line...

combine (Node w') (Node w'') = Node (w' { siblings = w''.siblings })

Ideally, my tra would be like so (chaining result of tra1 into tra2):

tra :: forall f. Monad f => (Node -> f Node) -> Node -> f Node
tra g w = tra2 g =<< tra1 g w

But I can't do that, because wander asks for Applicative constraint and not a Monad. And I can't chain the results of Applicative.

I can also do this generically if my Node implements Semigroup:

tra :: forall f. Applicative f => (Node -> f Node) -> Node -> f Node
tra g w = lift2 append fw' fw''
  where
  fw' = tra1 g w
  fw'' = tra2 g w

But I don't have Semigroup for my real use-case...

Is there really no generic way to combine two traversals?

I really hope someone can come up with better approach. If not, I'll mark this answer as accepted after a while.

EDIT:

Turns out I'm not the only one wishing to do that, and apparently it's impossible due to the "sequencing" nature of such transformation - it requires a Monad while we only have an Applicative https://github.com/ekmett/lens/issues/109

Going to accept this answer since there is probably no better answer than "there is no answer".