Шаблон Haskell с именем поля записи в качестве переменной?

У меня есть следующий фрагмент кода, реализующий монаду. Я пытаюсь использовать его для упрощения настройки полей с более сложной логикой в ​​дальнейшем.

data Rec = Rec {
    alpha :: Int,
    beta  :: Double,
} deriving (Show)
defaultRec = Rec 0 0 0

data Record r = Record { runRecord :: Rec -> (Rec, r) }
instance Monad Record where
    return r = Record $ \s -> (s, r)
    a >>= b  = Record $ \s -> let (q, r) = runRecord a s in runRecord (b r) q

createRecord f = fst $ runRecord f defaultRec

changeAlpha x  = Record $ \s -> (s { alpha = x }, ())

Я бы использовал такой код:

myRecord = createRecord (changeAlpha 9)

Этот код работает, но я бы хотел использовать Template Haskell для упрощения функции changeAlpha. Было бы здорово иметь что-то вроде этого:

changeBeta x = $(makeChange beta) x

Теперь я дошел до этого:

changeBeta x = Record $ $([| \z -> \s -> (s { beta = z }, ()) |]) x

Но как только я изменю его на это:

changeBeta f x = Record $ $([| \z -> \s -> (s { f = z }, ()) |]) x

Я получаю это:

TestTH.hs:21:49: `f' is not a (visible) constructor field name

Никакие вариации не работают. Это возможно?


person Ana    schedule 12.12.2011    source источник
comment
Разве $([|x|]) не просто x? то есть $() и [||] немедленно отменяют друг друга. Итак, ваша последняя рабочая версия changeBeta это просто changeBeta x = Record $ \s -> (s { beta = z }, ())   -  person pat    schedule 31.12.2011


Ответы (3)


Проблема в том, что вы можете соединять только типы, выражения или списки объявлений. Метка поля записи не является ни тем, ни другим, поэтому вам придется использовать комбинаторы TH, чтобы создать выражение типа Q Exp, а затем соединить его, хотя вы все равно можете использовать оксфордские скобки для остальных частей:

makeChange :: Name -> Q Exp
makeChange x = [|
    \z -> Record $ \s -> ( $(recUpdE [| s |] [fieldExp x [| z |]]), () ) |]

Чтобы использовать его, вам нужно будет указать имя поля, которое вы хотите изменить:

changeBeta :: Double -> Record ()
changeBeta x = $(makeChange 'beta) x
person hammar    schedule 12.12.2011

Разве вы не заново изобретаете монаду и линзы State(T)?

http://hackage.haskell.org/packages/archive/data-lens/2.0.2/doc/html/Data-Lens-Strict.html

Существует код шаблона линзы данных, который генерирует линзы для каждой «записи», начиная с _ в типе.

person Marek Sieradzki    schedule 12.12.2011

Я думаю, что f это просто имя; вам нужно «раскавычить» его, так как он из среды, а не просто внутри скобок кавычки [| |]. Я нашел пример здесь [ ссылка, см. раздел "10.1.1 Выбор из кортежа" ]. В кавычках используется $, поэтому я думаю, что ваш окончательный код будет выглядеть примерно так:

changeField f = [| \z s -> s { $(varE f) = z } |]

К сожалению, моя версия ghc (7.0.3), кажется, жалуется на ошибку $. (Это дает ошибку синтаксического анализа.) Надеюсь, что этот вопрос привлечет больше внимания, мне кажется, это хорошо (хотя, возможно, удалите несвязанную часть монады).

person gatoatigrado    schedule 12.12.2011