函数式语言实际上如何 define/translate 硬件原语?

How would a functional language actually define/translate primitives to hardware?

假设我定义了一些基元,这里使用 javascript:

const TRUE = x => y => x;
const FALSE = x => y => y;
const ZERO = f => a => a;
const ONE = f => a => f(a);
const TWO = f => a => f(f(a));

如果一种语言纯粹是功能性的,它如何将这些原语翻译成物理的东西?例如,通常我看到的不是纯函数的类似函数的东西,比如:

const TWO = f => a => f(f(a));
const inc = x => x+1;
console.log(TWO(inc)(0));
// 2

但这又是一种 'trick' 来打印一些东西,在本例中是一个数字。但是,如何将纯功能性的东西转化为实际可以做某事的东西呢?

如果函数的结果(return 值)仅取决于您提供给它的输入,则该函数是纯函数。

如果一门语言的所有功能都是纯的,那么它就是纯功能的语言¹。

因此很明显,像 getchar 这样的“实用程序”在许多普通的 non-functional 语言中是相当常见的 函数 ,但在函数中会造成问题语言,因为它们没有输入²,但它们每次仍然给出不同的输出。

看起来函数式语言至少需要放弃纯粹性才能做到 I/O,不是吗?

不完全是。如果一门语言想要成为纯函数式的,它就不能破坏函数的纯度,即使是为了 I/O。它仍然需要有用。你确实需要用它来完成事情,或者,正如你所说,你需要

something that can actually do something

如果是这样的话,像 Haskell 这样的纯函数式语言如何保持纯粹,同时为您提供与键盘、终端等交互的实用程序?或者,换句话说,纯函数式语言如何为您提供与普通语言的那些不纯函数具有相同“读取用户输入”角色的实体?

诀窍是这些函数秘密地(和柏拉图式地)接受一个参数,除了它们在其他语言中会有 0 个或多个参数,并吐出一个额外的 return 值:这两个家伙是函数执行“动作”之前和之后的“真实世界”。有点像说getcharputchar的签名不是

char getchar()
void putchar(char)

但是

[char, newWorld] = getchar(oldWorld)
[newWorld] = putchar(char, oldWorld)

这样你就可以给你的程序一个“初始”世界,所有那些在普通语言中不纯的函数,在函数式语言中,将不断发展的世界传递给彼此,就像奥运火炬一样。

现在你可能会问:这样做有什么好处?

像 Haskell 这样的纯函数式语言的要点在于,它从你身上抽象出这种机制,对你隐藏 *word 东西,这样你就不能做像这样的傻事以下

[firstChar, newWorld] = getchar(oldWorld)
[secondChar, newerWorld] = getchar(oldWorld) // oops, I'm mistakenly passing
                                             // oldWorld instead of newWorld

语言 只是没有提供让您接触“真实世界”的工具。如果是这样,您将拥有与 C 等语言中相同的自由度,并且最终会遇到那些语言中常见的错误类型。

一种纯功能性的语言,相反,它对您隐藏了这些东西。它基本上将你的自由限制在比 non-functional 语言允许的更小的范围内,让实际运行程序的 runtime 机器(你无法控制),代您维护管道。

(A good reference)


¹ Haskell 就是这样一种语言(据我所知,周围没有任何其他主流的纯函数式语言); JavaScript 不是,即使它提供了几种用于进行某些函数式编程的工具(想想箭头函数,它们本质上是 lambda,以及 Lodash 库)。

² 不,您从键盘输入的内容不是 getchar 函数的输入;您 调用 getchar 时不带参数,并将其 return 值分配给某个变量,例如char c = getchar()let c = getchar(),或任何语言语法。