Пакет линз с алгебраическими типами


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

import Control.Lens

data Type = A { _a :: Char } | B

makeLenses ''Type

test1 = _a (A 'a')
test2 = (A 'a') ^. a

No instance for (Data.Monoid.Monoid Char)
  arising from a use of `a'
Possible fix:
  add an instance declaration for (Data.Monoid.Monoid Char)
In the second argument of `(^.)', namely `a'
In the expression: (A 'a') ^. a
In an equation for `test2': test2 = (A 'a') ^. a
Я мог бы просто пойти с _a, но тип данных в моей реальной программе гораздо глубже, и я вроде как намеревался использовать lens, чтобы уменьшить объем работы, которую мне нужно сделать. Я просматривал библиотеку линз, но там так много всего, и я не уверен, имеет ли он дело с этим сценарием или это просто что-то, чего нет в библиотеке линз поддержка.

В качестве примечания, если я действительно использую моноидную строку для типа данных вместо Char, он затем компилируется и дает правильный ответ, я понятия не имею, почему.

Edit: прочитав комментарий хаммара, я попробовал это, и это работает:

test2 = (A 'a') ^? a
test3 = B ^? a

Но это довольно странно-получить "может быть" из этого для чего-то, что должно существовать.

1 13

1 ответ:

Просто чтобы ответить на этот вопрос, моя проблема заключалась в том, что у меня был алгебраический тип, где некоторые поля были общими между различными конструкторами, но была пара полей, которые не были разделены, умрут во время выполнения, если я попытаюсь их использовать.

data Exercise =
  BarbellExercise {
    name   :: String,
    weight :: Int,
    reps   :: Int
  } |
  BodyWeightExercise {
    name   :: String,
    reps   :: Int
  }

exer1 = BarbellExercise "Squats" 235 15
exer2 = BarbellExercise "Deadlifts" 265 15
exer3 = BodyWeightExercise "Pullups" 12
exer4 = BarbellExercise "Overhead Press" 85 15

workout = [exer1, exer2, exer3, exer4]

test = do
  mapM_ displayExercise workout

  where
    displayExercise x = putStrLn $ "Exercise: " ++ (name x) ++ " You must perform " ++ (show $ reps x) ++ "@" ++ (show $ weight x)

Это компилируется, но умирает во время выполнения, если я делаю ошибку, используя весовую функцию. Понятная ошибка. Когда Lens использует шаблон haskell для создания экземпляров, он замечает это и изменяет свое поведение, чтобы предотвратить ошибку. Вы можно было бы удалить методы доступа к полю, но в моем случае большинство полей были одинаковыми между типами данных. Вот как я должен был написать тип данных, как только я заметил, что поля не совпадают:

data Exercise =
  BarbellExercise
   String -- ^ name
   Int    -- ^ reps
   Int    -- ^ weight
     |
  BodyWeightExercise
    String -- ^ name
    Int    -- reps


name :: Exercise -> String
name (BarbellExercise n _ _) = n
name (BodyWeightExercise n _) = n

reps :: Exercise -> Int
reps (BarbellExercise _ r _) = r
reps (BodyWeightExercise _ r) = r

Делая это таким образом, хотя это немного менее чисто, ошибка будет поймана во время компиляции. Заставляя меня писать функции самостоятельно, я замечал бы любые частичные функции, когда я их писал.

Я бы хотел, чтобы ghc предупредил меня. Похоже, так оно и будет на самом деле легко для него обнаружить такую вещь.