I'm reading a tutorial on lenses and, in the introduction, the author motivates the lens
concept by showing a few examples of how we might implement OOP-style "setter"/"getter" using standard Haskell. I'm confused by the following example.
Let's say we define a User
algebraic data types as per Figure 1 (below). The tutorial states (correctly) that we can implement "setter" functionality via the NaiveLens
data type and the nameLens
function (also in Figure 1). An example usage is given in Figure 2.
I'm perplexed as to why we need such an elaborate construct (i.e., a NaiveLens
datatype and a nameLens
function) in order to implement "setter" functionality, when the following (somewhat obvious) function seems to do the job equally well: set' a s = s {name = a}
.
HOWEVER, given that my "obvious" function is none other than the lambda function that's part of nameLens
, I suspect there is indeed an advantage to using the construct below but that I'm too dense to see what that advantage is. Am hoping one of the Haskell wizards can help me understand.
Figure 1 (definitions):
data User = User { name :: String
, age :: Int
} deriving Show
data NaiveLens s a = NaiveLens { view :: s -> a
, set :: a -> s -> s
}
nameLens :: NaiveLens User String
nameLens = NaiveLens name (\a s -> s {name = a})
Figure 2 (example usage):
λ: let john = User {name="John",age=30}
john :: User
λ: set nameLens "Bob" john
User {name = "Bob", age = 30}
it :: User
Say you added an
Email
data type:And added this as a field to your
User
type:The real power of lenses comes from being able to compose them, but this is a bit of a tricky step. We would like some function that looks like
For an explanation of how this was written, I'll defer to this post, where I shamelessly lifted it from. The resulting
set
field of this new lens can be thought of as taking a new value and a top-level record, looking up the lower record and setting its value toc
, then setting that new record for the top-level record.Now we have a convenient function for composing our lenses:
I've used
...
as the composition operator here because I think it's rather easy to type and still is similar to the.
operator. This now gives us a way to drill down into a structure, getting and setting values fairly arbitrarily. If we had adomain
lens written similarly, we could get and set that value in much the same way. This is what makes it look like it's OOP member access, even when it's simply fancy function composition.If you look at the lens library (my choice for lenses), you get some nice tools to automatically build the lenses for you using template haskell, and there's some extra stuff going on behind the scenes that lets you use the normal function composition operator
.
instead of a custom one.