Skip to content

Instantly share code, notes, and snippets.

@gseitz
Created July 27, 2012 21:34
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save gseitz/3190574 to your computer and use it in GitHub Desktop.
Save gseitz/3190574 to your computer and use it in GitHub Desktop.
Edward "The Lens Machine" Kmett on his take on MultiLenses
[22:34] < mightybyte> But data-lens isn't up to date with it.
[22:34] < edwardk> ghci> :t sumOf folded
[22:34] < edwardk> sumOf folded :: (Num c, Data.Foldable.Foldable f) => f c -> c
[22:35] < mightybyte> Hah! I was wondering how long you'd be able to resist.
[22:35] < edwardk> but we can use sumOf with any Lens, Traversal or Fold, etc.
[22:35] < edwardk> for instance: sumOf (traverse.traverse):: (Num c, Traversable t1, Traversable t) => t (t1 c) -> c
[22:35] < edwardk> sumOf (traverse._2), ec.
[22:36] < edwardk> toListOf (traverse.traverseLeft) :: Traversable t => t (Either c c1) -> [c]
[22:36] < edwardk> basically traverse is a valid Traversal, but there are lots of others
[22:38] < edwardk> so multilenses become 'Traversals' and you can do things like: productOf (traverseValueAt 4), etc.
[22:38] < edwardk> anything you can do with a Traversable can be done with a Traversal, and every Lens is a Traversal, so you get a ton of combinators for free
[22:38] < mightybyte> What's a multilens?
[22:38] < edwardk> transposeOf traverse :: Traversable t => t [c] -> [t c]
[22:38] < edwardk> but you can transpose with other lenses, for instance
[22:39] < edwardk> Applicative f => (c -> f d) -> a -> f b
[22:39] < edwardk> instead of Functor f => (c -> f d) -> a -> f b
[22:39] < edwardk> every Lens is a Traversal (which roconnor used to call a MultiLens)
[22:40] < edwardk> so the nice thing is that because they compose this way you can use them when you want to traverse or fold very easily
[22:41] < edwardk> traverse._2 :: (Applicative f, Traversable t) => (a -> f b) -> t (c, a) -> f (t (c, b))
[22:42] < edwardk> if those examples make any sense =)
[22:42] < mightybyte> What's a traversal?
[22:43] < edwardk> type TraversalFamily a b c d = forall f. Applicative f => (c -> f d) -> a -> f b
[22:43] < edwardk> when you take a look at the type of traverse
[22:43] < edwardk> it fits this pattern
[22:43] < edwardk> traverse :: Traversable t => TraversalFamily (f a) (f b) a b
[22:44] < edwardk> now, everything that is in Data.Traversable and Data.Foldable can be done on anything that can supply 'traverse', so what happens when we generalize it to allow for any other traversal?
[22:45] < edwardk> an example of something maybe you might care about more =)
[22:46] < edwardk> traverseByteString :: Traversal ByteString Word8
[22:46] < edwardk> anyOf traverseByteString (==0x80)
[22:46] < mightybyte> Slick
[22:46] < edwardk> all the combinators you expect from Foldable/Traversable can then be applied to bytestrings
[22:46] < mightybyte> This sounds way more general than just lenses.
[22:46] < edwardk> yep
[22:46] < edwardk> there are more traversals than lenses
[22:46] < mightybyte> Should it get a different name?
[22:47] < edwardk> but most the combinators in Control.Lens work with them
[22:47] < edwardk> they are a 'Traversal'
[22:47] < edwardk> =)
[22:47] < edwardk> you can compose a Traversal with a Lens and get a Traversal
[22:47] < edwardk> and it just works
[22:47] < edwardk> if you compose a Traversal with a Getter you get a Fold, and you can do all the things you can do with Foldables
[22:48] < edwardk> -- if you say, just have a function name :: Foo -> ByteString, then you can do stuff like anyOf (getting name . traverseByteString) (==0x80) myFoo
[22:49] < edwardk> because anyOf only requires a Fold
[22:49] < edwardk> after all
[22:49] < edwardk> :t any
[22:49] < lambdabot> forall a. (a -> Bool) -> [a] -> Bool
[22:49] < edwardk> :t Data.Foldable.any
[22:49] < lambdabot> forall a (t :: * -> *). (Data.Foldable.Foldable t) => (a -> Bool) -> t a -> Bool
[22:49] < edwardk> any only needs Foldable
[22:49] < mightybyte> Hmmm, it definitely sounds interesting.
[22:51] < edwardk> so the nice thing is you can just supply lenses, folds, traversals, getters and setters, and their compositions do the right thing, and the requirements for their compositions wind up being the least possible
[22:51] < edwardk> consider:
[22:51] < edwardk> sumOf :: ((c -> Const (Sum c) d) -> a -> Const (Sum c) b) -> a -> c
[22:51] < edwardk> sumOf l = getSum . foldMapOf l Sum
[22:51] < edwardk> sumOf doesn't actually require a Num instance for 'c'
[22:51] < mightybyte> Dang
[22:52] < edwardk> the thing you PASS it will require it for you, if it is a lens that can see multiple values
[22:52] < mightybyte> Yeah
[22:52] < edwardk> sumOf _2 :: (c1, c) -> c
[22:52] < edwardk> has no constraints and inlines away to 'snd'
[22:53] < edwardk> sumOf (folded._2) :: (Num c, Data.Foldable.Foldable f) => f (c1, c) -> c
[22:53] < mightybyte> Very nice
[22:53] < edwardk> sumOf (_2.folded) :: (Num c, Data.Foldable.Foldable f) => (c1, f c) -> c
[22:53] < edwardk> and sumOf folded = sum
[22:54] < edwardk> but you can use mapMOf traverseOf_ etc. for these things as well
[22:54] < edwardk> so all the things you know how to do with traversable/foldable just generalizes to other very monomorphic types
[22:54] < edwardk> e.g. there is no mapM_ for ByteString
[22:54] < edwardk> mapMOf_ traverseByteString
[22:55] < mightybyte> Oh, so a lot of standard libraries can be significantly simplified?
[22:55] < edwardk> mapMOf_ traverseByteString :: (Monad m, TraverseByteString b) => (GHC.Word.Word8 -> m e) -> b -> m ()
[22:55] < edwardk> yeah
[22:55] < mightybyte> Wow, this needs to go in base
[22:55] < edwardk> basicaly once you have a lens, fold or traversal you get all those auxillary combinators for free
[22:56] < gseitz> | FreeLens? ;)
[22:56] < edwardk> ghci> traverseElement 3 ^= 100 $ [1,2,3,4,5] => [1,2,3,100,5]
[22:56] < edwardk> even though that isn't a legal lens, its a valid traversal
[22:57] < edwardk> so the ^= combinator which accepts any setter, will take the traversal. as every traversal is a valid setter
[22:57] < mightybyte> Fuckin' A
[22:57] < mightybyte> That's awesome right there
[22:58] < edwardk> note those compose with other lenses
[22:58] < edwardk> snapletValue . traverseElement 3 ^= ...
[22:58] < mightybyte> Did you come up with these ideas?
[22:58] < edwardk> yes
[22:58] < edwardk> traverse as a multilens was roconnor
[22:58] < edwardk> the rest was me
[22:58] * | mightybyte bows...
[22:59] < edwardk> the new names were something i was working on when i should have been working on my slides for boston haskell ;)
[22:59] < mightybyte> heh
[22:59] < edwardk> my goal right now is to get the cleanest possible api for these
[22:59] < edwardk> which is why i'm doing the rapid iterations
[22:59] < mightybyte> Yeah
[22:59] < edwardk> before i have users ;)
[22:59] < c_wraith> by the way, I think he released another version *during* this conversation. :)
[22:59] < c_wraith> or else hackagebot is slow
[22:59] < mightybyte> Then why did you put it up on hackage?
[23:00] < edwardk> ghci> traverse ^%= (+1) $ [1,2,3,4] => [2,3,4,5]
[23:00] < edwardk> mightybyte: because i have a couple of packages that depend on it now
[23:00] < edwardk> and users still give feedback ;)
[23:00] < mightybyte> lol
[23:00] < edwardk> i did
[23:01] < edwardk> of course you don't need all the power of traverse to map
[23:01] < edwardk> mapped ^%= (+1) $ [1,2,3,4]
[23:01] < edwardk> also works
[23:01] < edwardk> mapped :: Functor f => SetterFamily (f a) (f b) a b
[23:01] < edwardk> and those compose with folds, etc. no problem
[23:01] < edwardk> er with traversals and lenses at least
[23:02] < mightybyte> So all of this really just falls out of the definition of Traversable?
[23:02] < edwardk> pretty much, i took that and generalized
[23:02] < edwardk> all the other combinators came for free
[23:02] < edwardk> this replaces my reducers library for the most part
[23:03] < edwardk> and Control.Lens.Rep replaces representable-functors
[23:03] < edwardk> that is particularly useful for defining monads, etc
[23:03] < edwardk> you can do something like
[23:03] < edwardk> data Pair a = Pair { _first :: a, _second :: a }; makeLenses ''Pair
[23:03] < edwardk> then
[23:04] < edwardk> instance Representable Pair where rep f = Pair (f first) (f second)
[23:04] < edwardk> and you get a ton of classes for free
[23:04] < edwardk> instance Monad Pair where return = pureRep; (>>=) = bindRep
[23:04] < edwardk> instance Distributive Pair where distribute = distributeRep
[23:05] < edwardk> and you can use all the combinators from Control.Lens.Rep on the type
[23:05] < edwardk> the 3 lines worth of definitions are pretty nice when it comes to getting the monad, etc. for free ;)
[23:05] < edwardk> I use that notion of representability for my physics package
[23:06] < edwardk> instance Representable Quaternion where rep f = Quaternion (f e) (f i) (f j) (f k) -- and now Quaternions form a monad, etc.
[23:06] < edwardk> and you can use q^.i to access the i component of the quat
[23:06] < edwardk> i ^= 0 $ q -- to update it
[23:07] < edwardk> another interesting bit is you don't have to import separate modules for working with Lazy and Strict state any more
[23:07] < edwardk> focus just works
[23:07] < mightybyte> Pretty cool stuff.
[23:07] < c_wraith> edwardk: I feel like when this is ready it deserves a series of blog posts at least the size of your series on deriving IO
[23:07] < edwardk> c_wraith: thats pretty much the plan
[23:07] < mightybyte> I've definitely wanted the ability to easily set a particular alement of a list before.
[23:08] < edwardk> you can use that to update the nth element of any traversable even, so it works on Seq, etc.
[23:09] < edwardk> or even on the value in the nth key of a map
[23:09] < mightybyte> Yeah, this sounds like Haskell's answer to Scala collections.
[23:09] < edwardk> hahah
[23:09] < mightybyte> Without all the obtuseness.
[23:09] < edwardk> =)
[23:10] < mightybyte> And without fucking classy-prelude.
[23:10] < edwardk> yep
[23:10] < edwardk> the nice thing is you can build something wiht just the functionality that makes sense and they all compose
[23:11] < edwardk> (1 :+ 2) ^. getting magnitude
[23:11] < c_wraith> actually, that's exactly what I was thinking - this whole thing looks a *lot* lot scala collections
[23:11] < edwardk> (0, 1 :+ 2) ^._2.getting magnitude
[23:11] < c_wraith> and it might have the issues that scala collections do:
[23:11] < c_wraith> when you look at one or two pieces in isolation, their types can be ridiculously obtuse
[23:11] < edwardk> the difference between this and scala collections are that i'm refusing to do those horrible 'everything can do anything' flatMap returning pairs nonsense
[23:12] < c_wraith> And you don't see that something very simple falls out when you put the pieces together in normal ways
[23:12] < edwardk> i'm trying to mitigate that with a TON of examples in the documentation
[23:12] < c_wraith> yeah, that's all you can do
[23:12] < edwardk> and to make them more like tinker toys, so that no matter what you put together makes sense
[23:12] < edwardk> er no matter what you put together
[23:12] < edwardk> it, bah
[23:13] < edwardk> the _1 and _2 are the most scala-y things about it ;)
[23:14] < edwardk> the types are deliberately very specific to maximize the numbers of ways each combinator can be used
[23:14] < edwardk> which does make them quite scary in places =)
[23:14] < edwardk> i'vebeen trying to include the more specific types you'll probably think of it as as documentation above them
[23:14] < edwardk> e.g. traverseOf_ :: Functor f => ((c -> Const (Traversed f) d) -> a -> Const (Traversed f) b) -> (c -> f e) -> a -> f ()
[23:15] < edwardk> can be thought of as
[23:15] < edwardk> -- > traverseOf_ :: Applicative f => FoldFamily a b c d -> (c -> f e) -> a -> f ()
[23:15] < edwardk> or
[23:15] < edwardk> -- > traverseOf_ :: Functor f => GetterFamily a b c d -> (c -> f e) -> a -> f ()
[23:15] < edwardk> so if you give me a Getter, you can traverseOf_ over any Functor, but if you give me a FoldFamily you need to supply an Applicative
[23:16] < edwardk> notice this was done without me using any scary type system extensions, even though the typeclass constraint effectively changed ;)
[23:16] < mightybyte> Yeah, very slick
[23:16] < edwardk> the obligation was pushed on the getter-like argument, not on the combinator itself
[23:17] < edwardk> the funny thing is when i started it i hated what i finally started calling Traversals
[23:17] < edwardk> because I didn't have a good way to reason about them
[23:17] < edwardk> the name change made the rest of the code obvious
[23:24] < edwardk> anyways, <rambling>off</rambling>
[23:24] < c_wraith> you just rambled the word "off". strange. :P
[23:25] < edwardk> yep
[23:25] < edwardk> </rant>
[23:25] < edwardk> whatever. you are the web guys. you know how these tag things work ;)
[23:27] < mightybyte> If it all ends up working the way you're making it sound, it seems like this is an absolute must to have as a part of base.
[23:29] < mightybyte> And makes clear what I've thought for awhile that lenses are very fundamental and they should definitely be standardized somehow.
[23:30] < edwardk> the nice thing to me is that nothing needs to be added to the Prelude for end users to be able to write lenses with just the prelude
[23:31] < mightybyte> Yeah, that's quite compelling.
[23:32] < edwardk> now onto the next challenge
[23:32] < edwardk> can one generalize MultiPlate to a MultiPlateFamily ;)
[23:33] < mightybyte> lol
[23:42] < mightybyte> Wow, Representable is super cool.
[23:42] < edwardk> =)
[23:42] < edwardk> i had it before but it was buried in theory-land
[23:43] < edwardk> getting it pretty much for free by choosing lenses as the representation made my week
[23:43] < mightybyte> Just a week? :)
[23:43] < edwardk> i'm hard to impress these days ;)
[23:44] < edwardk> besides the Traversal thing also came up this week
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment