Python是"与"monadic?

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的数字(有NaNDivideByZero作为特殊情况)......

就像我说的,早上3点.

out*_*tis 23

这几乎是微不足道的,但第一个问题是它with不是一个函数,也没有把函数作为一个参数.您可以通过编写函数包装器轻松解决此问题with:

def withf(context, f):
    with context as x:
        f(x)
Run Code Online (Sandbox Code Playgroud)

由于这是如此微不足道,你无法分辨withfwith.

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的注意,每个接口类型构造,我挂的类型withM a -> (a -> b) -> b,其中M是上下文管理器界面(__enter____exit__方法).在类型之间bindwith类型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声明是资源管理,单子只是为了得到它的有效途径.

  • Python有__duck typing__.类型无关紧要 - 协议和接口都可以.所以任何协议或接口本质上都是一种类型 - 你会在整个Guido的着作和Python文档中找到这种哲学.所以在Python中__protocol本身___的实现是一种类型构造,它由对象实现它是一个单元函数,而由`with`语句使用它是一个绑定操作. (8认同)
  • 尼斯.我还想补充一点,在最基本的层面上,monad或多或少是一种使用延续传递方式的很酷的方式:>> =需要一个"生产者"和一个"回调"; 这也基本上就是`with`:一个像`open(...)`这样的生产者,以及一旦创建它就被调用的代码块. (5认同)
  • 坚持`>> =`取一个'a - > M b`就是它的monadic.如果它改为使用'a - > b`,它将是一个组合器,这更为通用.Monads都是关于增强类型的. (2认同)

sdc*_*vvc 9

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没有<-箭头的块).

所以我会回答这个问题:是withFilemonadic吗?

你可以认为它可以像这样写:

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使用异常来避免这样做.