4 ms·
As someone who is used to code in python and typescript explain to me in layman terms why the HELL would you need something like this: "...in lenses or other
by artemonster 2y ago
As someone who is used to code in python and typescript explain to me in layman terms why the HELL would you need something like this:
"...in lenses or other optics. The simplest complete example is a Lens s t a b: it can extract an a from an s and overwrite it with a b. Doing so would produce a value of type t."
- kristopolous 2y agoHaskell is by mathematicians and for mathematicians. I know this sounds absurd but many actually find it to be the easiest programming language for them. I've met mathematicians who claim they were not able to code until they found Haskell.
- cstrahan 2y agoSure, I can do that. Why the HELL would you need objects and object oriented programming? Wherever you do foo.blah(), you could just do foo_class__do_blah(obj)… so what do objects really grant you that you can’t do with basic procedural programming? Why do you need Python’s function decorators? You can just manually copy and paste whatever logic you need into each and every function, instead of decorating them. Why have an iterator protocol? Every datum can have it’s own bespoke way of iteration that you simply have to memorize on a case by case basis (and can not be abstracted over in any common way), why make everything conform to the same interface? Why have anonymous functions that can close over their lexical scope (what people usually refer to as “closures”)? Consider: let x = 0; let inc = () => x++; You could just write: function inc(bindings) { return bindings.x++; } And then remember to pass along bindings whenever you need to call inc. Besides, that’s more or less what’s going on behind the scenes. I could keep going. We have these facilities not because they are essential (go program in assembler, and you’ll see how few programming techniques and concepts your processor actually cares about), but because they are nice to have. They are nice to have because they provide us tools for abstraction, so by solving the general case, you solve a host of concrete cases. Lenses let you abstract over data traversal and projection. Maybe you don’t appreciate that, and that’s fine. There are C programmers who don’t mind writing 100 different hash tables for different keys and elements. If you are a Python/TS developer, I’m pretty sure you’d find that surprising — if you want a dictionary/hashtable, you just use a dict and call it done — you wouldn’t go build a new dictionary class for each and every key-type/value-type pair. Different people have different tolerances for the drudgery of solving the same problem for the 100th time. However: while I don’t understand why someone would choose vanilla ice cream if they’re given the option of chocolate, I wouldn’t act flabbergasted in such a scenario. Why would I? Why should I expect my desires to be the same as everyone else’s? The only reason I can think of that someone would express shock at someone choosing vanilla over chocolate is if they think that the preference for vanilla indicates something is fundamentally wrong them. Or in other words: being a condescending bigot. That’s kinda how you’re coming off. In the event that you were actually asking in good faith: if you like to broaden your skills so you can finish programming work faster, I’d recommend reading up on lenses. If you don’t care about that, that’s fine too, but I’d recommend against expressing shock at the fact that others do care about these types of things.
- rcxdude 2y agoTo be fair, idiomatic python code almost never has the constraints that would make a lens useful. Python programmers would just mutate their datastructures, or if they are trying not to mutate the original, copy it and then mutate it, or use a closure to represent the access to the nested structure, something that is about as succinct as representing it via lenses in functional languages. That's why it seems kinda unnecessary as a construct to someone who's only used to thinking about coding that way.
- _jackdk_ 2y agoI dunno mate, I think GP's a little sharp, but it's great that he asked his question and expressed just how baffled he was instead of closing the tab and going "those Haskellers are a bunch of loonies". But I think you came out swinging here in a way that's not helpful. Accusing GP of bigotry because he didn't understand, and asked a question to understand?! I agree with the technical points in your post and disagree with its venom. It's important to notice patterns and abstract over them, and to teach people to spot commonalities they haven't yet noticed in their code. Next time I think you'll have better luck starting with the assumption of good faith and going from there.
- pona-a 2y agoLenses seem to be a functional pattern that groups getters and setters into composable functions. Consider pair(a, b) with first(pair) and rest(pair). I can define first-lens and rest-lens for [get-first, set-first] and [get-rest, set-rest] respectively. I could use them as simple getter/setter groupings with lens-view(rest-lens, pair) or lens-set(first-lens, 4, pair), which isn't so useful by itself, or I can compose then to get third-lens with lens-compose(rest-lens, first-lens), allowing me to lens-set(third-lens, 7, pair) without a nested call. I just tried to summarize what I could read [1] [2] [3] and I don't claim to entirely understand it, but it's still better than the sibling comment dismissing it as abstract nonsense. [1] https://docs.racket-lang.org/lens/lens-intro.html https://docs.racket-lang.org/lens/lens-intro.html [2] https://hackage.haskell.org/package/lenses-0.1.4/docs/Data-Lenses.html https://hackage.haskell.org/package/lenses-0.1.4/docs/Data-L... [3] https://theswiftdev.com/lenses-and-prisms-in-swift/ https://theswiftdev.com/lenses-and-prisms-in-swift/
- dmkolobov 2y agoIt’s essentially a way to give types to “mutable” views into larger data structures. s - the larger data structure a - a view value b - a “update” value t - the data structure after an update is applied Of course, everything is still immutable in the same way that cons-ing a value onto a list is immutable. The change in type between s and t allows you to nest these views, hide information, etc.
- _jackdk_ 2y agoYou've got the right general idea but the difference between `s` and `t` is not for nesting - you can write a composition operator for a "simple" lens where s=t and a=b. `b` and `t` are just for type-changing updates, like when you replace a field in a tuple or every element in a list.
- rcxdude 2y agoBasically because in languages where everything is immutable, 'setting' a value nested in a datastructure is a big hassle. Lenses give you a means to effectively do foo.bar.x = "baz" succinctly (and a few other bits besides).
- _jackdk_ 2y ago(Author here.) It's a very fair question. I'm pretty sure lenses were invented because Haskell's built-in records are not ergonomic, but then the community discovered that they're extremely useful, general, and flexible. Here is a Redux (JS) example of the problem that lenses help solve, where you are working with immutable data and want to replace one field of a deeply-nested structure: https://redux.js.org/usage/structuring-reducers/immutable-update-patterns https://redux.js.org/usage/structuring-reducers/immutable-up... You can think of a lens as a getter/setter, stored together as a first-class value. I'm not great at TS but hopefully you can see what I'm getting at with these types: A Lens<S,T,A,B> is a pair of functions: * get: (s: S) => A * set: (s: S, b: B) => T The most obvious lenses you can make are lenses that represent record fields (object properties?). But you can make lenses into other things, like a function that returns a lens into the Nth bit of an integer, or a function that takes a map key and returns a Lens from Map<K,V> to Optional<V>. You use lenses by passing them to functions that then operate on your data. Three common ones are `view`, `set`, and `modify`: * view: (lens: Lens<S,T,A,B>, s: S) => A * set: (lens: Lens<S,T,A,B>, s: S, b: B) => T * modify: (lens: Lens<S,T,A,B>, s: S, f: (a: A) => B) => T So far, it doesn't look like we've bought much. But the real payoff comes from when you notice that lenses compose: * compose: (outer: Lens<S,T,X,Y>, inner: Lens<X,Y,A,B>) => Lens<S,T,A,B> This solves the "nested record problem" because you can compose the lenses for each field in the chain to get a lens from the outer structure to the field you're interested in, and functions like `set` and `modify` will correctly copy all the fields at each layer when you call `set` or `modify`. Haskell's lens libraries give you compact operators for doing this, so it all looks neat and tidy. A reasonable response to all this machinery is, "OK, your language has bad records and you built a library to paper over them. So what?" To this I say: 1. Every language has this problem when working with nested immutable data, though not every language has the tools to compactly encode lenses and make them ergonomic to use; and 2. Lenses generalise to give you other "optics". A `Traversal<S,T,A,B>` picks out zero or more `A`s from `S`, and can replace them all with `B`s. `modify` and `set` then generalise, and perform their update on _every_ matching `A` selected by the `Traversal`, and you also get `toListOf`, which collects all matching `A`s into a list. There are other optics too, but this post is getting long. Once you have the more powerful optics, you get a very expressive toolkit for querying and updating data, that works on any data structure that has optics defined. (And Haskell has tools to derive many optics automatically.) Chris Penner's wonderful book, _Optics By Example_[1], has a bunch of exercises that involve slicing and dicing a big slab of Kubernetes YAML with queries like "collect a list of all 'containerPort's alongside their Pod's 'name' (from 'metadata')" and operations like "set a resource request of `memory: "256M"` for every container". The answers are extremely compact and elegant, and I don't know how you'd achieve anything similar with other methods. I hope this helped, because there is definitely a really useful and powerful toolkit hiding behind that absurd-looking type signature. [1]: https://leanpub.com/optics-by-example/ https://leanpub.com/optics-by-example/
- kqr 2y agoAs others pointed out, you don't need any convenience from a modern language. But what optics (often sloppily referred to as lenses) give you are really powerful getters and setters. They can do what getters and setters in Python do, but they can also - target multiple fields in multiple objects at the same time, or - target fields only when a condition is true, - they can target deeply nested fields, - they can target computed values which have a compound effect on the underlying fields, - they can return a new full object where the targeted fields are transformed by an arbitrary function, etc. This means optics are extremely broadly useful. They can be used as OO-style getters and setters, but they can also be used to build and navigate JSON objects, or parse HTML content. It's a common interface that can be used for getting and modifying data in any broader context.