haskell 101|派生关键字是什么意思?

zbwhf8kr  于 2022-11-14  发布在  其他
关注(0)|答案(1)|浏览(180)

实际上,我正在尝试实现一个自定义数据类型,它是string类的子集(例如,这样我就可以对错误输入进行一些类型检查)。但我一辈子都找不到任何地方来解释每个类型关键字的含义。根据以下内容:Haskell Docs这是7个基本的:Eq,Ord,Enum,Ix,Bounded,Read,和Show。除了打印语句需要的Show和布尔风格比较检查需要的Eq,我不完全确定其他5个,谷歌也没有提供太多帮助。所以我希望你们能给我一些启发,也许能给我指出正确的方向。
于是疑问道:
1.这7个基本派生词是什么/添加它们有什么作用?
1.有没有办法在ghci中运行类似derives Stringderives "abc"的东西?
下面是我正在编写的代码。实际上我刚刚创建了Card数据类型,正如你所看到的,它只是一个字符串,用于执行一些更严格的参数检查。我正在尝试将它传递给match,以匹配之前接受的3个字符串作为参数但是为了使用字符串操作语法(比如将其分解为一个字符列表),我认为我需要确定适当的derive关键字,以便它以这种方式工作。

match :: Card -> Card -> Card -> Bool
match [] [] [] = True
match [a] [b] [c] = (a == b && b == c)
  || (a /= b && b /= c && a /= c)
match (a:as) (b:bs) (c:cs) = match [a] [b] [c]
  && match as bs cs

data Card = Card String
            deriving (Show,Eq)
card :: String -> Card
card x | length x /= 4 = error "Card input must be 4 characters."
  | (x!!0 /= 'o') && (x!!0 /= 's') && (x!!0 /= 'd') = error "char 0 (shape) must be o s or d."
  | (x!!1 /= '1') && (x!!1 /= '2') && (x!!1 /= '3') = error "char 1 (number) must be 1 2 or 3."
  | (x!!2 /= 'r') && (x!!2 /= 'p') && (x!!2 /= 'g') = error "char 2 (color) must be r p or g."
  | (x!!3 /= 'f') && (x!!3 /= 's') && (x!!3 /= 'o') = error "char 3 (shade) must be f s or o."
  | otherwise = Card x

(更新示例1)

data Card = Card Shape Number Color Shade deriving (Show, Eq)

data Shape = Oval | Squiggle | Diamond deriving Eq
instance Show Shape where
    show Oval     = "Oval"
    show Squiggle = "Squiggle"
    show Diamond  = "Diamond"

data Number = One | Two | Three deriving Eq
instance Show Number where
    show One   = "One"
    show Two   = "Two"
    show Three = "Three"

data Color = Red | Pink | Green deriving Eq
instance Show Color where
    show Red   = "Red"
    show Pink  = "Pink"
    show Green = "Green"

data Shade = Full | Half | Empty deriving Eq
instance Show Shade where
    show Full  = "Full"
    show Half  = "Half"
    show Empty = "Empty"

shape :: Char -> Either ShapeError Shape
shape 'o' = Right Oval
shape 's' = Right Squiggle
shape 'd' = Right Diamond
shape x   = Left (NotOSD x)

number :: Char -> Either NumberError Number
number '1' = Right One
number '2' = Right Two
number '3' = Right Three
number x   = Left (Not123 x)

color :: Char -> Either ColorError Color
color 'r' = Right Red
color 'p' = Right Pink
color 'g' = Right Green
color x   = Left (NotRPG x)

shade :: Char -> Either ShadeError Shade
shade 'f' = Right Full
shade 's' = Right Half
shade 'o' = Right Empty
shade x   = Left (NotFSO x)

card :: String -> Either SetError Card
card [shp, n, c, shd] = Card <$> shape shp <*> number n <*> color c <*> shade shd
card x = Left (NotCard x)

data CardError = NotCard String
instance Show CardError where
    show (NotCard x) = "Card must be 4 characters long, not " ++ show x

data SetError = CardError | ShapeError | NumberError | ColorError | ShadeError deriving Eq

data ShapeError = NotOSD Char
instance Show ShapeError where
    show (NotOSD x) = "Shape must be o, s, or d, not " ++ show x

data NumberError = Not123 Char
instance Show NumberError where
    show (Not123 x) = "Number must be 1, 2, or 3, not " ++ show x

data ColorError = NotRPG Char
instance Show ColorError where
    show (NotRPG x) = "Color must be r, p, or g, not " ++ show x

data ShadeError = NotFSO Char
instance Show ShadeError where
    show (NotFSO x) = "Shade must be f, s, or o, not " ++ show x

当前错误

match.hs:84:34:
    Couldn't match type ‘ShapeError’ with ‘SetError’
    Expected type: Either SetError Shape
      Actual type: Either ShapeError Shape
    In the second argument of ‘(<$>)’, namely ‘shape shp’
    In the first argument of ‘(<*>)’, namely ‘Card <$> shape shp’

match.hs:84:48:
    Couldn't match type ‘NumberError’ with ‘SetError’
    Expected type: Either SetError Number
      Actual type: Either NumberError Number
    In the second argument of ‘(<*>)’, namely ‘number n’
    In the first argument of ‘(<*>)’, namely
      ‘Card <$> shape shp <*> number n’

match.hs:84:61:
    Couldn't match type ‘ColorError’ with ‘SetError’
    Expected type: Either SetError Color
      Actual type: Either ColorError Color
    In the second argument of ‘(<*>)’, namely ‘color c’
    In the first argument of ‘(<*>)’, namely
      ‘Card <$> shape shp <*> number n <*> color c’

match.hs:84:73:
    Couldn't match type ‘ShadeError’ with ‘SetError’
    Expected type: Either SetError Shade
      Actual type: Either ShadeError Shade
    In the second argument of ‘(<*>)’, namely ‘shade shd’
    In the expression:
      Card <$> shape shp <*> number n <*> color c <*> shade shd

match.hs:85:16:
    Couldn't match expected type ‘SetError’
                with actual type ‘CardError’
    In the first argument of ‘Left’, namely ‘(NotCard x)’
    In the expression: Left (NotCard x)
Failed, modules loaded: none.
dxxyhpgq

dxxyhpgq1#

在编写Haskell时,不要害怕定义大量的类型和小函数。小函数更容易理解,并且可以用各种方式组合来定义更大的函数。

您是否关心 * 类型类 *...

为了让您的问题马上解决,Show示例仅用于从某个值生成StringEq用于比较两个具有==的相同类型的值。

  • Read
  • Ord
  • Enum
  • Bounded

Read类似于Show的逆函数,但通常不使用。(下面显示的解析器比Read示例更容易编写,并且您将无法使用派生的Read示例来定义大多数类型。)其他三个解析器可能与程序的其他部分相关,但与您所问的任何代码无关。
派生的Show示例基本上只是将数据构造函数的名称转换为字符串,但为了保持良好的风格,该字符串应该是有效的Haskell代码。派生的Eq示例只是检查两个数据构造函数是否相同。带参数的数据构造函数的定义是递归派生的:将使用"Card"show x来定义show (Card x),而将Card c1 == Card c2定义为c1 == c2
不过,您的大部分问题都是关于模式匹配的(如注解中所提到的),这与类型类无关。

......还是 * 类型 *?

让我们从你的四个基本概念开始:形状、数字、颜色和底纹。为每个类型定义一个单独的类型。下面是Shape的类型示例:

data Shape = ShapeO | ShapeS | ShapeD deriving (Show, Eq)

尽可能为数据构造函数使用描述性名称(O真的是Oval的缩写吗?如果是,请使用Oval),并记住名称必须全局唯一(无论如何,在模块内),因此不能将O同时用于ShapeShade。有时从构造函数名派生的字符串对你的Show示例来说就足够了,有时就不够了。如果不够的话,不要定义一个Show示例,而是编写你自己的Thing -> String函数。
是派生Show示例还是手动编写它可能取决于您选择的数据构造函数名称以及您 * 希望 * 从值中得到什么字符串。派生的Eq示例几乎肯定是足够的,因为它们不依赖于您用于构造函数的名称。
(As顺便说一句,Number比较棘手,因为不能使用123作为数据构造函数名(除非需要对数字进行数学运算,否则可以考虑使用data Number = One | Two | Three之类的名称,而不是过于宽泛的data Number = Number Int)。
现在,您的Card类型可以是四个属性类型的简单组合,而不仅仅是原始字符串的 Package 器。

data Card = Card Shape Number Color Shade deriving (Show, Eq)

(But对于Show,任何具有自己的字符串转换函数的属性都可能迫使您编写自己的Card -> String函数,而不是依赖Show示例。)

正在解析:字符串到值

你会希望函数能将字符解析成一个合适的值,而不仅仅是检查它是否能代表一个合适的值。('o'代表ShapeO),有时候它不是('x'不代表Shape)。所以你的解析器应该考虑到这一点,但不是简单地用error引发异常。相反,让返回类型告诉调用者发生了什么,并让 * 他们 * 决定程序是否需要被终止。2例如,

shape :: Char -> Either String Shape
shape 'o' = Right ShapeO
shape 's' = Right ShapeS
shape 'd' = Right ShapeD
shape x = Left $ "Shape must be o, s, or d, got ++ show x

shape返回RightLeft值这一事实通常足以让调用方决定下一步要做什么;由Left Package 的错误消息更多地用于向用户报告错误。
一旦您拥有了各个属性的所有解析器,那么Card的解析器就变得非常简单了,这要归功于Either String类型的Applicative示例。

card :: String -> Either String Card
card [shp, n, c, shd] = Card <$> shape shp <*> number n <*> color c <*> shade shd
card x = Left $ "Card must e 4 characters long, got " ++ show x

如果有任何属性产生Left值,card会传回第一个无法剖析之属性的Left值。只有当输入字串刚好有4个正确的字符时,char才会产生以Right Package 的Card值。
您可能还决定定义专用的错误类型,而不是使用任意字符串。

data ShapeError = NotOSD Char
errorMessage :: ShapeError -> String    
errorMessage (NotOSD x) = "Shape must be o, s, or d, not " ++ show x

然后

shape :: String -> Either ShapeError Shape
shape 'o' = Right ShapeO
shape 's' = Right ShapeS
shape 'd' = Right ShapeD
shape x = Left (NotOSD x)

基本上,捕获错误类型中有关错误的 * 信息 *,并让其Show示例生成一个人类可读的字符串 *,其中 * 包含该信息(如果需要)。
如果各部分相等,则各部分相等
match同样是平凡的,它利用了为Card派生的Eq示例,而Card是从所有属性类型的Eq示例派生的。

match :: Card -> Card -> Card -> Bool
match c1 c2 c3 = c1 == c2 && c2 == c3 || (c1 /= c2 && c2 /= c3 && c3 /= c1)

相关问题