-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Showing
10 changed files
with
885 additions
and
8 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -6,6 +6,7 @@ name = "pypi" | |
[packages] | ||
|
||
[dev-packages] | ||
pytest = "*" | ||
|
||
[requires] | ||
python_version = "3.10" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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" | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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), | ||
])() | ||
``` |
Oops, something went wrong.