这就是我想要做的:
INPUT: [1,2,3,-1,-2,-3]
OUTPUT:[1,1,1,-1,-1,-1]
我试过这个:
signNum (x:n) = map(if x>0
then 1
else -1)n
谁能告诉我我在哪里犯了逻辑错误?
第一个问题是map
需要一个函数。因此,您必须将if
语句包装在 lambda 中。但是,这仍然不会完全符合您的要求。而不是将列表分成头部和尾部,您真的希望将您的函数映射到整个列表。
请记住,map
它只需要一个函数并将其应用于每个元素。由于您想将每个元素转换为1
or -1
,您只需要将适当的函数映射到您的列表。
所以最后,你得到:
sigNum ls = map (\ x -> if x > 0 then 1 else - 1) ls
在这种情况下,将函数分解为更小的部分可能更容易。
在最低级别,可以计算signum
单个数字的 ,即:
signum :: (Num a, Ord a) => a -> a
signum x = if x > 0 then 1 else -1
一旦你有了这个,你就可以在一个数字列表中使用它,就像你对任何函数一样:
signNum ls = map signum ls
(ps 是什么signum 0
意思?您当前的定义有signum 0 = -1
.
如果您需要扩展功能以包含这种情况,最好使用警卫:
signum x | x < 0 = -1
| x == 0 = 0
| otherwise = 1
或案例陈述:
signum x = case compare x 0 of
LT -> -1
EQ -> 0
GT -> 1
)
您的评论表明您希望能够通过理解来做到这一点。
如果你确实想通过理解来做到这一点,你可以这样做
signNum ls = [ if x>0 then 1 else -1| x <- ls ]
...但你不能把条件放在右手边
brokenSignNum ls = [ 1| x <- ls, x > 0 ]
因为在右侧放置一个条件会删除任何不满足条件的东西 - 你所有的否定都会被忽略!这将缩短您的列表而不是替换元素。我们试试看
brokenSignNum2 ls = [ 1| x <- ls, x > 0 ] ++ [ -1| x <- ls, x <= 0 ]
这与您的原始列表长度相同,但所有正面都在前面。
摘要:您必须将此条件表达式放在左侧,因为这是唯一可以发生替换的地方 - 在右侧它会删除。
请注意,您的 if 语句将 0 视为负数。你确定你想要那个吗?也许你会更好地单独定义一个数字的符号:
sign x | x == 0 = 0 -- if x is zero, use zero
| x > 0 = 1 -- use 1 for positives
| x < 0 = -1 -- use -1 for negatives
workingSignNum1 ls = [sign x | x <- ls]
但是sign
(几乎)与函数相同signum
,所以我们不妨使用它
workingSignNum2 ls = [signum x | x <- ls]
现在,对于基本上意味着“替换x
为sign x
所有列表ls
”的大量语法。我们经常做这种事情,所以我们可以写一个函数来做:
replaceUsing :: (a -> b) -> [a] -> [b]
replaceUsing f xs = [f x | x <- xs]
但是已经有一个功能可以做到这一点!它被称为map
。所以我们可以在列表中使用 map :
quiteSlickSignNum :: Num a => [a] -> [a]
quiteSlickSignNum ls = map signum ls
甚至更光滑:
slickSignNum :: Num a => [a] -> [a]
slickSignNum = map signum
这就是我定义它的方式。
sign
几乎一样signum
?sign
接受一个数字并返回一个数字 , 1
, 0
or -1
,但是 的类型是1
什么?
好吧,1
具有类型Num a => a
,因此您可以将其与任何数字类型一起使用。这意味着
sign
接受任何类型的数字并返回任何类型的数字,所以它的类型是
sign :: (Num a,Num b) => a -> b
所以我的版本sign
可以给你一个不同的类型。如果你尝试一下,你会发现3 * sign 4.5
给你3
,不是3.0
,所以你可以从中得到一个整数,但如果你这样做3.14 * sign 7.4
,你会得到3.14
,所以你也可以得到一个小数类型。相比之下,
signum :: Num a => a -> a
所以它只能把你给它的类型还给你——3 * signum 4.5
给你3.0
。
错误消息“no instance for Num
”是新 Haskelers 最难破译的错误消息之一。首先,这是您尝试编写的函数的完全多态类型签名(我将其添加到源文件中以得到与您相同的错误):
signNum :: (Ord a, Num a) => [a] -> [a]
发现错误
现在,编译错误消息说:
无法从 prog.hs:3:17 处的文字 '1' 产生的上下文 (Ord a, Num a) 推断出 (Num (a -> a))
请注意,错误消息为我们提供了问题的位置。它说“文字 1”file_name.hs:line_number:column_number
是问题所在。
signNum (x:n) = map(if x>0
then 1 -- <-- here's the problem! (according to that message)
else -1)n
了解错误
现在,错误消息还建议了一些可能的修复,但是每当您遇到“没有实例Num
”时,建议的“可能修复”几乎总是错误的,因此请忽略它们。(我希望 GHC 能够为Num
这样的相关内容提供更好的错误消息)。
回想一下错误消息所说的内容:
无法推断 (Num (a -> a)) ... 源自文字 '1' ...
这意味着您将文字1
放在上下文预期类型的某处
a -> a
。1
显然不是函数,所以要么上下文错误,要么数字 1 错误。
那么字面量 1 的上下文是什么?
找到错误(准确)
(if x > 0
then <<hole>>
else -1)
Haskell 中的 if 语句产生一个值。if 语句的分支必须具有相同的类型,if 语句的类型由分支的类型决定。
在这里,另一个分支的值为-1
,它是一个数字。因此,我们期望<<hole>>
具有相同的类型:数字。好吧,这显然不是问题(因为 1是一个数字),所以让我们看一下该表达式的上下文。
map <<hole>> n
该map
函数需要一个函数作为它的第一个参数。但是,我们知道<<hole>>
会产生一个数字。尤里卡!这是差异:我们map
在它期望函数的地方给出了一个数字。
纠正错误
显而易见的解决方案——现在我们确切地知道问题出在哪里和在哪里——是给出map
一个函数,而不是一个数字。有关详细信息,请参阅其他各种答案。