-
Notifications
You must be signed in to change notification settings - Fork 274
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add strict over variants #1017
base: master
Are you sure you want to change the base?
Add strict over variants #1017
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
cabal build all --enable-tests
cabal-docspec
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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. |
@@ -342,6 +342,10 @@ cloneIndexedSetter l pafb = taintedDot (runIdentity #. l (Indexed $ \i -> Identi | |||
-- >>> 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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@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 Solo Now, 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 t In 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 #-}
#endif Now, 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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Where should this type be defined and exported?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please tell that to others in that email message too. I wasn't aware that I don't need to think about that anymore.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
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
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This pull request has overgrown what I feel comfortable to comment about. I'd ask @ekmett to review this.
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