Skip to content

threecifanggen/python-functional-programming

Repository files navigation

fppy

开发状态:

coverage open-issues close-issues version building

依赖:

Python Python Python

一个基于python的函数式编程类库,仅做学习使用。主要功能如下:

  • 常量定义
  • 惰性求值/惰性属性
  • 抽象代数
  • 常用组合子
  • 列表类
    • 基于Iterable的惰性列表
    • 基于元组定义的列表
    • 基于类定义的列表
    • 基于类从头定义的惰性列表
  • 偏函数
  • 基于性质测试(Property-based Testing)
  • 更多功能的函数装饰器
  • State类
  • 设计模式
    • 错误处理
      • Option类
      • Either类
      • Try类
    • Lens
    • Cake

如何安装

PYPI软件库:

pip install fppy-learn

下载源码自己安装:

pip install poetry

git clone https://github.com/threecifanggen/python-functional-programming.git
cd python-functional-programming

poetry install

快速功能预览

函数修饰器

from fppy.base import F_

@F_
def f(x):
    return x + 1

>>> f(1)
2
>>> f.apply(1)
2
>>> f.and_then(lambda x: x ** 3)(1) # (x + 1) ** 3
8
>>> f.compose(lambda x: x ** 3)(1) # (x ** 3) + 1
1
>>> f.map([1, 2, 3])
[2, 3, 4]

常量定义

>>> from fppy.const import Const
>>>
>>> const = Const()
>>> const.a = 1
>>> const.a
1
>>> const.a = 2
# raise ConstError

列表类

一个可以实现mapreduce等操作的惰性列表

元组实现的列表

1. 新建
from fppy.cons_list_base import *

a = cons_apply(1, 2, 3)
head(a) # 1
tail(a) # cons(2, cons(3, ()))
2. 打印
>>> print_cons(cons_apply(1, 2, 3))
1, 2, 3, nil
3. 列表操作
a = cons_apply(1, 2, 3)
map_cons_curry(lambda x: x + 1)(a) # cons_apply(2, 3, 4)
filter_cons_curry(lambda x: x % 2 == 0)(a) # cons_apply(2)
fold_left_cons_curry(lambda x, y: x + y)(0)(a) # 6

类实现的列表

from fppy.cons_list import Cons

Cons.maker(1, 2, 3)\
    .map(lambda x: x + 1)\
    .filter(lambda x: x % 2 == 0)\
    .fold_left(lambda x, y: x + y, 0)

从头实现的惰性列表

from fppy.lazy_list_base import LazyCons

LazyCons.from_iter(1)(lambda x: x)\
    .map(lambda x: x + 1)\
    .filter(lambda x: x % 2 == 0)\
    .take(3)\
    .fold_left(lambda x, y: x + y, 0)

惰性列表

1. 新建
from fppy.lazy_list import LazyList

# 定义正整数无穷列表
ll = LazyList.from_iter(2)(lambda x: x + 2)

# 从List对象定义
ll = LazyList([1, 2, 3])

# 从生成器、迭代器定义
x = (i for i in range(100))
ll = LazyList(x)
2. map、filter、collect
LazyList([1, 2, 3])\
    .map(lambda x: x + 1)\
    .filter(lambda x: x % 2 == 0)\
    .collect() # 返回[2, 4]
3. 其他

其他方法参考文档。

常见组合子

1. Y组合子

下面的例子是计算阶乘:

from fppy.combinator import Y

fac = Y(lambd f: lambda x: 1 if (x ==0) else x * f(x - 1))

2. Z组合子

下面是计算指数函数的Z组合子实现

from fppy.combinator import Z

power = Z(lambda f: lambda x, n: 1 if (n == 0) else x * f(x, n - 1))

偏函数

这里的偏函数是指Partial Function,即定义域取不完整的函数;而不是高阶函数中的Partial Applied Function的概念。

定义一个如下函数:

  • 如果x > 0,则计算1 / x
  • 如果x < 0,则计算log(-x)
from math import log
from fppy.partail_function import PartialFunction
# 直接定义
pf = PartialFunction\
    .case(lambda x: x > 0)\
    .then(lambda x: 1 / x)\
    .case(lambda x: x < 0)\
    .then(lambda x: log(-x))

## 计算
pf.apply(1) # 返回1
pf.apply(-1) # 返回0
pf.apply(0) # 返回NoOtherCaseError

## 判断是否在某点有定义
pf.is_defined_at(0.4) # 返回True
pf.is_defined_at(0) # 返回False

我们还可以使用or_else来组合偏函数,比如上面的函数可以如下实现:

pf_greater_then_0 = PartialFunction\
    .case(lambda x: x > 0)\
    .then(lambda x: 1 / x)

pf_less_then_0 = PartialFunction\
    .case(lambda x: x < 0)\
    .then(lambda x: log(-x))

pf = pf_greater_then_0.or_else(pf_less_then_0)

惰性求值

1. 惰性属性

from fppy.lazy_evaluate import lazy_property

@dataclass
class Circle:
    x: float
    y: float
    r: float

    @lazy_property
    def area(self):
        print("area compute")
        return self.r ** 2 * 3.14

以上定义了一个圆的类,具体获取area时,仅第一次会计算(通过打印"area compute"显示)。

2. 惰性值

Python没有代码块的概念,所以必须把惰性求值过程包在一个函数内,以下是调用方法:

from fppy.lazy_evaluate import lazy_val

def f():
    print("f compute")
    return 12

lazy_val.a = f

调用结果下:

>>> lazy_val.a
f compute
12
>>> lazy_val.a
12

这就表示仅第一次调用时发生了计算。

错误处理

1. Option

from fppy.option import Just, Nothing

>>> Just(1).map(lambda x: x + 1)
Just(2)
>>> Just(1).flat_map(lambda x: Just(x * 3))
Just(3)
>>> Just(1).get
1
>>> Just(1).get_or_else(2)
1
>>> Just(1).filter(lambda x: x < 0)
Nothing()
>>> Just(1).filter(lambda x: x > 0)
Just(1)

与偏函数合用会有很多妙处:

from math import log
from fppy.partail_function import PartialFunction

pf = PartialFunction\
    .case(lambda x: x > 0)\
    .then(lambda x: 1 / x)\
    .case(lambda x: x < 0)\
    .then(lambda x: log(-x))


>>> pf.lift(1)
Just(1)
>>> pf.lift(0)
Nothing()
>>> Just(1).collect(pf)
Just(1)
>>> Just(0).collect(pf)
Nothing()

>>> Just(1).collect(pf)
Just(1.)
>>> Just(1).collect(pf).map(lambda x: int(x) - 1)
Just(-1)
>>> Just(1).collect(pf).map(lambda x: int(x) - 1).collect(pf)
Just(0)
>>> Just(1).collect(pf).map(lambda x: int(x) - 1).collect(pf).collect(pf)
Nothing()
>>> Just(1).collect(pf).map(lambda x: int(x) - 1).collect(pf).collect(pf).collect(pf)
Nothing()
>>> Just(1).collect(pf).map(lambda x: int(x) - 1).collect(pf).collect(pf).collect(pf).get_or_else(2)
2

2. Either

(待完善)

3. Try

Try单子时一个非常方便地处理错误的类,它的逻辑时传递错误类一直到最后处理,可以获取到错误发生时的错误类型和输入值,方便调试:

>>> from fppy.try_monad import Try
>>> res = Try(1).map(lambda x: x / 0).map(lambda x: x + 1)
>>> res.error
ZeroDivisionError('division by zero')
>>> res.get_or_else(2)
2
>>> res.get_error_input()
1