[HN Gopher] Lens: Lenses, Folds and Traversals
       ___________________________________________________________________
        
       Lens: Lenses, Folds and Traversals
        
       Author : hyperbrainer
       Score  : 83 points
       Date   : 2025-07-01 12:08 UTC (4 days ago)
        
 (HTM) web link (hackage.haskell.org)
 (TXT) w3m dump (hackage.haskell.org)
        
       | moomin wrote:
       | Let's just say that if you wanted to understand lenses, this is
       | not where you should start; and if you wanted to move to more
       | advanced scenarios, I wouldn't start here either.
        
         | xtoilette wrote:
         | where would you start?
        
           | neanderzander wrote:
           | I found this accessible:
           | 
           | https://academy.fpblock.com/haskell/tutorial/lens/
        
           | wk_end wrote:
           | Assuming you've got experience with Javascript, read the
           | "Motivation" section on the monocle-ts website:
           | 
           | https://gcanti.github.io/monocle-ts/
        
           | raluk wrote:
           | https://blog.jle.im/entry/lenses-products-prisms-sums.html
        
             | cosmic_quanta wrote:
             | What a great read! I had never encountered or thought about
             | prisms before, but now it I see how useful they could be.
             | 
             | Thank you for sharing
        
           | KPGv2 wrote:
           | Optics by Example by Chris Penner is good.
           | https://leanpub.com/optics-by-example/
        
           | raluk wrote:
           | Great exercise driven course is:
           | https://github.com/system-f/lets-lens
        
         | ohdeargodno wrote:
         | A first good step is getting rid of Haskell's obscure and
         | impenetrable syntax, and checking implementations that would be
         | more readable.
         | 
         | Kotlin's Arrow library hits a good middle ground between FP
         | wizardry and readability, and their documentation on lenses are
         | understandable for the average person: https://arrow-
         | kt.io/learn/immutable-data/lens/ / https://arrow-
         | kt.io/learn/immutable-data/intro/
        
           | epgui wrote:
           | > Haskell's obscure and impenetrable syntax
           | 
           | Uhhh... Haskell syntax is simpler than python's or
           | javascript's. It's neither obscure nor impenetrable, but it
           | sounds like it's _different_ than what you 're used to.
        
             | HappMacDonald wrote:
             | Haskell has enough punctuation to make Larry Wall blush
        
             | ohdeargodno wrote:
             | This is such low hanging bait that I'm not even interested
             | in interacting further with it than: Haskell's syntax is
             | obscure and impenetrable for the vast majority of software
             | engineers because it was designed by FP nerds with zero
             | interest in ergonomics.
             | 
             | It doesn't make it a bad syntax. It is, however,
             | objectively terrible for anyone unfamiliar with it.
        
             | pxeger1 wrote:
             | Being different to what users of ~all top-20 languages are
             | used to is a great way to be obscure and somewhat
             | impenetrable.
        
           | KPGv2 wrote:
           | Arrow is how I went from minimal FP to bloody good at it. The
           | Arrow devs are also very giving with their time, and we still
           | interact occasionally online even though I've moved on to
           | Unison for the most part.
        
         | smegma2 wrote:
         | Agreed, I think this tutorial is good:
         | https://hackage.haskell.org/package/lens-tutorial-1.0.5/docs...
        
       | kccqzy wrote:
       | My biggest piece of advice for people using lenses is to ditch
       | all the operators. Things like ^. or ^.. or ^? or ^@.. or even
       | <<|>~ are all real operators. Yet they look like line noise.
       | Nobody fully remembers them anyways. Just ditch all operators.
       | Use named functions. The function toListOf is immediately clear
       | what it's doing (that it takes a structure and a fold to convert
       | to a list) but ^.. is not.
       | 
       | In general I avoid all custom operators and only use operators
       | that are in packages preinstalled by the compiler (basically just
       | base and containers).
        
         | kqr wrote:
         | I agree strongly with this and take it one step further: I
         | avoid the infix backticks that turn functions `into` operators.
         | 
         | But I'm not a hardliner. I do use backticks sometimes when
         | building joins with Esqueleto and I do use a limited set of
         | lens operators, like ^. and sometimes the %= variants if the
         | situation calls for it.
        
         | amelius wrote:
         | Maybe a text-editor should allow the user to look at source
         | code through different "lenses" (pun intended) and show the
         | meanings of symbols whenever the user wants to see them.
        
           | ashton314 wrote:
           | Emacs (of course) has `prettify-symbols-mode` which lets you
           | describe symbols (eg lambda) and replacement characters (eg
           | l); the effect is purely in the display system--the
           | underlying buffer does not get modified.
        
         | chowells wrote:
         | I strongly recommend using the lens operators. They are
         | uniformly named such that you can trivially identify their
         | behavior based on their lexical construction, and using them
         | reduces mental parsing overhead significantly.
         | 
         | For the former assertion: ^. means "get a single result". ^..
         | means "get multiple results". ^? means "get zero or one
         | result". ^@.. means "get multiple results, along with their
         | indices". <<|>~ means "modify a value by combining the target
         | with the |> operator from Snoc, then return a tuple of the old
         | target value and the full structure including the combined
         | value". There is a tiny language in the pattern of operator
         | names, and it's worth the 3 minutes of work it takes to learn
         | it.
         | 
         | And as a reward for learning it, you get to write expressions
         | with far fewer parentheses. This is a massive win.
         | Parenthesized expressions introduce a miserable minigame during
         | reading, where you have to properly match each paren to its
         | correct partner keeping a mental stack to handle nesting. By
         | contrast, the lens operators give you the far simpler mental
         | parsing task of separating the optic, the input, and the
         | operation on the input. There's no nesting involved. The
         | process is a simple visual scan that doesn't require keeping a
         | mental stack. It's a lot easier to quickly read and comprehend.
         | 
         | About the only thing you lose is the ability to easily read
         | code out loud. I don't limit myself to thinking in sounds, but
         | I guess for some people it's important to communicate code out
         | loud. For those kinds of pedagogical purposes, I guess it's ok
         | to pass on the operators. But for code I'm going to work with
         | over a long period of time I'd much rather have the readability
         | advantages of the operators.
        
           | kccqzy wrote:
           | Having fewer parentheses is not a win, it makes more things
           | implicit and forces everyone to remember operator precedence.
           | In my opinion operator precedence is _never_ worth
           | remembering other than plus minus multiply and divide.
           | 
           | I find heavily parenthesized expressions easy to read, just
           | because I tend to break them into multiple lines and the
           | indentation serves as a guide. Don't put too many of them on
           | a single line.
        
             | chowells wrote:
             | That might be a strong argument in many languages, but in
             | Haskell you really don't need to memorize operator
             | precedence. In nearly every case, the types tell you the
             | precedence. They don't literally, but most expressions only
             | type check in one particular parse tree.
             | 
             | As a result, you just don't think about precedence when
             | reading code. If you assume the code type checked
             | correctly, you know that it all just makes sense. You don't
             | need to create a parse tree. You just trust.
             | 
             | (Actually, this is the huge advantage of Haskell in most
             | every case. You don't need to understand everything. You
             | just trust that it does what makes sense, and you're right.
             | The compiler enforces it.)
        
           | jbggs wrote:
           | couldn't find/replace be used to swap between operators and
           | readable function names?
        
           | iroddis wrote:
           | You may want to check out J as a language. It is wonderfully
           | terse and allows for point-free programming, and has all of
           | the advantages you point to above.
        
         | aranchelk wrote:
         | I disagree. There are many operators that you'll never use but
         | if you memorize (^.), (.~), and (%~), you're pretty much set
         | for a lot of real-world software development.
         | 
         | Per Kmett's original talk/video on the subject, I can confirm
         | my brain shifted pretty quickly to look at them like OOP field
         | accessors. And for the three above, the mnemonics are
         | effective:
         | 
         | "^." is like an upside down "v" for view.
         | 
         | ".~" looks like a backwards "s" for setters.
         | 
         | "~%" has an tilde so it's a type of setter and "%" has a circle
         | over a circle, so it's over.
         | 
         | I'll also add that my experience in recent versions of
         | PureScript things get even nicer: visible type application lets
         | you define record accessors on the fly like:
         | 
         | foo ^. ln@"bar" <<< ln@"baz"
         | 
         | "." Is unfortunately a restricted character and is not the
         | composition operator like Haskell, but I alias "<<<" with ".."
         | 
         | The pretty obvious question with the above is: why don't you
         | just write "foo.bar.baz". In my case I use a framework that
         | uses passed lenses for IoC, but I think "%~" is always nicer
         | and less repetitive than the built-in alternative.
        
       | haskman wrote:
       | If you are looking for a more accessible introduction to lenses,
       | this guide to optics in PureScript is great (and PureScript is
       | basically Haskell).
       | https://thomashoneyman.com/articles/practical-profunctor-len...
        
       | eigenspace wrote:
       | Julia actually has a very nice implementation of lenses in the
       | Accessors.jl package:
       | https://juliaobjects.github.io/Accessors.jl/dev/
       | 
       | I find it to be a lot more comprehensible and transparent than
       | the Haskell version.
        
       | rrgok wrote:
       | I don't understand why Haskell can't provide an imperative
       | interface (at the grammar level, not semantic level) to get/set
       | values in a type. If you can provide the do-notation to
       | "simulate" imperative code, then why not?
        
         | ethan_smith wrote:
         | Haskell's design prioritizes referential transparency and
         | equational reasoning, which would be compromised by imperative
         | get/set operations that mutate state directly - lenses provide
         | a purely functional alternative that maintains these
         | properties.
        
       | johnfn wrote:
       | Everyone is like "Haskell is such a cool language, it's so much
       | more clear concise and understandable than that stupid language
       | you like so much" (their words, not mine). Then you ask them how
       | they write `foo.bar.baz = 1` and you get 50k words of
       | documentation, 113 new operators[1] like `<<<>~`, and a library
       | with 20 new dependencies. I make fun of them only because I love
       | them - I think Haskell has brought us a lot of cool things like
       | Maybe and Either - but how has no one ever taken a step back and
       | gone "wow, this seems a tad complex for what we're trying to
       | accomplish"?
       | 
       | [1]: I'm not even exaggerating - https://hackage-
       | content.haskell.org/package/lens-5.3.5/docs/...
        
         | fud101 wrote:
         | I just want one good monad tutorial that doesn't mention
         | haskell. Just one.
        
           | upghost wrote:
           | Monads are a set of annotated functions or methods that
           | participate in shared encapsulating middleware. It's kind of
           | like writing an interpreter for existing code by changing
           | shape of the inputs, outputs, and possibly even flow control
           | of the execution of those functions -- but without writing an
           | interpreter.
           | 
           | The easiest example would be something like wrapping a bunch
           | of arithmetic operations with a "cumulative" monad.
           | Effectively this changes your add, sub, mul, div functions
           | such that instead of taking 2 floats and returning a float,
           | they take a hashmap and return a hashmap. The hashmap
           | consists of the original args as well as the cumulative
           | total, for whatever reason. The details of the hashmap are
           | hidden from you, you use the functions as per normal.
           | 
           | You could also make the wrapper monad have some state, and
           | then batch the operations while making them appear to execute
           | sequentially, or make it appear you are doing pure logic when
           | I/O is happening under the hood.
           | 
           | While you can do monads in dynamic languages, it can be hard
           | to reason about changes to the code without strong compiler
           | support, so typically you see it more often implemented in
           | statically typed languages.
           | 
           | In dynamic languages such as lisp you might be better off
           | writing a small interpreter, and in OO languages there are
           | other patterns that might serve the purpose better.
           | 
           | I still don't know what a monoid is though. Or an
           | applicative.
        
             | KPGv2 wrote:
             | > I still don't know what a monoid is though
             | 
             | In short, it's a data type with addition defined. So
             | 
             | "a" + "b" = "ab" <-- string and concat is a monoid
             | 
             | 1 + 5 = 6 <-- nat and natural number addition is a monoid
             | 
             | [1] + [2] = [1, 2] <-- list and concat is a monoid
             | 
             | *edit* (it also has a zero defined, such as "" or 0 or [])
             | 
             | > Or an applicative
             | 
             | you can think of these as "i'd use a functor here via fmap,
             | except my mapping function takes more than one argument"
        
           | KPGv2 wrote:
           | A monad is just a flatmappable. The end. That's the whole
           | tutorial. If you're coming from JS/TS and know how to
           | construct a singleton array and can use
           | Array.prototype.flatMap, you already can do monads. Anything
           | else "monadic" is not a monad. It's a property of something
           | else that can be derived from what I wrote above, OR it's a
           | property of one specific monad not monads in general.
           | 
           | A monad is a flatmappable.
           | 
           | A monad is a flatmappable.
           | 
           | A monad is a flatmappable.
        
             | Xmd5a wrote:
             | It doesn't cover monad transformers though.
        
         | munchler wrote:
         | Because foo.bar.baz = 1 has a side-effect, and side-effects,
         | though powerful, are extremely prone to error. Lenses take more
         | effort, but give us the same amount of power without all the
         | errors. Many people believe the trade-off is worthwhile.
        
           | johnfn wrote:
           | immer.js allows you to do foo.bar.baz = 1 without side
           | effects.
        
             | munchler wrote:
             | Thanks, I just took a look. It works by relaxing
             | constraints to allow mutation on a "draft" copy of the
             | data. Interesting idea! (But verboten in Haskell, of
             | course.)
        
               | johnfn wrote:
               | But there's nothing 'verboten' about this in Haskell!
               | Haskell _could_ allow for the exact same syntax, and do
               | the exact same thing behind the scenes. Oh but no, LENSES
               | of course, you must rewrite obvious code in the most
               | obtuse manner possible. :)
        
               | munchler wrote:
               | Unlike JavaScript, it would require fundamental changes
               | to the language itself, so I don't think it's really a
               | fair comparison.
        
       | srik wrote:
       | I learned lenses from the mentioned Edward Kmett video but wish
       | I'd learned from the "Optics by Example" book instead; it's more
       | cohesive, comprehensive and can save you a bunch of time -
       | https://leanpub.com/optics-by-example/
        
       | 0_gravitas wrote:
       | If you're in the Clojure world and feel an appetite for something
       | like Optics, checkout the Specter library from
       | RedPlanetLabs/Nathan Marz; it's Optics by another name, but
       | functionally/philosophically quite similar.
       | 
       | https://github.com/redplanetlabs/specter
        
       ___________________________________________________________________
       (page generated 2025-07-05 23:02 UTC)