[HN Gopher] Monocle: Optics Library for Scala
___________________________________________________________________
Monocle: Optics Library for Scala
Author : curling_grad
Score : 36 points
Date : 2024-11-22 13:48 UTC (3 days ago)
(HTM) web link (www.optics.dev)
(TXT) w3m dump (www.optics.dev)
| wk_end wrote:
| Also available for TypeScript:
|
| https://gcanti.github.io/monocle-ts/
| solid_fuel wrote:
| I haven't encountered this pattern before. Is there some more
| information on what problems this is designed to solve?
| Nullabillity wrote:
| If monads are programmable semicolons (ways to chain
| operation), lenses are programmable dots (ways to delegate
| access to data). Other optics are largely generalizations of
| that pattern.
| AlotOfReading wrote:
| Lens are the functional version of getters and setters. A lens
| takes a product type (struct, class, etc) and allows you to
| view or update part of it. Prisms are something similar for sum
| types (variants) that allow you to look at a value if it's
| present and err otherwise.
|
| The optical analogy comes from how these operations resemble
| zooming in on structures with a magnifying glass and the entire
| family of related transformations is called optics.
| jeremyjh wrote:
| Lenses make it easier to read and update members deep in a
| hierarchy of read-only data structures.
| dkarl wrote:
| The problem most programmers would be familiar with is making
| an update inside a deeply nested immutable data structure. For
| example, suppose you want to update a user's billing address,
| and you have an immutable data structure that looks like this:
| user { billingInfo: { card, address }, name, subscription {
| level, expiration }, status { level, since } }
|
| The structure is immutable, so you can't make the update in
| place. On the other hand, you're only changing one field, so it
| would be wasteful to make a complete deep copy. The efficient
| way to create an updated instance is to create a new user
| instance and a new billingInfo instance while reusing the name,
| subscription, and status instances.
|
| You can think of this as the equivalent of a _setter_ for
| immutable data structures.
|
| This is an artificial example, because the cost of making a
| deep copy of this user structure is probably not that bad, and
| the boilerplate to make an efficient update is not all that
| bad, either. You would use an optics library when 1) you need
| efficient updates and 2) it's worth investing a little effort
| to hide the boilerplate.
|
| Optics also let you concisely express access into a deeply
| nested structure, the _getter_ paired with the _setter_. In my
| experience, updates are the motivation for setting up optics,
| and concise access is a nice thing you get as a bonus.
| openplatypus wrote:
| Yes, using Monocle for years now. So happy to have rich ecosystem
| of libraries in Scala land.
| ldjkfkdsjnv wrote:
| Every scala code base I have worked on, that wasnt written by
| small team of experts, turned into a huge pile of crap. A small
| squad of people that treat the language like a religion create an
| impenetrable masterpiece
| Sunscratch wrote:
| Every <insert any language here> code base I have worked on,
| that wasnt written by small team of experts, turned into a huge
| pile of crap...
| ldjkfkdsjnv wrote:
| :-)
| wtfparanoid wrote:
| well aligned scala teams are a great thing, impenetrable code
| is not - maybe a poor choice of adjective?
| threeseed wrote:
| A lot of work has been done in Scala 3 to simplify everything.
|
| And with the arrival of virtual threads in the JVM there are
| new concurrency libraries e.g. Ox [1] and Gears [2] which
| remove the need to use FP concepts like monads. Which have been
| the major source of much of the complexity.
|
| For all its problems it is a seriously under-rated platform
| especially Scala.js which IMHO is far better and simpler than
| Typescript.
|
| [1] https://github.com/softwaremill/ox
|
| [2] https://github.com/lampepfl/gears
| henning wrote:
| So behind the scenes, every one of those statements will make a
| whole new user object with a whole new address object so that it
| remains immutable? And whether that will actually have any real-
| world performance impact is I guess entirely situational. Still,
| what happens if you do that with a big object graph?
|
| Also, the original strong need for immutable data in the first
| place is safety under concurrency and parallelism?
| kelnos wrote:
| This is in general how "mutations" are supposed to be done in a
| language like Scala (and is not unique to this library). Yes,
| Scala does have a set of mutable collections, but the immutable
| collections are heavily optimized to make creating a "new"
| collection with a mutation much cheaper than having to copy the
| entire collection.
|
| Of course, copying a case class in order to change a field
| likely does require a full copy of the object, though since
| this is the JVM, things like strings can be shared between
| them.
|
| Ultimately this pattern is... fine. Most uses don't end up
| caring about the extra overhead vs. that of direct mutation. I
| don't recall if the Scala compiler does this, but another
| optimization that can be used is to actually mutate an
| immutable object when the compiler knows the original copy
| isn't used anywhere else after the mutation.
|
| > _Also, the original strong need for immutable data in the
| first place is safety under concurrency and parallelism?_
|
| That's one of the uses, but multiple ownership in general is
| another, without the presence of concurrency.
|
| On top of that, there's the general belief (which I subscribe
| to) that mutation introduces higher cognitive load on someone
| understanding the code. Immutable data is much easier to reason
| about.
| Nullabillity wrote:
| > Still, what happens if you do that with a big object graph?
|
| The only thing that really matters here is how deep the graph
| is. Any unchanged object can just be reused as-is.
| sriram_malhar wrote:
| Yes, behind the scenes every one of those statements will make
| a shallow copy of the object. But it isn't just that object
| necessarily. For example, if you modify a tree node, then not
| only does that node needs cloning, its parent does too (since
| the modified parent needs to point to the new node), and so on
| until the root, which results in h = O(log(n)) new objects to
| create an entirely new tree. (h is the height of the tree).
|
| What you get out if it is (a) safety, (b) understandability,
| which are wonderful properties to have as long as the end
| result is performing adequately. Implementing concurrent tree
| or graph traversals under conventional mutation is painful; the
| Java collection libraries simply throw a
| ConcurrentModificationException. The equivalent code for
| readonly traversals of immutable data structures is simplicity
| itself. You also get versioning and undo's for free.
| threeseed wrote:
| > actually have any real-world performance impact
|
| There are many techniques like this within Scala that would
| never be feasible if it wasn't for the fact that the JVM is
| ridiculously fast. You could write the worst code imaginable
| and in many cases would still have better performance than
| Python, Javascript etc.
| neonsunset wrote:
| Also in F#: https://fsprojects.github.io/FSharpPlus/lens.html
| itronitron wrote:
| This has nothing to do with optics, which is a branch of physics
| that studies the behavior and properties of light.
| Xophmeister wrote:
| It's a metaphor.
___________________________________________________________________
(page generated 2024-11-25 23:00 UTC)