Skip to content

Commit

Permalink
Finish Monoid and HKT
Browse files Browse the repository at this point in the history
Add pytest for case
  • Loading branch information
penguin-wwy committed Mar 8, 2022
1 parent d392b97 commit a5453c4
Show file tree
Hide file tree
Showing 10 changed files with 885 additions and 8 deletions.
1 change: 1 addition & 0 deletions Pipfile
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ name = "pypi"
[packages]

[dev-packages]
pytest = "*"

[requires]
python_version = "3.10"
4 changes: 2 additions & 2 deletions doc/ADT.md
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ class Nat:


class Z(Nat):
pass
""" Zero """


class S(Nat):
Expand Down Expand Up @@ -159,7 +159,7 @@ from typing import List, Dict


class JsonValue:
pass
""" json value type """


class JsonBool(JsonValue):
Expand Down
110 changes: 110 additions & 0 deletions doc/HKT.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
# 十分钟魔法练习:高阶类型

### By 「玩火」 改写 「penguin」

> 前置技能:Python基础
## 常常碰到的困难

写代码的时候常常会碰到语言表达能力不足的问题,比如下面这段用来给 `F` 容器中的值进行映射的代码:

```python
from typing import TypeVar, Callable

F = TypeVar("F")
A = TypeVar("A")
B = TypeVar("B")


def from_to(a: F[A], f: Callable[[A], B]) -> F[B]:
pass
```

Python会告诉你`TypeError: 'TypeVar' object is not subscriptable`

最简单粗暴的解决方案就是把type hint都删掉,如:

```python
def from_to(a, f):
pass
```

## 高阶类型

假设类型的类型是 `Type` ,比如 `int``String` 类型都是 `Type`

而对于 `List` 这样带有一个泛型参数的类型来说,它相当于一个把类型 `T` 映射到 `List<T>` 的函数,其类型可以表示为 `Type -> Type`

同样的对于 `Dict` 来说它有两个泛型参数,类型可以表示为 `(Type, Type) -> Type`

像这样把类型映射到类型的非平凡类型就叫高阶类型(HKT, Higher Kinded Type)。

虽然Python中存在这样的高阶类型但是我们并不能用一个泛型参数表示出来,也就不能写出如上 `F<A>` 这样的代码了,因为 `F` 是个高阶类型。

> 如果加一层解决不了问题,那就加两层。
虽然在Python中不能直接表示出高阶类型,但是我们可以通过加一个中间层来在保留完整信息的情况下强类型地模拟出高阶类型。

首先,我们需要一个中间层:

```python
from typing import Generic, TypeVar

BoxType = TypeVar('BoxType', covariant=True)
ValueType = TypeVar('ValueType', covariant=True)

class HKT(Generic[BoxType, ValueType]):
""" Higher Kinded Type """
```

然后我们就可以用 `HKT[BoxType, ValueType]` 来表示 `F<A>` ,这样操作完 `HKT` 后我们仍然有完整的类型信息来还原 `F<A>` 的类型。

这样,上面 `from_to` 就可以写成:

```python
from abc import abstractmethod
from typing import Generic, TypeVar, Callable

A = TypeVar("A")
B = TypeVar("B")
BoxType = TypeVar("BoxType", covariant=True)
ValueType = TypeVar("ValueType", covariant=True)

class HKT(Generic[BoxType, ValueType]):
""" Higher Kinded Type """

@abstractmethod
def value(self) -> ValueType:
""" :return value """

@staticmethod
@abstractmethod
def with_value(v: A) -> BoxType:
""" :return new box instance """


class Box(HKT['Box', A]):

def __init__(self, v: A):
self._value = v

@property
def value(self) -> A:
return self._value

@staticmethod
def with_value(v: B):
return Box(v)


def from_to(a: HKT[BoxType, A], f: Callable[[A], B]) -> HKT[BoxType, B]:
return a.with_value(f(a.value))

```

举个例子:

```python
int_box = Box(1)
assert from_to(int_box, lambda x: str(x)).value == "1"
```
165 changes: 165 additions & 0 deletions doc/Monoid.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
# 十分钟魔法练习:单位半群

### By 「玩火」 改写 「penguin」

> 前置技能:Python基础
## 半群(Semigroup)

半群是一种代数结构,在集合 `A` 上包含一个将两个 `A` 的元素映射到 `A` 上的运算即 `<> : (A, A) -> A` ,同时该运算满足**结合律**`(a <> b) <> c == a <> (b <> c)` ,那么代数结构 `{<>, A}` 就是一个半群。

比如在自然数集上的加法或者乘法可以构成一个半群,再比如字符串集上字符串的连接构成一个半群。

## 单位半群(Monoid)

单位半群是一种带单位元的半群,对于集合 `A` 上的半群 `{<>, A}``A` 中的元素 `a` 使 `A` 中的所有元素 `x` 满足 `x <> a``a <> x` 都等于 `x`,则 `a` 就是 `{<>, A}` 上的单位元。

举个例子, `{+, 自然数集}` 的单位元就是 0 , `{*, 自然数集}` 的单位元就是 1 , `{+, 字符串集}` 的单位元就是空串 `""`

用 Python 代码可以表示为:

```python
from functools import reduce
from typing import Generic, TypeVar, Iterator

T = TypeVar("T")


class Monoid(Generic[T]):
def empty(self) -> T:
...

def append(self, a: T, b: T) -> T:
...

def appends(self, x: Iterator[T]) -> T:
return reduce(lambda a, b: self.append(a, b), x)
```

## 应用:~~Optional~~Maybe

本质上Type hint只是个标签而已,所以如果需要Optional的效果的话需要先造一个,并且对它定义一个 Monoid :

```python
class Maybe(Generic[T]):
Maybe = TypeVar("Maybe")
value: T

@classmethod
def empty(cls) -> Maybe:
return _nil

@classmethod
def of_nullable(cls, v) -> Maybe:
return Some(v) if v is not None else cls.empty()

@classmethod
def of(cls, v) -> Maybe:
assert v is not None
return Some(v)

def is_present(self) -> bool:
return self.value

def get(self):
return self.value


class Some(Maybe):

def __init__(self, v: T):
self.value = v


class Nil(Maybe):

def __init__(self):
self.value = None


_nil = Nil()


class MaybeM(Monoid[Maybe[T]]):

def empty(self) -> Maybe[T]:
return Maybe.empty()

def append(self, a: Maybe[T], b: Maybe[T]) -> Maybe[T]:
return a if a.is_present() else b
```

这样对于 appends 来说我们将获得一串 Maybe 中第一个不为空的值,对于需要进行一连串尝试操作可以这样写:

```python
assert MaybeM[int]().appends([Maybe.of_nullable(x) for x in [None, 2, 3]]).get() == 2
```

## 应用:Ordering

对于 `Comparable` 接口可以构造出:

```python
class OrderingM(Monoid[int]):

def empty(self) -> int:
return 0

def append(self, a: int, b: int) -> int:
return a if a != 0 else b
```

同样如果有一串带有优先级的比较操作就可以用 appends 串起来,比如:

```python
def int_tup_greater(a: tuple[int, ...], b: tuple[int, ...]):
return a if OrderingM().appends([
a[i] - b[i] for i in range(0, min(len(a), len(b)))
]) >= 0 else b


assert int_tup_greater((5, 6, 7), (5, 4, 3, 2)) == (5, 6, 7)
```

这样的写法比一连串 `if-else` 优雅太多。

## 扩展

在 Monoid 接口里面加 default 方法可以支持更多方便的操作:

```python
import functools
from typing import Callable


class ExtMonoid(Monoid[T]):

def when(self, c: bool, then: T) -> T:
return then if c else self.empty()

def cond(self, c: bool, then: T, els: T) -> T:
return then if c else els

class Todo(ExtMonoid[Callable]):

def empty(self) -> Callable:
return lambda: None

@staticmethod
def _lambda_call(a: Callable, b: Callable):
a()
b()

def append(self, a: Callable, b: Callable) -> Callable:
return functools.partial(Todo._lambda_call, a, b)
```

然后就可以像下面这样使用上面的定义:

```python
Todo().appends([
lambda: print(1),
lambda: print(2),
lambda: print(3),
])()
```
Loading

0 comments on commit a5453c4

Please sign in to comment.