Artyom Kazak | 10 Oct 23:00 2013

Conditional lens

Hello!

I am working with TypeReps, and while writing some functions I have
noticed that I could use lenses to simplify them; however, I have stumbled
upon some difficulties.

First I’ll try to clarify which functions I want to write:

     * a function for converting TypeRep of, say, `Maybe x` to `[x]`
       (for all x). It requires checking if the TyCon is `Maybe` and
       replacing it with []-TyCon. If it wasn’t `Maybe`, I return Nothing.

     * a similar function for replacing `Char`s and `Lazy.Text`s to just
       `Text`. Again, if the TypeRep-to-be-replaced doesn’t satisfy my
       conditions, I return Nothing.

These two functions (and some others, I suppose) can be written concisely
with the help of one combinator. I don’t know how to write it as
a composable Lens, so I’ll give it here as an ad-hoc Lens-modifying
function instead:

     ifL :: (a -> Bool) -> Lens s t a b -> Lens s (Maybe t) (Maybe a) b
     ifL p l = lens getter setter
       where
         get s = getConst $ l Const s
         getter s   = let a = get s
                      in  if p a then Just a else Nothing
         setter s b = let a = get s
                      in  if p a then Just (set l b s) else Nothing

(Continue reading)

Edward Kmett | 11 Oct 06:14 2013
Picon

Re: Conditional lens

`ifL` isn't a legal lens for several reasons.

Lens s t a b generally requires that the types a subsumes b and b subsumes a, and that s subsumes t and t subsumes s.

Lens s (Maybe t) (Maybe a) b is a huge red flag.

There is an 'illegal prism' provided by lens that is a more principled version of this, however.

>>> (0,2) & _1.filtered even .~ 2

(2,2)

filtered only claims to be a Fold, because that is all it can legally claim to pass the laws for, however it is implemented in such a way that you can use it as a Traversal or even a Prism. To sleep soundly you should ensure that the elements you write back pass the filter function as True, otherwise you'll violate a law and the lens police will come for you in the night.

You can of course implement the method you want without any of these shenanigans and still sleep well at night, though.

changeTyCon tc tc' = tyCon $ \a -> if a == tc then Just tc' else Nothing

changeTyCon tc tc' = tyCon $ \a -> tc' <$ guard (a == tc)

-Edward



On Thu, Oct 10, 2013 at 5:00 PM, Artyom Kazak <yom <at> artyom.me> wrote:
Hello!

I am working with TypeReps, and while writing some functions I have
noticed that I could use lenses to simplify them; however, I have stumbled
upon some difficulties.

First I’ll try to clarify which functions I want to write:

    * a function for converting TypeRep of, say, `Maybe x` to `[x]`
      (for all x). It requires checking if the TyCon is `Maybe` and
      replacing it with []-TyCon. If it wasn’t `Maybe`, I return Nothing.

    * a similar function for replacing `Char`s and `Lazy.Text`s to just
      `Text`. Again, if the TypeRep-to-be-replaced doesn’t satisfy my
      conditions, I return Nothing.

These two functions (and some others, I suppose) can be written concisely
with the help of one combinator. I don’t know how to write it as
a composable Lens, so I’ll give it here as an ad-hoc Lens-modifying
function instead:

    ifL :: (a -> Bool) -> Lens s t a b -> Lens s (Maybe t) (Maybe a) b
    ifL p l = lens getter setter
      where
        get s = getConst $ l Const s
        getter s   = let a = get s
                     in  if p a then Just a else Nothing
        setter s b = let a = get s
                     in  if p a then Just (set l b s) else Nothing

It works like this:

    > (0, 2) & ifL even fs .~ "hello"
    Just ("hello",2)

    > (1, 2) & ifL even fs .~ "hello"
    Nothing

With `ifL`, my initial ugly

    changeTyCon :: TyCon -> TyCon -> TypeRep -> Maybe TypeRep
    changeTyCon tc tc' t | t^.tyCon == tc = Just $ t & tyCon .~ tc'
                         | otherwise      = Nothing

boils down to

    changeTyCon tc tc' = ifL (== tc) tyCon .~ tc'

Why did I call the initial version “ugly”? Well, because

    a) it manually handles `Maybe`s, and
    b) it has to perform both getting and setting (two passes).

So, my questions are:

    1. What would be the idiomatic way to write `ifL`?

    2. How can I do something like `t ^. ifL (== tc) tyCon`?
       Currently it doesn’t work because view’s type has been
       simplified in lens-3.9.

    3. Perhaps it would be better to represent `ifL` as a Traversal
       which simply ignores values that don’t match the condition?
       Then I could (?) use `failover` to do what I want. I searched
       for something filter-like in lens library, but haven’t found
       anything.

    4. If I haven’t missed anything and it indeed can’t be done with bare
       lens, would `ifL` or something similar be welcome as an addition
       to the library?

Thanks!

_______________________________________________
Haskell-Cafe mailing list
Haskell-Cafe <at> haskell.org
http://www.haskell.org/mailman/listinfo/haskell-cafe

Gmane