学习新语言时,我的标准技术之一是实现Hello World遗传算法,该算法试图通过与某些输入字符串匹配的遗传算法技术来生成字符串。
由于我对Haskell的经验不足(我必须比较的最接近的是Kotlin),我搜索了一个示例代码,以便可以将我对基本遗传算法的现有理解与该代码相匹配,并基于已经在阅读中的内容对Haskell进行一些理解/语言研究。
我遇到了本教程:https://www.arcadianvisions.com/blog/2011/haskell-genetic-algorithm-hello-world.html
设置好环境后,我将其转录为原子,我不了解的每个部分我都做了一个快速的google,如果15分钟后我不了解语法/语义,我将继续进行转录,以便日后赶上那些特殊的部分。
因此,我了解了大多数代码,例如函数应用程序的顺序,monad(无论如何我都觉得monad差不多),数据类型,函数类型,currying,类型替换等。但是,语法中有几分我在阅读/研究中没有看到过的/语义学,也不确定它们的作用,但是在上面链接到的代码示例中,它们会出现很多。我希望有人可以向我解释:
1 | (++) |
我假设()和<>是一些特殊的语法,并且其中的内容是语义?当我将它们悬停在原子上时(我正在使用atom-haskell-ghc),我可以看到类型Functor f => Applicative (f :: * -> *) where <*> :: f (a -> b) -> f a -> f b看起来像单子,但我真的不理解使用方的奇怪语法/语义。我是否应该将它们视为另一个函数(具有怪异的infix别名?)。
这是显示上面几个示例的特定行:
1 | mate :: RandomGen g => Gene -> Gene -> Rand g Gene |
相关讨论
运算符说明
在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 | (+1) <$> [1, 4, 2, 5] == [2, 5, 3, 6] |
(<*>)
再次难以理解此功能(<*>) :: 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 | [f1, f2, ..., fm] <*> [x1, x2, ..., xn] |
因此,例如对于Maybe意味着,如果左操作数是Nothing,或者右操作数是Nothing,或者两者都是Nothing,那么如果两个都是Just,则结果为Nothing s(所以Just f <*> Just x),那么我们得到一个Just (f x):
好。
1 | Just f <*> Just x == Just (f x) |
(,)
这是2元组的构造函数:(,) :: a -> b -> (a,b)因此将a和b作为输入,并构造2元组,其中第一项是第一个参数,第二项是第二个参数。参数。例如:
好。
1 | (,) 4 'a' == (4, 'a') |
(x !!)
这是中缀运算符的一部分。您可以使用infix运算符,例如,指定左侧或右侧部分。在这种情况下,您将构造一个部分应用的函数。例如:
好。
1 | ([1, 4, 2, 5] !!) == (!!) [1, 4, 2, 5] |
因此,对于后者而言,这意味着我们构造了一个函数,该函数采用将作为左操作数填充的参数作为输入。所以:
好。
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引用整个列表