【问题标题】:Functional style with conditional returns in pythonpython中具有条件返回的功能样式
【发布时间】:2021-06-12 22:31:54
【问题描述】:

假设我们有一个结构如下的函数:

def f():
    # ...
    # some computations
    # ...
    if something_is_wrong_with_previous_computations:
        return None
    # ...
    # some other computations
    # ...
    if something_is_wrong_with_previous_computations2:
        return some_variable
    #...
    return result

在我看来,在函数中间使用 return 语句根本不起作用。如果我们使用某种 lispy 语言,我们将拥有 let(然后可以使用 let* 编写计算)语句,这将帮助我们轻松处理这些情况。不幸的是,我们这里没有。我们该怎么办?

  • 模拟let 创建大量嵌套函数并就地调用它们?
  • 使用 Maybe monad 之类的东西还是其他类似的复杂东西?
  • 不要浪费我们的时间并强制编写它?
  • 还有别的吗?

【问题讨论】:

  • 你能举一个例子来说明你将如何在 LISP(任何方言)中做到这一点吗?我很好奇,因为我不知道 let 在这种情况下如何提供帮助并且想知道。
  • @YevhenKuzmovych “为了伟大的正义” ©。开个玩笑,更多的是为了学习,然后是为了生产
  • @jbmeerkat 好的,我同意,这不是关于let,而是let*(方案):```(定义(f)(let*([v1 #| some-computation | #] [v2 #| some-computation-2 |#] #|etc|#) (if wrong1 Null (let* (#|some-more-computations|#) (if wrong2 some-var (let* ... )))))) ```(我希望你能读懂)))
  • 据我了解,Scheme 中的示例与您在 Python 中的问题中编写的代码非常相似,但返回除外。我认为这里的关键区别在于,在 Scheme 中“返回”是隐式的,但在 Python 中它们是严格明确的。您可以在 Python 中通过单次返回来执行此操作,但如果会更像 C 风格,则无法使用 pastebin.com/xgGD0iBn
  • 不同之处在于 Lisps 是表达式语言:Python 不是。我的观点是,如果您尝试用非表达式语言编写程序,就好像它们是表达式语言一样……使用一种表达式语言比尝试将另一种语言扭曲成一种语言更容易。

标签: python functional-programming lisp


【解决方案1】:

函数中的return 语句anywhere 不起作用。

在 Python 中,你别无选择。

Lisp 代码

(defun sgnum (x)
  (cond
    ((< x 0) -1)
    ((zerop x) 0)
   (t 1)))

变成 Python 为

def sgnum(x):
  if x < 0:
    return -1
  elif x == 0:
    return 0
  else:
    return 1

在 Lisp 中,当我们使用变量赋值或“程序特性”时,我们知道我们已经偏离了函数式编码:显式的 progn 构造,或隐式的等价物,或者像 prog 或 @ 这样的表亲987654326@。 Lisp 中的函数函数总是有一个由单个表达式(或者可能根本没有表达式)组成的主体。

您可以重新定义 Python 中“函数式编码”的含义。这些怎么样 规则:

  1. “功能函数”中的每条语句都必须是一条语句;它后面不能有另一个语句。因此,一个函数的整个主体是一条语句,其中嵌入了单个语句。

  2. 函数中的任何语句都不允许控制通过它。每个语句都必须返回。因此return 不仅被认为是“功能性的”,而且对于实现这一目标至关重要。

  3. 一个变量可以被定义,但不能被重新定义。并行的、互斥的控制流可以为同一个变量分配不同的值,但在同一个控制流中不能多次分配变量。

使用这些规则,您可以让程序拥有类似于纯 Lisp 风格的程序的控制流图:控制图基本上是具有嵌入式计算的决策树和变量绑定,其叶子是要返回的值。

说到变量绑定,我们大概应该有第四条规则:

  1. 语句前面可能有一系列不包含副作用的新变量赋值。这样一个序列,连同它后面的语句,算作一个语句。

可以说也是第五个:

  1. 不得使用对任何包含的表达式或语句进行多次计算的语句。

否则,我们允许循环,这些循环不起作用。这很棘手,因为一些循环结构的行为相对良好,例如隐式地将虚拟变量跨过列表的元素。您可以判断它不起作用的唯一方法是,在循环中捕获的词法闭包很容易显示只有一个变量被变异,而不是每次迭代都绑定一个新变量。

根据这些规则,sgnum 是“功能性的”:它只包含一个 if/elif/else 语句,不允许控制通过它:每个分支都返回:

这个版本的sgnum 不再是“功能性”了:

def sgnum(x):
  if x < 0:
    return -1
  if x == 0:
    return 0
  return 1

它依次包含三个语句。而以下是“功能性的”,尽管它也包含三个语句:

def distance(x0, y0, x1, y1):
  xd = x1 - x0
  yd = y1 - y0
  return math.sqrt(xd * xd + yd * yd)

这些符合规则。前两个语句绑定新变量,符合规则 3,因此 4 允许在语句之前。 return 语句符合规则 1 和 2。这非常类似于:

(defun distance (x0 y0 x1 y1)
  (let ((xd (- x1 x0))
        (yd (- y1 y0)))
    (sqrt (+ (* xd xd) (* yd yd)))))

最后,请注意我们的规则与“函数中只有一个出口点”的古老编程建议有何不同。您可能在某些编码约定中发现的那个小花絮是相当反功能的。要在非平凡函数中实现单点返回,需要通过多个语句和/或变量赋值的命令式控制流。从功能上看,这是一条短视的、愚蠢的规则;但在推荐它的上下文中它是有意义的,因为它可以帮助改进结构非常糟糕的命令式代码。

【讨论】:

  • 您的约定似乎很合理。通过从 if 的返回,我们真的在模仿来自 Lisp 的 if。虽然我会争论第五条规则。我们可以在下面的语句中概括这一点:我们必须允许 Python 中的循环,否则任何非平凡的逻辑都不能用这种语言表达。 Maps 和 reduce 很好,但有时它们完全不够(就像任何高阶函数一样)。至少我看不出如何使用它们而不造成巨大的混乱。在函数式语言中,我们会对这样的情况进行尾递归。但我们没有。
猜你喜欢
  • 1970-01-01
  • 2021-01-15
  • 1970-01-01
  • 1970-01-01
  • 2021-06-08
  • 2019-02-21
  • 1970-01-01
  • 2021-11-09
  • 1970-01-01
相关资源
最近更新 更多