Nice to see this passed around on Hacker News. I think the whole concept of lenses is super cool and useful, but suffered from the usual Haskellificiation problems of being presented in an unnecessarily convoluted way.
I think Accessors.jl has a quite nice and usable implementation of lenses, it's something I use a lot even in code where I'm working with a lot of mutable data because it's nice to localize and have exact control over what gets mutated and when (and I often find myself storing some pretty complex immutable data in more 'simple' mutable containers)
If you’re young, new to data science and hoping to get a job in it after some time, then I absolutely recommend learning Python instead of Julia.
But if you are just interested in learning a new language and trying it in data science OR are not currently looking to enter the data science job market, then by all means: Julia is great and in many ways superior to Python for data science.
It’s just that «everyone» is doing data science in Python, and if you’re new to DS, then you should also know Python (but by all means learn Julia too!).
Yes lenses are pairs of functions that allow bidirectional data transformations. One function acts like a getter and one function acts like a setter. The signatures of the functions are designed to compose nicely.
This allows to compose complex transformations from a few simple building blocks.
In the end it is really just function composition but in a very concise and powerful way.
In your cambria example the lens is defined as yaml. So this yaml needs to be parsed and interpreted and the applied to the target data. The rules that are allowed to be used in the yaml format must be defined somewhere.
With pure functional lenses the same kind of transformation rules can be defined just by function composition of similar elemental rules that are itself only pairs of functions.
> So this yaml needs to be parsed and interpreted and the applied to the target data. The rules that are allowed to be used in the yaml format must be defined somewhere.
I wasn't trying to get into the specific technology. The Julia still needs to be parse, and while Yaml has them separate, CUE does not (which is where I write things like this and have building blocks for lenses [1], in the conceptual sense)
In the conceptual sense, or at least an example of one, lenses are about moving data between versions of a schema. It sounds like what you are describing is capable of this as well? (likely among many other things both are capable of)
Yes functional lenses are very good at transforming between between schematas.
You can think of it as an functional programming based embedded domain specific language for transforming immutable data structures into each other. Sure there are other ways to do it but its like generalized map/filter/reduce class of functions vs doing the same imperatively by hand or in other ways
It's about the annotation triggering a code pre-processor.
For example in Lombok, the @Data annotation will create a getter and a setter for every private member, and @Getter and @Setter will do the individual methods respectively.
Annotating a class will do every private member, or you can annotate a specific member.
A lens is a shortcut to making a getter/setter for something several elements deep, where instead of calling:
`parentObject.getChild().setChildAttribute()`
you can call:
`parentObject.setChildAttributeViaLens()`
and not need to write multiple functions in both classes, or even use multiple annotations.
You use place syntax like what is used with incf or setf, denoting part of some complex object. But the modification is made to the corresponding part of a copy of the object, and the entire new object is returned.
Say you want to do obj.child.foo[3].bar += 2 but without mutation, but instead all the data is immutable and you need to do a deep copy along the path.
Lenses are an embedded dsl for doing this via syntax that reads similar to to the mutable variant.
Additionally it allows to compose many of such transformations.
The other replies covered the answer about immutability well, but I have the further question: why isn't this built into languages as syntax sugar, so that OP's suggested line would work with immutable structures?
As a dilettante at programming language design, I have my own toy language. It uses exclusively immutable data structures (C++ "immer"). I present it to the programmer as simple value semantics. `obj.foo[5].bar.a = 2` works and sets `obj` to a new structure where the path through `foo`, `bar`, to `a` has all been rewritten. Since I put it in as a language feature, users don't have to learn about lenses. Why isn't this technique more common in programming language design? Is it so offensive that the syntax `obj.a = 2` ends up rebinding `obj` itself? The rule in my language is that assignment rebinds the leftmost "base" of a chain of `.` field and `[]` array index accesses on the LHS. I'm ignorant of the theory or practical consideration that might lead a competent designer not to implement it this way.
The difference doesn't matter when you have a shallow structure and can access fields directly and have a few lines of code. But field access does not compose easily if you have a nested hierarchy of objects. Your natural choice in the "OOP style" is to write a lot of boiler plate to point to each different field you want to get/set. Say you get bored of the tedium and want "higher-order" accessors that compose well -- because ultimately all look-up operations are fundamentally similar in a sense, and you only need to write traversals once per data structure. Eg: Instead of writing yet another depth-first search implementation with for loops, you could easily tie together a standard DFS implementation (traversal) from a library, with accessors for the fields you care to work with.
One way to think of the goal of functional paradigm is to allow extreme modularity (reuse) with minimal boilerplate [1]. The belief is minimal boilerplace + maximum reuse (not in ad-hoc ways, but using the strict structure of higher-order patterns) leads to easily maintainable bug-free code -- especially in rapidly evolving codebases -- for the one-time cost of understanding these higher-order abstractions. This is why people keep harping on pieces that "compose well". The emphasis on immutability is merely a means to achieve that goal, and lenses are part of the solution to allow great ergonomics (composability) along with immutability. For the general idea, look at this illustrative blog post [2] which rewrites the same small code block ten times -- making it more modular and terse each time.
Once the language is expressive enough to compose pieces well and write extremely modular code, the next bit that people get excited about is smart compilers that can: transform this to efficient low-level implementations (eg. by fusing accesses), enforce round-trip consistency between get & set lenses (or complain about flaws), etc.
You can uhhh abstract over the property which seems cool if you’re into abstracting things but also probably shouldn’t be the thing you’re abstracting over in application code.
Or on second look the sibling comment is probably right and it’s about immutability maybe.
It's a similar idea to map() but for more complex objects than arrays. When people use "map" in Javascript (or most any other language that supports it) do they do so because "they are terrified of mutability, and are willing to abandon performance?"
Your comment reads like the response of someone who is struggling to understand a concept.
Nice to see this passed around on Hacker News. I think the whole concept of lenses is super cool and useful, but suffered from the usual Haskellificiation problems of being presented in an unnecessarily convoluted way.
I think Accessors.jl has a quite nice and usable implementation of lenses, it's something I use a lot even in code where I'm working with a lot of mutable data because it's nice to localize and have exact control over what gets mutated and when (and I often find myself storing some pretty complex immutable data in more 'simple' mutable containers)
Guys, What's you're opinion on Julia?
I am thinking of using it for data science work.
Any draw backs? or advantages I should know about?
If you’re young, new to data science and hoping to get a job in it after some time, then I absolutely recommend learning Python instead of Julia.
But if you are just interested in learning a new language and trying it in data science OR are not currently looking to enter the data science job market, then by all means: Julia is great and in many ways superior to Python for data science.
It’s just that «everyone» is doing data science in Python, and if you’re new to DS, then you should also know Python (but by all means learn Julia too!).
Was hoping this was data lenses, like cambria from ink&switch
https://www.inkandswitch.com/cambria/
Not sure how "A Lens allows to access or replace deeply nested parts of complicated objects." is any different from writing a function to do the same?
Julia curious, very little experience
Yes lenses are pairs of functions that allow bidirectional data transformations. One function acts like a getter and one function acts like a setter. The signatures of the functions are designed to compose nicely. This allows to compose complex transformations from a few simple building blocks.
In the end it is really just function composition but in a very concise and powerful way.
In your cambria example the lens is defined as yaml. So this yaml needs to be parsed and interpreted and the applied to the target data. The rules that are allowed to be used in the yaml format must be defined somewhere. With pure functional lenses the same kind of transformation rules can be defined just by function composition of similar elemental rules that are itself only pairs of functions.
To be clear, cambria is not mine
> So this yaml needs to be parsed and interpreted and the applied to the target data. The rules that are allowed to be used in the yaml format must be defined somewhere.
I wasn't trying to get into the specific technology. The Julia still needs to be parse, and while Yaml has them separate, CUE does not (which is where I write things like this and have building blocks for lenses [1], in the conceptual sense)
In the conceptual sense, or at least an example of one, lenses are about moving data between versions of a schema. It sounds like what you are describing is capable of this as well? (likely among many other things both are capable of)
[1] https://hofstadter.io/getting-started/data-layer/#checkpoint...
Yes functional lenses are very good at transforming between between schematas.
You can think of it as an functional programming based embedded domain specific language for transforming immutable data structures into each other. Sure there are other ways to do it but its like generalized map/filter/reduce class of functions vs doing the same imperatively by hand or in other ways
hmm, that makes it sound closer to CUE, where all values are immutable
CUE is in the logical family with Prolog and is not Turing Complete
It's about the annotation triggering a code pre-processor.
For example in Lombok, the @Data annotation will create a getter and a setter for every private member, and @Getter and @Setter will do the individual methods respectively.
Annotating a class will do every private member, or you can annotate a specific member.
A lens is a shortcut to making a getter/setter for something several elements deep, where instead of calling:
`parentObject.getChild().setChildAttribute()` you can call: `parentObject.setChildAttributeViaLens()`
and not need to write multiple functions in both classes, or even use multiple annotations.
Lenses make it more convenient to use immutable structs, which Julia encourages (particularly as they unlock various optimisations).
Certain aspects of this me of the modf macro for Common Lisp:
https://github.com/smithzvk/modf
You use place syntax like what is used with incf or setf, denoting part of some complex object. But the modification is made to the corresponding part of a copy of the object, and the entire new object is returned.
I have to admit I don’t really understand the point of doing this instead of just obj.a = 2 or whatever.
Say you want to do obj.child.foo[3].bar += 2 but without mutation, but instead all the data is immutable and you need to do a deep copy along the path.
Lenses are an embedded dsl for doing this via syntax that reads similar to to the mutable variant. Additionally it allows to compose many of such transformations.
The other replies covered the answer about immutability well, but I have the further question: why isn't this built into languages as syntax sugar, so that OP's suggested line would work with immutable structures?
As a dilettante at programming language design, I have my own toy language. It uses exclusively immutable data structures (C++ "immer"). I present it to the programmer as simple value semantics. `obj.foo[5].bar.a = 2` works and sets `obj` to a new structure where the path through `foo`, `bar`, to `a` has all been rewritten. Since I put it in as a language feature, users don't have to learn about lenses. Why isn't this technique more common in programming language design? Is it so offensive that the syntax `obj.a = 2` ends up rebinding `obj` itself? The rule in my language is that assignment rebinds the leftmost "base" of a chain of `.` field and `[]` array index accesses on the LHS. I'm ignorant of the theory or practical consideration that might lead a competent designer not to implement it this way.
The difference doesn't matter when you have a shallow structure and can access fields directly and have a few lines of code. But field access does not compose easily if you have a nested hierarchy of objects. Your natural choice in the "OOP style" is to write a lot of boiler plate to point to each different field you want to get/set. Say you get bored of the tedium and want "higher-order" accessors that compose well -- because ultimately all look-up operations are fundamentally similar in a sense, and you only need to write traversals once per data structure. Eg: Instead of writing yet another depth-first search implementation with for loops, you could easily tie together a standard DFS implementation (traversal) from a library, with accessors for the fields you care to work with.
One way to think of the goal of functional paradigm is to allow extreme modularity (reuse) with minimal boilerplate [1]. The belief is minimal boilerplace + maximum reuse (not in ad-hoc ways, but using the strict structure of higher-order patterns) leads to easily maintainable bug-free code -- especially in rapidly evolving codebases -- for the one-time cost of understanding these higher-order abstractions. This is why people keep harping on pieces that "compose well". The emphasis on immutability is merely a means to achieve that goal, and lenses are part of the solution to allow great ergonomics (composability) along with immutability. For the general idea, look at this illustrative blog post [2] which rewrites the same small code block ten times -- making it more modular and terse each time.
[1] https://www.cs.kent.ac.uk/people/staff/dat/miranda/whyfp90.p...
[2] https://yannesposito.com/Scratch/en/blog/Haskell-the-Hard-Wa...
Once the language is expressive enough to compose pieces well and write extremely modular code, the next bit that people get excited about is smart compilers that can: transform this to efficient low-level implementations (eg. by fusing accesses), enforce round-trip consistency between get & set lenses (or complain about flaws), etc.
You can uhhh abstract over the property which seems cool if you’re into abstracting things but also probably shouldn’t be the thing you’re abstracting over in application code.
Or on second look the sibling comment is probably right and it’s about immutability maybe.
Immutability is a central concept in functional programming.
This is equivalent to that for people who are irrationally terrified of mutability, and are willing to abandon performance.
It's a similar idea to map() but for more complex objects than arrays. When people use "map" in Javascript (or most any other language that supports it) do they do so because "they are terrified of mutability, and are willing to abandon performance?"
Your comment reads like the response of someone who is struggling to understand a concept.
Only the get half is `map`-like. In combination it's more like a property descriptor, which is far easier to understand and much more efficient.
And, if it wasn't obvious, it's only the `set` half where lenses suck for performance.
Is this like setf in lisp?