6 ms·
Monocle: Optics Library for Scala
- wk_end 2y agoAlso available for TypeScript: https://gcanti.github.io/monocle-ts/ https://gcanti.github.io/monocle-ts/
- solid_fuel 2y agoI haven't encountered this pattern before. Is there some more information on what problems this is designed to solve?
- Nullabillity 2y agoIf 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 2y agoLens 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 2y agoLenses make it easier to read and update members deep in a hierarchy of read-only data structures.
- dkarl 2y agoThe 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.
- usrusr 2y agoDoes it offer creating a mutable view on top of the immutable structure that can be used to accumulate a whole set of changes to later be materialized into a copy with the changes? (or to be used directly, at whatever cost would be required) That's something I've been wondering why it's not more of an established thing. It would basically be docker, but for in-memory reference graphs instead of filesystems.
- munchler 2y agoYou are perhaps looking for software transactional memory. https://en.wikipedia.org/wiki/Software_transactional_memory https://en.wikipedia.org/wiki/Software_transactional_memory
- wk_end 2y agoImmer does this for JS/TS. https://immerjs.github.io/immer/ https://immerjs.github.io/immer/
- lmm 2y agoYou can compose together a bunch of edit operations (an edit command, if you like) and apply them at once at the end, is that what you mean?
- usrusr 2y agoKind of. Can you use the interim state fork (original plus changes, not yet committed into a new immutable) as a "readable" type, the union of mutable and immutable variants? That would resolve choice dilemmas like the decision between mutable and immutable builders: keep the mutable where it is only used by the creating thread, materialise into an immutable when it needs to cross thread boundaries, fork where a thread needs to adapt a little and have all consumers typed to the readable version so that they can work with either, without requiring a new immutable.
- lmm 2y ago> Can you use the interim state fork (original plus changes, not yet committed into a new immutable) as a "readable" type, the union of mutable and immutable variants? Not really, not at a language semantics level - although in practice if you applied your current mutation to the original object, did something with the result, and then threw the "partially edited object" away, the JVM's escape analysis might catch that and never fully materialise the "partially edited object". Note that nothing is really mutable, not even your edit operations - you form your combined edit by composing together a bunch of smaller edits, but all those edits are immutable.
- kqr 2y agoAside from making it convenient to build getters and setters for nested data, optics also have conditional accessors and collection accessors as building blocks. Conditional accessors have since become a popular language feature with e.g. the elvis operator ?. in C#. Optics make it possible to innovate on features like that in library code rather than as language features. Something I've yet to see made into a language feature that is common in optics libraries are iterating accessors. E.g. to reset all counters we can, with optics, say something like (in Haskell syntax, since that is what I know) stats.each.count .= 0 and that sets the count to zero for all objects in the stats array. If not all stats objects have a count (some might be of a gauge type, for example) we can compose in a conditional accessor that resolves the count field only if it is not null: stats.each.count._Just .= 0 In the above statement, nothing is language syntax --it's all library functions and operators. But it still combines really well on the page. Once one knows optics, one rarely has to think very hard about how to do any get or update operation, even when the data types become complicated.
- 62951413 2y agoThere's one book that discusses a few related questions from a very mainstream perspective: Functional and Reactive Domain Modeling (https://www.manning.com/books/functional-and-reactive-domain-modeling https://www.manning.com/books/functional-and-reactive-domain...). The emphasis here is on practical while most similar resources tend to go too hard in the Haskell-on-the-JVM direction.
- openplatypus 2y agoYes, using Monocle for years now. So happy to have rich ecosystem of libraries in Scala land.
- ldjkfkdsjnv 2y agoEvery 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 2y agoEvery <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 2y ago:-)
- wtfparanoid 2y agowell aligned scala teams are a great thing, impenetrable code is not - maybe a poor choice of adjective?
- threeseed 2y agoA 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 https://github.com/softwaremill/ox [2] https://github.com/lampepfl/gears https://github.com/lampepfl/gears
- wiml 2y agoYou're going to have that problem with any codebase written by people who don't particularly know the language. Typescript written by PHP programmers, Python written by Java programmers, you'll quickly get a huge impenetrable pile of crap. You can optimize your codebase to be modified by an ever rotating group of people who don't fully understand it, or by a smaller group of people who do. Both are legitimate choices in specific contexts. But if you take a codebase written one way and try to maintain it the other way, your productivity will tank.
- henning 2y agoSo 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 2y agoThis 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 2y ago> 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 2y agoYes, 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.
- neonsunset 2y agoAlso in F#: https://fsprojects.github.io/FSharpPlus/lens.html https://fsprojects.github.io/FSharpPlus/lens.html
- itronitron 2y agoThis has nothing to do with optics, which is a branch of physics that studies the behavior and properties of light.
- wbl 2y agohttps://ncatlab.org/nlab/show/lens+%28in+computer+science%29 https://ncatlab.org/nlab/show/lens+%28in+computer+science%29
- Xophmeister 2y agoIt’s a metaphor.
- fn-mote 2y agoThis is why I read the comments before I click on the link. :)
- solomonb 2y agoWords can have many uses. https://en.wikipedia.org/wiki/Optic_(disambiguation) https://en.wikipedia.org/wiki/Optic_(disambiguation) > In computer science, optics are a general class of bidirectional transformations
- signaru 2y agoThe name, monocle, also further misleads those expecting the physics topic. They actually have a nice logo with a lens and the lambda symbol which is often the symbol used for wavelength.
- evertedsphere 2y ago"this has nothing to do with classes, which in sociology and related fields are strata which society can be analysed as being divided into"
- dkarl 2y agoVirtually everything in computer science is a metaphor. A computer was a human being before it was a machine. A block of memory, an array of values, an index into a structure, most of the vocabulary we use every day is built out of metaphors.
- mhitza 2y agoDoes the LSP provide clear autocomplete on what properties can be accessed on the bound _ ? Asking as someone that doesn't use Scala at all, but has seen the hit-and-miss of some FP language LSPs.
- lmm 2y agoIntelliJ or the older Scala-IDE for Eclipse certainly does, so I'd be very disappointed if the LSP impl (which the Scala maintainers have been pushing as the official IDE replacement these days) didn't.
- valenterry 2y agoYes it does, otherwise the code would actually not compile.
- cultofmetatron 2y agoam I the only one who isn't a little disappointed that this wasn't a library to model the physics of physical lenses?
- itronitron 2y agoBy "isn't a little disappointed" do you mean very disappointed or not disappointed at all? Although that may not be an important distinction as I think the two groups are roughly the same size.
- cultofmetatron 2y agowoops, I meant to say "is a little dissapointed"
- btreecat 2y agoI'm still trying to reason what the value proposition of Scala is in a business sense. Developer are more expensive and harder to find, the tooling is weaker, the ecosystem less deep, performance suspect, and the overall XP feels clunky. Plus our hardware is procedural, and contains/manages state within the instruction pipeline. I strongly believe it doesn't belong as a core business technology.
- noelwelsh 2y agoI don't think this comment reflects reality. A few issues: * Complaints about tooling and ecosystem were valid maybe 10 years ago, but not now. * You don't say what your point of comparison is. (weaker than what?) * Performance has never been an issue for general programming tasks * You disregard the value that language brings * Running functional programs on a CPU has not been an issue since, roughly, the 1970s when the basics of compiling FP languages were worked out.
- btreecat 2y agoNone of that answered the question of what's the value proposition is, while also missing the point of several criticism.
- noelwelsh 2y agoThe Scala value prop is a modern programming language (= safer, more correct code, which implies faster development) + access to the JVM and JS ecosystems (and WASM and Native in more nascent states.) Given your comment about stateful CPUs I imagine you don't think programming languages are particularly important. Not everyone does, but opinions do differ on that. > also missing the point of several criticism I can't correct my misunderstandings if you don't state what they are.
- fifilura 2y agoApache Flink has deprecated support for Scala. https://cwiki.apache.org/confluence/display/FLINK/FLIP-265+Deprecate+and+remove+Scala+API+support https://cwiki.apache.org/confluence/display/FLINK/FLIP-265+D... An aggregation engine like that was otherwise a good fit for a functional-ish language like Scala. That said - it is a nice language. Native support for simple functional language constructs (map / flatmap, Option, Try), without having to go all-in on functional style.