用空参数调用函数的术语

Term to call a function with empty parameter

在函数式编程中,调用不接受参数的函数的正确名称是什么?例如:

// 2 types of execute functions
type Function =
    | UnitFunction of (unit -> unit)
    | OperandFunction of (unit16 -> unit)

let nop () =
    ()

let shiftRegisterA operand =
    registers.A <- registers.A <<< operand

nop可以叫UnitFunctionshiftRegisterA可以叫OperandFunction吗?

你说的一般是函数的arity。因此,没有参数的函数将被称为空函数,而具有单个参数的函数将被称为一元函数。

虽然您仍然可以在 F# 函数的上下文中使用此术语并得到普遍理解,但这并不完全准确。 F# 函数总是采用单个参数和 return 单个值,因为函数默认是柯里化的。

在您的例子中,nop 的类型为 unit -> unit。这意味着它接受一个 unit 类型的参数(该类型的唯一 () 值),并且 return 是 unit 类型的结果。不过,这仍然使它成为一元函数。

在汇编编程中,调用函数是将参数压入堆栈(或寄存器,具体取决于调用约定)然后跳转到函数地址的过程。

在Java、C#、C等更高级的语言中,这个过程或多或少对我们是隐藏的。但是,当我们调用不带参数的函数时,我们会看到它的痕迹,即 void.

在 F#、haskell 等函数式编程语言中,函数的概念更接近于从单一输入产生答案的数学函数。

要了解 F# 中的所有函数都接受单个输入,让我们看一下以下函数:

// val f: (int*int) -> int
let f (x,y) = x + y

f 接受一对整数并产生一个整数。从 f 的角度来看,该对是解构以产生答案的单个值。

// val g: int -> int -> int
let g x y = x + y

这显然看起来像是一个函数,它接受两个整数来生成一个整数,但如果我们稍微重写一下,我们就会发现情况并非如此:

// val h: int -> int -> int
let h x = fun y -> x + y

h 等同于 g 但这里我们看到 h 实际上采用单个整数生成一个函数,该函数接受一个整数来生成一个整数。

签名中的 -> 是右结合的,我们添加括号我们可以更清楚地看到 gh 实际上采用单个输入。

// val g: int -> (int -> int)
let g x y = x + y

// val h: int -> (int -> int)
let h x = fun y -> x + y

let gx = g 1 2
let gy = (g 1) 2
let hx = h 1 2
let hy = (h 1) 2

在我看来,F# 中的函数比 C#/Java 中的函数具有更高的抽象级别,因为 C#/Java 函数在概念上比 F# 函数更接近汇编语言函数是。

此外,如果每个函数都需要一个参数,那么对于不接受任何参数的函数来说意义不大。

但是这个函数呢?

// val i: unit -> int
let i () = 3

它不接受没有参数产生3吗?不,它接受单位值 (),这只是 unit.

类型的值

在 Haskell 中,他们有一个接受 void 并产生答案的函数的名称:

absurd :: Void -> a

一个值也许可以看作是一个不带参数的函数,但我不是范畴论专家。

返回示例代码:

type Function =
| UnitFunction of (unit -> unit)
| OperandFunction of (unit16 -> unit)

函数式方法是这样的:

type Abstraction =
| Concrete of obj
| Function of Abstraction -> Abstraction

Abstraction 是值或函数。

查看代码,它似乎模拟了一些看起来接近汇编语言的东西,所以在这种情况下,可以将函数视为推送参数并跳转到一个地址。

type Function =
| VoidFunction    of (unit -> unit)
| UnaryFunction   of (unit16 -> unit)
| BinaryFunction  of (unit16 -> unit16 -> unit)

希望这很有趣。

PS.

似乎 unit 类型是一个小细节,但在我看来,它可以带来很多好处。

  • 无需声明。
  • 简化泛型编程(void 情况通常需要特殊情况,考虑 Task<'T>Task)。
  • 允许我们像数学函数一样思考函数,而不是跳转到内存中的地址。