2016-01-05 4 views
4

Этот вопрос касается Эдварда А. Kmett в lens package (версия 4.13)Make Линзы (TH) с одноименным полем Имя с помощью makeClassy

У меня есть несколько различных data типов, все из которых имеют поле который обозначает максимальное количество содержащихся элементов (бизнес-правило подвержено изменению времени выполнения, а не проблеме реализации коллекции). Я бы назвал это поле capacity во всех случаях, но я быстро столкнулся с конфликтами пространства имен.

Я вижу в документации lens, что есть шаблон makeClassy, но я не могу найти документацию для него, которую я понимаю. Будет ли эта функция шаблона позволять мне иметь несколько объективов с тем же именем?


EDITED: Позвольте мне добавить, что я вполне способен кодировать вокруг проблему. Я хотел бы знать, если makeClassy будет решить проблема.

ответ

5

Я нашел документация немного неясна; должен был выяснить, что различные вещи 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 также получить способность неродственного типа.

1

Шаблоны не обязательны; вы всегда можете создавать свои собственные классы и линзы.

class Capacitor s where 
    capacitance :: Lens' s Int 

Теперь любой тип с емкостью можно сделать экземпляром этого класса.


Альтернативный подход состоит в факторе вне емкости:

data Luggage a = Luggage { clothes :: a, capacity :: !Int } 
+0

Да, я иду по этой дороге, но было бы здорово, если бы библиотека шаблонов сделала это для меня? Код, который я не пишу, это код, который мне не нужно отлаживать. –

0

Префикса имени поля с подчеркиванием и именем типа данных, а затем использовать makeFields:

data Structure = Structure { _structureCapacity :: Int } 
makeFields ''Structure 

data OtherStructure = OtherStructure { _otherStructureCapacity :: String } 
makeFields ''Structure 
+0

'makeFields '' Structure' не компилируется (даже если вы включили расширение' TypeSynonymInstances'). Причины этого изложены здесь: http://stackoverflow.com/a/8663534/1417275. Я считаю это довольно ограниченным, поскольку я не могу использовать 'makeFields' для любых типов данных с более высокими полями порядка. –