Haskell是否真正纯粹(是处理系统外输入和输出的任何语言)?

在涉及函数式编程方面的Monad之后,该功能实际上是否使语言变得纯粹,还是仅仅是另一种“摆脱监狱免费卡”,以便在黑板数学之外推理现实世界中的计算机系统?

编辑:

这不是有人在这篇文章中说过的火焰诱饵,而是一个真正的问题,我希望有人能够抨击我,并说,证明,这是纯粹的。

此外,我正在考虑关于其他不太纯粹的功能语言和一些使用良好设计并比较纯度的OO语言的问题。 到目前为止,在我的FP非常有限的世界里,我仍然没有注意到Monads的纯度,但是我很高兴知道,但是我确实喜欢在纯度赌注中更重要的不变性的想法。


采取以下迷你语言:

data Action = Get (Char -> Action) | Put Char Action | End

Get f意味着:读取一个字符c ,并执行操作fc

Put ca意味着:写人物c ,并执行动作a

这是一个打印“xy”的程序,然后询问两个字母并以相反的顺序打印它们:

Put 'x' (Put 'y' (Get (a -> Get (b -> Put b (Put a End)))))

你可以操纵这些程序。 例如:

conditionally p = Get (a -> if a == 'Y' then p else End)

这是类型的Action -> Action - 它需要一个程序,并提供另一个程序,首先要求确认。 这是另一个:

printString = foldr Put End

这具有类型String -> Action - 它接受一个字符串并返回一个写入该字符串的程序,如

Put 'h' (Put 'e' (Put 'l' (Put 'l' (Put 'o' End))))

Haskell中的IO的工作方式类似。 尽管执行它需要执行副作用,但您可以纯粹的方式构建复杂的程序而不执行它们。 您正在计算程序描述(IO操作),而不是实际执行它们。

在像C这样的语言中,你可以写一个函数void execute(Action a)来实际执行程序。 在Haskell中,通过编写main = a指定该操作。 编译器创建一个执行动作的程序,但是你没有其他的方式来执行一个动作(除了脏的技巧)。

显然, GetPut不仅是选项,还可以将其他API调用添加到IO数据类型,如操作文件或并发。

添加结果值

现在考虑下面的数据类型。

data IO a = Get (Char -> Action) | Put Char Action | End a

前一个Action类型等同于IO () ,即IO值总是返回“unit”,与“void”相当。

这种类型与Haskell IO非常相似,只有在Haskell IO中是一种抽象数据类型(您无权访问定义,仅限于某些方法)。

这些是可以以某种结果结束的IO操作。 像这样的值:

Get (x -> if x == 'A' then Put 'B' (End 3) else End 4)

具有类型IO Int并且对应于C程序:

int f() {
  char x;
  scanf("%c", &x);
  if (x == 'A') {
    printf("B");
    return 3;
  } else return 4;
}

评估和执行

评估和执行之间有区别。 您可以评估任何Haskell表达式并获取一个值; 例如,将2 + 2 :: Int评估为4 :: Int。 您只能执行类型为IO a的Haskell表达式。 这可能有副作用; 执行Put 'a' (End 3)将字母a屏幕。 如果您评估IO值,如下所示:

if 2+2 == 4 then Put 'A' (End 0) else Put 'B' (End 2)

你得到:

Put 'A' (End 0)

但是没有副作用 - 你只进行了一次无害的评估。

你将如何翻译

bool comp(char x) {
  char y;
  scanf("%c", &y);
  if (x > y) {       //Character comparison
    printf(">");
    return true;
  } else {
    printf("<");
    return false;
  }
}

变成IO值?

修正一些字符,说'v'。 现在, comp('v')是一个IO动作,它将给定的字符与'v'进行比较。 类似地, comp('b')是一个IO动作,它将给定的字符与'b'进行比较。 一般来说, comp是一个函数,它接受一个字符并返回一个IO动作。

作为C语言的程序员,你可能会争辩说comp('b')是一个布尔值。 在C中,评估和执行是相同的(即它们意味着相同的事情,或者同时发生)。 不在Haskell中。 comp('b')评估为一些IO操作,在执行后给出一个布尔值。 (确切地说,它如上所述评估为代码块,仅用'b'代替x。)

comp :: Char -> IO Bool
comp x = Get (y -> if x > y then Put '>' (End True) else Put '<' (End False))

现在, comp 'b'评估为Get (y -> if 'b' > y then Put '>' (End True) else Put '<' (End False))

数学上也有意义。 在C中, int f()是一个函数。 对于数学家来说,这是没有意义的 - 一个没有参数的函数? 功能的要点是采取论据。 函数int f()应该等于int f 。 它不是,因为C中的函数混合了数学函数和IO操作。

头等舱

这些IO值是一流的。 就像你可以有一个整数元组列表[[(0,2),(8,3)],[(2,8)]]你可以用IO构建复杂的值。

 (Get (x -> Put (toUpper x) (End 0)), Get (x -> Put (toLower x) (End 0)))
   :: (IO Int, IO Int)

IO操作的元组:首先读取一个字符并将其打印为大写字母,然后读取一个字符并将其返回小写字母。

 Get (x -> End (Put x (End 0))) :: IO (IO Int)

IO值,读取字符x并结束,返回将x写入屏幕的IO值。

Haskell具有特殊的功能,可以轻松处理IO值。 例如:

 sequence :: [IO a] -> IO [a]

它接收IO操作列表,并返回一个按顺序执行它们的IO操作。

单子

单子是一些组合子(就像conditionally上面那样),它们允许你在结构上编写程序。 有一个由类型组成的函数

 IO a -> (a -> IO b) -> IO b

给定IO a,函数a - > IO b返回IO b类型的值。 如果你将第一个参数写成C函数af()并将第二个参数写为bg(ax)它将返回一个g(f(x)) 。 根据Action / IO的上述定义,您可以自己编写该函数。

通知单子对于纯度来说不是必不可少的 - 你可以像上面那样写程序。

纯度

关于纯度的关键在于参考透明度,并区分评估和执行。

在Haskell中,如果你有f x+fx你可以用2*fx替换它。 在C中, f(x)+f(x)通常与2*f(x) ,因为f可以在屏幕上打印某些东西,或者修改x

由于纯度,编译器拥有更多的自由,并且可以更好地进行优化。 它可以重新排列计算,而在C中它必须考虑是否会改变程序的含义。


重要的是要明白,单子之间并没有什么特别的关系 - 所以他们在这方面肯定不会代表“走出监狱”牌。 没有必要的编译器(或其他)来实现或使用monads,它们是在纯粹的Haskell功能环境中定义的。 特别是,sdcvvc展示了如何以纯功能的方式定义monad,而没有任何资源去实现后门。


“黑板数学之外”的计算机系统理由是什么意思? 那会是什么样的推理? 推测?

副作用和纯粹的功能是一个观点问题。 如果我们将名义上的副作用函数看作是从一个世界状态转移到另一个状态的函数,那么它又是纯粹的。

我们可以通过赋予它第二个参数,一个世界,使它的每一个副作用函数都是纯粹的,并且当它完成时要求它传递给我们一个新的世界。 我根本不了解C++ ,但是说read有这样的签名:

vector<char> read(filepath_t)

在我们新的“纯粹风格”中,我们像这样处理:

pair<vector<char>, world_t> read(world_t, filepath_t)

事实上,每个Haskell IO操作都是如此。

所以现在我们已经有了一个纯粹的IO模型。 谢天谢地。 如果我们不能这样做,那么Lambda微积分和图灵机不是等同的形式,然后我们会做一些解释。 我们还没有完成,但留给我们的两个问题很简单:

  • world_t结构中发生了什么? 描述每一粒沙子,草叶,破碎的心和金色的夕阳?

  • 我们有一个非正式的规则,即我们只使用一次世界 - 在每次IO操作之后,我们会丢弃与它一起使用的世界。 然而,随着所有这些世界的漂浮,我们必然会让它们混在一起。

  • 第一个问题很简单。 只要我们不允许检查世界,事实证明我们不需要为存储任何东西而烦恼。 我们只需要确保一个新的世界不等于任何以前的世界(以免编译器狡猾地优化一些世界生产操作,就像它有时在C++ )。 有很多方法可以处理这个问题。

    至于世界变得混乱起来,我们希望隐藏在图书馆内部的世界,这样就没有办法摆脱这个世界,因此也没有办法将它们混淆起来。 原来,monad是在计算中隐藏“副通道”的好方法。 输入IO monad。

    前一段时间,在Haskell邮件列表中提出了像你这样的问题,然后我更详细地介绍了“side-channel”。 这是Reddit的主题(链接到我原来的电子邮件):

    http://www.reddit.com/r/haskell/comments/8bhir/why_the_io_monad_isnt_a_dirty_hack/

    链接地址: http://www.djcxy.com/p/43197.html

    上一篇: Is Haskell truly pure (is any language that deals with input and output outside the system)?

    下一篇: What is Haskell missing for totality checking?