Conversation
|
@ekmett and @RyanGlScott , what do you think? Two questions in my mind:
|
|
Another question: as @phadej suggested, I wrote these to take specific traversals, rather than |
|
Combinators can generally ask for any functor that they need, so that's fine. The main uses for ATraversal/AnIndexedTraversal is to allow users to pass around lists of traversals and the like, and to reconstitute them into real Traversals / IndexedTraversals through cloneTraversal. |
|
These should be marked INLINE, and the current morphology would likely lead to %!~ and %!@~ probably, (and %!= and %!@= respectively for state). The nice thing about the state one is you can use the usual strict function over a monad encoding I think using <$!>. |
src/Control/Lens/Traversal.hs
Outdated
| -- fields. | ||
| -- | ||
| -- @ | ||
| -- over traverse (const ⊥) [1,2] = [⊥, ⊥] |
There was a problem hiding this comment.
These can be turned into proper doctests. length $ ... and matching like
...
...Exception...
...
for an error. It's not the prettiest, but it's an honest test.
There was a problem hiding this comment.
To my mind doctests wouldn't do as good a job of documenting, but I can write them anyway. How do I run those locally?
There was a problem hiding this comment.
cabal build all --enable-tests
cabal-docspec
There was a problem hiding this comment.
To my mind doctests wouldn't do as good a job of documenting, but I can write them anyway.
There is barely any non-doctest examples in lens. They may do less good job as documenting, but at least they stay up to date. There are hundreds of examples in lens package. No-one would ever go through them manually and verify they still type-check/work/etc.
There was a problem hiding this comment.
I'm getting errors from cabal-docspec about mismatched GHC and plan versions. Unfortunately, cabal-docspec doesn't seem to have much in the way of documentation, so I can't figure out where to go from there.
Are you suggesting traversing in the ambient monad? Or just pattern matching out of |
|
It's a state monad variant, so in theory the <$!> trick should be usable in the ambient monad. Not wedded to doing it that way, was just indicating it's probably doable and quite clean. |
| -- >>> over _1 show (10,20) | ||
| -- ("10",20) | ||
| -- | ||
| -- Like 'fmap', @over@ is normally lazy in the result(s) of calling the |
There was a problem hiding this comment.
I'm also on the edge with this "warning".
Shouldn't the strict version also have a similar warning that it actually forces all the computations up front. I.e. over' traverse f aList is most likely a wrong thing to do. (And the examples used are indeed contrived).
There was a problem hiding this comment.
Yes, they're contrived. Should I give a datatype definition and makeLenses and all that jazz? A more realistic case might be a record with a lazy field. If you use over several times, you'll build a thunk chain in there. over' traverse isn't too far from what, say, Data.Map.Strict.map does, though it's probably less efficient.
There was a problem hiding this comment.
That's a problem (to give an non contrived examples). I think in majority of relevant cases you'd rather make the field strict. In fact, there are people which would prefer Data.Map.Strict to be a separate datatype. (The type system doesn't help to keep the Map thunk free.)
i.e. while over' is useful, I don't think it should be commonly used.
There was a problem hiding this comment.
The only case I can think out of my head is Seq. Where over' traverse doesn't leave thunks, but AFAIK ruins asymptotics.
There was a problem hiding this comment.
@phadej over' could quite reasonably be used with ix for Array, SmallArray (if that has an instance), Vector, Seq, Map, or IntMap. I imagine some folks just use at instead, but that's semantically overkill (and potentially expensive) for just forcing.
|
This "solves" #944 |
|
It occurs to me that there's another approach to this issue, and it's not entirely clear whether one or the other is better. First, an exceedingly simple monad transformer that probably belongs in newtype SoloT m a = SoloT
{ runSoloT :: m (Solo a) }
-- Note: in the below instances, clever implementations of <$,
-- <*, *>, or >> will almost certainly be wrong; the defaults are
-- correct.
instance Monad m => Functor (SoloT m) where
fmap = liftM
instance Monad m => Applicative (SoloT m) where
pure = SoloT . pure . Solo
liftA2 = liftM2
(<*>) = ap
instance Monad m => Monad (SoloT m) where
m >>= f = SoloT $ runSoloT m >>= \(Solo a) -> runSoloT (f a)
instance MonadTrans SoloT where
lift = SoloT . fmap SoloNow, we can strictify optics in a monadic context: strictly :: Monad m => LensLike (SoloT m) s t a b -> LensLike m s t a b
strictly l f s = runSoloT (l (\a -> SoloT $ f a >>= \ !b -> pure $ Solo b) s) >>= \(Solo t) -> pure tIn particular, we can write overly' :: LensLike (SoloT Identity) s t a b -> (a -> b) -> s -> t
overly' l = over (strictly l)I still think a direct implementation of |
|
|
|
Sorry, no it's not. This stuff is confusing! Let's consider both. |
|
Ah, now I understand why that felt off. The monad transformer is the wrong thing, but there's a very similar (slightly lazier) applicative transformer that I believe does exactly what I mean. type BoxT :: (Type -> Type) -> Type -> Type
newtype BoxT m a = BoxT
{ runBoxT :: m (Solo a) }
deriving (Functor)
instance Apply m => Apply (BoxT m) where
liftF2 f (BoxT m) (BoxT n) = BoxT (liftF2 (liftA2 f) m n)
{-# INLINE liftF2 #-}
instance Applicative m => Applicative (BoxT m) where
pure = BoxT . pure . Solo
{-# INLINE pure #-}
BoxT m <*> BoxT n = BoxT (liftA2 (<*>) m n)
{-# INLINE (<*>) #-}
#if MIN_VERSION_base(4,10,0)
liftA2 f (BoxT m) (BoxT n) = BoxT (liftA2 (liftA2 f) m n)
{-# INLINE liftA2 #-}
#endifNow, we can be exceedingly general: strictly :: (Functor m, Profunctor p, Profunctor q) => Optical p q (BoxT m) s t a b -> Optical p q m s t a b
strictly l f = rmap (fmap getSolo .# runBoxT) $ l (rmap (\mb -> BoxT ((Solo $!) <$> mb)) f)
{-# INLINE strictly #-} |
src/Control/Lens/Traversal.hs
Outdated
| -- 'Setter'. | ||
| newtype BoxT m a = BoxT | ||
| { runBoxT :: m (Solo a) } | ||
| deriving (Functor, Foldable, Traversable) |
There was a problem hiding this comment.
Where should this type be defined and exported?
There was a problem hiding this comment.
So you don't want to wait until SoloT is added somewhere, as you proposed in private mail to me and Ross (maintainer of transformers)?
I understand that you have time to work on stuff during holidays, but I honestly don't, so will politely ignore you until the next year.
There was a problem hiding this comment.
SoloT turned out to be the wrong sort of transformer for this purpose. I made a mistake and changed course.
There was a problem hiding this comment.
Please tell that to others in that email message too. I wasn't aware that I don't need to think about that anymore.
5083a08 to
b1be2ff
Compare
|
I don't know how much to overgeneralize things. We could, for example, write over' :: (Profunctor p, Profunctor q) => Optical p q Solo s t a b -> p a b -> q s t
over' l f = rmap getSolo $ l (rmap (Solo $!) f)Is that more useful or more confusing? |
ab7c984 to
4eab46d
Compare
Will |
Yes. |
|
So can |
|
In that scheme, over :: (Profunctor p, Profunctor q) => Optical p q Identity s t a b -> p a b -> q s t
over l f = runIdentity #. l (Identity #. f) |
1b0e3dd to
9de9c7c
Compare
|
It occurs to me that when used in a non-trivial functor, the traversal produced by strictlyT :: (Traversable f, Profunctor p, Profunctor q) => Optical p q (BoxT f) s t a b -> Optical p q f s t a b
strictlyT l f = rmap (getSolo . sequenceA .# runBoxT) $ l (rmap (BoxT #. fmap (Solo $!)) f)
strictlyM :: (Monad f, Profunctor p, Profunctor q) => Optical p q (BoxT f) s t a b -> Optical p q f s t a b
strictlyM l f = rmap ((>>= \(Solo r) -> pure r) .# runBoxT) $ l (rmap (BoxT #. fmap (Solo $!)) f)Is either of these worth having? |
* Add `strictly` to turn a lazy (standard) traversal into a strict one that forces targets before installing them. * Add `over'`, `iover'`, `modifying'`, `imodifying'`, and corresponding operators `%!~`, `%!@~`, `%!=`, and `%!@=`. * Adjust documentation. Closes ekmett#1016
|
i think they are worth having, i hate the names, but not the functionality. |
|
@ekmett should the monadic version use the |
Add
over'andiover', which force the result(s) of applying the passed function.Closes #1016