This question is regarding Edward A. Kmett's lens package (version 4.13)
I have a number of different data
types all of which have a field that denotes the maximum number of elements contained (a business rule subject to run time change, not a collection implementation issue.) I would like to call this field capacity
in all cases, but I quickly run into namespace conflicts.
I see in the lens
documentation that there is a makeClassy
template, but I cannot find documentation for it that I con understand. Will this template function allow me to have multiple lenses with the same field name?
EDITED:
Let me add that I am quite capable of coding around the problem. I would like to know if makeClassy
will solve the problem.
I found the documentation a bit unclear too; had to figure out what the various things Control.Lens.TH did by experimentation.
What you want is makeFields
:
{-# LANGUAGE FunctionalDependencies
, MultiParamTypeClasses
, TemplateHaskell
#-}
module Foo
where
import Control.Lens
data Foo
= Foo { fooCapacity :: Int }
deriving (Eq, Show)
$(makeFields ''Foo)
data Bar
= Bar { barCapacity :: Double }
deriving (Eq, Show)
$(makeFields ''Bar)
Then in ghci:
*Foo
λ let f = Foo 3
| b = Bar 7
|
b :: Bar
f :: Foo
*Foo
λ fooCapacity f
3
it :: Int
*Foo
λ barCapacity b
7.0
it :: Double
*Foo
λ f ^. capacity
3
it :: Int
*Foo
λ b ^. capacity
7.0
it :: Double
λ :info HasCapacity
class HasCapacity s a | s -> a where
capacity :: Lens' s a
-- Defined at Foo.hs:14:3
instance HasCapacity Foo Int -- Defined at Foo.hs:14:3
instance HasCapacity Bar Double -- Defined at Foo.hs:19:3
So what it's actually done is declared a class HasCapacity s a
, where capacity is a Lens'
from s
to a
(a
is fixed once s
is known). It figured out the name capacity
by stripping off the (lowercased) name of the data type from the field; I find it pleasant not to have to use an underscore on either the field name or the lens name, since sometimes record syntax is actually what you want. You can use makeFieldsWith
and the various lensRules
to have some different options for calculating the lens names.
In case it helps, using ghci -ddump-splices Foo.hs
:
[1 of 1] Compiling Foo ( Foo.hs, interpreted )
Foo.hs:14:3-18: Splicing declarations
makeFields ''Foo
======>
class HasCapacity s a | s -> a where
capacity :: Lens' s a
instance HasCapacity Foo Int where
{-# INLINE capacity #-}
capacity = iso (\ (Foo x_a7fG) -> x_a7fG) Foo
Foo.hs:19:3-18: Splicing declarations
makeFields ''Bar
======>
instance HasCapacity Bar Double where
{-# INLINE capacity #-}
capacity = iso (\ (Bar x_a7ne) -> x_a7ne) Bar
Ok, modules loaded: Foo.
So the first splice made the class HasCapacity
and added an instance for Foo
; the second used the existing class and made an instance for Bar
.
This also works if you import the HasCapacity
class from another module; makeFields
can add more instances to the existing class and spread your types out across multiple modules. But if you use it again in another module where you haven't imported the class, it'll make a new class (with the same name), and you'll have two separate overloaded capacity
lenses that are not compatible.
makeClassy
is a bit different. If I had:
data Foo
= Foo { _capacity :: Int }
deriving (Eq, Show)
$(makeClassy ''Foo)
(noticing that makeClassy
prefers you to have an underscore prefix on the fields, rather than the data type name)
Then, again using -ddump-splices
:
[1 of 1] Compiling Foo ( Foo.hs, interpreted )
Foo.hs:14:3-18: Splicing declarations
makeClassy ''Foo
======>
class HasFoo c_a85j where
foo :: Lens' c_a85j Foo
capacity :: Lens' c_a85j Int
{-# INLINE capacity #-}
capacity = (.) foo capacity
instance HasFoo Foo where
{-# INLINE capacity #-}
foo = id
capacity = iso (\ (Foo x_a85k) -> x_a85k) Foo
Ok, modules loaded: Foo.
The class it's created is HasFoo
, rather than HasCapacity
; it's saying that anything from anything where you can get a Foo you can also get the capacity of the Foo
. And the class hard-codes that the capacity
is an Int
, rather than overloading it as you had with makeFields
. So this still works (because HasFoo Foo
, where you just get the Foo
by using id
):
*Foo
λ let f = Foo 3
|
f :: Foo
*Foo
λ f ^. capacity
3
it :: Int
But you can't use this capacity
lens to also get the capacity of an unrelated type.