我正在学习Haskell。
学习新语言时,我的标准技术之一是实现Hello World遗传算法,该算法尝试通过与某些输入字符串匹配的遗传算法技术生成字符串。
由于我对Haskell缺乏经验(我必须比较最接近的是Kotlin)我搜索了一个示例代码,因此我可以将我对基本遗传算法的现有理解与代码相匹配,并根据我已经知道对Haskell的一些理解正在进行阅读/研究语言。
我遇到了这个教程:https://www.arcadianvisions.com/blog/2011/haskell-genetic-algorithm-hello-world.html
我在设置环境之后将其转录为原子,每个部分我都不明白我做了一个快速谷歌,如果我在15分钟后没有理解语法/语义,我会继续抄写以后赶上那些特定部分的意图。
所以,我理解了大部分代码,例如函数应用程序的顺序,monad(想想我几乎还有monad),数据类型,函数类型,currying,类型替换等。但是,那里是我在阅读/研究中看不到的几个语法/语义,并不确定它们是做什么的,但它们在我上面链接的代码示例中出现了很多。我希望有人可以向我解释一下:
(++)
(:)
<$>
<*>
(,)
(x !!)
p@(info@())
我假设()
和<>
是一些特殊的语法,其中的内容是语义的?当我将它悬停在原子上时(我使用atom-haskell-ghc)我可以看到Functor f => Applicative (f :: * -> *) where <*> :: f (a -> b) -> f a -> f b
类型看起来像monad,但我并不真正理解这种奇怪的语法/消费端的语义。我是否应该将这些视为另一个函数(带有奇怪的中缀别名?)。
以下是展示上述几个例子的特定线:
mate :: RandomGen g => Gene -> Gene -> Rand g Gene
mate gene1 gene2 = (++) <$> flip take gene1 <*> flip drop gene2 <$> pivot
where pivot = getRandomR (0, length gene1 - 1)
答案 0 :(得分:7)
在Haskell中,您可以定义一个函数,该函数具有包含在括号之间的符号序列作为标识符,例如(++)
或(:)
,它是运算符,可以是既用作函数,如(++) x y
,又用作中缀运算符,如x ++ y
。在窗帘后面,Haskell编译器会将中缀运算符转换为函数调用,因此x ++ y
与(++) x y
完全等效(除了运算符具有不同的优先级规则)。
(++)
这是追加函数:(++) :: [a] -> [a] -> [a]
它将两个列表作为输入,并构造一个相同类型元素的列表,其中包含第一个列表的元素,后跟元素第二个清单。例如:
(++) [1, 4, 2, 5] [1, 3, 0, 2] == [1, 4, 2, 5, 1, 3, 0, 2]
(:)
这是列表类型[a]
的构造函数。它的类型为 (:) :: a -> [a] -> [a]
。它将元素和元素列表(所有相同类型)作为输入,它构造一个以第一个元素开头的列表,后跟第二个参数的元素。例如:
(:) 1 [4, 2, 5] = [1, 4, 2, 5]
(<$>)
您在问题<$>
中写道,但正如您可能想到的那样,这意味着定义了一个函数(<$>) :: Functor f => (a -> b) -> f a -> f b
。
Functor
是一个类型类。 Haskell中的几种类型是仿函数。最简单的是列表[]
和Maybe
。 (<$>)
将函数f :: a -> b
和函数实例(例如列表[a]
)作为输入。然后它会将其转换为仿函数实例[b]
。如何完成此操作取决于Functor
实例的实现方式((<$>)
的{{1}}语义与Maybe
的语义不同。)
虽然类比不完整,但您有时可以看到[]
作为元素集合(Functor
基本上是零Maybe
或Nothing
的集合元素)。然后,它将通过函数引导这些元素,从而映射集合包含的元素,例如:
Just x
(+1) <$> [1, 4, 2, 5] == [2, 5, 3, 6]
(+1) <$> Nothing == Nothing
(+1) <$> (Just 2) == Just 3
这个函数(<*>) :: Applicative f => f (a -> b) -> f a -> f b
有点难以理解。它使用(<*>)
类型。
作为Applicative
实例的类型必须实现两个函数:Applicative
和pure :: Applicative a => a -> f a
(或者程序员可以决定实现(<*>) :: Applicative f => f (a -> b) -> f a -> f b
,但让我们忽略这一点。)
您再次可以将liftA2
(至少对于热门实例)看作一个集合(例如Applicative
或[]
)。因此,我们将这些函数集合(所有类型都为Maybe
)和a -> b
的集合作为输入。然后我们&#34;乘以&#34;这些,例如对于列表:
a
因此,对于 [f1, f2, ..., fm] <*> [x1, x2, ..., xn]
== [f1 x1, f1 x2, ..., f1 xn,
f2 x1, f2 x2, ..., f2 xn,
...,
fm x1, fm x2, ..., fm xn]
来说,如果左操作数是Maybe
,或者右操作数是Nothing
,或者两者都是Nothing
,那么这意味着一个Nothing
,如果两者都是Nothing
s(所以Just
),那么我们会获得Just f <*> Just x
:
Just (f x)
Just f <*> Just x == Just (f x)
Just f <*> Nothing == Nothing
Nothing <*> Just x == Nothing
Nothing <*> Nothing == Nothing
这是 2-tuple 的构造函数: (,)
因此将(,) :: a -> b -> (a,b)
和a
作为输入,它构造了一个2元组,其中第一个项是第一个参数,第二个项是第二个参数。例如:
b
(,) 4 'a' == (4, 'a')
这是section of an infix operator。您可以使用中缀运算符,例如指定左侧或右侧部分。在这种情况下,您构造了一个部分应用的函数。例如:
(x !!)
因此,对于后者,这意味着我们构造了一个函数,该函数将一个参数作为输入,该参数将作为左操作数填充。所以:
([1, 4, 2, 5] !!) == (!!) [1, 4, 2, 5]
(!! 2) == flip (!!) 2
(!!) :: [a] -> Int -> a
函数将列表和(!! 2) [1, 4, 2, 5] == (!!) [1, 4, 2, 5]
作为输入,并返回该索引处的元素(从零开始的索引)。
Int
与上述相反,p@(info@())
不是函数或运算符(这些实际上是相同的),但是keyword。
它在模式匹配中用于获取对模式的引用,例如匹配子模式(或获取对子模式的引用)。
例如,假设我们想要模式匹配2元组,并且我们想要引用整个元组,并且第一个元素,我们可以使用:
@
因此,如果我们再调用somefunction total@(left, _) = ...
,则表示somefunction (4, 'a')
将保留total
,(4, 'a')
将保留left
。
答案 1 :(得分:6)
这些大多是常规功能。
<>
不是特殊语法,只是函数名称的一部分。
()
是常规括号,它将事物分组并像大多数其他语言一样定义优先级,重要的注意事项是,当你想引用一个运算符函数(如++
)时,它必须在括号。
++
是列表并置函数[1,2] ++ [3,4] = [1,2,3,4]
,或者不使用中缀表示法(++) [1,2] [3,4] = [1,2,3,4]
:
是'cons'函数,它会将一个元素添加到列表1 : [2, 3, 4] = [1,2,3,4]
或(:) 1 [2, 3, 4] = [1,2,3,4]
而不使用中缀表示法。
<$>
是fmap
的中缀运算符别名
<*>
是适用的应用功能
,
是元组构造函数(,) 1 2 = (1, 2)
!!
是列表索引函数[1,2,3] !! 1 = 2
。请注意,由于这些是单链接列表,因此索引是O(n)操作。
@
用于定义“as pattern”。模式匹配时,它允许您为参数指定名称,同时还使用模式匹配对其进行解构。例如,模式f (xs@[x1, x2])
匹配两个元素列表,您可以使用x1
和x2
来引用单个元素,xs
来引用整个列表