WeN*_*ers 35 monads haskell functional-programming referential-transparency
在功能编程方面触及Monads之后,该功能是否实际上使语言变得纯粹,或者它是否只是另一个"从监狱免费卡中获取"来推理现实世界中的计算机系统,在黑板数学之外?
编辑:
这不是有人在这篇文章中所说过的火焰诱饵,而是一个真正的问题,我希望有人可以用枪击我说,证明,这是纯粹的.
此外,我正在研究关于其他不那么纯粹的功能语言和一些使用良好设计和比较纯度的OO语言的问题.到目前为止,在我非常有限的FP世界中,我仍然没有理解Monads的纯度,你会很高兴地知道我喜欢不变性的想法,这在纯度赌注中更为重要.
sdc*_*vvc 74
采用以下迷你语言:
data Action = Get (Char -> Action) | Put Char Action | End
Run Code Online (Sandbox Code Playgroud)
Get f
意思是:读取一个字符c
,然后执行操作f c
.
Put c a
意思是:写字符c
,并执行操作a
.
这是一个打印"xy"的程序,然后请求两个字母并以相反的顺序打印它们:
Put 'x' (Put 'y' (Get (\a -> Get (\b -> Put b (Put a End)))))
Run Code Online (Sandbox Code Playgroud)
你可以操纵这样的程序.例如:
conditionally p = Get (\a -> if a == 'Y' then p else End)
Run Code Online (Sandbox Code Playgroud)
这是类型Action -> Action
- 它需要一个程序,并提供另一个程序,首先要求确认.这是另一个:
printString = foldr Put End
Run Code Online (Sandbox Code Playgroud)
这有类型String -> Action
- 它需要一个字符串并返回一个写入字符串的程序,如
Put 'h' (Put 'e' (Put 'l' (Put 'l' (Put 'o' End))))
.
Haskell中的IO类似地工作.虽然执行它需要执行副作用,但您可以以纯粹的方式构建复杂的程序而不执行它们.您正在计算程序描述(IO操作),而不是实际执行它们.
在像C这样的语言中,你可以编写一个void execute(Action a)
实际执行程序的函数.在Haskell中,您可以通过编写来指定该操作main = a
.编译器创建一个执行操作的程序,但是没有其他方法可以执行操作(除了脏技巧).
显然Get
,Put
不仅是选项,您还可以向IO数据类型添加许多其他API调用,例如对文件或并发操作.
添加结果值
现在考虑以下数据类型.
data IO a = Get (Char -> Action) | Put Char Action | End a
Run Code Online (Sandbox Code Playgroud)
前Action
一种类型相当于IO ()
,即始终返回"单位"的IO值,与"void"相当.
这种类型与Haskell IO非常相似,仅在Haskell IO中是一种抽象数据类型(您无法访问定义,只能访问某些方法).
这些是IO操作,可以以某些结果结束.像这样的值:
Get (\x -> if x == 'A' then Put 'B' (End 3) else End 4)
Run Code Online (Sandbox Code Playgroud)
有类型IO Int
并且对应于C程序:
int f() {
char x;
scanf("%c", &x);
if (x == 'A') {
printf("B");
return 3;
} else return 4;
}
Run Code Online (Sandbox Code Playgroud)
评估和执行
评估和执行之间存在差异.您可以评估任何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)
Run Code Online (Sandbox Code Playgroud)
你得到:
Put 'A' (End 0)
Run Code Online (Sandbox Code Playgroud)
但是没有副作用 - 你只进行了评估,这是无害的.
你会怎么翻译?
bool comp(char x) {
char y;
scanf("%c", &y);
if (x > y) { //Character comparison
printf(">");
return true;
} else {
printf("<");
return false;
}
}
Run Code Online (Sandbox Code Playgroud)
进入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))
Run Code Online (Sandbox Code Playgroud)
现在,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)
Run Code Online (Sandbox Code Playgroud)
IO动作元组:首先读取一个字符并将其打印为大写,然后读取一个字符并将其返回小写.
Get (\x -> End (Put x (End 0))) :: IO (IO Int)
Run Code Online (Sandbox Code Playgroud)
一个IO值,它读取一个字符x
并结束,返回写入x
屏幕的IO值.
Haskell具有特殊功能,可以轻松操作IO值.例如:
sequence :: [IO a] -> IO [a]
Run Code Online (Sandbox Code Playgroud)
它接受IO动作列表,并返回一个按顺序执行它们的IO动作.
单子
Monads是一些组合器(conditionally
如上所述),它允许您更结构地编写程序.有一个由类型组成的函数
IO a -> (a -> IO b) -> IO b
Run Code Online (Sandbox Code Playgroud)
给定IO a,函数a - > IO b,返回类型IO b的值.如果你把第一个参数写成C函数a f()
,第二个参数b g(a x)
返回一个程序g(f(x))
.鉴于Action/IO的上述定义,您可以自己编写该函数.
注意monad对纯度不是必不可少的 - 你可以像我上面那样编写程序.
纯度
关于纯度的基本要素是参考透明度,并区分评估和执行.
在Haskell中,如果你有,f x+f x
你可以替换它2*f x
.在C中,f(x)+f(x)
一般不一样2*f(x)
,因为f
可以在屏幕上打印或修改x
.
由于纯度,编译器具有更多的自由度并且可以更好地进行优化.它可以重新排列计算,而在C中,它必须考虑是否会改变程序的含义.
关于计算机系统"在黑板数学之外"的理由是什么意思?会是什么样的推理?航位推算?
副作用和纯函数是一个观点问题.如果我们将名义上的副作用函数视为将我们从世界的一个状态带到另一个世界的函数,那么它又是纯粹的.
我们可以通过赋予它第二个论点,一个世界,并且要求它在完成时通过我们一个新的世界,使每个副作用函数变得纯粹.我根本不知道C++
但是说read
有这样的签名:
vector<char> read(filepath_t)
Run Code Online (Sandbox Code Playgroud)
在我们新的"纯粹风格"中,我们这样处理:
pair<vector<char>, world_t> read(world_t, filepath_t)
Run Code Online (Sandbox Code Playgroud)
事实上,这是每个Haskell IO动作的工作原理.
所以现在我们有了一个纯粹的IO模型.谢天谢地.如果我们不能这样做,那么Lambda微积分和图灵机可能不是等同的形式,然后我们会做一些解释.我们还没有完成,但留给我们的两个问题很简单:
是什么在变world_t
结构?每一粒沙子,一片草叶,破碎的心和金色的夕阳的描述?
我们有一个非正式的规则,我们只使用一个世界 - 在每次IO操作之后,我们抛弃我们使用它的世界.然而,随着所有这些世界的流动,我们必然会把它们混为一谈.
第一个问题很简单.只要我们不允许检查世界,事实证明我们不必为将任何东西存入其中而烦恼.我们只需要确保一个新的世界不等于任何以前的世界(以免编制者狡猾地优化一些世界生产的操作,就像它有时一样C++
).有很多方法可以解决这个问题.
至于世界变得混乱,我们想隐藏在图书馆内传递的世界,这样就无法进入世界,因此无法将它们混淆起来.事实证明,monads是一种在计算中隐藏"侧通道"的好方法.输入IO monad.
前段时间,在Haskell邮件列表上询问了像你这样的问题,我更详细地进入了"侧通道".这是Reddit线程(链接到我原来的电子邮件):
http://www.reddit.com/r/haskell/comments/8bhir/why_the_io_monad_isnt_a_dirty_hack/
归档时间: |
|
查看次数: |
4337 次 |
最近记录: |