9 ms·
Exercises for understanding Haskell Lenses (2019)
- azalemeth 4y agoI must confess that it took me until the first code sample to realise that the "lenses" mentioned are not those described by 1/u+1/v=1/f....
- seanparsons 4y agoI think most people would find these clearer if they used the functions view/set/etc, rather than the various operators which are just alternatives to those.
- JadeNB 4y ago> I think most people would find these clearer if they used the functions view/set/etc, rather than the various operators which are just alternatives to those. Everyone has their own tastes. Someone unfamiliar with the notation of ordinary algebra might argue for "you will have to find two numbers that the difference between the two is 10 (that is, so much as is our number) & that we make the product of these two quantities, the one multiplied by the other, exactly 1, that is, the cube of the third part of the variable" (https://www.maa.org/press/periodicals/convergence/how-tartaglia-solved-the-cubic-equation-tartaglias-description-of-his-solution https://www.maa.org/press/periodicals/convergence/how-tartag...) as easier to understand than "find u and v such that u - v = 10 and u v = 1", but I think most modern readers would agree that people uncomfortable with the algebra are better served by learning how to read the latter than by sticking with the former. (And, I think, also that it doesn't help either to keep the variables but replace the symbolic operations by words: `(and (eq (subtract u v) 10) (eq (mult u v) 1))`, in pseudo-Lisp.)
- simiones 4y agoThe thing is, basic algebra notation has two major advantages over ad-hoc operators in some Haskell library: (1) it is widely taught and understood and extremely widely applicable, and (2) each operator has a standard name that is widely explained. In contrast, most Haskell notation I've seen is either an ad-hoc invention for some library, or it is an ASCII version of notation in a niche domain like category theory. Even Haskell's >>= operator for flatMap/bind seems to be an invention, as far as I can tell the equivalent concept in CT is Kleisli composition, denoted by a sharp sign and the regular composition operator (as far as Wikipedia shows - I'm not formally trained in CT). Additionally, people rarely if ever give a proper name to this notation in Haskell, making it completely impenetrable to even represent the formulas in your mind. How am I supposed to read `user1 ^. name` ? When I see `∇⨯f` I know how to read it (del cross f, or nabla cross f, or curl f) because that was an explicit part of how I was taught the operation (and note that it is not an arbitrary digraph, it can really be computed as the cross product of the pseudo-vector nabla and f), but Haskell tutorials and documentation completely skip this step, in my experience.
- rssoconnor 4y agoNot that you would necessarily use the same words in Haskell, but I'm curious how you would read `user1^.name` in Pascal, or `user1->name` in C or `(*user1).name` in C? (Edit: I'd also be curious about `x += y` and `x << y` in C.)
- AnimalMuppet 4y agouser1^ is what the user1 pointer points to, if I remember my Pascal correctly. It's a structure and the .name says to take the name field of that structure. So ^. isn't a digraph, it's two separate operators. Same with your C example. It's doing the exact same thing in C, except the operators (* and .) are separated from each other. -> is a digraph. It means the same as `*.` I don't usually pronounce it, I just think of it as itself. If I have to say it to myself mentally, I say "sub". I'm not sure I've ever tried to say it aloud to a coworker; if I did, I might have said "arrow" or something. You do have to learn these things, just like you have to learn all the other operators. But simiones still has a point - at least the math-based operators are much more widely known and understood than the category-based ones.
- anon291 4y agoBut what are their names? That was the standard applied to Haskell.. that the operators needed well understood names. C++ does not have that. Worse still, C++ operators are completely unintellible without context. For example, '>>=' (commonly called bind in haskell) is well-specified. Anything I see it used with is going to be a Monad, and thus follow certain laws. Examining the imports, I can immediately tell what any operator is. In C++? Forget about it. The 'left-shift' operator which is supposed to shift bits to the left, can somehow also print things to standard output. In what world can the terminal be bit-shifted left? In fact, we understand this because no one reads 'std::cout << "Hello world"' as 'shift std::cout left by "Hello world", because such a thing is non-sense, whereas '1 << 2' is '1 shifted two bits to the left'. EDIT: And then, when you add in external libraries, it gets worse. `<<` can be used for creating lexers and parsers in boost if I recall correctly. Completely lawless, and, when you survey the ecosystem, also dangerous. So many bugs in C++ and such due to this.
- wakamoleguy 4y agoIn Haskell, replacing the symbolic operations doesn't imply we have to abandon infix operations: (and (eq (subtract u v) 10) (eq (mult u v) 1)) could be: (u `subtract` v `eq` 10) `and` (u `mult` v `eq` 1)
- JadeNB 4y ago> (and (eq (subtract u v) 10) (eq (mult u v) 1)) could be: > (u `subtract` v `eq` 10) `and` (u `mult` v `eq` 1) True. I used Lisp because I think it's reasonably common to use words for arithmetic operations in many Lisp dialects, but not in Haskell. But it's not the prefix notation to which I was objecting, but the idea of spelling out every operation when we've got a notation specifically designed to facilitate rapid thought and computation without that.
- mejutoco 4y agoFrom the beginning of the article: > The basic operators here are ^., .~, and %~. If you’re not a fan of funny-looking operators, Control.Lens provides more helpfully-named functions called view, set, and over (a.k.a. mapping); these three operators are just aliases for the named functions, respectively. For these exercises, we’ll be using the operators.
- n0us 4y ago...yes and it would be clearer if they used the named functions
- thomastjeffery 4y agoBut the person who "is not a fan of funny-looking operators" still has to read them; and that's the part that makes funny-looking operators undesirable to them in the first place!
- mejutoco 4y agoTo be fair, if after reading that paragraph they find it so unsufferable, they can skip the article. The author makes a choice and acknowledges other opinions but, at the end of the day, has their opinion.
- worldsayshi 4y agoWhen finding out about lenses some years ago I really felt that they could be immensely useful in the way they could potentially separate all data mapping concerns from the rest of the code base. But I quickly ended up in dependency or type hell when I tried using them. I hope that people will take inspiration from the concept in the future.
- deleted 4y ago[deleted]
- bradrn 4y ago> But I quickly ended up in dependency or type hell when I tried using them. Wouldn’t know about the dependency issues, but the newer ‘optics’ library [https://hackage.haskell.org/package/optics https://hackage.haskell.org/package/optics] has much simpler type signatures and greatly improved error messages. Personally I don’t like it, since it hides the implementation details too much for my taste, but it seems to be becoming pretty popular in the Haskell community.
- gnull 4y agoIt may be a matter of experience. I probably would not be able to use them during my first or second year of slowly learning Haskell, but later when I learned to naturally think in terms of functors, applicative and traversables, I just sat down and used them. A lot of the time when I couldn't completely untangle some type definition in lens library, I'd just go with gut feeling or guess what it does by analogy with stuff I already understood, and surprisingly often it compiled and worked like I expected. I found Hoogle very useful for this guesswork.
- gnull 4y agoI must admit though that I've spent quite a lot of effort on playing with types that time, and often neglected carefully designing the architecture of my program and the way I'm applying lenses there, which made me regret it later.
- jerf 4y agoI have as a general principle that it is difficult to understand a solution until you have the problem. You will need to attain a certain degree of fluency in Haskell before you have the problem that deep access into structures or any of the other problems lens can solve [1] is a clearly-separated problem that rates among your biggest. Until you reach that point, I advise not to touch lens. By the time that is your biggest problem, it is likely that your other Haskell experiences will make it not too frightening, but it's not a very good gateway into Haskell. [1]: One of my favorite uses, though not a common one, is that you can use it to abstract a pair (thing to operate on, how to operate on it) in a way where both the elements are still fully composable, where the lens is sitting in the second spot of that tuple, and whatever is operating on that only needs the type of the "how to operate on it" part, it is oblivious to the type of the "thing to operate on". I used it to build a game engine where the engine did not have a hard-coded concept of "player 1 goes, then player 2 goes, then player 1 goes", etc.; instead, the game logic itself decided that and all the engine got was a combination of what structs to manipulate and what to perform on that struct. (The engine itself did networking and converted things to and from JSON, while letting the pluggable game logic do everything else.) An interesting use for lenses beyond just "better record syntax" as I started passing the lenses themselves around in my code as first-class values. But probably ultimately an even worse way to get into Haskell than using lenses as record syntax improvements.... my point here is just that lenses do have uses beyond record accessing, that's just their headline usage.
- bradrn 4y ago(2019) — although it hasn’t affected the correctness of the article, as far as I can tell
- macpete42 4y agoLovely all these nonsense operators like ^. .~ just to save a few characters and make everything more magic than it is.
- vintermann 4y agoOne of the bad things about having mathematicians design your language.
- niek_pas 4y agoI take your point, but this is not a language design problem -- it's a library design problem. These are just function names.
- vintermann 4y agoI kind of see idioms like lenses as part of the language. But the language allows defining your own (infix) operators, so the core language designers weren't entirely innocent either. The abundance of custom operators in Haskell kind of made me appreciate Lisp and Forth a little more! There are of course many good things about having your language designed by mathematicians, too.
- anentropic 4y agoit's a culture problem, the library is like this because that's the culture of the language
- whateveracct 4y agoIt's not a culture problem it's just culture. Just because you don't like it doesn't mean Haskell is wrong lol.
- klntsky 4y ago> just to save a few characters and make everything more magic than it is No, this is not the true motivation. Human attention spans on symbols, the more succinct the code is, the more meaning it can encompass in the same number of characters. Writing abstracted enough code often requires inventing new languages to hide the complexity behind abstractions. Software development in Haskell is particularly focused on DSLs, lens language being one of them. ^.. is a relatively simple operator though, it just abstracts away traversals.
- SaddledBounding 4y agoThe title needs to be changed, because: > This isn’t meant as a lens tutorial. Prerequisites are some basic knowledge of how to use Haskell, GHCI, and familiarity of the idea of lenses, if not the specifics. With that out of the way, it's kind of disappointing that the article isn't a tutorial. As it is, it just lists the lens operators, gives you alternate, but still non-obvious names for them, and foists some exercises upon you. It could have easily given one short example for each operator without adding any significant tedium for those who already have an idea of what they do, and then it would have been a perfectly fine tutorial. So here's some examples, (Hoogle has some nice examples too; thanks Hoogle): person = Person {_name = "Alice", _title = "Dr"} -- get field with magic lens person ^. title == "Dr" -- create new object with new field value person & name .~ "Bob" == person & (name .~ "Bob") == (name .~ "Bob") person == Person {_name = "Bob", _title = "Dr"} -- create new object with function applied to field value person & name %~ lowercase == (name %~ lowercase) person == Person {_name = "alice", _title = "Dr"}
- zeendo 4y agoMy all-Haskell company has avoided the use of Lens whenever possible and I haven't missed it for a second. Its payoff doesn't seem to justify the cognitive overhead (concepts, operators, etc) that it brings. I'd highly recommend that newcomers to Haskell avoid Lens in their own code for as long as possible. The juice really isn't worth the squeeze.
- djha-skin 4y agoThe whole lens thing was what really turned me off to Haskell in the first place. It seems like I would never get it and it's hard to want to be in a language where you don't get something.
- _huayra_ 4y agoIs there a language where you get everything? (i.e. there's nothing you don't get) Based on the people who have amount of "C++ experts" I've interviewed over the years who don't know much about template metaprogramming or weird rules about initialization and class layout, yet have shipped meaningful code while avoiding most of the weird parts of the language. Even languages like Python can get weird, such as default arg initialization: def add_item_to_list(item, list_to_add=[]): list_to_add.append(item) return item a = add_item_to_list(1) a.append(3) b = add_item_to_list(2) What do `a` and `b` print? This is my go-to Python 1st-round question, and many prolific Python coders don't get through this. From what little I've learned of Haskell so far, it seems like one can eschew the weird PL-heavy parts of the language and just be productive (with the tradeoffs the language contains, e.g. GC).
- goostavos 4y agoYou ask this in interviews...? What makes this a good 1st round question in your mind? What is there to learn about the candidate? It seems to me that it's one of those things you've either encountered (and go "well, that's weird") or... haven't encountered (and go "well, that's weird" upon having it explained). Have pop quizzes about the sharp edges of various languages proved useful as a filtering mechanism?
- shinzui 4y agoLens is one of my favorite libraries, but I can understand why optics, in general, is a divisive topic. Like many things in Haskell, you won't appreciate optics without suffering through a steep learning curve. Optics don't just save you a few keystrokes. Instead, they make tedious code that you would never write trivial. And more importantly, they're composable. So it's impossible to appreciate their effectiveness without using them on a real-world app. For example, I've worked in an app with three layers, each with its record types, and without the lens library, the pain of converting between records would have been insufferable. Instead, most developers would couple the three layers together to avoid the problem, which makes the codebase brittle.
- ablob 4y agoDo you know of an application I could look at to see this first hand?
- valyagolev 4y agowe use lenses in react/typescript together with jotai atoms to express data paths in complex structures. it’s an amazing basic idea that is done a disservice by the proliferation of weird concepts and operators
- dotneter 4y agoAre there any advantages to use lenses instead of immer?
- valyagolev 4y agowe use both a lens library and an immutable data structures library. lenses are not a data structure, but a path into it
- wk_end 4y agoFor anyone - anyone who's faffed around with JavaScript's spread syntax when working with nested immutable data, anyway - struggling to understand the general concept or utility of optics, I'd encourage looking into monocle-ts. The motivation [0] that kicks off the README instantly made me a believer, and TypeScript's idiosyncratic type system makes using the library remarkably natural; it fits right in without needing to deal with anything analogous to Template Haskell. [0] https://github.com/gcanti/monocle-ts#motivation https://github.com/gcanti/monocle-ts#motivation
- ToJans 4y agoI remember being stuck on Haskell lenses for a while, until I asked the brilliant Scott Wlaschin during a speakers' dinner in Lithuania to explain lenses in layman's terms to me, to which he simply replied "it's just like property getters and setters". Short, to the point, and I immediately understood the core value prop. There's of course a lot more to it, but in essence that's what it's about...