Python的“with”是一元的吗?

2024-10-01 17:40:55 发布

您现在位置:Python中文网/ 问答频道 /正文

像我前面许多勇敢的先驱一样,我正在努力穿越无路的荒原,那就是理解单胞菌。在

我仍在蹒跚学步,但我还是忍不住注意到Python的with语句有某种类似monad的特性。考虑一下这个片段:

with open(input_filename, 'r') as f:
   for line in f:
       process(line)

open()调用视为“unit”,将块本身视为“bind”。实际的monad没有公开(呃,除非f是monad),但是模式是存在的。不是吗?还是我把所有的FP都误认为是monadry?或者只是凌晨3点什么都有可能吗?在

一个相关的问题:如果我们有单子,我们需要例外吗?在

在上面的片段中,I/O中的任何失败都可以从代码中隐藏。磁盘损坏、没有命名文件和空文件都可以被相同地处理。所以不需要一个可见的IO异常。在

当然,Scala的Option类型类消除了令人恐惧的Null Pointer Exception。如果您将数字重新考虑为单子(特殊情况下,NaN和{}为特殊情况)。。。在

就像我说的,凌晨3点。在


Tags: 文件inputwithline情况特性语句open
3条回答

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

def withf(context, f):
    with context as x:
        f(x)

因为这太微不足道了,所以您不必费心区分withf和{}。在

with是monad的第二个问题是,作为语句而不是表达式,它没有值。如果可以给它一个类型,它将是M a -> (a -> None) -> None(这实际上是上面withf的类型)。实际上,您可以使用Python的_来获取with语句的值。在Python 3.1中:

^{pr2}$

由于withf使用的是函数而不是代码块,_的另一种方法是返回函数的值:

def withf(context, f):
    with context as x:
        return f(x)

还有一件事阻止with(和withf)成为一元绑定。块的值必须是与with项具有相同类型构造函数的一元类型。实际上,with更通用。考虑到agf指出每个接口都是一个类型构造函数,我将with的类型标记为M a -> (a -> b) -> b,其中M是上下文管理器接口(__enter____exit__方法)。在bind和{}类型之间是M a -> (a -> N b) -> N b。要成为monad,with必须在运行时失败,而b不是{}。此外,虽然可以单独使用with作为绑定操作,但这样做几乎没有意义。在

您需要进行这些细微区分的原因是,如果您错误地认为with是一元的,那么您将错误地使用它并编写由于类型错误而失败的程序。换句话说,你会写垃圾。你需要做的是区分一个特定事物的结构(例如,一个单子)和一个可以以该事物的方式使用的结构(例如,一个单子)。后者要求程序员遵守纪律,或者定义额外的构造来执行规程。以下是with(类型是M a -> (a -> b) -> M b)的一个几乎一元的版本:

def withm(context, f):
    with context as x:
        return type(context)(f(x))

在最后的分析中,您可以将with看作是一个组合子,但它比monads所要求的组合子(bind)更通用。使用monad的函数可能多于所需的两个函数(例如,list monad还具有cons、append和length),因此,如果您为上下文管理器定义了适当的绑定运算符(例如withm),那么{}就涉及monad的意义而言可能是一元的。在

Haskell对文件有一个等价的with,它被称为withFile。这个:

with open("file1", "w") as f:
    with open("file2", "r") as g:
        k = g.readline()
        f.write(k)

相当于:

^{pr2}$

现在,withFile可能看起来像是一元的东西。其类型为:

withFile :: FilePath -> IOMode -> (Handle -> IO r) -> IO r

右边看起来像(a -> m b) -> m b。在

在cd6{cd6}中,你可以使用另一个

所以我要回答这个问题:withFile是一元的吗?在

你可以这样写:

do f <- withFile "file1" WriteMode
   g <- withFile "file2" ReadMode
   k <- hGetLine g
   hPutStr f k

但这不是类型检查。但它不能。在

这是因为在Haskell中IO monad是连续的

do x <- a
   y <- b
   c

在执行a之后,b被执行,然后c。没有“回头路” 最后清理a或类似的东西。withFile另一方面, 必须在执行块后关闭句柄。在

还有另一个monad,叫做continuation monad,它允许这样做 东西。但是,现在有两个单子,IO和continuations,同时使用两个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

太难看了。因此,答案是:有点,但这需要处理许多微妙的问题,使问题变得相当不透明。在

Python的with只能模拟monad所能做的有限的一部分——添加输入和终结代码。我不认为你能模拟

do x <- [2,3,4]
   y <- [0,1]
   return (x+y)

使用with(这可能与一些肮脏的黑客)。相反,用于:

for x in [2,3,4]:
    for y in [0,1]:
        print x+y

这里有一个Haskell函数-forM

forM [2,3,4] $ \x ->
  forM [0,1] $ \y ->
    print (x+y)

我重新编写了关于yield的阅读,它与单子的相似度比with更为相似: http://www.valuedlessons.com/2008/01/monads-in-python-with-nice-syntax.html

A related question: if we have monads, do we need exceptions?

基本上没有,你可以创建一个返回Either A B的函数,而不是抛出a或返回B的函数。Either A的monad的行为将与异常类似-如果一行代码将返回错误,则整个块将返回错误。在

但是,这意味着除法将使用Integer -> Integer -> Either Error Integer等类型来捕获被零除的操作。在使用除法的任何代码中,都必须检测出错误(显式模式匹配或使用绑定),甚至是最有可能出错的代码。Haskell使用异常来避免这样做。在

是。

在定义的正下方,Wikipedia says

In object-oriented programming terms, the type construction would correspond to the declaration of the monadic type, the unit function takes the role of a constructor method, and the binding operation contains the logic necessary to execute its registered callbacks (the monadic functions).

在我看来,这与上下文管理器协议、对象对上下文管理器协议的实现以及with语句非常相似。在

来自@Owen的评论:

Monads, at their most basic level, are more or less a cool way to use continuation-passing style: >>= takes a "producer" and a "callback"; this is also basically what with is: a producer like open(...) and a block of code to be called once it's created.

完整的维基百科定义:

A type construction that defines, for every underlying type, how to obtain a corresponding monadic type. In Haskell's notation, the name of the monad represents the type constructor. If M is the name of the monad and t is a data type, then "M t" is the corresponding type in the monad.

{a2}听起来像。在

A unit function that maps a value in an underlying type to a value in the corresponding monadic type. The result is the "simplest" value in the corresponding type that completely preserves the original value (simplicity being understood appropriately to the monad). In Haskell, this function is called return due to the way it is used in the do-notation described later. The unit function has the polymorphic type t→M t.

对象对上下文管理器协议的实际实现。在

A binding operation of polymorphic type (M t)→(t→M u)→(M u), which Haskell represents by the infix operator >>=. Its first argument is a value in a monadic type, its second argument is a function that maps from the underlying type of the first argument to another monadic type, and its result is in that other monadic type.

这对应于the ^{} statement及其套件。在

所以是的,我会说with是一个单子。我搜索了PEP 343和所有相关的拒绝和撤销的政治公众人物,没有一个提到“monad”这个词。它当然适用,但似乎with语句的目标是资源管理,而monad只是获得它的一种有用方法。在

相关问题 更多 >

    热门问题