Я нашел документация немного неясна; должен был выяснить, что различные вещи Control.Lens.TH сделали путем экспериментов.
Что вы хотите 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)
Тогда в 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
Так что это на самом деле сделано объявлен класс HasCapacity s a
, где мощность является Lens' от х к (a фиксируется, как только s известно). Он выяснил название «capcity», сняв с поля (с нижней) имя типа данных из поля; Мне приятно не использовать знак подчеркивания ни имени поля, ни имени объектива, поскольку иногда синтаксис записи на самом деле является тем, что вы хотите. Вы можете использовать makeFieldsWith и различные lensRules, чтобы иметь несколько разных опций для расчета имен объективов.
В случае помогает, используя 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.
Так первый splace сделал класс HasCapcity и добавил экземпляр для Foo; второй использовал существующий класс и сделал экземпляр для Bar.
Это также работает, если вы импортируете класс HasCapcity из другого модуля; makeFields может добавлять дополнительные экземпляры в существующий класс и распространять ваши типы через несколько модулей. Но если вы снова используете его в другом модуле, где у вас нет, он импортирует класс, он сделает новый класс (с тем же именем), и у вас будет две отдельные перегруженные capacity
линзы, которые несовместимы ,
makeClassy немного отличается. Если бы я был:
data Foo
= Foo { _capacity :: Int }
deriving (Eq, Show)
$(makeClassy ''Foo)
(заметив, что makeClassy предпочитает вас иметь подчеркивание префикс на полях, а не имя типа данных)
Затем, снова используя -ddump-сращивания:
[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.
Класс, который он создал, это HasFoo, а не HasCapacity; он говорит, что что-либо из чего-либо, где вы можете получить Foo, вы также можете получить способность Foo. И класс жестких кодов, что capcity является Int
, а не перегружать его, как у вас с makeFields
. Так что это все еще работает (потому что HasFoo Foo
, где вы просто получить Foo с помощью id
):
*Foo
λ let f = Foo 3
|
f :: Foo
*Foo
λ f ^. capacity
3
it :: Int
Но вы не можете использовать этот объектив capcity
также получить способность неродственного типа.
Да, я иду по этой дороге, но было бы здорово, если бы библиотека шаблонов сделала это для меня? Код, который я не пишу, это код, который мне не нужно отлаживать. –