我正在学习Haskell。
学习新语言时,我的标准技术之一是实现Hello World遗传算法,该算法试图通过与某些输入字符串匹配的遗传算法技术来生成字符串。
由于我对Haskell的经验不足(我必须比较的最接近的是Kotlin),我搜索了一个示例代码,以便可以将我对基本遗传算法的现有理解与该代码相匹配,并基于已经在阅读中的内容对Haskell进行一些理解/语言研究。
我遇到了本教程:https://www.arcadianvisions.com/blog/2011/haskell-genetic-algorithm-hello-world.html
设置好环境后,我将其转录为原子,我不了解的每个部分我都做了一个快速的google,如果15分钟后我不了解语法/语义,我将继续进行转录,以便日后赶上那些特殊的部分。
因此,我了解了大多数代码,例如函数应用程序的顺序,monad(无论如何我都觉得monad差不多),数据类型,函数类型,currying,类型替换等。但是,语法中有几分我在阅读/研究中没有看到过的/语义学,也不确定它们的作用,但是在上面链接到的代码示例中,它们会出现很多。我希望有人可以向我解释:
1 2 3 4 5 6 7
| (++)
(:)
<$>
<*>
(,)
(x !!)
p@(info@()) |
我假设()和<>是一些特殊的语法,并且其中的内容是语义?当我将它们悬停在原子上时(我正在使用atom-haskell-ghc),我可以看到类型Functor f => Applicative (f :: * -> *) where <*> :: f (a -> b) -> f a -> f b看起来像单子,但我真的不理解使用方的奇怪语法/语义。我是否应该将它们视为另一个函数(具有怪异的infix别名?)。
这是显示上面几个示例的特定行:
1 2 3
| 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) |
-
Haskell Wiki概述了一些wiki.haskell.org/Keywords,其他是您可以通过hoogle查找的功能:haskell.org/hoogle/?hoogle=%28%2B%2B%29
-
是的,我已经设置好了,所以我可以通过终端进行查找。病了,谢谢。
-
嗯,帽子挂钩链接说(++)执行列表串联。我以为++是...括号是否多余?等等,我会用我不明白的地方发布实际代码。
-
没有括号也不是多余的,(++)是连接的函数。但是您可以使用诸如运算符之类的功能,因此(++) x y == x ++ y。
-
啊,实际上x ++ y是(++)x y的中缀变化吗?我实际上更喜欢(++)x y变体,因为它会与我已经在haskell中学习/编写的内容保持一致。人们喜欢用一种"标准"方式书写吗?
-
好吧,没有正式的Haskell语言法庭会因为使用一种语法而不是另一种语言而使您入狱:)。我认为通常通过定义类型(?)的运算符(此处?可以是任何符号序列) ,这暗示着更多地使用infix,但是由于您本身并未将所有参数都添加到函数中,因此您可以将它们用作其他函数的一部分,例如zipWith (++) [[1], [4]], [[2], [5]]。
-
但有人同意:) mail.haskell.org/pipermail/haskell/2003-July/012260.html
-
大声笑,你是对的,没什么大不了的infix / prefix,但是必须阅读其他人的代码并且不知道他们以哪种方式编写,尤其是当他们链接函数时,这有点令人困惑。
-
"认为它像鼬鼠吗?" en.wikipedia.org/wiki/Weasel_program
运算符说明
在Haskell中,您可以定义一个函数,该函数具有一系列用括号括起来的符号序列作为标识符,例如(++)或(:),该运算符既可以用作函数,又可以用作(++) x y,也可以用作中缀运算符,例如x ++ y。在幕后,Haskell编译器会将infix运算符转换为函数调用,因此x ++ y与(++) x y完全等效(除了运算符具有不同的优先级规则的事实)。
好。
(++)
这是附加函数:(++) :: [a] -> [a] -> [a],它将两个列表作为输入,并构造一个包含相同类型元素的列表,该列表包含第一个列表的元素,后跟第二个列表的元素。例如:
好。
1
| (++) [1, 4, 2, 5] [1, 3, 0, 2] == [1, 4, 2, 5, 1, 3, 0, 2] |
(:)
这是列表类型[a]的构造函数。它的类型为(:) :: a -> [a] -> [a]。它以一个元素和一个元素列表(均为相同类型)为输入,构造一个从第一个元素开始,然后是第二个参数元素的列表。例如:
好。
1
| (:) 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实例的实现方式(Maybe的(<$>)与[]的语义不同)。
好。
尽管类推还不完整,但有时可以将Functor视为元素的集合(Maybe基本上是零个Nothing或一个Just x元素的集合)。然后,它将通过函数引导这些元素映射集合中包含的元素,例如:
好。
1 2 3
| (+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)和集合a作为输入。然后我们将它们"相乘",例如对于一个列表:
好。
1 2 3 4 5
| [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,那么如果两个都是Just,则结果为Nothing s(所以Just f <*> Just x),那么我们得到一个Just (f x):
好。
1 2 3 4
| Just f <*> Just x == Just (f x)
Just f <*> Nothing == Nothing
Nothing <*> Just x == Nothing
Nothing <*> Nothing == Nothing |
(,)
这是2元组的构造函数:(,) :: a -> b -> (a,b)因此将a和b作为输入,并构造2元组,其中第一项是第一个参数,第二项是第二个参数。参数。例如:
好。
(x !!)
这是中缀运算符的一部分。您可以使用infix运算符,例如,指定左侧或右侧部分。在这种情况下,您将构造一个部分应用的函数。例如:
好。
1 2
| ([1, 4, 2, 5] !!) == (!!) [1, 4, 2, 5]
(!! 2) == flip (!!) 2 |
因此,对于后者而言,这意味着我们构造了一个函数,该函数采用将作为左操作数填充的参数作为输入。所以:
好。
1
| (!! 2) [1, 4, 2, 5] == (!!) [1, 4, 2, 5] |
(!!) :: [a] -> Int -> a函数将一个列表和一个Int作为输入,并返回该索引处的元素(从零开始的索引)。
好。
p@(info@())
与上述相反,@不是函数或运算符(实际上它们是相同的),而是关键字。
好。
它用于模式匹配,以获取对模式和例如匹配子模式的引用(或获取对子模式的引用)。
好。
例如,假设我们要对2个元组进行模式匹配,并且想要引用整个元组,并且第一个元素可以使用:
好。
1
| somefunction total@(left, _) = ... |
因此,如果我们随后调用somefunction (4, 'a'),则意味着total将保存(4, 'a'),而left将保存4。
好。
好。
这些大多数都是常规功能。
<>不是特殊的语法,只是函数名称的一部分。
()是常规的圆括号,可以像大多数其他语言一样对事物进行分组并定义优先级,重要的一点是,当您要引用运算符(如++)时,必须在圆括号中。
-
++是列表连接函数[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)操作。
-
@用于定义"作为模式"。模式匹配时,它允许您给参数命名,同时还可以通过模式匹配来破坏它。例如,模式f (xs@[x1, x2])匹配两个元素列表,您可以在其中使用x1和x2引用单个元素,使用xs引用整个列表
-
是的,这就是答案。 除了威廉·范·昂塞姆所说的,现在这还有意义。 令我惊讶的是,您可以使用<$>或或作为函数名称。 那么,()用于优先级(如数学或布尔逻辑),!!是索引,@是模式,其他所有符号都可以争抢吗? <*~~LM>> foo :: Int ->整数:P
-
不完全是。 在Haskell中,一个标识符可以是一个函数名,然后可能只在常规的下划线下加单引号,或者是一个运算符,然后它由运算符组成,例如<**>。 如果您想要真正的命名疯狂,则应该切换到Lisp,其中名称中使用-来分隔单词,而不是camelCase,而+和-是通常的普通标识符。