[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)