代码之家  ›  专栏  ›  技术社区  ›  lazy python

也许Python中的monad具有方法链接

  •  12
  • lazy python  · 技术社区  · 10 年前

    我正在尝试在python中实现Maybe monad。 然而,我也想要某种链接能力。

    所以我有一节课:

    class Maybe:
        def __init__(self, val):
            self.val = val
    
        def do(self, func):  # Bind function
            if self.val is None:
                return None
            else:
                return func(self.val)
    

    我有两个功能:

    def double(number):
        try:
            result = number * 2
            return Maybe(result)
        except:
            return Maybe(None)
    
    def square(number):
        try:
            result = number * number
            return Maybe(result)
        except:
            return Maybe(None)
    

    下面是我如何使用这个:

     result = Maybe(5).do(double).do(square)
        print(result.val)
    

    我正在寻找一种方法来链接多个函数,每个函数执行一个特定的任务。每个函数都将上一个函数的输出作为输入。如果链中的任何函数引发异常,则链应断开。

    这是模仿Maybe monad的正确方法吗?

    这也是处理异常的正确方法吗?

    这可以改进吗?

    谢谢。

    4 回复  |  直到 10 年前
        1
  •  9
  •   Joe Kington    10 年前

    这样做的缺点是它故意抑制错误,这在python中通常被认为是一个坏主意。

    但是,您可以捕获并存储 Maybe 实例并将其报告回去。

    例如:

    class Maybe(object):
        def __init__(self, val, error=None):
            self.val = val
            self.error = error
    
        def __repr__(self):
            if self.val is not None:
                return repr(self.val)
            else:
                return repr(self.error)
    
        def do(self, func):
            if self.val is None:
                return self
            try:
                return Maybe(func(self.val))
            except Exception as e:
                return Maybe(None, e)
    
    def squared(x):
        return x * x
    
    def addone(x):
        return x + 1
    
    result1 = Maybe(5).do(squared).do(addone)
    result2 = Maybe('a').do(squared).do(addone)
    print result1
    print result2
    

    这产生了:

    26
    TypeError("can't multiply sequence by non-int of type 'str'",)
    

    这与DanD的答案类似,但其优点是存储发生的错误,而不是完全抑制错误。

    无论你如何划分它,这个习惯用法都会感觉有些“不和谐”,但这是一种稍微更稳健的处理方法。

        2
  •  6
  •   Dan D.    10 年前

    以下是您所要求的,并让函数更加简洁。还更新了以正确捕获异常:

    class Maybe:
        def __init__(self, val, err=None):
            self.val = val
            self.err = err
    
        def __repr__(self):
            if self.err is not None:
               return 'Maybe('+repr(self.val)+', '+repr(self.err)+')'
            else:
               return 'Maybe('+repr(self.val)+')'
    
        def do(self, func):  # Bind function
            if self.val is not None:
                try:
                    val = func(self.val)
                except Exception as e:
                    return Maybe(None, e)
                if not isinstance(val, Maybe):
                    return Maybe(val)
                else:
                     return val
            else:
                return Maybe(None, self.err)
    
    
    def double(number):
        result = number * 2
        return result
    
    def square(number):
        result = number * number
        return result
    
    result = Maybe(5).do(double).do(square)
    print(result.val)
    print(result)
    result2 = Maybe('a').do(double).do(square)
    print(result2.val)
    print(result2)
    

    打印:

    100
    Maybe(100)
    None
    Maybe(None, TypeError("can't multiply sequence by non-int of type 'str'",))
    
        3
  •  4
  •   FMc TLP    10 年前

    我可能会考虑一些调整:

    • 按照其他人的建议,存储异常。
    • 制作 Maybe 可调用以消除 do .
    • 通过 xs kws 通过调用,以防任何函数采用其他参数。

    例如:

    class Maybe:
        def __init__(self, val, error = None):
            self.val = val
            self.error = error
    
        def __call__(self, func, *xs, **kws):
            v = None
            e = None
            if self.val is not None:
                try:
                    v = func(self.val, *xs, **kws)
                except Exception as e:
                    pass
            return Maybe(v, error = e)
    
    def double(n):
        return n * 2
    
    def square(n):
        return n * n
    
    def multiply_by(n, by):
        return n * by
    
    def message(n, *xs, **kws):
        return 'Message: {} {} {}'.format(n, xs, kws)
    
    ms = [
        Maybe(5)(double)(square),
        Maybe(4)(square)(square)(double),
        Maybe(3)(square)(square),
        Maybe(7)(multiply_by, 3),
        Maybe(9)(message, 1, 2, 3, foo = 'blah'),
        Maybe('hi')(double)(square),
    ]
    
    for m in ms:
        print(dict(val = m.val, error = m.error))
    
        4
  •  4
  •   tday03    10 年前

    这个问题已经得到了回答,但我觉得使用Maybe monad的界面可以变得更易读。允许左右调用的接口,例如:

     square(double(Maybe(5)))
    

    此外,最好向用户返回一个Maybe类型以供使用,这样他们就可以选择进一步链接,而不是立即检索值。该功能类似于Swift的可选类型。

    class Maybe(object):
      def __init__(self, val=None)
         self.val
    
      def unwrap(self):
         return self.val
    
      def calc(self, func):
         try:
           return func(self.val)
         except:
           return None
    
    def double(number):
        return Maybe(maybe.calc(lambda x: x*2))
    
    def square(number):
        return Maybe(maybe.calc(lambda x: x**2))
    
    print square(double(Maybe(5))) # prints Maybe type
    print square(double(Maybe(5))).unwrap() # prints '100'
    print square(double(None)) # prints 'None'
    

    异常不会存储在我的回答中,因为调用者需要 maybe monad 检查异常本身,并在连锁操作失败时转换为“无”(即None)。这允许用户在使用链的条件下轻松处理故障。