12 ms·
Haskell Lenses In Pictures
- barrkel 13y agoI've used the same idea behind lenses and bijective functions to fairly good effect as a basis for declarative data binding in a UI framework (actually, combined AJAX and web server framework). When your data mappings can be expressed this way, it's easy to get a lot done with very little work; things like RoR+JS-du-jour look primitive in comparison. A potential downside is that you have to warp your thinking a little bit in order to make them fit the paradigm, and some instinctive imperative approaches don't work as well.
- boothead 13y agoDo you have a link, this sounds extremely interesting. I've just started reading Adowey's category theory book, it's amazing when you start to notice the same basic fundamental structures everywhere!
- barrkel 13y agoNo specific link. My primary interest in the topic is very applied; primarily in bidirectional data binding expressions mapping data and business objects to UI controls, possibly over a transport. A lens is just a pair of (setter, getter). It's a first-class reference that you can pass around, much like you'd pass around a pointer to a location; except you can transform it. If you use bijective functions to do the transformation, you can wrap both the getter and setter, one with the function and the other with its inverse. These lenses are a little like pipes, wormholes or ducts that are attached at one end to your model / data layer / business layer / wherever. I simply plugged them into the UI, and added some extra control information for reactive UI updates at the web application server request / response boundary. The upshot was that the user could poke at the UI, a request would be sent down effectively addressing that particular lens endpoint, business logic as necessary would run, and then the minimal UI update would come back in the response - only lenses whose values had changed would be sent back. The business logic never had to specifically tell the UI which bits needed to be refreshed; the bits that needed to be sent were inferred automatically. I wrote a data-binding language specifically to implement this scheme. It was called Gravity, in part because it accreted more and more responsibility in the system :)
- sfvisser 13y agoNice writeup. Although I really like the fact that lenses (from the `lens` package) can be composed using regular function composition, instead of the more generalized category composition like `fclabels` uses, I still think it's weird that the direction of composition is flipped. I reads more like OO, but a lot less like idiomatic Haskell.
- dons 13y agoI strongly prefer the regular composition order. But I also use fclabels (FTW!) Changing the direction of composition makes no sense.
- tel 13y agoComposition order is the weirdest thing about Control.Lens.
- dan00 13y ago"Changing the direction of composition makes no sense." I also can't stand all these operators of the lens package. It feels like reading Perl code. The only annoying thing about all the other lens packages is the need for hiding '.' from Prelude.
- boothead 13y agoI thought that function composition (.) arises from the category instance for (->)? Actually on checking it seems that Prelude re-exports (.) from Data.Function. I'm sure I read somewhere that these had been unified, was I wrong?
- tome 13y agoI believe the argument against unifying is that you get very hard to decipher error messages.
- crntaylor 13y agoI also think it's a bit weird to flip direction of composition, but there are a couple of advantages -- 1. It keeps the "imperative" feel, so that you can write person.address.zipCode rather than zipCode.address.person 2. There's an argument that mathematical functions are "back to front" and that it would be more natural to write functions left to right, i.e. (x)f rather than f(x), so that the function composition f.g is "apply f then apply g" rather than "apply g then apply f" which is where we are at the moment.
- IanCal 13y agoSomething that might be worth pointing out to folks newer to haskell is this syntax location.x `over` (+10) $ player1 Here, `over` is not a string. This is not stringly typed programming. It translates to over (location.x) (+ 10) $ player1 It's used to take a function that would normally be function_name argument1 argument2 and allow you to call it like so argument1 `function_name` argument2 so plus 1 2 = 1 `plus` 2 There's nothing special you need to do, and it's purely a style thing. Sometimes it's easier to read. Edit - fix from DanWaterworth, the . in location.x is (if I understand the problem correctly) function composition and therefore location.x is location . x, so my re-ordering possibly changed things more than I thought. The principle is the same, but I've wrapped location.x in parentheses, just to be sure. I'm sure someone more well versed in haskell can correct me on this :)
- ozataman 13y agoFor example: I personally prefer the standard form here, as (to me) it reads a bit more like English: over (location.x) (+ 10) $ player1 "over location's x, add 10"
- tel 13y agoThere's also the player1 & location.x .~ (+10) format which has a nice scansion.
- drostie 13y agoIt seems like there should be a way to define player1 as its own (root) lens so that one is simply writing player1.location.x .~ (+10), or so. Are we simply not doing this because it makes reading the data structure rather difficult?
- tel 13y agoThat can kind of be done using `Context`s, but those are really just implementation details and should rarely be used. Broadly, you wouldn't want to do that because it implies mutation. There's not much value in `player1.location.x %~ (+10)` because it can't modify player1... just return a modified (player1'new). Of course, stateful mutation is just a monadic effect, so you can wrap it up into a State monad, which Lens already has shortcuts for do location.x += 10 location.y += 10 which has type like `MonadState ThingWithLocation m => m ()` and we can then apply it to whatever `player` we want as a pure function.
- alipang 13y agoAnother great writeup on lenses I highly recommend, http://www.haskellforall.com/2013/05/program-imperatively-using-haskell.html http://www.haskellforall.com/2013/05/program-imperatively-us....
- TeeWEE 13y agoHaha I see titties (.)(.)
- verroq 13y agoI still don't get how people can claim that functional programming is inituative when mutating a nested struct requires a separate package and metaprogramming hacks.
- sfvisser 13y agoIt's a trade-off. You can either use direct mutation using some sort of impure reference type, or use a pure mutation using first class accessor labels. Impure references are probably easier to use, but are harder to reason about and allow for more subtle bugs in your code. Accessor labels require a bit more trickery, but give a more principled approach and result in code that is less error-prone and easier to compose. Haskell doesn't force you in any of those directions. Pick the approach that bests fits your domain and likings. Personally I'm convinced pure code is a way better default, however for certain domains I certainly resort to using `IORef`s, `TVar`s, etc.
- jbri 13y agoWhy do you need to mutate things in order to write a program? Operational programming may have damaged your brain. (With regards to lenses, note that: This isn't mutating anything, and it's not a metaprogramming "hack" either. Functions as first-class entities instead of opaque blobs of code is one of the big wins of functional programming.)
- marshray 13y ago> Why do you need to mutate things in order to write a program? To change the state of the universe from one in which my program is not written to one where it is. Furthermore, if the program I write mutates nothing upon execution there is no point in running it.
- Bogdanp 13y ago| mutating a nested struct requires a separate package and metaprogramming hacks. But it doesn't. And you would have known this if only you had read the fucking article. data Position = Position { x :: Int, y :: Int } data Player = Player { pos :: Position } moveRight :: Int -> Player -> Player moveRight n (Player (Position x y)) = Player (Position (x + n) y)
- bittired 13y agoI like the cartoons used, but why set the bar high with chunky bacon: http://mislav.uniqpath.com/poignant-guide/ http://mislav.uniqpath.com/poignant-guide/ http://mislav.uniqpath.com/poignant-guide/images/the.foxes-4b.png http://mislav.uniqpath.com/poignant-guide/images/the.foxes-4...
- auvrw 13y agoi like the cartoons too, 'cept they reminded me of learnyouahaskell.com at first. when/where did adding frames from movies get popular?
- MatthewPhillips 13y agoI think it's a great convention for Haskell to use. Learn You a Haskell uses it to wonderful effect. Haskell is hard to learn (at least at first). Even for people with a functional programming background. The cartoons makes it look like a children's book, which has a psychology effect of making the material seem easier.
- ams6110 13y agoSee also: The Little Lisper a.k.a The Little Schemer
- dangoor 13y agoBrian McKenna just released fantasy-lenses which gives you lenses for JavaScript: https://github.com/puffnfresh/fantasy-lenses https://github.com/puffnfresh/fantasy-lenses
- michaelsbradley 13y agoMichael Greenberg also published a JavaScript lenses library back in 2007, as part of his thesis at the University of Pennsylvania: http://www.cis.upenn.edu/~mgree/ugrad/lenses/docs/overview-summary-lens.js.html http://www.cis.upenn.edu/~mgree/ugrad/lenses/docs/overview-s... It's a bit dated now, to be sure, as there have since been many developments in the field of "bidirectional programming". See, for example, Daniel Wagner's paper on "Edit Lenses": http://dmwit.com/papers/201107EL.pdf http://dmwit.com/papers/201107EL.pdf
- brandonbloom 13y agoClojure has provided an excellent model for how to do similar operations in a dynamically typed language with pervasive maps & vectors: (update-in player1 [:location :x] + 10) This works especially nicely with the threading macros. Here's some random example I'll make up: (-> player1 (update-in [:location :x] + 10) (assoc-in [:inventory 3 :equipped] true)) The general idea is that a tree of associative data structures (which include integer-indexed vectors) can have a path to a location in the tree reified as normal data. Then, you can run any ordinary function on a deep position in the tree without any of the applicative functors machinery.
- marshray 13y agoI like FP as much as the next guy (who dabbles in it out of curiosity). But I can't get over thinking that the need to invoke function composition composition for operations which are trivial in other languages suggests that somewhere we ended up trying to use the wrong tool for the job.
- bonobo 13y agoThis isn't the first time I got myself wondering "where do people derive these ideas from?" I need to read and re-read (and perhaps read once again) a post full of pictures every time someone try to explain me a new concept in Haskell. And the more it happens the more I'm convinced that this language is not an everyday tool. I don't want people to misinterpret me, I decided to learn Haskell and I don't regret it. But I often feel it's more like trying to cut vegetables with a katana: it's challenging, it's fun to practice, and surely it's a powerful tool, but even if I become a master at it I won't accomplish much alone, and I definetly won't want people trying this next to me. The thought of a bad programmer trying to look smart while fiddling in my Haskell code scares me to no end.
- marshray 13y agoLike "a bad programmer trying to look smart" is exactly how Haskell makes me feel!
- tome 13y ago(.).(.) and the term "function composition composition" are a really bad idea (except as fun maths puzzles). Just use \f g x y -> f (g x y) and be done with it.