【问题标题】:How exactly does operator <> work in Purescript?运算符 <> 在 Purescript 中究竟是如何工作的?
【发布时间】:2018-04-24 08:45:59
【问题描述】:

作为 Purescript 的新手,我很难弄清楚为什么以下代码(取自“PureScript By Example”)会如此工作:

> flip (\n s -> show n <> s) "Ten" 10
"10Ten"

这对我来说很有意义:flip 调用它的第一个参数(lambda 表达式),它的第二个和第三个参数以相反的顺序调用,因此产生了连接的字符串。

但我想知道为什么我们会通过这个 sn-p 得到以下响应:

> flip (\n s -> show n <> s) 10 "Ten"

Could not match type Int with type String

这是我的思路:运算符&lt;&gt; 实际上是Data.Semigroup.append 的简写,调用n 推导为StringData.Semigroup 的一个实例)和s 推导为@987654329 @。那么为什么 &lt;&gt; 不能将 Int 附加到 String ? (我想是因为它是右关联的,但我不确定......)

【问题讨论】:

  • show nn 变成一个字符串。因此,&lt;&gt; 运算符被解析为来自字符串的半群实例。它需要两个相同类型的参数,但在第二个示例中s10,它不是字符串。
  • 我不知道运算符优先级在这里是如何工作的。请参阅下面的答案和 cmets。
  • 不错的一个!它与优先级无关,而与 flip 的语义有关,它可以完成您手动执行的操作,即翻转给定函数的参数。因为你翻转了两次(一次使用 ˋflip,一次手动),你最终会得到一个 Int 字符串应该是......向 lambda 添加签名会更清楚。
  • 但是如果操作符&lt;&gt; 优先于函数应用,那么函数体实际上就是show (n &lt;&gt; s),所以第一次调用也不会被编译,对吧?

标签: type-conversion purescript


【解决方案1】:

说清楚……

flip (\n s -> show n <> s) "Ten" 10  == show 10 <> "Ten"
flip (\n s -> show n <> s) 10 "Ten"  == show "Ten" <> 10

(&lt;&gt;)Data.Semigroup.append 的别名)具有以下类型:

append :: a -> a -> a

也就是说,它的参数必须是相同的类型(它们必须匹配)。但是在你的第二次调用中,你传递给它一个String 和一个Int,因此会出现类型错误。

如果您来自弱类型语言(如带有隐式类型强制的 javascript),这种行为可能会令人惊讶。

【讨论】:

  • 对,但在我的第一次通话中,我还传递了两种不同的类型,只是顺序不同:IntString,所以我认为它们不匹配要么。
  • 是的,但是您正在调用show,其类型为a -&gt; String,并且函数应用程序(前缀)具有最高优先级。所以在第一种情况下,你的函数体实际上是(show n) &lt;&gt; s
  • 现在一分钱掉了。所以它的运算符优先级使第一次调用合法。
  • 致所有想了解 PureScript 类型推导机制的人:也可以看看下面@The Unix Man 的回答
【解决方案2】:

所以让我们把它拆开一点。一、原函数:

flip (\n s -> show n <> s) "Ten" 10

如果我们取每个部分的类型,我们会看到什么?

> :t flip
forall a b c. (a -> b -> c) -> b -> a -> c

当然,这只是flip,它接受一个双参数函数并将其转换为另一个参数翻转的函数。

接下来,这是有趣的部分,flip(a -&gt; b -&gt; c),变成了b -&gt; a -&gt; c

> :t (\n s -> show n <> s)
forall t4. Show t4 => t4 -> String -> String

好的,那么,t4 -&gt; String -&gt; String 是怎么产生的?这个函数中唯一引用String 的函数是show

> :t show
forall a. Show a => a -> String

另外,这就是 t4 上的 Show 约束的来源。 PureScript 告诉我们的是(\n s -&gt; show n &lt;&gt; s) 是一个函数,它接受两个参数,并返回一个String

它调用了我们的第一个参数t4,这是此会话的有效且唯一的类型变量。除了show 需要Show 的实例,它也无法告诉我们任何关于t4 的信息,t4 必须是Show 的实例。

现在,在我们的Show =&gt; t4 上调用show 将返回一个String,我们希望show 这样做:

> :t show
forall a. Show a => a -> String

(是的,我们已经看到了。)因此,在我们的函数(\n s -&gt; show n &lt;&gt; s) 中,show n 术语的类型为String。这是因为show 已经完全应用于t4,它是Show 的一个实例,因此编译器可以推断出show n 将具有showString 的类型。

这就是有趣的地方。 &lt;&gt; 在其最一般的形式中只有一个类型参数:

> :t (<>)
forall a. Semigroup a => a -> a -> a

它是一个函数,它接受两个a 类型的值,并返回一个a 类型的新值。请注意,虽然函数有一个类型参数,但它只有一个类型参数,所以任何特定的 (&lt;&gt;) 在其类型中都不会是多态的。

现在,虽然我们的函数 在其第一个参数中是多态的,但它也只有一个类型变量。无论如何,这实际上有点牵强,因为让我们看看flip 做了什么:

> :t flip (\n s -> show n <> s)
forall t5. Show t5 => String -> t5 -> String

哇。我们的多态参数仍然存在,但现在是第二个。这就是flip 所做的。那么这意味着什么,真的吗?

> flip (\n s -> show n <> s) "Ten" 10

在这种情况下,"Ten"String,这是我们的 flipped 函数所期望的。 10 是,嗯,是这样的:

> :t 10
Int

在这种情况下,它是IntIntShow 的实例吗?

> show (10 :: Int)
"10"

是的!因此,值"Ten"10 满足flipped 函数的参数forall a. Show a =&gt; String -&gt; a -&gt; String,其中"Ten"String10Int 是@987654382 的一个实例@。

现在,让我们看看失败的案例:

> flip (\n s -> show n <> s) 10 "Ten"

"Ten" 是一个StringStringshow 的实例吗?

> show "Ten"
"\"Ten\""

是的!整洁的!所以没关系。现在,10String 吗?嗯,不,可悲的是,它不是。所以10不能作为flipped函数的第一个参数。

【讨论】:

    猜你喜欢
    • 2020-05-26
    • 1970-01-01
    • 1970-01-01
    • 2010-12-24
    • 2021-06-19
    • 1970-01-01
    • 2021-12-07
    • 1970-01-01
    • 2023-04-01
    相关资源
    最近更新 更多