我开始学习Haskell并找到了一个很好的练习。它是以下内容:
grouping: Int -> [Student]->[(Team, Student)]
grouping teamNumber = zip ys
where ...
所以,练习希望我试着填补其余部分。该功能应该执行以下操作:
示例:grouping 2 ['Mark','Hanna','Robert','Mike','Jimmy'] = [(1,'Mark'),(2,'Hanna'),(1,'Robert'),(2,'Mike'),(1,'Jimmy')]
。
所以,我们正在组建由两名学生组成的团队,以及最后一名学生Jimmy'没有队友。
然后,我还会查找预定义函数zip
的功能。它获取两个列表参数,并将列表的每个元素连接到元组以构建元组列表。
我的想法: 1)我尝试构建两个函数"抓住"和"无限"。他们看起来如下:
grap :: Int -> [a] -> [a]
grab _ [] = []
grab n (x:xs) = if n <= 0 then [] else x : grab (n-1) xs
infinite :: Num a => a -> [a]
infinite x = x : infinite(x+1)
所以,他们做的是:使用infinite
我想创建一个无限列表。 grap
应该采用n
个元素。示例grap 2 (infinite 1) = [1,2]
。
我在的第一行使用这两个 -declaration来完成上面给定的功能。 所以,我有:
grouping: Int -> [Student]->[(Team, Student)]
grouping teamNumber = zip ys
where
xs = grap teamNumber (infinite 1)
因此,xs
现在是我的第一个zip
列表,尤其是整数列表。
但现在我的问题:zip
作为预定义函数还需要第二个列表,尤其是学生名字列表,但在给定函数中,它们只给zip一个参数,即{{1}作为一个列表。我怎么能理解呢?
答案 0 :(得分:4)
仔细查看grouping :: Int -> [Student]->[(Team, Student)]
的类型,以及为其声明声明的参数
grouping :: Int -> [Student]->[(Team, Student)]
grouping teamNumber = ...
如果grouping
提供了等号左侧列出的所有参数,那么返回类型(等号右侧的类型)是什么?
等号右侧的类型为[Student]->[(Team, Student)]
。在Haskell中,一个接受两个参数并返回结果的函数可以等效地看到或定义为一个函数,该函数接受第一个参数并返回一个(接受第二个参数并返回结果的函数)。所以我们可以说,例如,表达式
grouping 3 :: [Student]->[(Team, Student)]
(grouping 3)
是一个函数,它接收学生列表并返回这些学生的列表,标记为3组。据推测,如果将(grouping 3)
应用于您示例中的学生列表,我们就会
(grouping 3) [ 'Mark' , 'Hanna' , 'Robert' , 'Mike' , 'Jimmy' ] =
[(1,'Mark'),(2,'Hanna'),(3,'Robert'),(1,'Mike'),(2,'Jimmy')]
currying与以下类型和表达有什么关系?
zip :: [a] -> [b] -> [(a, b)]
zip ys
zip ys
的类型是什么,例如,ys :: [Bool]
?
这与你的问题有什么关系?
当您将此与grouping teamNumber
的类型一起考虑时,它如何告诉您练习中ys
的类型是什么?
从练习代码(忽略类型和where
子句)我们得到:
grouping teamNumber = zip ys
如果他们的类型将统一,那么Haskell中的两件事只能是=
。在这种情况下,grouping teamNumber
的类型必须与zip ys
的类型统一。
从第一部分开始,我们知道grouping teamNumber
的类型为[Student]->[(Team,Student)]
。
从第二部分开始,我们知道zip ys
的类型为[b] -> [(a, b)]
,其中a
是一种类型,ys
的类型为[a]
。
因此,我们知道(~
在Haskell中是类型相等)
[Student]->[(Team,Student)] ~ [b] -> [(a, b)]
如果我们将以下内容替换为类型变量b
和a
b ~ Student
a ~ Team
现在,我们知道ys
的类型为[a]
,如果我们进行相同的替换,则为[Team]
。
因此,如果ys :: [Team]
,则类型将是正确的。
如果您可以提供ys :: [Team]
,则可以通过将[Student]->[(Team,Student)]
作为第一个参数传递给{{1},从学生到为其团队标记的学生(ys
)生成一项功能}}。这个函数正是zip
在应用于单个参数grouping
时需要返回的函数。
答案 1 :(得分:2)
当您第一次遇到它时,可能会有点混乱。这是交易,几乎(我将忽略一些技术性)。
基本概念是这样的:在Haskell中,每个函数只需要一个参数。如果要模拟带有两个参数的函数,有两种方法可以执行此操作:
你可以编写一个带元组的函数。这是标准ML中的传统方法,但通常仅在Haskell中使用,因为它是非常明智的事情:
distanceFromOrigin :: (Double, Double) -> Double
distanceFromOrigin (x, y) = sqrt (x^2 + y^2)
你可以写一个curried函数。 currying背后的概念是,当你将一个函数应用于一个参数时,你可以得到另一个带有第二个参数的函数。我将使用lambda表示法非常明确地写出来:
product :: Double -> (Double -> Double)
product x = \y -> x * y
假设我从(product 3) 4
开始。我可以先减少(product 3)
来获取
(\y -> 3 * y) 4
然后我可以完成这项工作,获得12分。
Haskell提供了一些语法来帮助解决这类问题。首先,它让我写
product x y = x * y
表示
product x = \y -> x * y
其次,它使函数应用程序保持左关联,因此我可以将product 3 4
写成(product 3) 4
。
最后,它使->
类型的构造函数成为右关联的,因此我可以编写product :: Double -> Double -> Double
而不是product :: Double -> (Double -> Double)
。
答案 2 :(得分:1)
在Haskell中,以下内容是等效的:
f = (\x y -> ..x..y.. )
f = (\x -> (\y -> ..x..y.. )) -- this equivalence is known as "currying"
f x = (\y -> ..x..y.. ) -- partially applying f with x gives (\y->...)
f x y = ..x..y..
((\x -> ...)
当然是Haskell的匿名符号,所谓的&#34; lambda &#34;函数(\
是一个提醒希腊字母λ
。)
在Haskell中,函数就像其他值一样,因此函数调用没有特殊的语法,或者&#34;函数指针&#34;对于类型,上面自然需要
f :: a -> b -> t
f x :: b -> t -- the result of calling f w/ x (of type a) has type b->t
f x y :: t -- when f :: a->b->t, x :: a, y :: b, then f x y :: t
盯着它看一会儿。
这就是关于currying的问题。函数调用仅通过Haskell中的并置来表示,因此它与左侧相关联(f x y
实际上是((f x) y)
)。因为Haskell定义是自动 curried ,所以类型中的箭头与右侧相关联(a->b->c
实际上是a->(b->c)
)。