【问题标题】:SML function parameters: tuple, currying, type synonymsSML函数参数:元组、柯里化、类型同义词
【发布时间】:2019-05-21 14:48:33
【问题描述】:

我从这个开始

type int_pair = int * int

然后有这些

fun sip1 ((i,j) : int_pair) = (j,i)
fun sip1a (ip : int_pair) = (#2 ip, #1 ip)
fun sip2 (ip : int*int) = (#2 ip, #1 ip)
fun sip3 (i : int, j : int) = (j,i)

这些结果

: val sip1 = fn : int_pair -> int * int
: val sip1a = fn : int_pair -> int * int
: val sip2 = fn : int * int -> int * int
: val sip3 = fn : int * int -> int * int

它们都有效。令我困惑的是如何将sip1sip1a 设置为采用单个int_pair 变量ip 或类似元组的(i,j)。由于类型同义词,它如何处理两者?我只是假设正文中的任何类型的(a,b) 在返回时都会默认输入int*int,而且,在sip1 的表达式(j,i) 中,它似乎不需要引用(#1... ) 的sip1a。乍一看比较奇特。 sip2 似乎直截了当。但是然后在sip3 中,它似乎只是两个ints 进入和int*int 类型的结果出来;但是函数类型说它是int * int -> int * int。为什么不像我在其他语言中看到的那样键入int -> int -> int*int? IOW,为什么 ML 将多个传入变量视为一个元组?

【问题讨论】:

  • 参数(i, j) 不是“类元组”,它一个元组。所有函数都只接受一个参数。
  • 另见fun sip4 ((i,j) : int * int)fun sip5 ((i : int, j : int) : int*int)fun sip6 ((i : int, j : int) : int_pair)
  • 你所有的函数都有相同的类型,只是当你专门使用你的类型同义词时,解释器也是如此——如果你输入fun sip7 (ip: int * int) = ip : int_pair;,你会得到val sip7 = fn : int * int -> int_pair。跨度>
  • 是的,我已经看到“所有函数都只接受一个参数”的说法,但不知道该怎么做,因为我看到函数具有多个传入变量。所以技术上fun sip3 (i : int, j : int) = (j,i) 没有得到两个变量,而是一个打包为元组。那么为什么显然两个传入的因变量被认为只是一个呢?这背后的计算机科学理论是什么? - 我原来的问题可以表述为。这是来自 lambda 演算世界的东西吗?我看过 ML 教程,这些教程让你从 lambda 演算开始。 . . .
  • sip1sip3 使用模式匹配。模式匹配能够解包元组,将项目绑定到ij(在这种情况下)。 SML 函数定义能够使用模式匹配并不意味着这样定义的函数是多于 1 个变量的函数。

标签: types sml currying


【解决方案1】:

在 ML 中,所有函数都只接受一个参数。但是当我们需要发送多个参数时,ML 会将传统括号中的参数解释为单个 tuple,以符合仅一个参数的规则:

fun swap1 (i,j) = (j,i)
: val swap1 = fn : 'a * 'b -> 'b * 'a

上面用*分隔的两个参数表示它们是一个2元元组。

- swap1 (1,2);
val it = (2,1) : int * int

即使变量看起来是分开的,它们仍然被认为是一个单独的参数,同样是一个 2-arity 元组:

fun swap1a (i : int, j : int) = (j,i)
: val swap1 = fn : int * int -> int * int

如果我们创建一个与type 同义的类型,例如,

type int_pair = int * int

使用int_pair 的函数仍被键入为元组:

fun swap2 ((i,j) : int_pair) = (j,i)
: val swap2 = fn : int_pair -> int * int
fun swap2a (ip : int_pair) = (#2 ip, #1 ip)
: val swap2a = fn : int_pair -> int * int
fun swap2b (ip : int*int) = (#2 ip, #1 ip)
: val swap2b = fn : int * int -> int * int

ML 也有 curried 函数,其中多个参数是 curried,基本上意味着参数一次被接受,同样,它只保留一个-参数规则。柯里化允许我们部分地将函数应用到它的一些参数上,留下一个可以在以后进一步评估的残差函数。使用柯里化的函数的特殊 ML 语法是简单地使用不带括号并用空格分隔的参数:

fun swap3 i j = (j,i)
: val swap3 = fn : 'a -> 'b -> 'b * 'a

- swap3 1 2;
val it = (2,1) : int * int

另一种可视化是用匿名函数构造swap3

- val swap3a = fn i => fn j => (j,i)
- swap3a 1 2;
val it = (2,1) : int * int

现在,我们可以设想传入的1 2 被从右到左解析(讽刺地称为left-associatinga la lambda 演算:

(fn i => fn j => (j,i))(1,2)
(fn i => (2,i))(1)
(2,1)

注意fn i =>... 实际上是接受一个函数作为其输入,在本例中,匿名函数表达式fn j =>... 的值为(2,i)。其实这个精神在 REPL 就可以做到:

- ((fn i => fn j => (j,i)) 1) 2;
val it = (2,1) : int * int

正如评论者 Andreas Rossberg 所说(见上文 cmets),在大多数函数式语言中,多个参数函数使用柯里化。他指出元组的使用在笛卡尔积的意义上是笛卡尔。他还指出,这是正交,在这种情况下,这可能意味着这两种处理多个参数的方法,元组和柯里化,彼此正交,因为它们'完全相反、不重叠的方法,在语义上不会相互干扰。

【讨论】:

  • 几点评论:(1)我关于正交性的评论是关于不将元组和 n 元函数作为冗余概念引入; (2)括号的存在与否无关紧要,fun f (x) (y) = ... 也是一个柯里化函数; (3)所有参数都写成patterns,类型注解在patterns中没有操作作用,所以(x,y)(x : int, y)((x,y) : int * int)((((x) : int, ((y))) : int_pair))都是等价的patterns modulo type checks; (4) fn j => ... 在您的示例中不是输入,它是外部函数的 result
  • 是的,我将进一步研究正交性的含义。也感谢其他更正。在这些问题上,我经常看到“挥手”。是的,fun f x y = . . .fun f (x) (y) . . . 相同,但我的该信息来源并未提及这一事实,而是让它看起来没有括号是唯一且唯一的语法。我正在尝试为中学教育设计课程,我需要消除所有这些挥手致意的东西。 IOW,冗长不是问题。
猜你喜欢
  • 2013-10-04
  • 1970-01-01
  • 2017-06-09
  • 2021-01-31
  • 2018-01-03
  • 2018-02-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多