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 "capcity" 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 splace made the class HasCapcity 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 HasCapcity 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 capcity 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 capcity
lens to also get the capacity of an unrelated type.
The templates are optional; you can always make your own classes and lenses.
class Capacitor s where
capacitance :: Lens' s Int
Now any type with a capacity can be made an instance of this class.
An alternative approach is to factor out the capacity:
data Luggage a = Luggage { clothes :: a, capacity :: !Int }
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With