函数式编程——python为例

FP-PY

​ 最近在学spark,接触到了scala这门编程语言,学到了许多函数式编程的知识,同时也被scala中各种各样的操作符恶心到了:(::、:+、+:、:::)。确实也很有趣。

​ 由于第一门接触到的语言是python,因此在这里写一些关于python的函数式编程相关知识。理解函数式编程相关概念对你阅读代码的能力会很有帮助,有助于帮助你写出更好的代码。同时,理解函数式编程的相关概念理论,也会让你学习其他编程语言时事半功倍。经典的函数式编程语言像是haskell,scala等,大家有兴趣可以去学习一下这些编程语言。

​ 在这篇文章里,笔者只给出了部分函数式编程的内容,如果你很有兴趣,可以去看补充的内容。在那里给出了几个学习函数式编程的很好的项目。

1.高阶函数 (HOF)

函数是“一等公民”

什么是一等公民呢?当我们说函数是“一等公民”的时候,我们实际上说的是它们和其他对象都一样…所以就是普通公民(坐经济舱的人?)。函数真没什么特殊的,你可以像对待任何其他数据类型一样对待它们——把它们存在数组里,当作参数传递,赋值给变量…等等。

将函数作为参数,或将函数的返回作为参数

  • 函数可以赋值给变量
  • 函数可以作为函数的参数
  • 函数可以作为函数的返回值

demo1:将列表中的整数过滤出来

# 函数作为参数
def filter(func,xs):
    ls = []
    for x in xs:
        if func(x):
            ls.append(x)
    return ls

# 函数作为函数的返回值
def is_a(T):
    def is_T(x):
        if type(x) is T:        # is 操作符是Python语言的一个内建的操作符。它的作用在于比较两个变量是否指向了同一个对象
            return True
    return is_T					# 函数作为函数的返回值
print(filter(is_a(int),[0,'1',2,None]))		# [0,2]

2.闭包

闭包是访问在其作用域外的变量的一种方式。正式地说,闭包是一种用于实现词法作用域命名绑定的技术。它是存储一个函数和它的环境的一种方法。

闭包是一个作用域,它会捕获函数的局部变量,因此即使执行过程已经移出了定义它的那个代码块,也可以访问它们。也就是说,它们允许在声明变量的代码块已经执行完成之后,还是可以引用这个作用域。

简单来说:如果一个函数,访问到了它的外部(局部)变量的值,那么这个函数和他所处的环境叫做闭包

demo2:

def add():
    a = 4
    def add_to_five(b):
        return a+b
    return add_to_five

add_ = add()
print(add_(5))      # or  print(add()(5))

# 这也叫柯里化,柯里化一定有闭包
def add(a):
    def add_to_five(b):
        return a+b
    return add_to_five
print(add(4)(5))

add = lambda x:lambda y:x+y
print(add(4)(5))

函数add()返回了一个函数(在内部调用了add()),我们将它保存在了一个叫做add_to_five的变量中,并且柯里化地用一个参数5来调用它。

理想情况下,当函数add执行完成后,它的作用域,包括本地变量add(即+),x,y,都应该无法访问了。但是,add_to_five()的调用返回了8。这说明,add函数的状态被保存了,即使在代码块已经完成执行之后。否则,就不会知道add曾经被add(5)这样调用过,且x的值被设为了5。

词法作用域(lexical scoping)是它能找到x和add这两个已经完成执行的父级私有变量的原因。这个值就称为闭包。

3.柯里化

将一个多元函数转变为一元函数的过程。 每当函数被调用时,它仅仅接收一个参数并且返回带有一个参数的函数,直到传递完所有的参数。

函数柯里化:把一个参数列表的多个参数,变成多个参数列表(只传递给函数一部分参数来调用它,让它返回一个函数去处理剩下的参数。)

demo:

def sum(a,b):
    return a+b

# 柯里化
def curried_sum(a):
    def curried_sum_(b):
        return a+b
    return curried_sum_

print(sum(4,5))
print(curried_sum(4)(5))

curried_sum_ = curried_sum(4)
print(curried_sum_(5))

自动柯里化

将一个包含多个参数的函数转换成另一个函数,这个函数如果被给到的参数少于正确的数量,就会返回一个接受剩余参数的函数。

from toolz import curry

@curry
def add(a:int,b:int):
    return a+b

print(add(1,2))
print(add(1)(2))
print(add(1))  #(y) = 1+y

4.偏函数

"部分地"应用一个函数,即预设原始函数的部分参数来创建一个新的函数。

5.函数组合

把两个函数放在一起形成第三个函数的行为,一个函数的输入为另一个函数的输出。

demo:


import math
def compose(f,g):
    def compose_(a):
        return f(g(a))
    return compose_

print(compose(str,math.floor)(121.1221))

函数g处理了参数a,将结果给函数f,最终返回结果

6.纯函数

纯函数是这样一种函数,即相同的输入,永远会得到相同的输出,而且没有任何可观察的副作用。

demo

s = "zhangsan"
def f(name):
 	print(f"hi,{name}")
f('lisi')


# 下面的不是
def f1():
 	print(f"hi,f{s}")
f1()


greeting = None
def greet(name):
    global greeting
    greeting = 'Hi, {}'.format(name)

greet('Brianne')

greeting # "Hi, Brianne"

7.副作用

如果一个函数或者表达式除了返回一个值之外,还与外部可变状态进行了交互(读取或写入),则它是有副作用的。

def f1():
 	print(f"hi,f{s}")
f1()


greeting = None
def greet(name):
    global greeting
    greeting = 'Hi, {}'.format(name)

greet('Brianne')

greeting # "Hi, Brianne"

让我们来仔细研究一下“副作用”以便加深理解。那么,我们在纯函数定义中提到的万分邪恶的副作用到底是什么?“作用”我们可以理解为一切除结果计算之外发生的事情。

“作用”本身并没什么坏处,而且在本书后面的章节你随处可见它的身影。“副作用”的关键部分在于“副”。就像一潭死水中的“水”本身并不是幼虫的培养器,“死”才是生成虫群的原因。同理,副作用中的“副”是滋生 bug 的温床。

副作用是在计算结果的过程中,系统状态的一种变化,或者与外部世界进行的可观察的交互

副作用可能包含,但不限于:

  • 更改文件系统
  • 往数据库插入记录
  • 发送一个 http 请求
  • 可变数据
  • 打印/log
  • 获取用户输入
  • DOM 查询
  • 访问系统状态

8.幂等

如果函数执行多次结果还是相同的话,那就是幂等

sorted(sorted([2,1,12,31,42,]))

9.Point-free风格

定义函数时,不显式地指出函数所带参数。这种风格通常需要柯里化或者高阶函数。也叫 Tacit programming。

def map(func):
    def map_(xs):
        for x in xs:
            func(x)
    return map_

def add(a):
    def add_(b):
        return a+b
    return add_



print(map(add(1)))   # 并没有实际的输出

10.断定

根据输入返回 true 或 false。通常用在 filter 的回调函数中。

f = lambda a:a>2

print(list(filter(f,[1,2,3,5])))

11.契约(异常处理)

def throw(ex):
    raise ex

def contract(value):
    if type(value) is int:
        return True
    else:
        return throw(Exception("Contract violated: expected int -> boolean"))       # 自定义异常的抛出 


add1 = lambda num: contract(num) and num + 1

print(add1(2)) # 3
print(add1('some string')) # Contract violated: expected int -> boolean)

12.范畴

在范畴论中,范畴是指对象集合及它们之间的态射 (morphism)。在编程中,数据类型作为对象,函数作为态射。

一个有效的范畴遵从以下三个原则:

  1. 必有一个同一态射(identity morphism)将一个对象映射到它自身。即当 a 是范畴里的一个对象时,必有一个函数使 a -> a
  2. 态射必是可组合的。abc 是范畴里的对象,f 是态射 a -> bgb -> c 态射。g(f(x)) 一定与 (g • f)(x) 是等价的。
  3. 组合满足结合律。f • (g • h)(f • g) • h 是等价的。

由于这些准则是在非常抽象的层面控制着组合方式,因此范畴论对于发现组合的新方法来说是伟大的。

数学上,态射(morphism)是两个数学结构之间保持结构的一种过程抽象。

13.值跟变量

  • 任何可以赋给变量的东西叫做值。
  • 一旦被定义之后就不可以被重新赋值。

14.函子

函子是一个实现了 map 函数的对象。map 函数会遍历对象中的每个值并生成一个新的对象,遵守两个准则

1.一致性

形式:

object.map(x => x)object

2.组合性

形式:

object.map(compose(f, g))object.map(g).map(f)
def f(x):
    x +=1
    return x
def g(x):
    x = x*2
    return x

result1 = list(map(f,[1,2,3]))				# 在这里object放在了map里面
result2 = list(map(f,map(g,[1,2,3])))
print(result1)
print(result2)		# [3,5,7]

3.指示函子

一个对象,拥有一个of函数,可以将一个任何值放入它自身。

class Array(list):

  of = lambda *args: Array([a for a in args])

print(Array.of(1)) # [1]

15.引用透明性

如果一个表达式能够被它的值替代而不改变程序的行为,则它是引用透明的。

例如我们有 greet 函数:

greet = lambda x: 'hello, world.'

任何对 greet() 的调用都可以被替换为 Hello World!, 因此 greet 是引用透明的。

16.等式推理

当一个应用程序由表达式组成并且没有副作用时,我们可以从这些组成部分中得知系统的真相。

1.lambda函数(匿名函数)

也许你已经看到了,在前面我们用到了许多匿名函数

f = lambda x:x+1
# 等价于
def f_(x):
    return x+1
print(f(2))
print(f_(2))



f2 = lambda x,y: [x(i) for i in y]
print(f2(lambda x:x+1,[1,2,3]))
# 等价于
def f2_(func:Function,y:List) -> List:
    tmp_list = []
    for i in y:
        tmp_list.append(func(i))
    return tmp_list
print(f2_(lambda x:x+1,[1,2,3])) 

2.lambda演算

数学的一个分支

推荐一个小视频:https://www.bilibili.com/video/BV1d34y1v7xr?spm_id_from=333.999.0.0&vd_source=660c5a54096b80be102b4f1e8974debe

17.惰性求值

惰性求值是一种按需调用的求值机制,它将表达式的求值延迟到需要它的值为止,在函数式语言中,允许类似无限列表这样的结构存在,而这在非常重视命令顺序的命令式语言中通常是不可用的。

import random
def rand(): 
  while True:
    yield random.randint(1,101)		#生成器
    
randIter = rand()
print(next(randIter))

补充

这里大概介绍了函数式编程的一半知识。之后的某些内容会涉及到群论等数学知识;如果想要获得更多的知识:

  • https://github.com/llh911001/mostly-adequate-guide-chinese

    这是一份使用javascript介绍函数式编程的书籍,内容详细

  • https://github.com/jmesyou/functional-programming-jargon.py

    这是一份介绍python函数式编程的项目,笔者所写大部分来自这个项目,但由于这个项目很多使用 lambda 函数来介绍函数式编程,对于新手而言不太友好。因此在这里改成一些更容易接受的完整函数语句

  • https://github.com/hemanth/functional-programming-jargon

    这是一份包含多种语言函数式编程的项目

你可能感兴趣的:(函数式编程,python,scala,spark)