Mal*_*lio 35 python monads functional-programming
像我之前的许多愚蠢的先驱一样,我正在努力穿越理解Monads的无轨荒地.
我仍然蹒跚而行,但我不禁注意到Python的with
声明具有某种类似monad的质量.考虑这个片段:
with open(input_filename, 'r') as f:
for line in f:
process(line)
Run Code Online (Sandbox Code Playgroud)
将open()
调用视为"单元",将块本身视为"绑定".实际的monad没有暴露(呃,除非f
是monad),但模式就在那里.不是吗?或者我只是误将所有FP误认为是monadry?或者它只是凌晨3点,似乎有什么可信的?
一个相关的问题:如果我们有monad,我们是否需要例外?
在上面的片段中,I/O中的任何故障都可以从代码中隐藏.磁盘损坏,缺少指定文件和空文件都可以被视为相同.因此不需要可见的IO异常.
当然,Scala的Option
类型类已经消除了可怕的Null Pointer Exception
.如果你重新考虑作为Monads的数字(有NaN
和DivideByZero
作为特殊情况)......
就像我说的,早上3点.
out*_*tis 23
这几乎是微不足道的,但第一个问题是它with
不是一个函数,也没有把函数作为一个参数.您可以通过编写函数包装器轻松解决此问题with
:
def withf(context, f):
with context as x:
f(x)
Run Code Online (Sandbox Code Playgroud)
由于这是如此微不足道,你无法分辨withf
和with
.
with
作为一个monad 的第二个问题是,作为一个语句而不是一个表达式,它没有一个值.如果你可以给它一个类型,它将是M a -> (a -> None) -> None
(这实际上是withf
上面的类型).实际上,您可以使用Python _
来获取with
语句的值.在Python 3.1中:
class DoNothing (object):
def __init__(self, other):
self.other = other
def __enter__(self):
print("enter")
return self.other
def __exit__(self, type, value, traceback):
print("exit %s %s" % (type, value))
with DoNothing([1,2,3]) as l:
len(l)
print(_ + 1)
Run Code Online (Sandbox Code Playgroud)
由于withf
使用函数而不是代码块,替代方法_
是返回函数的值:
def withf(context, f):
with context as x:
return f(x)
Run Code Online (Sandbox Code Playgroud)
还有另一件事阻止with
(和withf
)成为monadic绑定.块的值必须是monadic类型,具有与项目相同的类型构造函数with
.实际上,with
更通用.考虑到AGF的注意,每个接口类型构造,我挂的类型with
为M a -> (a -> b) -> b
,其中M是上下文管理器界面(__enter__
和__exit__
方法).在类型之间bind
和with
类型M a -> (a -> N b) -> N b
.要成为一个单子,with
必须在运行时出现故障b
是不是M a
.而且,虽然你可以with
单独使用绑定操作,但这样做很少有意义.
你需要做出这些微妙区别的原因是,如果你错误地认为with
是monadic,你最终会误用它并编写因类型错误而失败的程序.换句话说,你会写垃圾.你需要做的是区分一个特定事物的构造(例如monad)和一个可以以那个东西的方式使用的构造(例如,monad).后者需要程序员的纪律,或者强制执行纪律的其他结构的定义.这是一个几乎是monadic版本with
(类型是M a -> (a -> b) -> M b
):
def withm(context, f):
with context as x:
return type(context)(f(x))
Run Code Online (Sandbox Code Playgroud)
在最后的分析中,你可以认为with
它就像一个组合子,但比monad所需的组合子(它是绑定的)更通用.使用monad可以有更多的函数而不是所需的两个函数(例如,list monad也有cons,append和length),所以如果你为上下文管理器定义了适当的绑定操作符(例如withm
),那么with
就可以是monadic涉及单子.
agf*_*agf 11
是.
在面向对象的编程术语中,类型构造将对应于monadic类型的声明,单元函数扮演构造函数方法的角色,绑定操作包含执行其注册的回调(monadic函数)所必需的逻辑.
这对我来说听起来与上下文管理器协议,对象的上下文管理器协议的实现以及with
语句完全相同.
来自@Owen对此帖的评论:
Monads,在最基本的层面上,或多或少是一种使用延续传递风格的很酷的方式:>> =需要一个"制作者"和一个"回调"; 这基本上也是这样的:像open(...)这样的生产者和一旦创建它就被调用的代码块.
完整的维基百科定义:
一种类型构造,为每种基础类型定义如何获得相应的monadic类型.在Haskell的表示法中,monad的名称表示类型构造函数.如果M是monad的名称而t是数据类型,则"M t"是monad中的对应类型.
这听起来像我的上下文管理器协议.
一个单元函数,它将基础类型中的值映射到相应monadic类型中的值.结果是相应类型中的"最简单"值,其完全保留原始值(简单性被适当地理解为monad).在Haskell中,由于在后面描述的标记中使用它的方式,该函数被称为返回.单位函数具有多态类型t→M t.
对象实际执行上下文管理器协议.
多态类型(M t)→(t→M u)→(M u)的绑定操作,其中Haskell由中缀运算符表示>> =.它的第一个参数是monadic类型的值,它的第二个参数是一个函数,它从第一个参数的基础类型映射到另一个monadic类型,其结果是在其他monadic类型中.
这相当于在with
声明及其套件.
是的,我会说with
是一个单子.我搜索了PEP 343以及所有相关的拒绝和撤回的PEP,并且他们都没有提到"monad"这个词.这当然适用,但它似乎对目标的的with
声明是资源管理,单子只是为了得到它的有效途径.
Haskell有一个等效的with
文件,它被称为withFile
.这个:
with open("file1", "w") as f:
with open("file2", "r") as g:
k = g.readline()
f.write(k)
Run Code Online (Sandbox Code Playgroud)
相当于:
withFile "file1" WriteMode $ \f ->
withFile "file2" ReadMode $ \g ->
do k <- hGetLine g
hPutStr f k
Run Code Online (Sandbox Code Playgroud)
现在,withFile
可能看起来像monadic.它的类型是:
withFile :: FilePath -> IOMode -> (Handle -> IO r) -> IO r
Run Code Online (Sandbox Code Playgroud)
右侧看起来像(a -> m b) -> m b
.
另一个相似之处:在Python中你可以跳过as
,在Haskell中你可以使用>>
而不是>>=
(或者,do
没有<-
箭头的块).
所以我会回答这个问题:是withFile
monadic吗?
你可以认为它可以像这样写:
do f <- withFile "file1" WriteMode
g <- withFile "file2" ReadMode
k <- hGetLine g
hPutStr f k
Run Code Online (Sandbox Code Playgroud)
但这不是类型检查.它不能.
这是因为在Haskell中,IO monad是顺序的:如果你写的话
do x <- a
y <- b
c
Run Code Online (Sandbox Code Playgroud)
之后a
被执行,b
被执行,然后c
.最后没有"回溯"清理a
或类似的东西.withFile
另一方面,在块执行后必须关闭句柄.
还有另一个monad,叫做continuation monad,允许做这样的事情.但是,你现在有两个monad,IO和continuation,并且同时使用两个monad的效果需要使用monad变换器.
import System.IO
import Control.Monad.Cont
k :: ContT r IO ()
k = do f <- ContT $ withFile "file1" WriteMode
g <- ContT $ withFile "file2" ReadMode
lift $ hGetLine g >>= hPutStr f
main = runContT k return
Run Code Online (Sandbox Code Playgroud)
那很难看.所以答案是:在某种程度上,但这需要处理许多细微之处,使问题变得相当不透明.
Python with
只能模拟monad可以做的有限的一点 - 添加输入和完成代码.我不认为你可以模拟,例如
do x <- [2,3,4]
y <- [0,1]
return (x+y)
Run Code Online (Sandbox Code Playgroud)
使用with
(可能有一些肮脏的黑客攻击).相反,用于:
for x in [2,3,4]:
for y in [0,1]:
print x+y
Run Code Online (Sandbox Code Playgroud)
并且有一个Haskell函数 - forM
:
forM [2,3,4] $ \x ->
forM [0,1] $ \y ->
print (x+y)
Run Code Online (Sandbox Code Playgroud)
我推荐阅读yield
哪些与monads更相似的内容with
:http:
//www.valuedlessons.com/2008/01/monads-in-python-with-nice-syntax.html
一个相关的问题:如果我们有monad,我们是否需要例外?
基本上没有,而不是抛出A或返回B的函数,你可以创建一个返回的函数Either A B
.然后monad Either A
将表现得像异常 - 如果一行代码将返回错误,整个块将会.
但是,这意味着除法将具有类型Integer -> Integer -> Either Error Integer
等等,以便将除法除以零.您必须在使用除法的任何代码中检测错误(显式模式匹配或使用绑定),或者甚至可能出错.Haskell使用异常来避免这样做.
归档时间: |
|
查看次数: |
3159 次 |
最近记录: |