Как получить классные линзы с перегруженными именами полей?

Я пытаюсь создать линзы для записей с одинаковыми именами полей. Наряду с этим я пытаюсь «обернуть/расширить» эти базовые записи и хочу, чтобы те же имена полей работали для обернутых/расширенных записей (что, как я полагаю, делают стильные линзы). Как заставить работать следующее:

-- Data types for context of the code snippet below

data Download = Download {
  userId :: UserId
  ,gid :: Gid
  ,logId :: LogId
  ,parentId :: Maybe DownloadId
  ,createdAt :: UTCTime
  ,updatedAt :: UTCTime
}

data File = File {
  downloadId :: DownloadId
  ,fpath :: String
  ,len :: Int
  ,createdAt :: UTCTime
  ,updatedAt :: UTCTime
}

data Url = Url {
  downloadId :: DownloadId
  ,fileId :: FileId
  ,url :: URL
  ,createdAt :: UTCTime
  ,updatedAt :: UTCTime
}

data DownloadObject = DownloadObject {
  _key :: DownloadId
  ,_dbDownload :: Download
  ,_dbFiles :: [FileObjects]
}

data FileObject = FileObject {
  _key :: FileId
  ,_dbFile :: File,
  ,_dbUrls :: [UrlObjects]
}

data UrlObject = UrlObject {
  _key :: UrlId
  ,_dbUrl :: Url
}

fetchDownload :: DownloadId -> DownloadObject

Учитывая эти типы данных, как мне заставить работать следующие линзы:

dload <- fetchDownload dloadId
dload ^. key -- of type DownloadId
dload ^. createdAt -- of type UTCTime
((dload ^. files) !! 1) ^. key -- of type FileId 
((dload ^. files) !! 1) ^. createdAt -- of type UTCTime

person Saurabh Nanda    schedule 12.08.2016    source источник


Ответы (1)


Используйте makeFields из Control.Lens.TH:

{-# language
  TemplateHaskell, MultiParamTypeClasses, FunctionalDependencies, FlexibleInstances #-}

import Control.Lens

data Bar a n = Bar {
  _barAge :: a,
  _barName :: n
} deriving Show

data Foo = Foo {
  _fooAge  :: Int,
  _fooName :: String
} deriving Show

makeFields ''Bar
makeFields ''Foo

Теперь у вас есть следующие классы:

class HasName s a | s -> a where
  name :: Lens' s a

class HasAge s a | s -> a where
  age :: Lens' s a

и соответствующие экземпляры для Foo и Bar. Пример:

> Foo 10 "foo" ^. age
10
> Bar 10 "bar" ^. age
10

Затем вы можете реализовать классы для объектов-оболочек. Пример:

data FooWrapper = FooWrapper {
  _fooWrapperKey :: Int,
  _fooWrapperFoo :: Foo
} deriving Show

makeFields ''FooWrapper

instance HasName FooWrapper String where
  name = foo . name

instance HasAge FooWrapper Int where
  age = foo . age
person András Kovács    schedule 12.08.2016
comment
Спасибо за ответ. Я думаю, вы упустили важную часть вопроса. Я хочу, чтобы линзы работали с обернутыми/расширенными записями, то есть DownloadObject, FileObject и UrlObject, НЕ Download, File и Url - person Saurabh Nanda; 12.08.2016
comment
Есть ли другой способ написать это без необходимости самостоятельно писать все шаблоны, связанные с объективом (экземпляры HasName HasAge и т. д.)? - person Saurabh Nanda; 12.08.2016
comment
Ужасный ответ: вы можете использовать Template Haskell, чтобы автоматизировать большую часть этого. API и документация для него сложны, но это выполнимо. Насколько мне известно, в библиотеке объективов нет способа сделать это. - person hao; 12.08.2016
comment
ах... как я скучаю по метапрограммированию Ruby и макросам Lisp! - person Saurabh Nanda; 12.08.2016