【问题标题】:Syntax and semantics of symbol characters in HaskellHaskell 中符号字符的语法和语义
【发布时间】:2018-02-06 22:11:55
【问题描述】:

我正在学习 Haskell。

在学习一门新语言时,我的标准技术之一是实现一个 Hello World 遗传算法,该算法尝试通过匹配某个输入字符串的遗传算法技术生成一个字符串。

由于我对 Haskell 缺乏经验(我要比较的最接近的东西是 Kotlin),我搜索了一个示例代码,以便我可以将我对基本遗传算法的现有理解与代码相匹配,并根据我已经对 Haskell 的一些理解进行直觉正在阅读/研究该语言。

我偶然发现了这个教程:https://www.arcadianvisions.com/blog/2011/haskell-genetic-algorithm-hello-world.html

我在设置环境后将其转录为 atom稍后再讨论这些特定部分。

所以,我理解了大部分代码,例如函数应用的顺序、monad(无论如何我认为我已经差不多有了 monad)、数据类型、函数类型、柯里化、类型替换等。但是,有几个我在阅读/研究中没有看到的一些语法/语义,并且不确定它们的作用,但它们在我上面链接的代码示例中出现了很多。我希望有人可以向我解释:

(++)
(:)
<$>
<*>
(,)
(x !!)
p@(info@())

我假设()&lt;&gt; 是一些特殊的语法,它们里面的东西是语义的?当我在 atom 中将鼠标悬停在它们上时(我正在使用 atom-haskell-ghc),我可以看到类型 Functor f =&gt; Applicative (f :: * -&gt; *) where &lt;*&gt; :: f (a -&gt; b) -&gt; f a -&gt; 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)

【问题讨论】:

  • Haskell wiki 有一些概述,wiki.haskell.org/Keywords 其他是您可以使用 hoogle 查找的函数:haskell.org/hoogle/?hoogle=%28%2B%2B%29
  • 是的,我有 hoogle 设置,所以我可以通过终端进行查找。我去看看,谢谢。
  • 嗯,帽子 hoogle 链接说 (++) 执行列表连接。我以为 ++ 这样做了……括号是多余的吗?等一下,我会发布一些我不明白的实际代码。
  • 好吧,没有 Haskell 官方语言法庭”会让你因为使用一种语法而不是另一种语法而入狱 :)。我认为通常是通过定义一个类似 @ 的运算符987654330@(这里的? 可以是任何符号序列),它更多地暗示了中缀的使用,但是由于您本身并没有将所有参数添加到函数中,例如,您可以将它们用作其他函数的一部分,比如zipWith (++) [[1], [4]], [[2], [5]]

标签: haskell


【解决方案1】:

关于运算符的说明

在 Haskell 中,您可以定义一个函数,该函数将一系列符号括在括号中作为标识符,例如 (++)(:) 这是一个 运算符,可以同时用作函数,如(++) x y,以及作为中缀运算符,如x ++ y。在幕后,Haskell 编译器会将中缀运算符转换为函数调用,因此x ++ y(++) x y 完全等价(除了运算符具有不同的优先级规则)。

(++)

这是 append 函数:(++) :: [a] -&gt; [a] -&gt; [a] 它接受两个列表作为输入,并构造一个相同类型元素的列表,其中包含第一个列表的元素,后跟第一个列表的元素第二个清单。例如:

(++) [1, 4, 2, 5] [1, 3, 0, 2] == [1, 4, 2, 5, 1, 3, 0, 2]

(:)

这是[a] 列表类型的构造函数。它的类型为 (:) :: a -&gt; [a] -&gt; [a]。它将一个元素和一个元素列表(都属于同一类型)作为输入,它构造一个从第一个元素开始的列表,然后是第二个参数的元素。例如:

(:) 1 [4, 2, 5] = [1, 4, 2, 5]

(&lt;$&gt;)

您在问题&lt;$&gt; 中写道,但正如您可能发现的那样,这意味着在某处定义了函数(&lt;$&gt;) :: Functor f =&gt; (a -&gt; b) -&gt; f a -&gt; f b

Functor 是一个类型类。 Haskell 中有几种类型是函子。最简单的是列表[]Maybe(&lt;$&gt;) 将函数 f :: a -&gt; b 和函子实例(例如列表 [a])作为输入。然后它将其转换为仿函数实例[b]。这是如何完成的,取决于Functor 实例是如何实现的(Maybe(&lt;$&gt;)[] 的语义不同)。

虽然类比不完整,但您有时可以将Functor 视为元素的集合(Maybe 基本上是零个Nothing 或一个Just x 元素的集合)。然后它将通过函数引导这些元素来映射集合包含的元素,例如:

(+1) <$> [1, 4, 2, 5] == [2, 5, 3, 6]
(+1) <$> Nothing == Nothing
(+1) <$> (Just 2) == Just 3

(&lt;*&gt;)

这个函数(&lt;*&gt;) :: Applicative f =&gt; f (a -&gt; b) -&gt; f a -&gt; f b 再次有点难以理解。它使用了Applicative 类型类。

作为Applicative 实例的类型必须实现两个函数:pure :: Applicative a =&gt; a -&gt; f a(&lt;*&gt;) :: Applicative f =&gt; f (a -&gt; b) -&gt; f a -&gt; f b(或者程序员可以决定改为实现liftA2,但我们在这里忽略它)。

您再次可以将Applicative(至少对于流行的实例)视为一个集合(如[]Maybe)。因此,在这里我们将这样的函数集合(所有类型为a -&gt; b)和as 的集合作为输入。然后,我们将这些“相乘”,例如对于列表:

   [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,如果两者都是Justs(所以Just f &lt;*&gt; 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 -&gt; b -&gt; (a,b) 因此将 ab 作为输入,它构造了一个 2-元组,其中第一项是第一个参数,第二项是第二个参数。例如:

(,) 4 'a' == (4, 'a')

(x !!)

这是section of an infix operator。您可以使用中缀运算符,例如指定左侧或右侧部分。在这种情况下,您将构建一个部分应用的函数。例如:

([1, 4, 2, 5] !!) == (!!) [1, 4, 2, 5]
(!! 2) == flip (!!) 2

因此,对于后者,这意味着我们构造了一个函数,该函数将一个参数作为输入,该参数将作为左操作数填充。所以:

(!! 2) [1, 4, 2, 5] == (!!) [1, 4, 2, 5]

(!!) :: [a] -&gt; Int -&gt; a 函数将一个列表和一个 Int 作为输入,并返回该索引处的元素(从零开始的索引)。

p@(info@())

与上述相反,@不是函数或运算符(实际上它们是相同的),而是keyword

它用于模式匹配以获取对模式的引用,例如匹配子模式(或获取对子模式的引用)。

例如,假设我们想要模式匹配一​​个 2 元组,并且我们想要引用整个元组和第一个元素,我们可以使用:

somefunction total@(left, _) = ...

因此,如果我们随后调用somefunction (4, 'a'),那么这意味着total 将持有(4, 'a'),而left 将持有4

【讨论】:

    【解决方案2】:

    这些大部分都是常规函数。

    &lt;&gt; 不是特殊语法,只是函数名的一部分。

    () 是常规括号,它像大多数其他语言一样对事物进行分组并定义优先级,重要的注意事项是当您想要引用运算符函数(如 ++)时,它必须在括号中。

    • ++是列表拼接函数[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] 之前,不带中缀符号。

    • &lt;$&gt;fmap 的中缀运算符别名

    • &lt;*&gt;是applicative应用函数

    • , 是元组构造函数(,) 1 2 = (1, 2)

    • !! 是列表索引函数[1,2,3] !! 1 = 2。请注意,由于这些是单链表,因此索引是一个 O(n) 操作。

    • @ 用于定义“as 模式”。模式匹配时,它允许您为参数命名,同时还可以使用模式匹配对其进行解构。例如,f (xs@[x1, x2]) 模式匹配一​​个包含两个元素的列表,您可以使用 x1x2 来引用单个元素,xs 来引用整个列表

    【讨论】:

    • 是的,这就是答案。除了Willem Van Onsem 所说的,这现在是有道理的。您可以使用 &lt;$&gt;&lt;PinkFairy&gt;&lt;HarryPoter&gt; 作为函数名称,这让我大吃一惊。那么,() 用于优先级(如数学或布尔逻辑),!! 是索引,@ 是模式,而其他所有符号都可以抢购? &lt;*~~LM&gt;&gt; foo :: Int -&gt; 诠释:P
    • 不完全是。在 Haskell 中,标识符可以是函数名,然后可能只允许在通常的 alnum-下划线之上使用单引号,也可以是运算符,然后它由运算符符号组成,例如 &lt;**&gt;。如果你想要真正的疯狂命名,你应该切换到 Lisp,其中 - 在名称中用于分隔单词而不是驼峰式,+ 和 - 是通常的普通标识符。
    猜你喜欢
    • 1970-01-01
    • 2020-09-23
    • 1970-01-01
    • 2018-08-05
    • 2014-05-11
    • 2011-12-14
    • 2021-05-01
    • 1970-01-01
    • 2012-06-15
    相关资源
    最近更新 更多