hei*_*wol 1 lisp python functional-programming
假设我们有一个具有以下结构的函数:
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
Run Code Online (Sandbox Code Playgroud)
在我看来,return
在函数中间使用语句根本不起作用。如果我们使用某种 lispy 语言,我们将拥有let
(然后可以用let*
)语句编写计算,这将帮助我们轻松处理这些情况。不幸的是,我们这里没有。我们应该做什么?
let
创建大量嵌套函数并就地调用它们?Maybe
monad 或其他类似的复杂东西?函数中任何位置的return
语句都不起作用。
在 Python 中,您别无选择。
Lisp 代码
(defun sgnum (x)
(cond
((< x 0) -1)
((zerop x) 0)
(t 1)))
Run Code Online (Sandbox Code Playgroud)
变成 Python 作为
(defun sgnum (x)
(cond
((< x 0) -1)
((zerop x) 0)
(t 1)))
Run Code Online (Sandbox Code Playgroud)
在 Lisp 中,当我们使用变量赋值或“程序特性”时,我们知道我们已经偏离了函数式编码:显式progn
构造,或隐式等价物,或任何类似prog
或 的表亲prog1
。Lisp 中的函数函数总是有一个由单个表达式(或者可能根本没有表达式)组成的函数体。
您可以重新定义 Python 中“函数式编码”的含义。这些规则如何:
“函数式函数”中的每个语句都必须是单个语句;它后面不能有另一个声明。因此,整个函数体是一个单一的语句,其中嵌入了单一的语句。
函数中的任何语句都不允许控制通过它。每个语句都必须返回。因此return
,不仅被认为是“功能性的”,而且对实现这一目标至关重要。
一个变量可以被定义,但不能被重新定义。并行、互斥的控制流可以为同一变量分配不同的值,但在同一控制流中不能多次分配变量。
有了这些类型的规则,你可以让程序拥有一个类似于纯 Lisp 风格的程序的控制流图:一个控制图基本上是一个带有嵌入式计算和变量绑定的决策树,其叶子是要返回的值。
说到变量绑定,我们大概应该有第四条规则:
可以说也是第五个:
否则,我们允许循环,这是不起作用的。这很棘手,因为一些循环结构的表现相对较好,例如在列表元素上隐式步进一个虚拟变量。您可以判断它不起作用的唯一方法是,循环中捕获的词法闭包很容易显示只有一个变量发生了变异,而不是每次迭代都绑定一个新变量。
根据这些规则,它sgnum
是“功能性的”:它只包含一个if/elif/else
语句,不允许控制通过它:每个分支返回:
这个版本sgnum
不再是“功能性的”:
def sgnum(x):
if x < 0:
return -1
elif x == 0:
return 0
else:
return 1
Run Code Online (Sandbox Code Playgroud)
它依次包含三个语句。而以下是“功能性的”,即使它也包含三个语句:
def sgnum(x):
if x < 0:
return -1
if x == 0:
return 0
return 1
Run Code Online (Sandbox Code Playgroud)
这些都符合规则。前两个语句绑定新变量,满足规则 3,因此允许 4 在语句之前。return 语句满足规则 1 和 2。这非常类似于:
(defun distance (x0 y0 x1 y1)
(let ((xd (- x1 x0))
(yd (- y1 y0)))
(sqrt (+ (* xd xd) (* yd yd)))))
Run Code Online (Sandbox Code Playgroud)
最后,请注意我们的规则与“在一个函数中只有一个退出点”的古老编程建议有何不同。您可能会在某些编码约定中发现的那些小花絮是非常反功能的。要在非平凡函数中实现单点返回,需要通过多个语句和/或变量赋值进行命令式控制流。从功能上看,这是一个近视、愚蠢的规则;但是在推荐使用的上下文中它是有意义的,因为它可以帮助改进结构非常糟糕的命令式代码。