This is a lot better now with record dot syntax. [1]
[1] https://ghc-proposals.readthedocs.io/en/latest/proposals/028...
This is a lot better now with record dot syntax. [1]
[1] https://ghc-proposals.readthedocs.io/en/latest/proposals/028...
Monads are now of course seen by Haskellers as being very useful in many different use cases beyond the original motivation.
IO in PureScript, which I’d describe as strictly evaluated Haskell, is still implemented using monads. The biggest benefit of this (as one might expect) is forcing the separation of pure and impure code, but a cool byproduct is asynchronous IO is also implemented in a monad. Various functions are available to launch async from synchronous IO and lift synchronous functions into async. The ergonomics of mixing sync and async are better than any other language I’ve used.
So theres an interesting question: once you have dot syntax, are there reasons you’d still want lenses? I’ve come up with two.
First, it is nice to use “over” (%~) to avoid specifying a location in a data structure twice (first to read it and apply a function to the read value, second to create a new data structure with the read value replaced with the output of the function. The deeper the data structure, the bigger the benefit.
The second case is writing functions that take lenses as an input and operate generically with larger data structures without knowing where in the larger data structure the values of interest reside ahead of time.
Only for the most basic uses. Architecting programs as folds or traversals can be powerful.
One simple example of lenses i can think of being useful for more than record access is:
> sumOf (folded . folded) [Just 1, Just 2]
3
More are in this presentation: > sum $ map (fromMaybe 0) <some list of Maybe Int>
Possibly with a different default value for Nothings. A lot of the optics libraries seem to focus a lot on making the general case as easy as possible while overcomplicating the specific case. `sumOf (folded . folded)` does have some advantages in working for lists of other types but I question how often you would reuse such a function for different types the first place. It just doesn't come up all too often in my own code, perhaps it is different elsewhere.The common refactor here would be to Either:
> sumOf (folded . folded) [Right 1, Right 2]
3
I don't like the "unwrap the Maybe" or "get rid of the pesky monad in my way" intuition that fromMaybe and catMaybes give beginners.> A lot of the optics libraries seem to focus a lot on making the general case as easy as possible while overcomplicating the specific case.
I kind of agree with this, but contend the universality of a single powerful and composable lens syntax simplifies things more overall even if it makes a single use site more complicated.
Just remembered another refactor that comes up a lot for me:
Refactoring `[a]` to `NonEmpty a`. A lot of times this tightens your code up, but i find if the call chain is monomorphic to list most won't bother with the refactor.
So I like lens for pushing away from that level of monomorphism that is resistant to better architecture.
> sum $ map (fromMaybe 0) [Just 1, Just 2]
3> sum . catMaybes
Lenses (imho) shine when you have to write data-extractors for arbitrary-complex and ill-documented APIs with corner cases or things like HTML scrapping. The beauty of lenses is that you get one composable language across libraries. For instance, if your HTTP-library has lenses and your XML-library has lenses, you can write things like safely composing a getElementsByTagName with some XML-parsing and data extractions:
> response ^.. responseBody . xml . folding universe . named (only "p") . text
and the bonus is that if you need to filter, you can do that with a similar mechanism (e.g., filtering based on some html meta-tag)
overall it feels like SQL-for-arbitrary-structures: dense but powerful
The focus on fancy type level abstractions is why Haskell is so interesting, but it is also one of the reasons I don't use Haskell in a business environment.
Everything is just so complicated, and every library does things somewhat differently.
This is my own idiosyncratic view but lately I've been fiddling with the proposition that the defining characteristic of functional programming languages is the definition and pervasive utilization of recursion schemes via composition as the primary organizational method for code. That is to say, such programs do not just borrow a few recursion schemes here or there (like chaining a map with a filter and calling it a "functional program"), but that the entire architecture is oriented around operating on data via such recursion schemes, stacked on top of each other.
So many of the previous touchstones keep getting moved into non-functional languages, like "functions as first-class citizens" (almost everything has this now) and "immutability" (less common, but common enough we can say with confidence now that having immutable values does not turn a language "functional"), but the destination languages remain quite distinct from something like Haskell.
From that point of view, lens is a development you'd expect any such language to eventually come up with and make extensive use of. It isn't the only way to compose recursion schemes together, but it's certainly one of the basic tools.
(An interesting element of this particular heterodox view is that this enables one to imagine a "pure functional language" that is not based on total immutability, though whether that's a good idea is another question.)
Record dot syntax is very unappealing. Would way prefer automatic lenses, or just some principled way to qualify using the constructor or equivalent. And take your hands off my compose operator tyvm!