Haskell是否真的是纯粹的(任何处理系统外部输入和输出的语言)? [英] Is Haskell truly pure (is any language that deals with input and output outside the system)?

查看:84
本文介绍了Haskell是否真的是纯粹的(任何处理系统外部输入和输出的语言)?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

在涉及函数式编程的Monad之后,该特性实际上是否使语言变得纯粹,还是仅仅是另一种摆脱监狱免费卡来推断现实世界中的计算机系统,而不是黑板数学?



编辑:



这不是火焰诱饵,因为有人在这篇文章中说过,但真正的问题是我希望有人能够打倒我,并说,证明它是纯粹的。



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

解决方案

以下迷你语言:

  data Action = Get(Char  - > Action)|把Char操作|结束

获取f 表示:读取一个字符 c ,并执行操作 fc

放入ca 表示:写入字符 c ,并执行操作 a



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

<$ (放置'结束)))))$ b $(put'y'(Get(\ a - > Get(\b-> b

您可以操作这些程序。例如:

  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))))



相若方式。虽然执行需要执行副作用,但您可以纯粹的方式构建复杂的程序而不执行它们。你在计算程序的描述(IO动作),而不是实际执行它们。

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

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



添加结果值



现在考虑以下数据类型。
$ b

  data IO a = Get(Char  - > Action)|把Char操作|结束

以前的 Action 类型是等效的到 IO(),即总是返回unit的IO值,可以与void相比。

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



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

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

的类型为 IO Int $ b $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $'
scanf(%c,& x);
if(x =='A'){
printf(B);
返回3;
}否则返回4;
}

评估和执行



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

  if 2 + 2 == 4然后把'A'(结束0)否则将'B'(结束2)

您获得:

pre $ code>放置'A'(End 0)

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



你会如何翻译

  bool comp(char x){
char y;
scanf(%c,& y);
if(x> y){//字符比较
printf(>);
返回true;
} else {
printf(<);
返回false;


$ / code $ / pre

转换为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'计算为获取(\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操作元组:首先读取一个字符并将其打印为大写字母,然后读取一个字符并将其返回小写字母。



<$ ($ x $> End(Put x(End 0))):: IO(IO Int)

一个IO值,读取一个字符 x 并结束,返回写入 x 到屏幕。



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

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

其中包含IO操作列表,并返回执行它们的IO操作顺序。
$ b Monads



单子是一些组合子(如有条件地),这允许您在结构上编写程序。有一个由

类型组成的函数

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

给出了IO a,函数a - > IO b返回了一个type 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中它必须考虑是否改变了程序的含义。


After touching on Monads in respect to functional programming, does the feature actually make a language pure, or is it just another "get out of jail free card" for reasoning of computer systems in the real world, outside of blackboard maths?

EDIT:

This is not flame bait as someone has said in this post, but a genuine question that I am hoping that someone can shoot me down with and say, proof, it is pure.

Also I am looking at the question with respect to other not so pure Functional Languages and some OO languages that use good design and comparing the purity. So far in my very limited world of FP, I have still not groked the Monads purity, you will be pleased to know however I do like the idea of immutability which is far more important in the purity stakes.

解决方案

Take the following mini-language:

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

Get f means: read a character c, and perform action f c.

Put c a means: write character c, and perform action a.

Here's a program that prints "xy", then asks for two letters and prints them in reverse order:

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

You can manipulate such programs. For example:

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

This is has type Action -> Action - it takes a program and gives another program that asks for confirmation first. Here's another:

printString = foldr Put End

This has type String -> Action - it takes a string and returns a program that writes the string, like

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

IO in Haskell works similarily. Although executing it requires performing side effects, you can build complex programs without executing them, in a pure way. You're computing on descriptions of programs (IO actions), and not actually performing them.

In language like C you can write a function void execute(Action a) that actually executed the program. In Haskell you specify that action by writing main = a. The compiler creates a program that executes the action, but you have no other way to execute an action (aside dirty tricks).

Obviously Get and Put are not only options, you can add many other API calls to the IO data type, like operating on files or concurrency.

Adding a result value

Now consider the following data type.

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

The previous Action type is equivalent to IO (), i.e. an IO value which always returns "unit", comparable to "void".

This type is very similar to Haskell IO, only in Haskell IO is an abstract data type (you don't have access to the definition, only to some methods).

These are IO actions which can end with some result. A value like this:

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

has type IO Int and is corresponding to a C program:

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

Evaluation and execution

There's a difference between evaluating and executing. You can evaluate any Haskell expression, and get a value; for example, evaluate 2+2 :: Int into 4 :: Int. You can execute Haskell expressions only which have type IO a. This might have side-effects; executing Put 'a' (End 3) puts the letter a to screen. If you evaluate an IO value, like this:

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

you get:

Put 'A' (End 0)

But there are no side-effects - you only performed an evaluation, which is harmless.

How would you translate

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

into an IO value?

Fix some character, say 'v'. Now comp('v') is an IO action, which compares given character to 'v'. Similarly, comp('b') is an IO action, which compares given character to 'b'. In general, comp is a function which takes a character and returns an IO action.

As a programmer in C, you might argue that comp('b') is a boolean. In C, evaluation and execution are identical (i.e they mean the same thing, or happens simultaneously). Not in Haskell. comp('b') evaluates into some IO action, which after being executed gives a boolean. (Precisely, it evaluates into code block as above, only with 'b' substituted for x.)

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

Now, comp 'b' evaluates into Get (\y -> if 'b' > y then Put '>' (End True) else Put '<' (End False)).

It also makes sense mathematically. In C, int f() is a function. For a mathematician, this doesn't make sense - a function with no arguments? The point of functions is to take arguments. A function int f() should be equivalent to int f. It isn't, because functions in C blend mathematical functions and IO actions.

First class

These IO values are first-class. Just like you can have a list of lists of tuples of integers [[(0,2),(8,3)],[(2,8)]] you can build complex values with IO.

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

A tuple of IO actions: first reads a character and prints it uppercase, second reads a character and returns it lowercase.

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

An IO value, which reads a character x and ends, returning an IO value which writes x to screen.

Haskell has special functions which allow easy manipulation of IO values. For example:

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

which takes a list of IO actions, and returns an IO action which executes them in sequence.

Monads

Monads are some combinators (like conditionally above), which allow you to write programs more structurally. There's a function that composes of type

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

which given IO a, and a function a -> IO b, returns a value of type IO b. If you write first argument as a C function a f() and second argument as b g(a x) it returns a program for g(f(x)). Given above definition of Action / IO, you can write that function yourself.

Notice monads are not essential to purity - you can always write programs as I did above.

Purity

The essential thing about purity is referential transparency, and distinguishing between evaluation and execution.

In Haskell, if you have f x+f x you can replace that with 2*f x. In C, f(x)+f(x) in general is not the same as 2*f(x), since f could print something on the screen, or modify x.

Thanks to purity, a compiler has much more freedom and can optimize better. It can rearrange computations, while in C it has to think if that changes meaning of the program.

这篇关于Haskell是否真的是纯粹的(任何处理系统外部输入和输出的语言)?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆