13

我正在使用镜头包进行编码。一切都很顺利,直到我尝试访问代数类型的某个字段:

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,但我的实际程序中的数据类型要深得多,我打算使用镜头来减少我必须做的工作量。我一直在查看镜头库,但那里有很多东西,我不确定他是否处理过这种情况,或者这只是镜头库不支持的东西。

附带说明一下,如果我实际上使用像 String 这样的幺半群而不是 Char 作为数据类型,那么它会编译并给出正确的答案,我不知道为什么。

编辑:在阅读了 hammar 的评论后,我尝试了这个,这很有效:

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

但是对于必须存在的东西来说,从中获得一个可能有点奇怪。

4

1 回答 1

5

为了回答这个问题,我的问题是我有一个代数类型,其中一些字段在不同的构造函数之间是共同的,但是如果我尝试使用它们,有几个不共享的字段会在运行时消失。

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)

如果我错误地使用了权重函数,这会编译但运行时会死掉。可以理解的错误。当镜头使用模板 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 会警告我。似乎真的很容易检测到这样的事情。

于 2013-04-25T13:52:58.607 回答