为什么我需要在我的Haskell代码中定义这两个变量?
-- file: ch03/BogusPattern.hs
data Fruit = Apple | Orange
deriving (Show)
apple = "apple"
orange = "orange"
whichFruit :: String -> Fruit
whichFruit f = case f of
"apple" -> Apple
"orange" -> Orange
在这段代码为什么我需要为什么我需要在我的Haskell代码中定义这两个变量?
apple = "apple"
orange = "orange"
?
而且我想这:
-- file: ch03/BogusPattern.hs
data Fruit = Apple | Orange
deriving (Show)
apple = "f1"
orange = "f2"
whichFruit :: String -> Fruit
whichFruit f = case f of
"f1" -> Apple
"f2" -> Orange
,并没有奏效。
Ghci表示f1
和f2
不在范围内。
whichFruit
函数不应该尝试将f
匹配到两个字符串中,并返回果实类型的基础?
谢谢。
你不需要苹果和橙色变量。哪个水果需要一个字符串。在您第一次尝试时,您可以拨打whichFruit "orange"
,它将匹配第二种情况(“橙色”)。在你的第二个例子中,你需要使用whichFruit "f2"
来完成同样的事情。
您也可以定义x = "orange"
并致电whichFruit x
以获得橙色。
拉斐尔的回答是完全正确的,但我想添加一些我认为相关的观点。
首先,更概念的东西。调用orange
和banana
变量并不完全正确。相反,它们是常量函数或仅仅是String
类型的常量。这是因为你实际上不能改变他们的价值观(因此,我们称之为不变性)。关于你的需求问题,我不确定,但可能这个例子的想法很简单,就是定义两个不同的“成果”,这样人们就可以在GHCi(或其他任何地方)通过apple
和whichFruit
以及orange
而不是字符串文字“apple”和“orange”。这就是说,尽管是一个简单的例子来说明非常基本的功能,但值得一提的是,不建议(函数)定义函数whichFruit
的方式。此功能仅适用于两种不同的输入:“苹果”和“橙色”。除此之外的任何内容都会导致whichFruit
意外失败。由于whichFruit
仅为其域的子集(String
)定义,因此称为部分函数。换句话说,类型签名告诉我们whichFruit
是一个函数,期望String
并返回一个Fruit
,但是当它被称为“香蕉”时,它会给出错误。想象一下,有人导入你的模块(它定义了whichFruit
)并开始使用它。在浏览GHCi的功能时,他发现whichFruit
并使用:t whichFruit
检查其类型签名。这很可能是他会想到whichFruit
与任何字符串工作,但是当他打电话whichFruit “banana”
:
*** Exception: BogusPattern.hs:(11,16)-(13,34): Non-exhaustive patterns in case
这是非常糟糕的,对不对?通过直面的是,人们可能会想到这个问题只与更加明确什么该函数需要作为参数,并试图只是改变代码看起来像:
type FruitName = String
whichFruit :: FruitName -> Fruit
whichFruit f = case f of
"apple" -> Apple
"orange" -> Orange
虽然使用一种类型的同义词肯定会增加代码的可读性,它肯定不能解决问题,一旦没有任何东西阻止调用者传递whichFruit
传递类似“foo”的东西。正因为如此,在Haskell中,我们努力编写总计(为其域的所有输入定义)的功能,从而产生更健壮的代码。通过简单的设置标志-Wall
在GHCI和加载模块,我们可以看到,即使编译器警告我们有关的缺陷:
λ: :set -Wall
λ: :l BogusPattern
[1 of 1] Compiling BogusPattern (BogusPattern.hs, interpreted)
...
BogusPattern.hs:11:16: Warning:
Pattern match(es) are non-exhaustive
In a case alternative:
Patterns not matched:
[]
(GHC.Types.C# #x) : _ with #x `notElem` ['a', 'o']
[GHC.Types.C# 'a']
(GHC.Types.C# 'a') : ((GHC.Types.C# #x) : _)
with
#x `notElem` ['p']
...
Ok, modules loaded: BogusPattern.
好......那么,我们如何解决这个问题?比较简单的方法是添加默认情况下:
whichFruit' :: FruitName -> Fruit
whichFruit' f = case f of
"apple" -> Apple
_ -> Orange
虽然它的工作原理,其结果可能是不希望(我们当然不希望whichFruit' "banana"
返回Orange
)。作为一种替代解决方案,人们可以以(MIS)添加一个新的价值构造的Fruit
数据类型来表示一个无效的水果和返回,在默认情况下(_
):
data Fruit = Apple | Orange | InvalidFruit
deriving (Show)
该解决方案是不是好原因有很多。从语义上讲,我们预计值构造函数可以构建与相应的类型构造函数的类型相匹配的值。在我们的例子中,我们预计Apple
和Orange
为Fruit
s(这很有意义),但InvalidFruit
应该表示什么意思?此外,事实证明,在Haskell中,我们有更好的方式来表示失败的可能性,也就是说,为了将失败的概念嵌入到whichFruit'
中,我们可以简单地使用例如Maybe
类型来重写它,如下所示:
whichFruit'' :: FruitName -> Maybe Fruit
whichFruit'' f = case f of
"apple" -> Just Apple
"orange" -> Just Orange
_ -> Nothing
这个解决方案看起来好多了,在我看来,它是最优的。唯一的缺点是它增加了“额外的开销”并使调用者函数有点复杂化,调用者函数必须处理带有可能失败的附加上下文(Maybe Fruit
)而不是仅值(Fruit
)的值。作为最后一点,我告诉你,使用这样的解决方案(或相关的解决方案,即:Either String Fruit
)是完全值得的,而且随着你对Haskell更有经验,处理这些“更复杂”的类型变得非常自然,你不会甚至通知。
你打电话给'whichFruit'? – 2014-09-01 04:40:54