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