Repository: threecifanggen/python-functional-programming (language: python)

Issue:
Try对象
# 实现Try对象

## Try

### apply

```python
Try.apply(1) # Success(1)
```

## Success

### unapply

```python
Success.unapply(Success(1)) # Just(1)
```

### map

```python
Success(1).map(lambda x: x + 1) # Success(2)
Success(1).map(lambda x: x / 0) # Failure(ZeroDivisionError('division by zero'), 1)
```

### flat_map

```python
Success(1).flat_map(lambda x: Success(1)) # Success(1)
```

## get/get_or_else

```
Success(1).get # 1
Success(1).get_or_else(2) # 1
```

## Fail

### map

```map
Fail(ZeroDivisionError('division by zero'),  1).map(lambda x: x + 1) # self
```

### flat_map

```python
Fail(ZeroDivisionError('division by zero'),1).map(lambda x: Success(1)) # self
```

## get/get_or_else

```
Fail(ZeroDivisionError('division by zero'),  1).get # ZeroDivisionError('division by zero')
Fail(ZeroDivisionError('division by zero'),  1).get_or_else(2) # 2
Fail(ZeroDivisionError('division by zero'),  1).get_input # 1
```




Discussion / hints:

Fix patch:
diff --git a/.gitignore b/.gitignore
index 45cd038..bb2a632 100644
--- a/.gitignore
+++ b/.gitignore
@@ -50,6 +50,7 @@ coverage.xml
 *.py,cover
 .hypothesis/
 .pytest_cache/
+.jekyll-cache
 
 # Translations
 *.mo
diff --git a/README.md b/README.md
index 4fd9a60..c77c2cc 100644
--- a/README.md
+++ b/README.md
@@ -326,4 +326,19 @@ Nothing()
 
 (待完善)
 
+### 3. Try
+
+`Try`单子时一个非常方便地处理错误的类，它的逻辑时传递错误类一直到最后处理，可以获取到错误发生时的错误类型和输入值，方便调试：
+
+```python
+>>> 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
+```
+
 
diff --git a/_site/LICENSE b/_site/LICENSE
new file mode 100644
index 0000000..b6e6547
--- /dev/null
+++ b/_site/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2021 sancifanggen
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/_site/README.md b/_site/README.md
new file mode 100644
index 0000000..4fd9a60
--- /dev/null
+++ b/_site/README.md
@@ -0,0 +1,329 @@
+# fppy
+
+开发状态：
+
+![coverage](badge/cov-badge.svg) ![open-issues](https://img.shields.io/github/issues/threecifanggen/python-functional-programming) ![close-issues](https://img.shields.io/github/issues-closed/threecifanggen/python-functional-programming) ![version](https://img.shields.io/github/v/release/threecifanggen/python-functional-programming?include_prereleases)
+
+依赖：
+
+![Python](https://img.shields.io/badge/Python-3.8-green?logo=python) ![Python](https://img.shields.io/badge/pathos-0.2.6-green) ![Python](https://img.shields.io/badge/drill-1.2.0-green)
+
+一个基于`python`的函数式编程类库，仅做学习使用。主要功能如下：
+
+- [x] 常量定义
+- [x] 惰性求值/惰性属性
+- [ ] 抽象代数
+- [x] 常用组合子
+- [x] 列表类
+  - [x] 基于`Iterable`的惰性列表
+  - [x] 基于元组定义的列表
+  - [x] 基于类定义的列表
+  - [x] 基于类从头定义的惰性列表 
+- [x] 偏函数
+- [x] 基于性质测试(Property-based Testing)
+- [x] 更多功能的函数装饰器
+- [ ] State类
+- [ ] 设计模式
+  - [ ] 错误处理
+    - [x] Option类
+    - [x] Either类
+    - [ ] Try类
+  - [ ] Lens
+  - [ ] Cake
+
+## 快速功能预览
+
+### 函数修饰器
+
+```python
+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]
+```
+
+### 常量定义
+
+```python
+from fppy import const
+
+>>> const.a = 1
+>>> const.a
+1
+>>> const.a = 2
+# Error
+```
+
+### 列表类
+
+一个可以实现`map`、`reduce`等操作的惰性列表
+
+#### 元组实现的列表
+
+##### 1. 新建
+
+```python
+from fppy.cons_list_base import *
+
+a = cons_apply(1, 2, 3)
+head(a) # 1
+tail(a) # cons(2, cons(3, ()))
+```
+
+##### 2. 打印
+
+```python
+>>> print_cons(cons_apply(1, 2, 3))
+1, 2, 3, nil
+```
+
+##### 3. 列表操作
+
+```python
+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
+```
+
+#### 类实现的列表
+
+```python
+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)
+```
+
+#### 从头实现的惰性列表
+
+```python
+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. 新建
+
+```python
+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
+
+```python
+LazyList([1, 2, 3])\
+    .map(lambda x: x + 1)\
+    .filter(lambda x: x % 2 == 0)\
+    .collect() # 返回[2, 4]
+```
+
+##### 3. 其他
+
+其他方法参考文档。
+
+### 常见组合子
+
+#### 1. Y组合子
+
+下面的例子是计算阶乘：
+
+```python
+from fppy.combinator import Y
+
+fac = Y(lambd f: lambda x: 1 if (x ==0) else x * f(x - 1))
+```
+
+#### 2. Z组合子
+
+下面是计算指数函数的Z组合子实现
+
+```python
+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)`
+
+```python
+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`来组合偏函数，比如上面的函数可以如下实现：
+
+```python
+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. 惰性属性
+
+```python
+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`没有代码块的概念，所以必须把惰性求值过程包在一个函数内，以下是调用方法：
+
+```python
+from fppy.lazy_evaluate import lazy_val
+
+def f():
+    print("f compute")
+    return 12
+
+lazy_val.a = f
+```
+
+调用结果下：
+
+```python
+>>> lazy_val.a
+f compute
+12
+>>> lazy_val.a
+12
+```
+
+这就表示仅第一次调用时发生了计算。
+
+### 错误处理
+
+### 1. Option
+
+```python
+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)
+```
+
+与偏函数合用会有很多妙处：
+
+```python
+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
+
+(待完善)
+
+
diff --git a/_site/badge/cov-badge.svg b/_site/badge/cov-badge.svg
new file mode 100644
index 0000000..1c7007c
--- /dev/null
+++ b/_site/badge/cov-badge.svg
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="99" height="20">
+    <linearGradient id="b" x2="0" y2="100%">
+        <stop offset="0" stop-color="#bbb" stop-opacity=".1"/>
+        <stop offset="1" stop-opacity=".1"/>
+    </linearGradient>
+    <mask id="a">
+        <rect width="99" height="20" rx="3" fill="#fff"/>
+    </mask>
+    <g mask="url(#a)">
+        <path fill="#555" d="M0 0h63v20H0z"/>
+        <path fill="#a4a61d" d="M63 0h36v20H63z"/>
+        <path fill="url(#b)" d="M0 0h99v20H0z"/>
+    </g>
+    <g fill="#fff" text-anchor="middle" font-family="DejaVu Sans,Verdana,Geneva,sans-serif" font-size="11">
+        <text x="31.5" y="15" fill="#010101" fill-opacity=".3">coverage</text>
+        <text x="31.5" y="14">coverage</text>
+        <text x="80" y="15" fill="#010101" fill-opacity=".3">89%</text>
+        <text x="80" y="14">89%</text>
+    </g>
+</svg>
diff --git a/_site/docs/Makefile b/_site/docs/Makefile
new file mode 100644
index 0000000..d0c3cbf
--- /dev/null
+++ b/_site/docs/Makefile
@@ -0,0 +1,20 @@
+# Minimal makefile for Sphinx documentation
+#
+
+# You can set these variables from the command line, and also
+# from the environment for the first two.
+SPHINXOPTS    ?=
+SPHINXBUILD   ?= sphinx-build
+SOURCEDIR     = source
+BUILDDIR      = build
+
+# Put it first so that "make" without argument is like "make help".
+help:
+	@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
+
+.PHONY: help Makefile
+
+# Catch-all target: route all unknown targets to Sphinx using the new
+# "make mode" option.  $(O) is meant as a shortcut for $(SPHINXOPTS).
+%: Makefile
+	@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
diff --git a/_site/docs/make.bat b/_site/docs/make.bat
new file mode 100644
index 0000000..061f32f
--- /dev/null
+++ b/_site/docs/make.bat
@@ -0,0 +1,35 @@
+@ECHO OFF
+
+pushd %~dp0
+
+REM Command file for Sphinx documentation
+
+if "%SPHINXBUILD%" == "" (
+	set SPHINXBUILD=sphinx-build
+)
+set SOURCEDIR=source
+set BUILDDIR=build
+
+if "%1" == "" goto help
+
+%SPHINXBUILD% >NUL 2>NUL
+if errorlevel 9009 (
+	echo.
+	echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
+	echo.installed, then set the SPHINXBUILD environment variable to point
+	echo.to the full path of the 'sphinx-build' executable. Alternatively you
+	echo.may add the Sphinx directory to PATH.
+	echo.
+	echo.If you don't have Sphinx installed, grab it from
+	echo.https://www.sphinx-doc.org/
+	exit /b 1
+)
+
+%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
+goto end
+
+:help
+%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
+
+:end
+popd
diff --git a/_site/docs/source/api.rst b/_site/docs/source/api.rst
new file mode 100644
index 0000000..6a73054
--- /dev/null
+++ b/_site/docs/source/api.rst
@@ -0,0 +1,78 @@
+==================
+API Reference
+==================
+
+
+基本函数
+======================
+
+函数组合
+--------------------
+
+.. autofunction:: fppy.base.compose
+.. autofunction:: fppy.base.I
+.. autofunction:: fppy.base.and_then
+
+
+函数修饰器
+--------------------
+
+.. autofunction:: fppy.base.F_
+
+
+常量与惰性
+====================
+
+定义常量
+------------
+
+.. automodule:: fppy.const
+
+惰性
+-------------
+
+.. autofunction:: fppy.lazy_evaluate.lazy_property
+.. autoclass:: fppy.lazy_evaluate.LazyValue
+
+
+组合子
+======================
+
+.. autofunction:: fppy.combinator.Y
+.. autofunction:: fppy.combinator.Z
+
+
+列表类
+===========================
+
+
+通过元组的实现
+------------------------
+
+.. autofunction:: fppy.cons_list_base.cons
+.. autofunction:: fppy.cons_list_base.head
+.. autofunction:: fppy.cons_list_base.tail
+
+
+常用函数
+^^^^^^^^^^^^^^^^^^^^^^^^
+
+.. autofunction:: fppy.cons_list_base.map_cons
+.. autofunction:: fppy.cons_list_base.map_cons_curry
+.. autofunction:: fppy.cons_list_base.filter_cons
+.. autofunction:: fppy.cons_list_base.filter_cons_curry
+.. autofunction:: fppy.cons_list_base.fold_left_cons
+.. autofunction:: fppy.cons_list_base.fold_left_cons_curry
+.. autofunction:: fppy.cons_list_base.equal_cons
+
+
+通过类实现的列表
+-------------------------------
+
+.. autoclass:: fppy.cons_list.ConsList
+
+
+通过类实现的惰性列表
+-------------------------------
+
+.. autoclass:: fppy.lazy_list_base.LazyConsList
\ No newline at end of file
diff --git a/_site/docs/source/conf.py b/_site/docs/source/conf.py
new file mode 100644
index 0000000..27eeb3f
--- /dev/null
+++ b/_site/docs/source/conf.py
@@ -0,0 +1,77 @@
+# Configuration file for the Sphinx documentation builder.
+#
+# This file only contains a selection of the most common options. For a full
+# list see the documentation:
+# https://www.sphinx-doc.org/en/master/usage/configuration.html
+
+# -- Path setup --------------------------------------------------------------
+
+# If extensions (or modules to document with autodoc) are in another directory,
+# add these directories to sys.path here. If the directory is relative to the
+# documentation root, use os.path.abspath to make it absolute, like shown here.
+
+import os
+import sys
+import sphinx_rtd_theme
+
+sys.path.insert(0, os.path.abspath('../../'))
+
+
+# -- Project information -----------------------------------------------------
+
+project = 'craw-data-all-xwz'
+copyright = '2020, Xiaowanzi Inc.'
+author = 'huangbaochen <huangbaochenwo@live.com>'
+
+# The full version, including alpha/beta/rc tags
+release = '0.1.0'
+
+
+# -- General configuration ---------------------------------------------------
+
+# Add any Sphinx extension module names here, as strings. They can be
+# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
+# ones.
+extensions = [
+    'sphinx.ext.autodoc',
+    'sphinx.ext.napoleon',
+    'sphinx.ext.mathjax',
+    'sphinxemoji.sphinxemoji',
+    "sphinx_rtd_theme",
+    'sphinx.ext.todo',
+    'sphinxcontrib.mermaid',
+]
+
+todo_include_todos = True
+sphinxemoji_style = 'twemoji'
+
+# Add any paths that contain templates here, relative to this directory.
+templates_path = ['_templates']
+
+# The language for content autogenerated by Sphinx. Refer to documentation
+# for a list of supported languages.
+#
+# This is also used if you do content translation via gettext catalogs.
+# Usually you set "language" from the command line for these cases.
+language = 'zh_CN'
+
+# List of patterns, relative to source directory, that match files and
+# directories to ignore when looking for source files.
+# This pattern also affects html_static_path and html_extra_path.
+exclude_patterns = []
+
+
+# -- Options for HTML output -------------------------------------------------
+
+# The theme to use for HTML and HTML Help pages.  See the documentation for
+# a list of builtin themes.
+#
+html_theme = "sphinx_rtd_theme"
+html_theme_path = [sphinx_rtd_theme.get_html_theme_path()]
+
+# Add any paths that contain custom static files (such as style sheets) here,
+# relative to this directory. They are copied after the builtin static files,
+# so a file named "default.css" will overwrite the builtin "default.css".
+html_static_path = ['_static']
+
+napoleon_include_private_with_doc = True
diff --git a/_site/docs/source/development_manual.rst b/_site/docs/source/development_manual.rst
new file mode 100644
index 0000000..fb9e84f
--- /dev/null
+++ b/_site/docs/source/development_manual.rst
@@ -0,0 +1,21 @@
+=========================
+开发文档
+=========================
+
+
+
+搭建开发环境
+=============================================
+
+你可以通过`poetry`来实现所有的环境搭建工作。
+
+>>> # 进入工作目录
+>>> cd /path/to/your/dir/
+>>> poetry install
+
+
+运行测试及生成文档
+========================================
+
+>>> poetry run tests
+>>> poetry run docs
diff --git a/_site/docs/source/index.rst b/_site/docs/source/index.rst
new file mode 100644
index 0000000..89e60cf
--- /dev/null
+++ b/_site/docs/source/index.rst
@@ -0,0 +1,23 @@
+.. fppy documentation master file, created by
+   sphinx-quickstart on Tue Oct  5 21:09:03 2021.
+   You can adapt this file completely to your liking, but it should at least
+   contain the root `toctree` directive.
+
+欢迎使用 `fppy` ！
+================================
+
+.. toctree::
+   :maxdepth: 2
+   :caption: Contents:
+
+   api
+   development_manual
+
+
+
+Indices and tables
+==================
+
+* :ref:`genindex`
+* :ref:`modindex`
+* :ref:`search`
diff --git a/_site/fppy/abstract_algebra.py b/_site/fppy/abstract_algebra.py
new file mode 100644
index 0000000..e53c4bb
--- /dev/null
+++ b/_site/fppy/abstract_algebra.py
@@ -0,0 +1,99 @@
+"""抽象代数模块
+"""
+from __future__ import annotations
+from abc import abstractmethod, ABC
+from functools import reduce
+from typing import Iterable
+from math import floor
+from pathos.multiprocessing import ProcessPool
+from pathos.abstract_launcher import AbstractWorkerPool
+from pathos.threading import ThreadPool
+
+class SemiGroup(ABC):
+    """半群
+    """
+    @abstractmethod
+    def join(self, x, y):
+        """聚合操作
+        """
+
+
+class Monoid(SemiGroup, ABC):
+    """幺半群
+    """
+
+    @property
+    @abstractmethod
+    def unit(self):
+        """单位元
+        """
+
+    def parrellel_reduce(self, pool: AbstractWorkerPool, n: int=4):
+        """并行计算的reduce
+        """
+        def helper(ls: Iterable):
+            f = self.mreduce
+            with pool(n) as p:
+                l_split = Monoid._split_list(ls, n)
+                return f(p.imap(f, l_split))
+        return helper
+
+    def thread_reduce(self, ls: Iterable, n: int=4):
+        """基于线程的reduce
+        """
+        f = self.mreduce
+        with ThreadPool(n) as p:
+            l_split = Monoid._split_list(ls, n)
+            return f(p.imap(f, l_split))
+
+    def process_reduce(self, ls: Iterable, n: int=4):
+        """进程的reduce
+
+        TODO: 使用 `pathos` 测试失败，需要改进
+        """
+        f = self.mreduce
+        with ProcessPool(n) as p:
+            l_split = Monoid._split_list(ls, n)
+            return f(p.imap(f, l_split))
+
+    def mreduce(self, ls: Iterable):
+        """普通的reduce
+        """
+        return reduce(self.join, ls, self.unit)
+
+    def dfs_reduce(self, it: Iterable):
+        """深度优先二分reduce
+        """
+        len_of_iter = len(it)
+        if len_of_iter == 2:
+            return self.join(it[0], it[1])
+        if len_of_iter == 1:
+            return self.join(self.unit, it[0])
+        return self.join(
+            self.dfs_reduce(iter[:floor(len_of_iter/2)]),
+            self.dfs_reduce(iter[floor(len_of_iter/2):])
+        )
+
+    @classmethod
+    def _split_list(cls, l, n):
+        """切分列表
+        """
+        k, j = divmod(len(l), n)
+        return (
+            l[i * k + min(i, j):(i + 1) * k + min(i + 1, j)]
+                for i in range(n)
+            )
+
+
+class Functor(ABC):
+    """函子
+    """
+    @abstractmethod
+    def map(self, f):
+        """map操作
+        """
+
+
+class Monad(Monoid, Functor, ABC):
+    """单子
+    """
diff --git a/_site/fppy/base.py b/_site/fppy/base.py
new file mode 100644
index 0000000..86d66ce
--- /dev/null
+++ b/_site/fppy/base.py
@@ -0,0 +1,134 @@
+"""基础函数
+"""
+from __future__ import annotations
+from functools import reduce
+from typing import Any, Callable, Generic, Iterable
+from .gt import S0, S, T, T1
+
+def I(x: S) -> S:
+    """Identity函数
+
+    Args:
+        x (S): 任意函数、值、对象、类
+
+    Returns:
+        S: 返回本身
+    """
+    return x
+
+
+def compose(*args: Callable) -> Callable:
+    """数学中的compose
+
+    >>> from fppy.base import compose
+    >>> compose(lambda x: x+1, lambda x: x**2)(1)
+    >>> 4
+
+    """
+    return reduce(lambda f, g: lambda x: f(g(x)), args)
+
+
+def and_then(*args: Callable[[Any], Any]) -> Callable[[Any], Any]:
+    """和compose采用不同的fold方向的函数
+    """
+    return reduce(lambda f, g: lambda x: g(f(x)), args)
+
+class _Function(Generic[S, T]):
+    """加强版函数
+    """
+    f: Callable[[S], T]
+
+    def __init__(
+            self,
+            f: Callable[[S], T],
+            name: str | None = None
+        ):
+        self.f = f
+        if name is None:
+            self.name = f.__name__
+        else:
+            self.name = name
+
+    def __call__(
+            self,
+            *args,
+            **kargs
+        ) -> T:
+        """执行函数
+        """
+        return self.f(*args, **kargs)
+
+    def apply(
+            self,
+            *args,
+            **kargs
+        ) -> T:
+        """应用函数 = 执行函数
+        """
+        return self.f(*args, **kargs)
+
+    @staticmethod
+    def F_(f: Callable[[S], T]) -> _Function[S, T]:
+        """函数盒子/修饰器
+        """
+        return _Function(f, f.__name__)
+
+    def and_then(
+        self, g: Callable[[T], T1] | _Function[T, T1]
+        ) -> _Function[S, T1]:
+        """先执行本身，再执行g
+        """
+        temp_g = g.f if isinstance(g, _Function) else g
+        def helper(*args, **kargs):
+            return temp_g(self.f(*args, **kargs))
+        return _Function(helper)
+
+    def compose(
+        self,
+        g: Callable[[S0], S] | _Function[S0, S]
+        ) -> _Function[S0, T]:
+        """compose函数
+        """
+        temp_g = g.f if isinstance(g, _Function) else g
+        def helper(*args, **kargs):
+            return self.f(temp_g(*args, **kargs))
+        return _Function(helper)
+
+    def map(
+        self,
+        l: Iterable[S],
+        to_lazy: bool = False
+        ) -> Iterable[T]:
+        """map函数
+        """
+        if to_lazy:
+            return map(self.f, l)
+        else:
+            return list(map(self.f, l))
+
+F_: Callable[[Callable[[S], T]], _Function[S, T]] = _Function.F_
+
+F_.__doc__ = """"函数修饰器。
+
+这是一个增强版的函数修饰器，可以做到和一般函数一样的使用，
+但是可以链式地串联其他函数。
+
+>>> from fppy.base import F_
+>>> @F_
+>>> def f(x):
+>>>     return x + 1
+>>>
+>>> f_composed = f(x).and_then(lambda x: x + 2)
+>>> f_composed(1)
+3
+>>> f_composed = f(x).compose(lambda x: x * 3)
+>>> f_composed(2)
+7
+
+
+Methods:
+    _Function.apply: 应用
+    _Function.compose: 数学上的compose结合函数
+    _Function.and_then: compose相反的操作
+    _Function.map: 将函数运用到一个可迭代(`Iterable`)的对象上
+"""
diff --git a/_site/fppy/cake.py b/_site/fppy/cake.py
new file mode 100644
index 0000000..e69de29
diff --git a/_site/fppy/combinator.py b/_site/fppy/combinator.py
new file mode 100644
index 0000000..2b5d861
--- /dev/null
+++ b/_site/fppy/combinator.py
@@ -0,0 +1,10 @@
+"""组合子
+"""
+
+# pylint: disable=unnecessary-lambda
+Y = lambda f: ((lambda g: f(g(g)))(lambda g: f(lambda y: g(g)(y))))
+Y.__doc__ = "Y组合子"
+
+# pylint: disable=unnecessary-lambda
+Z = lambda f: (lambda g: f(g(g)))(lambda g: f(lambda *y: g(g)(*y)))
+Z.__doc__ = "Z组合子"
diff --git a/_site/fppy/cons_list.py b/_site/fppy/cons_list.py
new file mode 100644
index 0000000..a572144
--- /dev/null
+++ b/_site/fppy/cons_list.py
@@ -0,0 +1,76 @@
+"""用类实现的列表
+"""
+from __future__ import annotations
+from dataclasses import dataclass
+from typing import Callable, Generic
+from .gt import S, T
+
+
+class ConsList(Generic[S]):
+    """类实现的列表
+
+    如何新建：
+
+    >>> Cons.maker(1, 2, 3, 4)
+
+    列表操作：
+
+    >>> Cons.maker(1, 2, 3, 4)\\
+    >>>     .map(lambda x: x + 1)\\
+    >>>     .filter(lambda x: x % 2 == 0)\\
+    >>>     .fold_left(lambda x, y: x + y, 0) # 返回6
+    """
+
+
+@dataclass
+class Empty(ConsList, Generic[S]):
+    """空列表
+    """
+    def map(self, _: Callable[[S], T]) -> Empty: # pylint: disable=no-self-use
+        """map
+        """
+        return Empty()
+
+    def filter(self, _: Callable[[S], T]) -> Empty: # pylint: disable=no-self-use
+        """filter
+        """
+        return Empty()
+
+    def fold_left(self, _: Callable[[S], T], init: S) -> Empty: # pylint: disable=no-self-use
+        """fold_left
+        """
+        return init
+
+@dataclass
+class Cons(ConsList, Generic[S]):
+    """基础列表
+    """
+    head: S
+    tail: S
+
+    @classmethod
+    def maker(cls, *args: S) -> ConsList[S]:
+        """列表创建
+        """
+        if len(args) == 0:
+            return Empty()
+        else:
+            return Cons(args[0], cls.maker(*args[1:]))
+
+    def map(self, f: Callable[[S], T]) -> ConsList[T]:
+        """map
+        """
+        return Cons(f(self.head), self.tail.map(f))
+
+    def filter(self, f: Callable[[S], bool]) -> ConsList[S]:
+        """filter
+        """
+        if f(self.head):
+            return Cons(self.head, self.tail.filter(f))
+        else:
+            return self.tail.filter(f)
+
+    def fold_left(self, f: Callable[[S, T], S], init: S) -> S:
+        """fold_left
+        """
+        return self.tail.fold_left(f, f(init, self.head))
diff --git a/_site/fppy/cons_list_base.py b/_site/fppy/cons_list_base.py
new file mode 100644
index 0000000..c23e668
--- /dev/null
+++ b/_site/fppy/cons_list_base.py
@@ -0,0 +1,153 @@
+"""用二元元组实现的列表
+"""
+from typing import Callable, Tuple
+from .gt import S, T, GenericTypeVar
+
+
+ListBase = GenericTypeVar[S]
+EmptyListBase = Tuple[()]
+ConsListBase = Callable[[], Tuple[S, ListBase[S]]]
+
+empty_list_base = ()
+
+def cons(head: S, tail: ListBase[S]) -> ConsListBase[S]:
+    """基于二元元组实现的List
+
+    Args:
+        head (S): 头部元素
+        tail (ListBase[S]): 尾部元素
+    """
+    def helper():
+        return (head, tail)
+    return helper
+
+def cons_apply(*args: S) -> ConsListBase[S]:
+    """一个方便的定义方式
+
+    Returns:
+        ConsListBase[S]: 返回结果
+    """
+    if len(args) == 0:
+        return empty_list_base
+    else:
+        return cons(args[0], cons_apply(*args[1:]))
+
+
+head: Callable[[ConsListBase[S]], S] = lambda cons_list: cons_list()[0]
+head.__doc__ == """获取List第一个元素""" # pylint: disable=pointless-statement
+
+tail: Callable[[ConsListBase[S]], S] = lambda cons_list: cons_list()[1]
+tail.__doc__ == """截取第一个元素后的List"""  # pylint: disable=pointless-statement
+
+def equal_cons(this: ListBase[S], that: ListBase[S]) -> bool:
+    """判断两个list是否相等
+
+    Args:
+        this (ListBase[S]): 第一个list
+        that (ListBase[S]): 第二个list
+
+    Returns:
+        bool: 返回第二个相等
+    """
+    if this == empty_list_base and that != empty_list_base:
+        return False
+    elif this != empty_list_base and that == empty_list_base:
+        return False
+    elif this == empty_list_base and that == empty_list_base:
+        return True
+    else:
+        return head(this) == head(that) and equal_cons(tail(this), tail(that))
+
+def print_cons_with_brackets(cons_list: ListBase[S]) -> str:
+    """打印ListBase的括号风格结果
+
+    Args:
+        cons_list (ListBase[S]): 需要打印的内容
+
+    Returns:
+        str: 返回可以打印的结果，用括号风格
+    """
+    if cons_list == ():
+        return "()"
+    else:
+        return (
+            f"({head(cons_list)}, "
+            f"{print_cons_with_brackets(tail(cons_list))})"
+        )
+
+def print_cons(cons_list: ListBase[S]) -> str:
+    """打印ListBasr的逗点风格结果
+
+    Args:
+        cons_list (ListBase[S]): 需要打印的内容
+
+    Returns:
+        str: [description]
+    """
+    if cons_list == ():
+        return "nil"
+    else:
+        return f"{head(cons_list)}, {print_cons(tail(cons_list))}"
+
+def map_cons(f: Callable[[S], T], cons_list: ListBase[S]) -> ListBase[T]:
+    """ListBase的map运算
+
+    Args:
+        f (ListBase[S]): 需要map的函数
+        cons_list (ListBase[S]): 待处理的结果
+
+    Returns:
+        ListBase[T]: 结果
+    """
+    if cons_list == ():
+        return empty_list_base
+    else:
+        return cons(f(head(cons_list)), map_cons(f, tail(cons_list)))
+
+def filter_cons(f: Callable[[S], bool], cons_list: ListBase[S]) -> ListBase[S]:
+    """ListBase的filter运算
+
+    Args:
+        f (Callable[[S], bool]): filter函数
+        cons_list (ListBase[S]): 待处理的结果
+
+    Returns:
+        ListBase[S]: 结果
+    """
+    if cons_list == ():
+        return empty_list_base
+    else:
+        hd, tl = head(cons_list), tail(cons_list)
+        if f(hd):
+            return cons(hd, filter_cons(f, tl))
+        else:
+            return filter_cons(f, tl)
+
+def fold_left_cons(
+    f: Callable[[T, S], T],
+    init: T, cons_list: ListBase[S]
+    ) -> ListBase[T]:
+    """ListBase的fold_left函数
+
+    Args:
+        f (Callable[[T, S], T]): 二元函数
+        cons_list (ListBase[S]): 待处理的list
+        init (T): 初始值
+
+    Returns:
+        ListBase[T]: 结果
+    """
+    if cons_list == ():
+        return init
+    else:
+        return fold_left_cons(f, f(init, head(cons_list)), tail(cons_list))
+
+
+map_cons_curry = lambda f: lambda cons_list: map_cons(f, cons_list)
+map_cons_curry.__doc__="""科里化版本的map_cons"""
+filter_cons_curry = lambda f: lambda cons_list: filter_cons(f, cons_list)
+filter_cons_curry.__doc__="""科里化版本的filter_cons"""
+fold_left_cons_curry = lambda f: (
+    lambda init: lambda cons_list: fold_left_cons(f, init, cons_list)
+)
+fold_left_cons_curry.__doc__="""科里化版本的fold_left_cons"""
diff --git a/_site/fppy/const.py b/_site/fppy/const.py
new file mode 100644
index 0000000..e600754
--- /dev/null
+++ b/_site/fppy/const.py
@@ -0,0 +1,28 @@
+"""常量定义
+"""
+import sys
+from .errors import ConstError
+
+class _const:
+    """ `const` 模块是一个项目定义一堆常量的方式。
+
+    Example:
+        这个模块允许像常量一样引用：
+
+        >>> from fppy import const
+        >>>
+        >>> const.a = 1
+        >>> const.a
+        1
+        >>> const.a = 2
+        # raise ConstError
+    """
+
+    def __setattr__(self, name, value):
+        if name in self.__dict__:
+            raise ConstError(f"Can't rebind const {name} with {value}")
+        else:
+            self.__dict__[name] = value
+
+
+sys.modules[__name__] = _const()
diff --git a/_site/fppy/either.py b/_site/fppy/either.py
new file mode 100644
index 0000000..97b96f2
--- /dev/null
+++ b/_site/fppy/either.py
@@ -0,0 +1,253 @@
+"""Either类型
+"""
+from __future__ import annotations
+from abc import ABC
+from typing import Callable, Generic, NoReturn
+from dataclasses import dataclass
+from .gt import S, T, T1
+from .option import Just
+
+@dataclass
+class _Either(ABC, Generic[S, T]):
+    """Either的Base
+    """
+    value: S
+
+
+@dataclass
+class Right(_Either, Generic[S, T]):
+    """Right类
+    """
+    value: T
+
+    @classmethod
+    def unapply(cls, right: Right[S, T]) -> Just[T]:
+        """uapply
+        """
+        if isinstance(right, Right):
+            return Just(right.value)
+        else:
+            raise TypeError("Right.unapply's parameter "
+            "must be an instance of Right")
+
+    def contains(self, x: T) -> bool:
+        """判断是否含有数据
+        """
+        return self.value == x
+
+    def exists(
+        self,
+        f: Callable[[T], bool]) -> bool:
+        """判断是否存在符合f的元素
+        """
+        return f(self.value)
+
+    def filter_or_else(
+        self,
+        f: Callable[[T], bool],
+        x: S
+        ) -> _Either[S, T]:
+        """过滤
+        """
+        if f(self.value):
+            return self
+        else:
+            return Left(x)
+
+    def flat_map(
+        self,
+        f: Callable[[T], _Either[S, T]]
+        ) -> _Either[S, T]:
+        """flat_map
+        """
+        res = f(self.value)
+        if isinstance(res, _Either):
+            return res
+        else:
+            raise TypeError(
+                "f must return an Either Object"
+            )
+
+    def map(
+        self,
+        f: Callable[[T], T1]
+        ) -> Right[S, T1]:
+        """map
+        """
+        return Right(f(self.value))
+
+    @property
+    def is_left(self):
+        """判断是否是left
+        """
+        return False
+
+    @property
+    def is_right(self):
+        """判断是否是right
+        """
+        return True
+
+    def for_each(
+        self,
+        f: Callable[[T], NoReturn]
+        ) -> Right[S, T]:
+        """运行副作用
+        """
+        f(self.value)
+        return self
+
+    def for_all(
+        self,
+        f: Callable[[T], bool]
+        ) -> bool:
+        """判断是否符合f
+        """
+        if f(self.value):
+            return True
+        else:
+            return False
+
+    def or_else(
+        self,
+        _: _Either[S, T]
+        ) -> _Either[S, T]:
+        """获取值
+        """
+        return self
+
+    def get_or_else(
+        self,
+        _: T
+        ) -> T:
+        """获取值
+        """
+        return self.value
+
+
+@dataclass
+class Left(_Either, Generic[S, T]):
+    """Left对象
+    """
+    value: S
+
+    @classmethod
+    def unapply(cls, left: Right[S, T]) -> Just[T]:
+        """unapply
+        """
+        if isinstance(left, Left):
+            return Just(left.value)
+        else:
+            raise TypeError("Left.unapply's parameter "
+            "must be an instance of Left")
+
+    def contains(self, x: S) -> bool:
+        """判断是否含有数据
+        """
+        return self.value == x
+
+    def exists(
+        self,
+        _: Callable[[T], bool]) -> bool:
+        """判断是否存在符合f的元素
+        """
+        return False
+
+    def filter_or_else(
+        self,
+        _: Callable[[T], bool],
+        __: S
+        ) -> Left[S, T]:
+        """过滤
+        """
+        return self
+
+    def flat_map(
+        self,
+        _: Callable[[T], _Either[S, T]]
+        ) -> _Either[S, T]:
+        """flat_map
+        """
+        return self
+
+    def map(
+        self,
+        _: Callable[[T], T1]
+        ) -> Left[S, T1]:
+        """map
+        """
+        return self
+
+    @property
+    def is_left(self):
+        """判断是否是left
+        """
+        return True
+
+    @property
+    def is_right(self):
+        """判断是否是Right
+        """
+        return False
+
+    def for_each(
+        self,
+        _: Callable[[T], NoReturn]
+        ) -> Left[S, T]:
+        """运行副作用
+        """
+        return self
+
+    def for_all(
+        self,
+        _: Callable[[T], bool]
+        ) -> bool:
+        """判断是否成立
+        """
+        return True
+
+    def or_else(
+        self,
+        x: _Either[S, T]
+        ) -> _Either[S, T]:
+        """获取值
+        """
+        return x
+
+    def get_or_else(
+        self,
+        x: T
+        ) -> T:
+        """获取值
+        """
+        return x
+
+
+class Either(_Either, Generic[S, T]):
+    """Either的同名对象
+    """
+    value: T
+
+    def __new__(cls, value):
+        """新建
+        """
+        return Right(value)
+
+    def apply(self, value) -> Right[S, T]:
+        """应用
+        """
+        return Right(value)
+
+    @classmethod
+    def cond(
+        cls,
+        cond_if: bool,
+        right_res: T,
+        left_res: S
+        ) -> _Either[S, T]:
+        """判断如果是返回Right否则返回Left
+        """
+        if cond_if:
+            return Right(right_res)
+        else:
+            return Left(left_res)
diff --git a/_site/fppy/errors.py b/_site/fppy/errors.py
new file mode 100644
index 0000000..32a4098
--- /dev/null
+++ b/_site/fppy/errors.py
@@ -0,0 +1,17 @@
+"""错误类
+"""
+class NoOtherCaseError(Exception):
+    """无其他可能性错误
+    """
+
+class ConstError(TypeError):
+    """常量错误
+    """
+
+class NotVoidFunctionError(TypeError):
+    """不是无参数函数错误
+    """
+
+class NothingHasNoSuchMethod(Exception):
+    """Nothing不包含此方法错误
+    """
diff --git a/_site/fppy/functor.py b/_site/fppy/functor.py
new file mode 100644
index 0000000..96ce4ae
--- /dev/null
+++ b/_site/fppy/functor.py
@@ -0,0 +1,39 @@
+"""函子相关类
+"""
+from __future__ import annotations
+from dataclasses import dataclass
+from typing import Callable, Generic
+from .gt import S, T
+
+class Box:
+    """Box类
+    """
+
+@dataclass
+class BoxValue(Generic[S]):
+    """Box值类
+    """
+    value: S
+
+    def map(self, f: Callable[[S], T]) -> BoxValue[T]:
+        """map
+        """
+        return BoxValue(f(self.value))
+
+    @staticmethod
+    def of(value: S) -> BoxValue[S]:
+        """定义值
+        """
+        return BoxValue(value)
+
+
+@dataclass
+class BoxFunction(Generic[S, T], Box):
+    """Box函数类
+    """
+    func: Callable[[S], T]
+
+    def fmap(self, value: S) -> BoxValue[T]:
+        """套用函数
+        """
+        return BoxValue(self.func(value))
diff --git a/_site/fppy/gt.py b/_site/fppy/gt.py
new file mode 100644
index 0000000..c704ef5
--- /dev/null
+++ b/_site/fppy/gt.py
@@ -0,0 +1,12 @@
+from typing import Generic, TypeVar
+
+S0 = TypeVar("S0")
+S = TypeVar("S")
+T = TypeVar("T")
+T1 = TypeVar("T1")
+
+class GenericTypeVar(Generic[S]):
+    """泛型类
+
+    方便直接定义值的类型
+    """
diff --git a/_site/fppy/lazy_evaluate.py b/_site/fppy/lazy_evaluate.py
new file mode 100644
index 0000000..1ce0cd4
--- /dev/null
+++ b/_site/fppy/lazy_evaluate.py
@@ -0,0 +1,74 @@
+"""惰性求值
+"""
+from typing import Callable
+from .errors import NotVoidFunctionError
+from .gt import S
+
+def lazy_property(func):
+    """惰性属性修饰器。
+
+    如我们可以定义下面一个 `Circle` 的类，定义其中
+    计算面积的属性为惰性属性
+
+    .. code-block:: python
+
+        @dataclass
+        class Circle:
+            x: float
+            y: float
+            r: float
+
+            @lazy_property
+            def area(self):
+                print("computing")
+                return 3.14 * r * r
+
+    调用时结果如下，可以发现仅第一次发生了计算：
+
+    >>> cir = Circle(0, 1, 1)
+    >>> cir.area
+    computing
+    3.14
+    >>> cir.area
+    3.14
+    """
+    attr_name = "_lazy_" + func.__name__
+
+    @property
+    def _lazy_property(self):
+        if not hasattr(self, attr_name):
+            setattr(self, attr_name, func(self))
+        return getattr(self, attr_name)
+
+    return _lazy_property
+
+class LazyValue:
+    """惰性值。
+
+    惰性值使用一个
+
+    Raises:
+        NotVoidFunctionError : 如果带入的值不是一个无参数的函数，
+            即 `Void -> S` 形式的函数，则报错。
+    """
+
+    def __setattr__(self, name: str, value: Callable[[], S]):
+        if not callable(value) or value.__code__.co_argcount > 0:
+            raise NotVoidFunctionError("value is not a void function")
+        super(LazyValue, self).__setattr__(name, (value, False)) # pylint: disable=super-with-arguments
+
+    def __getattribute__(self, name: str):
+        try:
+            _func, _have_called = super(LazyValue, self).__getattribute__(name) # pylint: disable=super-with-arguments
+            if _have_called:
+                return _func
+            else:
+                res = _func()
+                super(LazyValue, self).__setattr__(name, (res, True)) # pylint: disable=super-with-arguments
+                return res
+        except:
+            raise AttributeError(
+                f"type object 'Lazy' has no attribute '{name}'"
+            ) from Exception
+
+lazy_val = LazyValue()
diff --git a/_site/fppy/lazy_list.py b/_site/fppy/lazy_list.py
new file mode 100644
index 0000000..74b96bb
--- /dev/null
+++ b/_site/fppy/lazy_list.py
@@ -0,0 +1,159 @@
+"""惰性列表
+"""
+from typing import Iterable
+from dataclasses import dataclass
+from functools import reduce
+from itertools import (
+    islice,
+    takewhile,
+    accumulate,
+    repeat,
+    chain
+)
+
+
+@dataclass
+class Nil:
+    """列表结尾
+    """
+
+class LazyList:
+    """惰性列表
+    """
+    def __init__(
+        self,
+        x: Iterable):
+        self.iter_ = (i for i in x)
+
+    def split_head(
+        self
+        ):
+        """分割开头
+        """
+        try:
+            x = next(self.iter_)
+        except:
+            x = Nil()
+        return (x, self)
+
+    def map(self, f):
+        """map函数
+        """
+        return lazy_list(map(f, self.iter_))
+
+    def reduce(self, f):
+        """reduce函数
+        """
+        return reduce(f, self.iter_)
+
+    def foldleft(self, f, init):
+        """foldleft函数
+        """
+        return reduce(f, self.iter_, init)
+
+    @staticmethod
+    def from_iter(start):
+        """按递推公式生成惰性列表函数
+        """
+        def helper(f):
+            return LazyList(accumulate(repeat(start), lambda a, _: f(a)))
+        return helper
+
+    def collect(self):
+        """collect函数
+        """
+        return list(self.iter_)
+
+    def scan_left(self, f, init):
+        """类似reduce，但将每个结果输出
+
+        Attribute:
+            f[Callable]: reduce函数
+            init: 设定初始值
+        """
+        return LazyList(accumulate(chain([init], self.iter_), f))
+
+    def find(self, f):
+        """找到第一个符合f的元素
+        """
+        try:
+            x = next(self.iter_)
+            if f(x):
+                return x
+            else:
+                return self.find(f)
+        except:
+            return Nil()
+
+    def filter(self, f):
+        """filter函数
+        """
+        return lazy_list(filter(f, self.iter_))
+
+    def len(self):
+        """len函数
+        """
+        return self.foldleft(lambda x, _: x + 1, 0) 
+
+    def takewhile(self, f):
+        """按f为True取数据到
+        """
+        return lazy_list(takewhile(f, self.iter_))
+
+    def take(self, n):
+        """获取前N个数据
+        """
+        return lazy_list(enumerate(self.iter_))\
+            .takewhile(lambda x: x[0] < n)\
+            .map(lambda x: x[1])
+
+    def zip_with(self, other):
+        """zip函数
+        """
+        if isinstance(other, LazyList):
+            return LazyList(zip(self.iter_, other.iter_))
+        elif isinstance(other, Iterable):
+            return LazyList(zip(self.iter_, other))
+        else:
+            raise TypeError(
+                "other is not a Iterable object "
+                "or LazyList")
+
+    def for_all(self, func):
+        """所有值都符合func则返回True
+        """
+        return self.map(func).reduce(lambda x, y: x and y)
+
+    def __add__(self, other):
+        """两个列表添加
+        """
+        return self.concat(other)
+
+    def concat(self, other):
+        """两个列表添加
+        """
+        if isinstance(other, LazyList):
+            return LazyList(chain(self.iter_, other.iter_))
+        elif isinstance(other, Iterable):
+            return LazyList(chain(self.iter_, other))
+        else:
+            raise TypeError(
+                "other is not a Iterable object "
+                "or LazyList")
+
+    def drop(self, n):
+        """删除前N个数
+        """
+        return LazyList(islice(self.iter_, n, None))
+
+    @property
+    def last(self):
+        """获取最后一个值
+        """
+        return self.reduce(lambda _, y: y)
+
+
+def lazy_list(x: Iterable):
+    """生成LazyList
+    """
+    return LazyList(x)
diff --git a/_site/fppy/lazy_list_base.py b/_site/fppy/lazy_list_base.py
new file mode 100644
index 0000000..9893e03
--- /dev/null
+++ b/_site/fppy/lazy_list_base.py
@@ -0,0 +1,126 @@
+"""从头开始实现的惰性列表
+"""
+from __future__ import annotations
+from dataclasses import dataclass
+from typing import Callable, Generic
+from .gt import S, T
+from .cons_list import ConsList, Cons, Empty
+
+class LazyConsList(Generic[S]):
+    """从头实现的惰性列表
+
+    新建列表：
+
+    >>> from fppy.lazy_list_base import LazyCons, LazyEmpty
+    >>> LazyCons(1, LazyCons(2, LazyCons(3, LazyEmpt())))
+    >>> LazyCons.maker(1, 2, 3) # 与上面的方法结果相同
+
+    通过递推公式生成无穷列表：
+
+    >>> LazyCons.from_iter(1)(lambda x: x + 1) # 生成正整数
+    >>> LazyCons.from_iter(1)(lambda _: 1) # 生成无穷的1
+
+    获取前10的元素，包括（无穷列表）：
+
+    >>> LazyCons.from_iter(1)(lambda _: 1).take(10)
+
+    其他方法包括： `map` ， `filter` ， `fold_left`
+    """
+
+
+@dataclass
+class LazyEmpty(LazyConsList, Generic[S]):
+    """空的惰性列表
+    """
+    def map(self, _: Callable[[S], T]) -> LazyEmpty[S]:
+        """空惰性列表的map
+        """
+        return self
+
+    def collect(self) -> LazyEmpty[S]: # plint: disable:no-self-use
+        """空惰性列表的collect
+        """
+        return Empty()
+
+    def filter(self, _: Callable[[S], bool]) -> LazyEmpty[S]:
+        """空惰性列表的filter
+        """
+        return self
+
+
+@dataclass
+class LazyStop(LazyConsList, Generic[S]):
+    """用来设定过滤点的工具
+    """
+
+
+@dataclass
+class LazyCons(LazyConsList, Generic[S]):
+    """LazyCons
+    """
+    head: Callable[[], S]
+    tail: Callable[[], LazyConsList[S]]
+
+    def __init__(
+        self,
+        head: S | Callable[[], S],
+        tail: LazyConsList | Callable[[], LazyConsList[S]]
+        ):
+        if callable(head):
+            self.head = head
+        else:
+            self.head = lambda : head
+        if isinstance(tail, LazyConsList):
+            self.tail = lambda: tail
+        else:
+            self.tail = tail
+
+    @classmethod
+    def maker(cls, *args: S) -> LazyConsList:
+        """惰性列表创建函数
+        """
+        if len(args) == 0:
+            return LazyEmpty()
+        else:
+            return LazyCons(args[0], cls.maker(*args[1:]))
+
+    @classmethod
+    def from_iter(cls, start: S) -> Callable[[Callable[[S], S]], LazyCons[S]]:
+        """使用递推公式创建惰性列表
+        """
+        def helper(f: Callable[[S], S]) -> LazyCons[S]:
+            return LazyCons(start, lambda: cls.from_iter(f(start))(f))
+        return helper
+
+    def map(self, f: Callable[[S], T]) -> LazyConsList[T]:
+        """map函数
+        """
+        return LazyCons(lambda: f(self.head()), lambda : self.tail().map(f))
+
+    def collect(self) -> ConsList[S]:
+        """求值
+        """
+        if self.head() == LazyStop():
+            return self.tail().collect()
+        else:
+            return Cons(self.head(), self.tail().collect())
+
+    def filter(self, f: Callable[[S], bool]) -> LazyConsList[S]:
+        """filter
+        """
+        # if self.head() == LazyStop():
+        #     return LazyCons(LazyStop(), lambda : self.tail().filter(f))
+        return LazyCons(
+            lambda : self.head() if f(self.head()) else LazyStop(),
+            lambda : self.tail().filter(f)
+        )
+
+    def take(self, n):
+        """获取惰性列表前几个元素
+        """
+        if n == 0:
+            return LazyEmpty()
+        elif self.head() == LazyStop():
+            return LazyCons(lambda: LazyStop(), lambda: self.tail().take(n)) # pylint: disable=unnecessary-lambda
+        else:
+            return LazyCons(self.head, lambda: self.tail().take(n - 1))
diff --git a/_site/fppy/lazy_list_lambda.py b/_site/fppy/lazy_list_lambda.py
new file mode 100644
index 0000000..0c1932c
--- /dev/null
+++ b/_site/fppy/lazy_list_lambda.py
@@ -0,0 +1,80 @@
+def lazy_cons(head, tail):
+    """基于二元元组的LazyList
+    """
+    return (head, tail)
+
+def make_lazy_cons(*args):
+    if len(args) == 0:
+        return ()
+    else:
+        return lazy_cons(lambda: args[0], lambda: make_lazy_cons(*args[1:]))
+
+head = lambda lls: lls[0]() if callable(lls[0]) else lls[0]
+tail = lambda lls: lls[1]() if callable(lls[1]) else lls[1]
+
+def iterate_lazy_cons(start):
+    """生成LazyList
+    """
+    def helper(f):
+        return lazy_cons(
+            lambda: start,
+            lambda: iterate_lazy_cons(f(start))(f)
+        )
+    return helper
+
+def filter_lazy_cons(f):
+    def helper(lls):
+        if lls == ():
+            return ()
+        else:
+            return lazy_cons(
+                lambda : head(lls) if head(lls) != () and f(head(lls)) else (),
+                lambda : filter_lazy_cons(f)(tail(lls))
+            )
+    return helper
+
+def collect_lazy_cons(lls):
+    if lls == ():
+        return ()
+    elif head(lls) == ():
+        return collect_lazy_cons(tail(lls))
+    else:
+        return (head(lls), collect_lazy_cons(tail(lls)))
+
+def take_lazy_cons(n: int):
+    def helper(lls):
+        if n == 0:
+            return ()
+        elif head(lls) == ():
+            return take_lazy_cons(n)(tail(lls))
+        else:
+            return (head(lls), take_lazy_cons(n - 1)(tail(lls)))
+    return helper
+
+def map_lazy_cons(f):
+    def helper(lls):
+        if lls == ():
+            return ()
+        elif head(lls) == ():
+            return lazy_cons(
+                lambda: (),
+                lambda: map_lazy_cons(f)(tail(lls))
+            )
+        else:
+            return lazy_cons(
+                lambda: f(head(lls)),
+                lambda: map_lazy_cons(f)(tail(lls))
+            )
+    return helper
+
+def fold_left_lazy_cons(f):
+    def bring_start(start):
+        def helper(lls):
+            if lls == ():
+                return start
+            elif head(lls) == ():
+                return fold_left_lazy_cons(f)(start)(tail(lls))
+            else:
+                return fold_left_lazy_cons(f)(f(start, head(lls)))(tail(lls))
+        return helper
+    return bring_start
diff --git a/_site/fppy/lens.py b/_site/fppy/lens.py
new file mode 100644
index 0000000..e69de29
diff --git a/_site/fppy/option.py b/_site/fppy/option.py
new file mode 100644
index 0000000..2d698b7
--- /dev/null
+++ b/_site/fppy/option.py
@@ -0,0 +1,87 @@
+from __future__ import annotations
+from dataclasses import dataclass
+from typing import Callable, Generic
+from fppy.errors import NothingHasNoSuchMethod
+from .gt import S, T
+
+@dataclass
+class Option:
+    """Option类型
+    """
+
+@dataclass
+class Nothing(Option, Generic[S]):
+    """空类型
+    """
+
+    def collect(self, pf):
+        """计算
+
+        Args:
+            pf (PartialFunction): 一个偏函数
+
+        Returns:
+            Nothing: 返回空
+        """
+        return Nothing()
+
+    def get_or_else(self, v: S) -> S:
+        """获取数据如果没有返回 `v`
+
+        Args:
+            v (S): 默认值
+
+        Returns:
+            S: 返回值
+        """
+        return v
+
+    def filter(self, f: Callable[[S], Option[T]]) -> Option[T]:
+        """过滤函数
+
+        Args:
+            f (Callable[[S], Option[T]]): 带入过滤条件
+
+        Returns:
+            Option[T]: 返回值
+        """
+        return Nothing()
+
+    @property
+    def get(self):
+        """直接获取方法
+
+        Raises:
+            NothingHasNoSuchMethod: 获取不到任何值的报错
+        """
+        raise NothingHasNoSuchMethod("Nothing has no get method")
+
+@dataclass
+class Just(Option, Generic[S]):
+    value: S
+
+    def map(self, f: Callable[[S], T]) -> Option[T]:
+        return Just(f(self.value))
+
+    def flat_map(self, f: Callable[[S], Option[T]]) -> Option[T]:
+        res = f(self.value)
+        if isinstance(res, Option):
+            return res
+        else:
+            raise TypeError("result is not an Option object")
+
+    @property
+    def get(self) -> S:
+        return self.value
+
+    def collect(self, pf):
+        return pf.lift(self.value)
+
+    def get_or_else(self, v: S):
+        return self.value
+
+    def filter(self, f: Callable[[S], Option[T]]) -> Option[T]:
+        if f(self.value):
+            return self
+        else:
+            return Nothing()
\ No newline at end of file
diff --git a/_site/fppy/partial_function.py b/_site/fppy/partial_function.py
new file mode 100644
index 0000000..9f24f8b
--- /dev/null
+++ b/_site/fppy/partial_function.py
@@ -0,0 +1,78 @@
+from __future__ import annotations
+from dataclasses import dataclass, field
+from typing import Callable, List, Tuple
+from .errors import NoOtherCaseError
+
+@dataclass
+class _PartialCase:
+    case_func: Callable
+    case_list: List[Tuple[Callable, Callable]] = field(default_factory=list)
+
+    def then(
+            self,
+            return_func: Callable
+        ):
+        return _PartialFunction(
+            self.case_list + [(self.case_func, return_func)]
+        )
+    
+@dataclass
+class _PartialFunction:
+    case_list:  List[Tuple[Callable, Callable]]= field(default_factory=list)
+    
+    def apply(
+            self,
+            value
+        ):
+        if len(self.case_list) == 0:
+            raise NoOtherCaseError
+        else:
+            head = self.case_list[0]
+            tail = self.case_list[1:]
+            if head[0](value):
+                return head[1](value)
+            else:
+                return _PartialFunction(tail).apply(value)
+
+    def __call__(self, value):
+        return self.apply(value)
+    
+    def is_defined_at(self, value) -> bool:
+        """判断是否有定义
+        """
+        if len(self.case_list) == 0:
+            return False
+        else:
+            head = self.case_list[0]
+            tail = self.case_list[1:]
+            if head[0](value):
+                return True
+            else:
+                return (
+                    _PartialFunction(tail)
+                    .is_defined_at(value)
+                )
+
+    def case(self, case_func):
+        return _PartialCase(
+            case_func,
+            self.case_list
+        )
+    
+    def or_else(self, other:_PartialFunction):
+        return _PartialFunction(
+            self.case_list + other.case_list
+        )
+    
+    def lift(self, value):
+        from .option import Just, Nothing
+
+        if self.is_defined_at(value):
+            return Just(self.__call__(value))
+        else:
+            return Nothing
+
+class PartialFunction:
+    @staticmethod
+    def case(case_func):
+        return _PartialFunction().case(case_func)
\ No newline at end of file
diff --git a/_site/fppy/random.py b/_site/fppy/random.py
new file mode 100644
index 0000000..147157b
--- /dev/null
+++ b/_site/fppy/random.py
@@ -0,0 +1,91 @@
+from __future__ import annotations
+from dataclasses import dataclass
+from abc import abstractmethod
+from collections import ABC
+from typing import Callable, Tuple, TypeVar
+from .lazy_list import LazyList
+
+S = TypeVar("S")
+T = TypeVar("T")
+Rand = TypeVar("Rand")
+
+
+class RNG(ABC):
+
+    @abstractmethod
+    def next_int(self) -> Tuple[int, RNG]:
+        pass
+
+@dataclass
+class SimpleRNG(RNG):
+    seed: int
+    def next_int(self) -> Tuple[int, SimpleRNG]:
+        new_seed = (self.seed * 0x5DEECE66D + 0xB) & 0xFFFFFFFFFFFF
+        next_RNG = SimpleRNG(new_seed)
+        n = int(new_seed >> 16)
+        return (n, next_RNG)
+
+def curry_simple_rng(seed: int):
+    def helper():
+        new_seed = (seed * 0x5DEECE66D + 0xB) & 0xFFFFFFFFFFFF
+        next_func= curry_simple_rng(new_seed)
+        n = int(new_seed >> 16)
+        return (n, next_func)
+    return helper
+
+# Rand = GenericAlias(Callable[[RNG], Tuple[S, RNG]], S)
+
+rand_int: Rand[int] = SimpleRNG.next_int
+
+
+def unit(s: S) -> Rand[S]:
+    return lambda rng: (s, rng)
+
+def map(rand: Rand[S], f: Callable[[S], T]):
+    def helper(rng: RNG):
+        res, rng2 = rand(rng)
+        return (f(res), rng2)
+    return helper
+
+rand_non_negative_int: Rand[int] = map(rand_int, abs)
+rand_non_negative_even: Rand[int] = map(rand_int, lambda x: x - x % 2)
+rand_float: Rand[int] = map(rand_int, lambda x: x / 2 ** 32)
+
+def rng_lazy_list(count: int | None = None, seed: int = 1234):
+    int_start, rng_start = SimpleRNG(seed=seed).next_int()
+    if count is None:
+        return LazyList\
+            .from_iter((int_start, rng_start))\
+                (lambda x: SimpleRNG(x[0]).next_int())\
+            .map(lambda x: x[0])
+    else:
+        return LazyList\
+            .from_iter((int_start, rng_start))\
+                (lambda x: SimpleRNG(x[0]).next_int())\
+            .map(lambda x: x[0])\
+            .take(count)
+
+def rand_lazy_list(count: int | None = None, seed: int = 1234):
+    start = rand_int(SimpleRNG(seed))
+    if count is None:
+        return LazyList\
+            .from_iter(start)(lambda x: rand_non_negative_int(x[1]))\
+            .map(lambda x: x[0])
+    else:
+        return LazyList\
+            .from_iter(start)(lambda x: rand_non_negative_int(x[1]))\
+            .map(lambda x: x[0])\
+            .take(count)
+
+def curry_random_lazy_list(count: int | None = None, seed: int = 1234):
+    start = curry_simple_rng(seed)()
+    if count is None:
+        return LazyList\
+            .from_iter(start)(lambda x: x[1]())\
+            .map(lambda x: x[0])
+    else:
+        return LazyList\
+            .from_iter(start)(lambda x: x[1]())\
+            .map(lambda x: x[0])\
+            .take(count)
+
diff --git a/_site/fppy/try_monad.py b/_site/fppy/try_monad.py
new file mode 100644
index 0000000..73d937e
--- /dev/null
+++ b/_site/fppy/try_monad.py
@@ -0,0 +1,11 @@
+from dataclasses import dataclass
+from typing import Generic, List
+from .gt import S, T
+
+@dataclass
+class Try(Generic[S]):
+    value: None | S = None
+    state_list: List = []
+    
+    def of(self, value):
+        return ()
\ No newline at end of file
diff --git a/_site/notebooks/examples_of_cons_list.ipynb b/_site/notebooks/examples_of_cons_list.ipynb
new file mode 100644
index 0000000..3406537
--- /dev/null
+++ b/_site/notebooks/examples_of_cons_list.ipynb
@@ -0,0 +1,232 @@
+{
+ "cells": [
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "# 自己实现的列表以及惰性列表"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 4,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "import sys\n",
+    "sys.path.append(\"../\")\n"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 6,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "from fppy.cons_list import Empty, Cons\n",
+    "from fppy.cons_list_base import filter_cons"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 7,
+   "metadata": {},
+   "outputs": [
+    {
+     "ename": "TypeError",
+     "evalue": "'tuple' object is not callable",
+     "output_type": "error",
+     "traceback": [
+      "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m",
+      "\u001b[1;31mTypeError\u001b[0m                                 Traceback (most recent call last)",
+      "\u001b[1;32mC:\\Users\\ADMINI~1\\AppData\\Local\\Temp/ipykernel_25708/2895798992.py\u001b[0m in \u001b[0;36m<module>\u001b[1;34m\u001b[0m\n\u001b[1;32m----> 1\u001b[1;33m \u001b[0mfilter_cons\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;32mlambda\u001b[0m \u001b[0mx\u001b[0m\u001b[1;33m:\u001b[0m \u001b[0mx\u001b[0m\u001b[1;33m>\u001b[0m \u001b[1;36m2\u001b[0m\u001b[1;33m,\u001b[0m \u001b[1;33m(\u001b[0m\u001b[1;36m1\u001b[0m\u001b[1;33m,\u001b[0m \u001b[1;36m2\u001b[0m\u001b[1;33m,\u001b[0m \u001b[1;33m(\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m,\u001b[0m \u001b[1;36m3\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m",
+      "\u001b[1;32me:\\Dropbox\\project\\python-functional-programming\\notebooks\\..\\fppy\\cons_list_base.py\u001b[0m in \u001b[0;36mfilter_cons\u001b[1;34m(f, cons_list)\u001b[0m\n\u001b[0;32m    118\u001b[0m         \u001b[1;32mreturn\u001b[0m \u001b[0mempty_list_base\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m    119\u001b[0m     \u001b[1;32melse\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m--> 120\u001b[1;33m         \u001b[0mhd\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mtl\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mhead\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mcons_list\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mtail\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mcons_list\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m    121\u001b[0m         \u001b[1;32mif\u001b[0m \u001b[0mf\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mhd\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m    122\u001b[0m             \u001b[1;32mreturn\u001b[0m \u001b[0mcons\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mhd\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mfilter_cons\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mf\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mtl\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n",
+      "\u001b[1;32me:\\Dropbox\\project\\python-functional-programming\\notebooks\\..\\fppy\\cons_list_base.py\u001b[0m in \u001b[0;36m<lambda>\u001b[1;34m(cons_list)\u001b[0m\n\u001b[0;32m     34\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m     35\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m---> 36\u001b[1;33m \u001b[0mhead\u001b[0m\u001b[1;33m:\u001b[0m \u001b[0mCallable\u001b[0m\u001b[1;33m[\u001b[0m\u001b[1;33m[\u001b[0m\u001b[0mConsListBase\u001b[0m\u001b[1;33m[\u001b[0m\u001b[0mS\u001b[0m\u001b[1;33m]\u001b[0m\u001b[1;33m]\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mS\u001b[0m\u001b[1;33m]\u001b[0m \u001b[1;33m=\u001b[0m \u001b[1;32mlambda\u001b[0m \u001b[0mcons_list\u001b[0m\u001b[1;33m:\u001b[0m \u001b[0mcons_list\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m[\u001b[0m\u001b[1;36m0\u001b[0m\u001b[1;33m]\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m     37\u001b[0m \u001b[0mhead\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0m__doc__\u001b[0m \u001b[1;33m==\u001b[0m \u001b[1;34m\"\"\"获取List第一个元素\"\"\"\u001b[0m \u001b[1;31m# pylint: disable=pointless-statement\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m     38\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n",
+      "\u001b[1;31mTypeError\u001b[0m: 'tuple' object is not callable"
+     ]
+    }
+   ],
+   "source": [
+    "filter_cons(lambda x: x> 2, (1, 2, (), 3))"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "## 1. 列表\n",
+    "\n",
+    "### 1.1 生成列表"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 3,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "Cons(head=1, tail=Cons(head=2, tail=Cons(head=3, tail=Cons(head=4, tail=Empty()))))"
+      ]
+     },
+     "execution_count": 3,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "ls1 = Cons.maker(1, 2, 3, 4)\n",
+    "ls1"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 14,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "Cons(head=0, tail=Empty())"
+      ]
+     },
+     "execution_count": 14,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "Cons.maker(\n",
+    "           *[0]\n",
+    "        ).filter(lambda x: x % 2 == 0)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 10,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "Empty()"
+      ]
+     },
+     "execution_count": 10,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "ls2 = Cons.maker()\n",
+    "ls2"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "## 1.2 map"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 4,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "Cons(head=2, tail=Cons(head=3, tail=Cons(head=4, tail=Cons(head=5, tail=Empty()))))"
+      ]
+     },
+     "execution_count": 4,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "ls1.map(lambda x: x + 1)"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "## 1.3 filter"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 5,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "Cons(head=2, tail=Cons(head=4, tail=Empty()))"
+      ]
+     },
+     "execution_count": 5,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "ls1.filter(lambda x: x % 2 == 0)"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "## 1.4 Reduce"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 7,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "10"
+      ]
+     },
+     "execution_count": 7,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "ls1.fold_left(lambda x, y: x + y, 0)"
+   ]
+  }
+ ],
+ "metadata": {
+  "interpreter": {
+   "hash": "b00d0533784e0bd7ec39dc1f12727fcf9021e9a985583666278f4fb55e7dfcf4"
+  },
+  "kernelspec": {
+   "display_name": "Python 3.10.0 64-bit ('fppy2': conda)",
+   "name": "python3"
+  },
+  "language_info": {
+   "codemirror_mode": {
+    "name": "ipython",
+    "version": 3
+   },
+   "file_extension": ".py",
+   "mimetype": "text/x-python",
+   "name": "python",
+   "nbconvert_exporter": "python",
+   "pygments_lexer": "ipython3",
+   "version": "3.10.0"
+  },
+  "orig_nbformat": 4
+ },
+ "nbformat": 4,
+ "nbformat_minor": 2
+}
diff --git a/_site/notebooks/examples_of_either.ipynb b/_site/notebooks/examples_of_either.ipynb
new file mode 100644
index 0000000..44cd273
--- /dev/null
+++ b/_site/notebooks/examples_of_either.ipynb
@@ -0,0 +1,60 @@
+{
+ "cells": [
+  {
+   "cell_type": "code",
+   "execution_count": 2,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "import sys\n",
+    "sys.path.append(\"../\")\n",
+    "\n",
+    "from fppy.either import Either"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 3,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "Right(value=1)"
+      ]
+     },
+     "execution_count": 3,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "Either(1)"
+   ]
+  }
+ ],
+ "metadata": {
+  "interpreter": {
+   "hash": "b00d0533784e0bd7ec39dc1f12727fcf9021e9a985583666278f4fb55e7dfcf4"
+  },
+  "kernelspec": {
+   "display_name": "Python 3.10.0 64-bit ('fppy2': conda)",
+   "name": "python3"
+  },
+  "language_info": {
+   "codemirror_mode": {
+    "name": "ipython",
+    "version": 3
+   },
+   "file_extension": ".py",
+   "mimetype": "text/x-python",
+   "name": "python",
+   "nbconvert_exporter": "python",
+   "pygments_lexer": "ipython3",
+   "version": "3.10.0"
+  },
+  "orig_nbformat": 4
+ },
+ "nbformat": 4,
+ "nbformat_minor": 2
+}
diff --git a/_site/notebooks/examples_of_function_monad.ipynb b/_site/notebooks/examples_of_function_monad.ipynb
new file mode 100644
index 0000000..e74b131
--- /dev/null
+++ b/_site/notebooks/examples_of_function_monad.ipynb
@@ -0,0 +1,135 @@
+{
+ "cells": [
+  {
+   "cell_type": "code",
+   "execution_count": 34,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "def logger(x, log_str=\"log is:\\n\"):\n",
+    "    def logger_func(f):\n",
+    "        res = f(x)\n",
+    "        print_res = log_str + f\"\"\"{f.__name__}'s result is {x} => {res}\\n\"\"\"\n",
+    "        return (print_res, logger(res, print_res))\n",
+    "    return logger_func\n",
+    "\n",
+    "def get_logging(logger_tuple): return logger_tuple[0]\n",
+    "def get_logger(logger_tuple): return logger_tuple[1]"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 57,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "def f(x): return x + 1\n",
+    "def g(x): return x * 2"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 66,
+   "metadata": {},
+   "outputs": [
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "log is:\n",
+      "f's result is 1 => 2\n",
+      "g's result is 2 => 4\n",
+      "f's result is 4 => 5\n",
+      "f's result is 5 => 6\n",
+      "g's result is 6 => 12\n",
+      "\n"
+     ]
+    }
+   ],
+   "source": [
+    "from functools import reduce\n",
+    "\n",
+    "def apply_list(x, f_list):\n",
+    "    return reduce(lambda l_r, f: l_r[1](f), f_list, (\"log is:\\n\", logger(x)))\n",
+    "\n",
+    "res1 = apply_list(1, [f, g, f, f, g])\n",
+    "print(get_logging(res1))"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 70,
+   "metadata": {},
+   "outputs": [
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "log is:\n",
+      "f's result is 1 => 2\n",
+      "h's result is 2 => 4\n",
+      "g's result is 4 => 8\n",
+      "h's result is 8 => 64\n",
+      "f's result is 64 => 65\n",
+      "h's result is 65 => 4225\n",
+      "f's result is 4225 => 4226\n",
+      "h's result is 4226 => 17859076\n",
+      "g's result is 17859076 => 35718152\n",
+      "h's result is 35718152 => 1275786382295104\n",
+      "\n"
+     ]
+    }
+   ],
+   "source": [
+    "def map_apply_list(g):\n",
+    "    def helper(x, f_list):\n",
+    "        return reduce(lambda l_r, f: l_r[1](f)[1](g), f_list, (\"log is:\\n\", logger(x)))\n",
+    "    return helper\n",
+    "\n",
+    "def h(x): return x ** 2\n",
+    "\n",
+    "apply_h_after_every_run = map_apply_list(h)\n",
+    "res2 = apply_h_after_every_run(1, [f, g, f, f, g])\n",
+    "print(get_logging(res2))"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 71,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "def flat_map_apply_list(g):\n",
+    "    def helper(x, f_list):\n",
+    "        return reduce(lambda l_r, f: l_r[1](f)[1](g)[1], f_list, (\"log is:\\n\", logger(x)))\n",
+    "    return helper\n",
+    "\n",
+    "def h_flat(x): return lox ** 2"
+   ]
+  }
+ ],
+ "metadata": {
+  "interpreter": {
+   "hash": "b00d0533784e0bd7ec39dc1f12727fcf9021e9a985583666278f4fb55e7dfcf4"
+  },
+  "kernelspec": {
+   "display_name": "Python 3.10.0 64-bit ('fppy2': conda)",
+   "name": "python3"
+  },
+  "language_info": {
+   "codemirror_mode": {
+    "name": "ipython",
+    "version": 3
+   },
+   "file_extension": ".py",
+   "mimetype": "text/x-python",
+   "name": "python",
+   "nbconvert_exporter": "python",
+   "pygments_lexer": "ipython3",
+   "version": "3.10.0"
+  },
+  "orig_nbformat": 4
+ },
+ "nbformat": 4,
+ "nbformat_minor": 2
+}
diff --git a/_site/notebooks/examples_of_lazy_list_lambda.ipynb b/_site/notebooks/examples_of_lazy_list_lambda.ipynb
new file mode 100644
index 0000000..a2b22ae
--- /dev/null
+++ b/_site/notebooks/examples_of_lazy_list_lambda.ipynb
@@ -0,0 +1,235 @@
+{
+ "cells": [
+  {
+   "cell_type": "code",
+   "execution_count": 72,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "import sys\n",
+    "sys.path.append(\"../\")\n",
+    "\n",
+    "from typing import Callable, Tuple\n",
+    "from fppy.gt import S, T, GenericTypeVar\n",
+    "\n",
+    "VoidCallable = Callable[[], S]\n",
+    "\n",
+    "def lazy_cons(head: VoidCallable[S], tail: VoidCallable[S] | Tuple):\n",
+    "    \"\"\"基于二元元组的LazyList\n",
+    "    \"\"\"\n",
+    "    return (head, tail)\n",
+    "\n",
+    "def make_lazy_cons(*args: S) -> VoidCallable[S]:\n",
+    "    if len(args) == 0:\n",
+    "        return ()\n",
+    "    else:\n",
+    "        return lazy_cons(lambda: args[0], lambda: make_lazy_cons(*args[1:]))\n",
+    "\n",
+    "head = lambda lls: lls[0]()\n",
+    "tail = lambda lls: lls[1]()\n",
+    "\n",
+    "def iterate_lazy_cons(start: S):\n",
+    "    def helper(f: Callable[[S], S]):\n",
+    "        return lazy_cons(\n",
+    "            lambda: start, \n",
+    "            lambda: iterate_lazy_cons(f(start))(f)\n",
+    "        )\n",
+    "    return helper\n",
+    "\n",
+    "def filter_lazy_cons(f: Callable[[S], bool]):\n",
+    "    def helper(lls):\n",
+    "        if lls == ():\n",
+    "            return ()\n",
+    "        else:\n",
+    "            return lazy_cons(\n",
+    "                lambda : head(lls) if head(lls) != () and f(head(lls)) else (),\n",
+    "                lambda : filter_lazy_cons(f)(tail(lls))\n",
+    "            )\n",
+    "    return helper\n",
+    "\n",
+    "def collect_lazy_cons(lls):\n",
+    "    if lls == ():\n",
+    "        return ()\n",
+    "    elif head(lls) == ():\n",
+    "        return collect_lazy_cons(tail(lls))\n",
+    "    else:\n",
+    "        return (head(lls), collect_lazy_cons(tail(lls)))\n",
+    "\n",
+    "def take_lazy_cons(n: int):\n",
+    "    def helper(lls):\n",
+    "        if n == 0:\n",
+    "            return ()\n",
+    "        elif head(lls) == ():\n",
+    "            return take_lazy_cons(n)(tail(lls))\n",
+    "        else:\n",
+    "            return (head(lls), take_lazy_cons(n - 1)(tail(lls)))\n",
+    "    return helper"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 73,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "a = iterate_lazy_cons(1)(lambda x: x + 1)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 74,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "(1, (2, (3, ())))"
+      ]
+     },
+     "execution_count": 74,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "take_lazy_cons(3)(a)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 75,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "b = make_lazy_cons(1, 2, 3)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 76,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "(1, (3, ()))"
+      ]
+     },
+     "execution_count": 76,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "collect_lazy_cons(filter_lazy_cons(lambda x: x % 2 == 1)(b))"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 77,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "(1, (2, ()))"
+      ]
+     },
+     "execution_count": 77,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "take_lazy_cons(2)(b)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 78,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "(1, (2, (3, ())))"
+      ]
+     },
+     "execution_count": 78,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "collect_lazy_cons(b)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 79,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "def map_lazy_cons(f):\n",
+    "    def helper(lls):\n",
+    "        if lls == ():\n",
+    "            return ()\n",
+    "        if head(lls) == ():\n",
+    "            return lazy_cons(\n",
+    "                lambda: (),\n",
+    "                lambda: map_lazy_cons(f)(tail(lls))\n",
+    "            )\n",
+    "        else:\n",
+    "            return lazy_cons(\n",
+    "                lambda: f(head(lls)),\n",
+    "                lambda: map_lazy_cons(f)(tail(lls))\n",
+    "            )\n",
+    "    return helper"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 83,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "(2, (3, (4, (5, ()))))"
+      ]
+     },
+     "execution_count": 83,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "take_lazy_cons(4)(map_lazy_cons(lambda x: x+ 1)(a))"
+   ]
+  }
+ ],
+ "metadata": {
+  "interpreter": {
+   "hash": "b00d0533784e0bd7ec39dc1f12727fcf9021e9a985583666278f4fb55e7dfcf4"
+  },
+  "kernelspec": {
+   "display_name": "Python 3.10.0 64-bit ('fppy2': conda)",
+   "name": "python3"
+  },
+  "language_info": {
+   "codemirror_mode": {
+    "name": "ipython",
+    "version": 3
+   },
+   "file_extension": ".py",
+   "mimetype": "text/x-python",
+   "name": "python",
+   "nbconvert_exporter": "python",
+   "pygments_lexer": "ipython3",
+   "version": "3.10.0"
+  },
+  "orig_nbformat": 4
+ },
+ "nbformat": 4,
+ "nbformat_minor": 2
+}
diff --git a/_site/notebooks/examples_of_monad_for.ipynb b/_site/notebooks/examples_of_monad_for.ipynb
new file mode 100644
index 0000000..e1b585f
--- /dev/null
+++ b/_site/notebooks/examples_of_monad_for.ipynb
@@ -0,0 +1,78 @@
+{
+ "cells": [
+  {
+   "cell_type": "code",
+   "execution_count": 55,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "from __future__ import annotations\n",
+    "from typing import TypeVar, Generic, NoReturn\n",
+    "\n",
+    "T = TypeVar(\"T\")\n",
+    "\n",
+    "\n",
+    "class M(Generic[T]):\n",
+    "    value: T\n",
+    "    cnt: int = 1\n",
+    "\n",
+    "    def __init__(self, value: T) -> NoReturn:\n",
+    "        self.value = value\n",
+    "    \n",
+    "    def __iter__(self) -> M:\n",
+    "        return self\n",
+    "\n",
+    "    def __next__(self) -> T:\n",
+    "        if self.cnt == 1:\n",
+    "            self.cnt = 0\n",
+    "            return self.value\n",
+    "        else:\n",
+    "            raise StopIteration"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 56,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "[6]"
+      ]
+     },
+     "execution_count": 56,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "[i * 2 for i in M(*(i+ 1 for i in M(2)))]"
+   ]
+  }
+ ],
+ "metadata": {
+  "interpreter": {
+   "hash": "b00d0533784e0bd7ec39dc1f12727fcf9021e9a985583666278f4fb55e7dfcf4"
+  },
+  "kernelspec": {
+   "display_name": "Python 3.10.0 64-bit ('fppy2': conda)",
+   "name": "python3"
+  },
+  "language_info": {
+   "codemirror_mode": {
+    "name": "ipython",
+    "version": 3
+   },
+   "file_extension": ".py",
+   "mimetype": "text/x-python",
+   "name": "python",
+   "nbconvert_exporter": "python",
+   "pygments_lexer": "ipython3",
+   "version": "3.10.0"
+  },
+  "orig_nbformat": 4
+ },
+ "nbformat": 4,
+ "nbformat_minor": 2
+}
diff --git a/_site/notebooks/examples_of_random.ipynb b/_site/notebooks/examples_of_random.ipynb
new file mode 100644
index 0000000..022fd68
--- /dev/null
+++ b/_site/notebooks/examples_of_random.ipynb
@@ -0,0 +1,105 @@
+{
+ "cells": [
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "# 测试Random"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 4,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "from types import GenericAlias\n",
+    "from dataclasses import dataclass\n",
+    "from abc import abstractmethod, ABC\n",
+    "from __future__ import annotations\n",
+    "from typing import Any, Callable, Tuple, TypeVar\n",
+    "\n",
+    "S = TypeVar(\"S\")\n",
+    "T = TypeVar(\"T\")\n",
+    "\n",
+    "\n",
+    "class RNG(ABC):\n",
+    "    \n",
+    "    @abstractmethod\n",
+    "    def next_int(self) -> Tuple[int, RNG]:\n",
+    "        pass\n",
+    "\n",
+    "\n",
+    "@dataclass\n",
+    "class SimpleRNG(RNG):\n",
+    "    seed: int\n",
+    "    def next_int(self) -> Tuple[int, RNG]:\n",
+    "        new_seed = (self.seed * 0x5DEECE66D + 0xB) & 0xFFFFFFFFFFFF\n",
+    "        next_RNG = SimpleRNG(new_seed)\n",
+    "        n = int(new_seed >> 16)\n",
+    "        return (n, next_RNG)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 10,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "Rand = GenericAlias(Callable[[RNG], Tuple[S, RNG]], S)\n",
+    "\n",
+    "rand_int: Rand[int] = SimpleRNG.next_int\n",
+    "\n",
+    "def unit(s: S) -> Rand[S]:\n",
+    "    return lambda rng: (s, rng)\n",
+    "\n",
+    "def map(rand: Rand[S], f: Callable[[S], T]) -> Rand[T]:\n",
+    "    def helper(rng: RNG):\n",
+    "        res, rng2 = rand(rng)\n",
+    "        return (f(res), rng2)\n",
+    "    return helper"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 22,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "rand_non_negative_int: Rand[int] = map(rand_int, abs)\n",
+    "rand_non_negative_even: Rand[int] = map(rand_int, lambda x: x - x % 2)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": []
+  }
+ ],
+ "metadata": {
+  "interpreter": {
+   "hash": "b00d0533784e0bd7ec39dc1f12727fcf9021e9a985583666278f4fb55e7dfcf4"
+  },
+  "kernelspec": {
+   "display_name": "Python 3.10.0 64-bit ('fppy2': conda)",
+   "name": "python3"
+  },
+  "language_info": {
+   "codemirror_mode": {
+    "name": "ipython",
+    "version": 3
+   },
+   "file_extension": ".py",
+   "mimetype": "text/x-python",
+   "name": "python",
+   "nbconvert_exporter": "python",
+   "pygments_lexer": "ipython3",
+   "version": "3.10.0"
+  },
+  "orig_nbformat": 4
+ },
+ "nbformat": 4,
+ "nbformat_minor": 2
+}
diff --git a/_site/notebooks/examples_of_stream.ipynb b/_site/notebooks/examples_of_stream.ipynb
new file mode 100644
index 0000000..eb471c2
--- /dev/null
+++ b/_site/notebooks/examples_of_stream.ipynb
@@ -0,0 +1,277 @@
+{
+ "cells": [
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "# 数据流例子"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 15,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "# !pip install faker\n",
+    "import sys\n",
+    "sys.path.append(\"../\")\n",
+    "\n",
+    "from fppy.lazy_list import LazyList\n",
+    "from faker import Faker\n",
+    "from IPython.display import display\n",
+    "from datetime import datetime as dt"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "## 演示如何使用`Faker`"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 10,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "'Mozilla/5.0 (iPad; CPU iPad OS 9_3_6 like Mac OS X) AppleWebKit/533.1 (KHTML, like Gecko) CriOS/24.0.867.0 Mobile/77X748 Safari/533.1'"
+      ]
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    },
+    {
+     "data": {
+      "text/plain": [
+       "'2021-11-09 16:40:44'"
+      ]
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    },
+    {
+     "data": {
+      "text/plain": [
+       "44"
+      ]
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    },
+    {
+     "data": {
+      "text/plain": [
+       "'Joshua Nielsen'"
+      ]
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    }
+   ],
+   "source": [
+    "fk = Faker()\n",
+    "display(fk.chrome())\n",
+    "display(fk.date_time_between_dates().strftime(\"%Y-%m-%d %H:%M:%S\"))\n",
+    "display(fk.random_int(14, 50))\n",
+    "display(fk.name())"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "## 制作一个生成假数据的生成器"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 26,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "def data_stream_gen():\n",
+    "    \"\"\"数据流生成器\n",
+    "    \"\"\"\n",
+    "    fk = Faker()\n",
+    "    Faker.seed(0) # 为了让下面的显示结果一致\n",
+    "    while True:\n",
+    "        yield {\n",
+    "            \"create_time\": (\n",
+    "                fk\n",
+    "                .date_time_between_dates()\n",
+    "                .strftime(\"%Y-%m-%d %H:%M:%S\")),\n",
+    "            \"name\": fk.name(),\n",
+    "            \"email\": fk.email(),\n",
+    "            \"age\": fk.random_int(14, 50),\n",
+    "            \"address\": fk.address(),\n",
+    "            \"device\": {\n",
+    "                \"browser\": fk.chrome()\n",
+    "            }\n",
+    "        }"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "查看是不是可用："
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 24,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "[{'create_time': '2021-11-09 16:47:38',\n",
+       "  'name': 'Jennifer Green',\n",
+       "  'email': 'ysullivan@example.com',\n",
+       "  'age': 44,\n",
+       "  'address': '242 Christine Glen\\nWest Corey, TX 43780',\n",
+       "  'device': {'browser': 'Mozilla/5.0 (Windows NT 5.1) AppleWebKit/534.2 (KHTML, like Gecko) Chrome/46.0.833.0 Safari/534.2'}},\n",
+       " {'create_time': '2021-11-09 16:47:38',\n",
+       "  'name': 'Amy Stark',\n",
+       "  'email': 'johnponce@example.net',\n",
+       "  'age': 42,\n",
+       "  'address': '714 Mann Plaza Suite 839\\nSeanfurt, OK 32234',\n",
+       "  'device': {'browser': 'Mozilla/5.0 (iPad; CPU iPad OS 10_3_4 like Mac OS X) AppleWebKit/533.0 (KHTML, like Gecko) CriOS/28.0.827.0 Mobile/32P094 Safari/533.0'}},\n",
+       " {'create_time': '2021-11-09 16:47:38',\n",
+       "  'name': 'Joanne Keller',\n",
+       "  'email': 'christopher91@example.com',\n",
+       "  'age': 35,\n",
+       "  'address': '41352 Simmons Circle\\nPort Dustinbury, OK 83627',\n",
+       "  'device': {'browser': 'Mozilla/5.0 (Linux; Android 4.0.2) AppleWebKit/535.2 (KHTML, like Gecko) Chrome/26.0.893.0 Safari/535.2'}}]"
+      ]
+     },
+     "execution_count": 24,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "LazyList(data_stream_gen()).take(3).collect()"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "## 制作一些处理流数据的函数\n",
+    "\n",
+    "\n",
+    "1. `drop_device`：删除设备信息\n",
+    "2. `user_info`：将所有用户信息归并在一起\n",
+    "3. `change_create_time`：修改时间格式"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 16,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "def drop_device(x):\n",
+    "    \"\"\"删除device信息\n",
+    "    \"\"\"\n",
+    "    return { k:v for k, v in x.items() if k != 'device'}\n",
+    "\n",
+    "def user_info(x):\n",
+    "    \"\"\"将用户信息包在一起\n",
+    "    \"\"\"\n",
+    "    user_info_name = {\"name\", \"email\", \"age\", \"adress\"}\n",
+    "    return {**{\n",
+    "        'user_info': {\n",
+    "            k:v\n",
+    "            for k, v in x.items() if k in user_info_name\n",
+    "        },\n",
+    "    }, **{k: v for k, v in x.items() if k not in user_info_name}}\n",
+    "\n",
+    "def change_create_time(x):\n",
+    "    \"\"\"修改日期格式\n",
+    "    \"\"\"\n",
+    "    res = x.copy()\n",
+    "    res['create_time'] = dt.strptime(\n",
+    "        res['create_time'],\n",
+    "        \"%Y-%m-%d %H:%M:%S\"\n",
+    "    ).timestamp()\n",
+    "    return res"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "## 测试处理结果"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 25,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "[{'user_info': {'name': 'Jennifer Green',\n",
+       "   'email': 'ysullivan@example.com',\n",
+       "   'age': 44},\n",
+       "  'create_time': 1636447661.0,\n",
+       "  'address': '242 Christine Glen\\nWest Corey, TX 43780'},\n",
+       " {'user_info': {'name': 'Amy Stark',\n",
+       "   'email': 'johnponce@example.net',\n",
+       "   'age': 42},\n",
+       "  'create_time': 1636447661.0,\n",
+       "  'address': '714 Mann Plaza Suite 839\\nSeanfurt, OK 32234'},\n",
+       " {'user_info': {'name': 'Joanne Keller',\n",
+       "   'email': 'christopher91@example.com',\n",
+       "   'age': 35},\n",
+       "  'create_time': 1636447661.0,\n",
+       "  'address': '41352 Simmons Circle\\nPort Dustinbury, OK 83627'}]"
+      ]
+     },
+     "execution_count": 25,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "LazyList(data_stream_gen())\\\n",
+    "    .map(drop_device)\\\n",
+    "    .map(user_info)\\\n",
+    "    .map(change_create_time)\\\n",
+    "    .take(3)\\\n",
+    "    .collect()"
+   ]
+  }
+ ],
+ "metadata": {
+  "interpreter": {
+   "hash": "b00d0533784e0bd7ec39dc1f12727fcf9021e9a985583666278f4fb55e7dfcf4"
+  },
+  "kernelspec": {
+   "display_name": "Python 3.10.0 64-bit ('fppy2': conda)",
+   "name": "python3"
+  },
+  "language_info": {
+   "codemirror_mode": {
+    "name": "ipython",
+    "version": 3
+   },
+   "file_extension": ".py",
+   "mimetype": "text/x-python",
+   "name": "python",
+   "nbconvert_exporter": "python",
+   "pygments_lexer": "ipython3",
+   "version": "3.10.0"
+  },
+  "orig_nbformat": 4
+ },
+ "nbformat": 4,
+ "nbformat_minor": 2
+}
diff --git a/_site/poetry.lock b/_site/poetry.lock
new file mode 100644
index 0000000..746ab2d
--- /dev/null
+++ b/_site/poetry.lock
@@ -0,0 +1,1278 @@
+[[package]]
+name = "alabaster"
+version = "0.7.12"
+description = "A configurable sidebar-enabled Sphinx theme"
+category = "dev"
+optional = false
+python-versions = "*"
+
+[[package]]
+name = "appnope"
+version = "0.1.2"
+description = "Disable App Nap on macOS >= 10.9"
+category = "dev"
+optional = false
+python-versions = "*"
+
+[[package]]
+name = "astroid"
+version = "2.8.3"
+description = "An abstract syntax tree for Python with inference support."
+category = "dev"
+optional = false
+python-versions = "~=3.6"
+
+[package.dependencies]
+lazy-object-proxy = ">=1.4.0"
+wrapt = ">=1.11,<1.14"
+
+[[package]]
+name = "atomicwrites"
+version = "1.4.0"
+description = "Atomic file writes."
+category = "dev"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
+
+[[package]]
+name = "attrs"
+version = "21.2.0"
+description = "Classes Without Boilerplate"
+category = "dev"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
+
+[package.extras]
+dev = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface", "furo", "sphinx", "sphinx-notfound-page", "pre-commit"]
+docs = ["furo", "sphinx", "zope.interface", "sphinx-notfound-page"]
+tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface"]
+tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins"]
+
+[[package]]
+name = "babel"
+version = "2.9.1"
+description = "Internationalization utilities"
+category = "dev"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
+
+[package.dependencies]
+pytz = ">=2015.7"
+
+[[package]]
+name = "backcall"
+version = "0.2.0"
+description = "Specifications for callback functions passed in to an API"
+category = "dev"
+optional = false
+python-versions = "*"
+
+[[package]]
+name = "certifi"
+version = "2021.10.8"
+description = "Python package for providing Mozilla's CA Bundle."
+category = "dev"
+optional = false
+python-versions = "*"
+
+[[package]]
+name = "charset-normalizer"
+version = "2.0.7"
+description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet."
+category = "dev"
+optional = false
+python-versions = ">=3.5.0"
+
+[package.extras]
+unicode_backport = ["unicodedata2"]
+
+[[package]]
+name = "colorama"
+version = "0.4.4"
+description = "Cross-platform colored terminal text."
+category = "dev"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
+
+[[package]]
+name = "coverage"
+version = "5.5"
+description = "Code coverage measurement for Python"
+category = "dev"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4"
+
+[package.dependencies]
+toml = {version = "*", optional = true, markers = "extra == \"toml\""}
+
+[package.extras]
+toml = ["toml"]
+
+[[package]]
+name = "coverage-badge"
+version = "1.0.2"
+description = "Generate coverage badges for Coverage.py."
+category = "dev"
+optional = false
+python-versions = "*"
+
+[package.dependencies]
+coverage = ">=5.0.0,<6.0.0"
+
+[[package]]
+name = "decorator"
+version = "5.1.0"
+description = "Decorators for Humans"
+category = "dev"
+optional = false
+python-versions = ">=3.5"
+
+[[package]]
+name = "dill"
+version = "0.3.4"
+description = "serialize all of python"
+category = "main"
+optional = false
+python-versions = ">=2.7, !=3.0.*"
+
+[package.extras]
+graph = ["objgraph (>=1.7.2)"]
+
+[[package]]
+name = "docutils"
+version = "0.17.1"
+description = "Docutils -- Python Documentation Utilities"
+category = "dev"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
+
+[[package]]
+name = "drill"
+version = "1.2.0"
+description = "A small python library for quickly traversing XML data."
+category = "main"
+optional = false
+python-versions = "*"
+
+[[package]]
+name = "hypothesis"
+version = "6.23.2"
+description = "A library for property-based testing"
+category = "dev"
+optional = false
+python-versions = ">=3.6"
+
+[package.dependencies]
+attrs = ">=19.2.0"
+sortedcontainers = ">=2.1.0,<3.0.0"
+
+[package.extras]
+all = ["black (>=19.10b0)", "click (>=7.0)", "django (>=2.2)", "dpcontracts (>=0.4)", "lark-parser (>=0.6.5)", "libcst (>=0.3.16)", "numpy (>=1.9.0)", "pandas (>=0.25)", "pytest (>=4.6)", "python-dateutil (>=1.4)", "pytz (>=2014.1)", "redis (>=3.0.0)", "rich (>=9.0.0)", "importlib-resources (>=3.3.0)", "importlib-metadata (>=3.6)", "backports.zoneinfo (>=0.2.1)", "tzdata (>=2020.4)"]
+cli = ["click (>=7.0)", "black (>=19.10b0)", "rich (>=9.0.0)"]
+codemods = ["libcst (>=0.3.16)"]
+dateutil = ["python-dateutil (>=1.4)"]
+django = ["pytz (>=2014.1)", "django (>=2.2)"]
+dpcontracts = ["dpcontracts (>=0.4)"]
+ghostwriter = ["black (>=19.10b0)"]
+lark = ["lark-parser (>=0.6.5)"]
+numpy = ["numpy (>=1.9.0)"]
+pandas = ["pandas (>=0.25)"]
+pytest = ["pytest (>=4.6)"]
+pytz = ["pytz (>=2014.1)"]
+redis = ["redis (>=3.0.0)"]
+zoneinfo = ["importlib-resources (>=3.3.0)", "backports.zoneinfo (>=0.2.1)", "tzdata (>=2020.4)"]
+
+[[package]]
+name = "idna"
+version = "3.3"
+description = "Internationalized Domain Names in Applications (IDNA)"
+category = "dev"
+optional = false
+python-versions = ">=3.5"
+
+[[package]]
+name = "imagesize"
+version = "1.2.0"
+description = "Getting image size from png/jpeg/jpeg2000/gif file"
+category = "dev"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
+
+[[package]]
+name = "iniconfig"
+version = "1.1.1"
+description = "iniconfig: brain-dead simple config-ini parsing"
+category = "dev"
+optional = false
+python-versions = "*"
+
+[[package]]
+name = "ipython"
+version = "7.28.0"
+description = "IPython: Productive Interactive Computing"
+category = "dev"
+optional = false
+python-versions = ">=3.7"
+
+[package.dependencies]
+appnope = {version = "*", markers = "sys_platform == \"darwin\""}
+backcall = "*"
+colorama = {version = "*", markers = "sys_platform == \"win32\""}
+decorator = "*"
+jedi = ">=0.16"
+matplotlib-inline = "*"
+pexpect = {version = ">4.3", markers = "sys_platform != \"win32\""}
+pickleshare = "*"
+prompt-toolkit = ">=2.0.0,<3.0.0 || >3.0.0,<3.0.1 || >3.0.1,<3.1.0"
+pygments = "*"
+traitlets = ">=4.2"
+
+[package.extras]
+all = ["Sphinx (>=1.3)", "ipykernel", "ipyparallel", "ipywidgets", "nbconvert", "nbformat", "nose (>=0.10.1)", "notebook", "numpy (>=1.17)", "pygments", "qtconsole", "requests", "testpath"]
+doc = ["Sphinx (>=1.3)"]
+kernel = ["ipykernel"]
+nbconvert = ["nbconvert"]
+nbformat = ["nbformat"]
+notebook = ["notebook", "ipywidgets"]
+parallel = ["ipyparallel"]
+qtconsole = ["qtconsole"]
+test = ["nose (>=0.10.1)", "requests", "testpath", "pygments", "nbformat", "ipykernel", "numpy (>=1.17)"]
+
+[[package]]
+name = "isort"
+version = "5.9.3"
+description = "A Python utility / library to sort Python imports."
+category = "dev"
+optional = false
+python-versions = ">=3.6.1,<4.0"
+
+[package.extras]
+pipfile_deprecated_finder = ["pipreqs", "requirementslib"]
+requirements_deprecated_finder = ["pipreqs", "pip-api"]
+colors = ["colorama (>=0.4.3,<0.5.0)"]
+plugins = ["setuptools"]
+
+[[package]]
+name = "jedi"
+version = "0.18.0"
+description = "An autocompletion tool for Python that can be used for text editors."
+category = "dev"
+optional = false
+python-versions = ">=3.6"
+
+[package.dependencies]
+parso = ">=0.8.0,<0.9.0"
+
+[package.extras]
+qa = ["flake8 (==3.8.3)", "mypy (==0.782)"]
+testing = ["Django (<3.1)", "colorama", "docopt", "pytest (<6.0.0)"]
+
+[[package]]
+name = "jinja2"
+version = "3.0.2"
+description = "A very fast and expressive template engine."
+category = "dev"
+optional = false
+python-versions = ">=3.6"
+
+[package.dependencies]
+MarkupSafe = ">=2.0"
+
+[package.extras]
+i18n = ["Babel (>=2.7)"]
+
+[[package]]
+name = "lazy-object-proxy"
+version = "1.6.0"
+description = "A fast and thorough lazy object proxy."
+category = "dev"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*"
+
+[[package]]
+name = "markupsafe"
+version = "2.0.1"
+description = "Safely add untrusted strings to HTML/XML markup."
+category = "dev"
+optional = false
+python-versions = ">=3.6"
+
+[[package]]
+name = "matplotlib-inline"
+version = "0.1.3"
+description = "Inline Matplotlib backend for Jupyter"
+category = "dev"
+optional = false
+python-versions = ">=3.5"
+
+[package.dependencies]
+traitlets = "*"
+
+[[package]]
+name = "mccabe"
+version = "0.6.1"
+description = "McCabe checker, plugin for flake8"
+category = "dev"
+optional = false
+python-versions = "*"
+
+[[package]]
+name = "multiprocess"
+version = "0.70.12.2"
+description = "better multiprocessing and multithreading in python"
+category = "main"
+optional = false
+python-versions = "*"
+
+[package.dependencies]
+dill = ">=0.3.4"
+
+[[package]]
+name = "packaging"
+version = "21.0"
+description = "Core utilities for Python packages"
+category = "dev"
+optional = false
+python-versions = ">=3.6"
+
+[package.dependencies]
+pyparsing = ">=2.0.2"
+
+[[package]]
+name = "parso"
+version = "0.8.2"
+description = "A Python Parser"
+category = "dev"
+optional = false
+python-versions = ">=3.6"
+
+[package.extras]
+qa = ["flake8 (==3.8.3)", "mypy (==0.782)"]
+testing = ["docopt", "pytest (<6.0.0)"]
+
+[[package]]
+name = "pathos"
+version = "0.2.8"
+description = "parallel graph management and execution in heterogeneous computing"
+category = "main"
+optional = false
+python-versions = "*"
+
+[package.dependencies]
+dill = ">=0.3.4"
+multiprocess = ">=0.70.12"
+pox = ">=0.3.0"
+ppft = ">=1.6.6.4"
+
+[[package]]
+name = "pexpect"
+version = "4.8.0"
+description = "Pexpect allows easy control of interactive console applications."
+category = "dev"
+optional = false
+python-versions = "*"
+
+[package.dependencies]
+ptyprocess = ">=0.5"
+
+[[package]]
+name = "pickleshare"
+version = "0.7.5"
+description = "Tiny 'shelve'-like database with concurrency support"
+category = "dev"
+optional = false
+python-versions = "*"
+
+[[package]]
+name = "platformdirs"
+version = "2.4.0"
+description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"."
+category = "dev"
+optional = false
+python-versions = ">=3.6"
+
+[package.extras]
+docs = ["Sphinx (>=4)", "furo (>=2021.7.5b38)", "proselint (>=0.10.2)", "sphinx-autodoc-typehints (>=1.12)"]
+test = ["appdirs (==1.4.4)", "pytest (>=6)", "pytest-cov (>=2.7)", "pytest-mock (>=3.6)"]
+
+[[package]]
+name = "pluggy"
+version = "0.13.1"
+description = "plugin and hook calling mechanisms for python"
+category = "dev"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
+
+[package.extras]
+dev = ["pre-commit", "tox"]
+
+[[package]]
+name = "pox"
+version = "0.3.0"
+description = "utilities for filesystem exploration and automated builds"
+category = "main"
+optional = false
+python-versions = "*"
+
+[[package]]
+name = "ppft"
+version = "1.6.6.4"
+description = "distributed and parallel python"
+category = "main"
+optional = false
+python-versions = "*"
+
+[package.extras]
+dill = ["dill (>=0.3.4)"]
+
+[[package]]
+name = "prompt-toolkit"
+version = "3.0.20"
+description = "Library for building powerful interactive command lines in Python"
+category = "dev"
+optional = false
+python-versions = ">=3.6.2"
+
+[package.dependencies]
+wcwidth = "*"
+
+[[package]]
+name = "ptyprocess"
+version = "0.7.0"
+description = "Run a subprocess in a pseudo terminal"
+category = "dev"
+optional = false
+python-versions = "*"
+
+[[package]]
+name = "py"
+version = "1.10.0"
+description = "library with cross-python path, ini-parsing, io, code, log facilities"
+category = "dev"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
+
+[[package]]
+name = "pygments"
+version = "2.10.0"
+description = "Pygments is a syntax highlighting package written in Python."
+category = "dev"
+optional = false
+python-versions = ">=3.5"
+
+[[package]]
+name = "pylint"
+version = "2.11.1"
+description = "python code static checker"
+category = "dev"
+optional = false
+python-versions = "~=3.6"
+
+[package.dependencies]
+astroid = ">=2.8.0,<2.9"
+colorama = {version = "*", markers = "sys_platform == \"win32\""}
+isort = ">=4.2.5,<6"
+mccabe = ">=0.6,<0.7"
+platformdirs = ">=2.2.0"
+toml = ">=0.7.1"
+
+[[package]]
+name = "pyparsing"
+version = "2.4.7"
+description = "Python parsing module"
+category = "dev"
+optional = false
+python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*"
+
+[[package]]
+name = "pytest"
+version = "6.1.1"
+description = "pytest: simple powerful testing with Python"
+category = "dev"
+optional = false
+python-versions = ">=3.5"
+
+[package.dependencies]
+atomicwrites = {version = ">=1.0", markers = "sys_platform == \"win32\""}
+attrs = ">=17.4.0"
+colorama = {version = "*", markers = "sys_platform == \"win32\""}
+iniconfig = "*"
+packaging = "*"
+pluggy = ">=0.12,<1.0"
+py = ">=1.8.2"
+toml = "*"
+
+[package.extras]
+checkqa_mypy = ["mypy (==0.780)"]
+testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "requests", "xmlschema"]
+
+[[package]]
+name = "pytest-cov"
+version = "3.0.0"
+description = "Pytest plugin for measuring coverage."
+category = "dev"
+optional = false
+python-versions = ">=3.6"
+
+[package.dependencies]
+coverage = {version = ">=5.2.1", extras = ["toml"]}
+pytest = ">=4.6"
+
+[package.extras]
+testing = ["fields", "hunter", "process-tests", "six", "pytest-xdist", "virtualenv"]
+
+[[package]]
+name = "pytest-cover"
+version = "3.0.0"
+description = "Pytest plugin for measuring coverage. Forked from `pytest-cov`."
+category = "dev"
+optional = false
+python-versions = "*"
+
+[package.dependencies]
+pytest-cov = ">=2.0"
+
+[[package]]
+name = "pytest-coverage"
+version = "0.0"
+description = "Pytest plugin for measuring coverage. Forked from `pytest-cov`."
+category = "dev"
+optional = false
+python-versions = "*"
+
+[package.dependencies]
+pytest-cover = "*"
+
+[[package]]
+name = "pytest-html"
+version = "3.1.1"
+description = "pytest plugin for generating HTML reports"
+category = "dev"
+optional = false
+python-versions = ">=3.6"
+
+[package.dependencies]
+pytest = ">=5.0,<6.0.0 || >6.0.0"
+pytest-metadata = "*"
+
+[[package]]
+name = "pytest-metadata"
+version = "1.11.0"
+description = "pytest plugin for test session metadata"
+category = "dev"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*"
+
+[package.dependencies]
+pytest = ">=2.9.0"
+
+[[package]]
+name = "pytest-sugar"
+version = "0.9.4"
+description = "pytest-sugar is a plugin for pytest that changes the default look and feel of pytest (e.g. progressbar, show tests that fail instantly)."
+category = "dev"
+optional = false
+python-versions = "*"
+
+[package.dependencies]
+packaging = ">=14.1"
+pytest = ">=2.9"
+termcolor = ">=1.1.0"
+
+[[package]]
+name = "pytz"
+version = "2021.3"
+description = "World timezone definitions, modern and historical"
+category = "dev"
+optional = false
+python-versions = "*"
+
+[[package]]
+name = "requests"
+version = "2.26.0"
+description = "Python HTTP for Humans."
+category = "dev"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*"
+
+[package.dependencies]
+certifi = ">=2017.4.17"
+charset-normalizer = {version = ">=2.0.0,<2.1.0", markers = "python_version >= \"3\""}
+idna = {version = ">=2.5,<4", markers = "python_version >= \"3\""}
+urllib3 = ">=1.21.1,<1.27"
+
+[package.extras]
+socks = ["PySocks (>=1.5.6,!=1.5.7)", "win-inet-pton"]
+use_chardet_on_py3 = ["chardet (>=3.0.2,<5)"]
+
+[[package]]
+name = "snowballstemmer"
+version = "2.1.0"
+description = "This package provides 29 stemmers for 28 languages generated from Snowball algorithms."
+category = "dev"
+optional = false
+python-versions = "*"
+
+[[package]]
+name = "sortedcontainers"
+version = "2.4.0"
+description = "Sorted Containers -- Sorted List, Sorted Dict, Sorted Set"
+category = "dev"
+optional = false
+python-versions = "*"
+
+[[package]]
+name = "sphinx"
+version = "4.2.0"
+description = "Python documentation generator"
+category = "dev"
+optional = false
+python-versions = ">=3.6"
+
+[package.dependencies]
+alabaster = ">=0.7,<0.8"
+babel = ">=1.3"
+colorama = {version = ">=0.3.5", markers = "sys_platform == \"win32\""}
+docutils = ">=0.14,<0.18"
+imagesize = "*"
+Jinja2 = ">=2.3"
+packaging = "*"
+Pygments = ">=2.0"
+requests = ">=2.5.0"
+snowballstemmer = ">=1.1"
+sphinxcontrib-applehelp = "*"
+sphinxcontrib-devhelp = "*"
+sphinxcontrib-htmlhelp = ">=2.0.0"
+sphinxcontrib-jsmath = "*"
+sphinxcontrib-qthelp = "*"
+sphinxcontrib-serializinghtml = ">=1.1.5"
+
+[package.extras]
+docs = ["sphinxcontrib-websupport"]
+lint = ["flake8 (>=3.5.0)", "isort", "mypy (>=0.900)", "docutils-stubs", "types-typed-ast", "types-pkg-resources", "types-requests"]
+test = ["pytest", "pytest-cov", "html5lib", "cython", "typed-ast"]
+
+[[package]]
+name = "sphinx-rtd-theme"
+version = "1.0.0"
+description = "Read the Docs theme for Sphinx"
+category = "dev"
+optional = false
+python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*"
+
+[package.dependencies]
+docutils = "<0.18"
+sphinx = ">=1.6"
+
+[package.extras]
+dev = ["transifex-client", "sphinxcontrib-httpdomain", "bump2version"]
+
+[[package]]
+name = "sphinxcontrib-applehelp"
+version = "1.0.2"
+description = "sphinxcontrib-applehelp is a sphinx extension which outputs Apple help books"
+category = "dev"
+optional = false
+python-versions = ">=3.5"
+
+[package.extras]
+lint = ["flake8", "mypy", "docutils-stubs"]
+test = ["pytest"]
+
+[[package]]
+name = "sphinxcontrib-devhelp"
+version = "1.0.2"
+description = "sphinxcontrib-devhelp is a sphinx extension which outputs Devhelp document."
+category = "dev"
+optional = false
+python-versions = ">=3.5"
+
+[package.extras]
+lint = ["flake8", "mypy", "docutils-stubs"]
+test = ["pytest"]
+
+[[package]]
+name = "sphinxcontrib-htmlhelp"
+version = "2.0.0"
+description = "sphinxcontrib-htmlhelp is a sphinx extension which renders HTML help files"
+category = "dev"
+optional = false
+python-versions = ">=3.6"
+
+[package.extras]
+lint = ["flake8", "mypy", "docutils-stubs"]
+test = ["pytest", "html5lib"]
+
+[[package]]
+name = "sphinxcontrib-jsmath"
+version = "1.0.1"
+description = "A sphinx extension which renders display math in HTML via JavaScript"
+category = "dev"
+optional = false
+python-versions = ">=3.5"
+
+[package.extras]
+test = ["pytest", "flake8", "mypy"]
+
+[[package]]
+name = "sphinxcontrib-mermaid"
+version = "0.7.1"
+description = "Mermaid diagrams in yours Sphinx powered docs"
+category = "dev"
+optional = false
+python-versions = "*"
+
+[[package]]
+name = "sphinxcontrib-qthelp"
+version = "1.0.3"
+description = "sphinxcontrib-qthelp is a sphinx extension which outputs QtHelp document."
+category = "dev"
+optional = false
+python-versions = ">=3.5"
+
+[package.extras]
+lint = ["flake8", "mypy", "docutils-stubs"]
+test = ["pytest"]
+
+[[package]]
+name = "sphinxcontrib-serializinghtml"
+version = "1.1.5"
+description = "sphinxcontrib-serializinghtml is a sphinx extension which outputs \"serialized\" HTML files (json and pickle)."
+category = "dev"
+optional = false
+python-versions = ">=3.5"
+
+[package.extras]
+lint = ["flake8", "mypy", "docutils-stubs"]
+test = ["pytest"]
+
+[[package]]
+name = "sphinxemoji"
+version = "0.1.9"
+description = "An extension to use emoji codes in your Sphinx documentation"
+category = "dev"
+optional = false
+python-versions = "*"
+
+[package.dependencies]
+sphinx = ">=1.8"
+
+[[package]]
+name = "termcolor"
+version = "1.1.0"
+description = "ANSII Color formatting for output in terminal."
+category = "dev"
+optional = false
+python-versions = "*"
+
+[[package]]
+name = "toml"
+version = "0.10.2"
+description = "Python Library for Tom's Obvious, Minimal Language"
+category = "dev"
+optional = false
+python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*"
+
+[[package]]
+name = "traitlets"
+version = "5.1.0"
+description = "Traitlets Python configuration system"
+category = "dev"
+optional = false
+python-versions = ">=3.7"
+
+[package.extras]
+test = ["pytest"]
+
+[[package]]
+name = "urllib3"
+version = "1.26.7"
+description = "HTTP library with thread-safe connection pooling, file post, and more."
+category = "dev"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4"
+
+[package.extras]
+brotli = ["brotlipy (>=0.6.0)"]
+secure = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "certifi", "ipaddress"]
+socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"]
+
+[[package]]
+name = "wcwidth"
+version = "0.2.5"
+description = "Measures the displayed width of unicode strings in a terminal"
+category = "dev"
+optional = false
+python-versions = "*"
+
+[[package]]
+name = "wrapt"
+version = "1.13.2"
+description = "Module for decorators, wrappers and monkey patching."
+category = "dev"
+optional = false
+python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7"
+
+[metadata]
+lock-version = "1.1"
+python-versions = "^3.10"
+content-hash = "f20515cc27d4506029ba27cc011bce7feb0866417f3d126492bee2d5f5658349"
+
+[metadata.files]
+alabaster = [
+    {file = "alabaster-0.7.12-py2.py3-none-any.whl", hash = "sha256:446438bdcca0e05bd45ea2de1668c1d9b032e1a9154c2c259092d77031ddd359"},
+    {file = "alabaster-0.7.12.tar.gz", hash = "sha256:a661d72d58e6ea8a57f7a86e37d86716863ee5e92788398526d58b26a4e4dc02"},
+]
+appnope = [
+    {file = "appnope-0.1.2-py2.py3-none-any.whl", hash = "sha256:93aa393e9d6c54c5cd570ccadd8edad61ea0c4b9ea7a01409020c9aa019eb442"},
+    {file = "appnope-0.1.2.tar.gz", hash = "sha256:dd83cd4b5b460958838f6eb3000c660b1f9caf2a5b1de4264e941512f603258a"},
+]
+astroid = [
+    {file = "astroid-2.8.3-py3-none-any.whl", hash = "sha256:f9d66e3a4a0e5b52819b2ff41ac2b179df9d180697db71c92beb33a60c661794"},
+    {file = "astroid-2.8.3.tar.gz", hash = "sha256:0e361da0744d5011d4f5d57e64473ba9b7ab4da1e2d45d6631ebd67dd28c3cce"},
+]
+atomicwrites = [
+    {file = "atomicwrites-1.4.0-py2.py3-none-any.whl", hash = "sha256:6d1784dea7c0c8d4a5172b6c620f40b6e4cbfdf96d783691f2e1302a7b88e197"},
+    {file = "atomicwrites-1.4.0.tar.gz", hash = "sha256:ae70396ad1a434f9c7046fd2dd196fc04b12f9e91ffb859164193be8b6168a7a"},
+]
+attrs = [
+    {file = "attrs-21.2.0-py2.py3-none-any.whl", hash = "sha256:149e90d6d8ac20db7a955ad60cf0e6881a3f20d37096140088356da6c716b0b1"},
+    {file = "attrs-21.2.0.tar.gz", hash = "sha256:ef6aaac3ca6cd92904cdd0d83f629a15f18053ec84e6432106f7a4d04ae4f5fb"},
+]
+babel = [
+    {file = "Babel-2.9.1-py2.py3-none-any.whl", hash = "sha256:ab49e12b91d937cd11f0b67cb259a57ab4ad2b59ac7a3b41d6c06c0ac5b0def9"},
+    {file = "Babel-2.9.1.tar.gz", hash = "sha256:bc0c176f9f6a994582230df350aa6e05ba2ebe4b3ac317eab29d9be5d2768da0"},
+]
+backcall = [
+    {file = "backcall-0.2.0-py2.py3-none-any.whl", hash = "sha256:fbbce6a29f263178a1f7915c1940bde0ec2b2a967566fe1c65c1dfb7422bd255"},
+    {file = "backcall-0.2.0.tar.gz", hash = "sha256:5cbdbf27be5e7cfadb448baf0aa95508f91f2bbc6c6437cd9cd06e2a4c215e1e"},
+]
+certifi = [
+    {file = "certifi-2021.10.8-py2.py3-none-any.whl", hash = "sha256:d62a0163eb4c2344ac042ab2bdf75399a71a2d8c7d47eac2e2ee91b9d6339569"},
+    {file = "certifi-2021.10.8.tar.gz", hash = "sha256:78884e7c1d4b00ce3cea67b44566851c4343c120abd683433ce934a68ea58872"},
+]
+charset-normalizer = [
+    {file = "charset-normalizer-2.0.7.tar.gz", hash = "sha256:e019de665e2bcf9c2b64e2e5aa025fa991da8720daa3c1138cadd2fd1856aed0"},
+    {file = "charset_normalizer-2.0.7-py3-none-any.whl", hash = "sha256:f7af805c321bfa1ce6714c51f254e0d5bb5e5834039bc17db7ebe3a4cec9492b"},
+]
+colorama = [
+    {file = "colorama-0.4.4-py2.py3-none-any.whl", hash = "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"},
+    {file = "colorama-0.4.4.tar.gz", hash = "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b"},
+]
+coverage = [
+    {file = "coverage-5.5-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:b6d534e4b2ab35c9f93f46229363e17f63c53ad01330df9f2d6bd1187e5eaacf"},
+    {file = "coverage-5.5-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:b7895207b4c843c76a25ab8c1e866261bcfe27bfaa20c192de5190121770672b"},
+    {file = "coverage-5.5-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:c2723d347ab06e7ddad1a58b2a821218239249a9e4365eaff6649d31180c1669"},
+    {file = "coverage-5.5-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:900fbf7759501bc7807fd6638c947d7a831fc9fdf742dc10f02956ff7220fa90"},
+    {file = "coverage-5.5-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:004d1880bed2d97151facef49f08e255a20ceb6f9432df75f4eef018fdd5a78c"},
+    {file = "coverage-5.5-cp27-cp27m-win32.whl", hash = "sha256:06191eb60f8d8a5bc046f3799f8a07a2d7aefb9504b0209aff0b47298333302a"},
+    {file = "coverage-5.5-cp27-cp27m-win_amd64.whl", hash = "sha256:7501140f755b725495941b43347ba8a2777407fc7f250d4f5a7d2a1050ba8e82"},
+    {file = "coverage-5.5-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:372da284cfd642d8e08ef606917846fa2ee350f64994bebfbd3afb0040436905"},
+    {file = "coverage-5.5-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:8963a499849a1fc54b35b1c9f162f4108017b2e6db2c46c1bed93a72262ed083"},
+    {file = "coverage-5.5-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:869a64f53488f40fa5b5b9dcb9e9b2962a66a87dab37790f3fcfb5144b996ef5"},
+    {file = "coverage-5.5-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:4a7697d8cb0f27399b0e393c0b90f0f1e40c82023ea4d45d22bce7032a5d7b81"},
+    {file = "coverage-5.5-cp310-cp310-macosx_10_14_x86_64.whl", hash = "sha256:8d0a0725ad7c1a0bcd8d1b437e191107d457e2ec1084b9f190630a4fb1af78e6"},
+    {file = "coverage-5.5-cp310-cp310-manylinux1_x86_64.whl", hash = "sha256:51cb9476a3987c8967ebab3f0fe144819781fca264f57f89760037a2ea191cb0"},
+    {file = "coverage-5.5-cp310-cp310-win_amd64.whl", hash = "sha256:c0891a6a97b09c1f3e073a890514d5012eb256845c451bd48f7968ef939bf4ae"},
+    {file = "coverage-5.5-cp35-cp35m-macosx_10_9_x86_64.whl", hash = "sha256:3487286bc29a5aa4b93a072e9592f22254291ce96a9fbc5251f566b6b7343cdb"},
+    {file = "coverage-5.5-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:deee1077aae10d8fa88cb02c845cfba9b62c55e1183f52f6ae6a2df6a2187160"},
+    {file = "coverage-5.5-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:f11642dddbb0253cc8853254301b51390ba0081750a8ac03f20ea8103f0c56b6"},
+    {file = "coverage-5.5-cp35-cp35m-manylinux2010_i686.whl", hash = "sha256:6c90e11318f0d3c436a42409f2749ee1a115cd8b067d7f14c148f1ce5574d701"},
+    {file = "coverage-5.5-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:30c77c1dc9f253283e34c27935fded5015f7d1abe83bc7821680ac444eaf7793"},
+    {file = "coverage-5.5-cp35-cp35m-win32.whl", hash = "sha256:9a1ef3b66e38ef8618ce5fdc7bea3d9f45f3624e2a66295eea5e57966c85909e"},
+    {file = "coverage-5.5-cp35-cp35m-win_amd64.whl", hash = "sha256:972c85d205b51e30e59525694670de6a8a89691186012535f9d7dbaa230e42c3"},
+    {file = "coverage-5.5-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:af0e781009aaf59e25c5a678122391cb0f345ac0ec272c7961dc5455e1c40066"},
+    {file = "coverage-5.5-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:74d881fc777ebb11c63736622b60cb9e4aee5cace591ce274fb69e582a12a61a"},
+    {file = "coverage-5.5-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:92b017ce34b68a7d67bd6d117e6d443a9bf63a2ecf8567bb3d8c6c7bc5014465"},
+    {file = "coverage-5.5-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:d636598c8305e1f90b439dbf4f66437de4a5e3c31fdf47ad29542478c8508bbb"},
+    {file = "coverage-5.5-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:41179b8a845742d1eb60449bdb2992196e211341818565abded11cfa90efb821"},
+    {file = "coverage-5.5-cp36-cp36m-win32.whl", hash = "sha256:040af6c32813fa3eae5305d53f18875bedd079960822ef8ec067a66dd8afcd45"},
+    {file = "coverage-5.5-cp36-cp36m-win_amd64.whl", hash = "sha256:5fec2d43a2cc6965edc0bb9e83e1e4b557f76f843a77a2496cbe719583ce8184"},
+    {file = "coverage-5.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:18ba8bbede96a2c3dde7b868de9dcbd55670690af0988713f0603f037848418a"},
+    {file = "coverage-5.5-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:2910f4d36a6a9b4214bb7038d537f015346f413a975d57ca6b43bf23d6563b53"},
+    {file = "coverage-5.5-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:f0b278ce10936db1a37e6954e15a3730bea96a0997c26d7fee88e6c396c2086d"},
+    {file = "coverage-5.5-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:796c9c3c79747146ebd278dbe1e5c5c05dd6b10cc3bcb8389dfdf844f3ead638"},
+    {file = "coverage-5.5-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:53194af30d5bad77fcba80e23a1441c71abfb3e01192034f8246e0d8f99528f3"},
+    {file = "coverage-5.5-cp37-cp37m-win32.whl", hash = "sha256:184a47bbe0aa6400ed2d41d8e9ed868b8205046518c52464fde713ea06e3a74a"},
+    {file = "coverage-5.5-cp37-cp37m-win_amd64.whl", hash = "sha256:2949cad1c5208b8298d5686d5a85b66aae46d73eec2c3e08c817dd3513e5848a"},
+    {file = "coverage-5.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:217658ec7187497e3f3ebd901afdca1af062b42cfe3e0dafea4cced3983739f6"},
+    {file = "coverage-5.5-cp38-cp38-manylinux1_i686.whl", hash = "sha256:1aa846f56c3d49205c952d8318e76ccc2ae23303351d9270ab220004c580cfe2"},
+    {file = "coverage-5.5-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:24d4a7de75446be83244eabbff746d66b9240ae020ced65d060815fac3423759"},
+    {file = "coverage-5.5-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:d1f8bf7b90ba55699b3a5e44930e93ff0189aa27186e96071fac7dd0d06a1873"},
+    {file = "coverage-5.5-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:970284a88b99673ccb2e4e334cfb38a10aab7cd44f7457564d11898a74b62d0a"},
+    {file = "coverage-5.5-cp38-cp38-win32.whl", hash = "sha256:01d84219b5cdbfc8122223b39a954820929497a1cb1422824bb86b07b74594b6"},
+    {file = "coverage-5.5-cp38-cp38-win_amd64.whl", hash = "sha256:2e0d881ad471768bf6e6c2bf905d183543f10098e3b3640fc029509530091502"},
+    {file = "coverage-5.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:d1f9ce122f83b2305592c11d64f181b87153fc2c2bbd3bb4a3dde8303cfb1a6b"},
+    {file = "coverage-5.5-cp39-cp39-manylinux1_i686.whl", hash = "sha256:13c4ee887eca0f4c5a247b75398d4114c37882658300e153113dafb1d76de529"},
+    {file = "coverage-5.5-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:52596d3d0e8bdf3af43db3e9ba8dcdaac724ba7b5ca3f6358529d56f7a166f8b"},
+    {file = "coverage-5.5-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:2cafbbb3af0733db200c9b5f798d18953b1a304d3f86a938367de1567f4b5bff"},
+    {file = "coverage-5.5-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:44d654437b8ddd9eee7d1eaee28b7219bec228520ff809af170488fd2fed3e2b"},
+    {file = "coverage-5.5-cp39-cp39-win32.whl", hash = "sha256:d314ed732c25d29775e84a960c3c60808b682c08d86602ec2c3008e1202e3bb6"},
+    {file = "coverage-5.5-cp39-cp39-win_amd64.whl", hash = "sha256:13034c4409db851670bc9acd836243aeee299949bd5673e11844befcb0149f03"},
+    {file = "coverage-5.5-pp36-none-any.whl", hash = "sha256:f030f8873312a16414c0d8e1a1ddff2d3235655a2174e3648b4fa66b3f2f1079"},
+    {file = "coverage-5.5-pp37-none-any.whl", hash = "sha256:2a3859cb82dcbda1cfd3e6f71c27081d18aa251d20a17d87d26d4cd216fb0af4"},
+    {file = "coverage-5.5.tar.gz", hash = "sha256:ebe78fe9a0e874362175b02371bdfbee64d8edc42a044253ddf4ee7d3c15212c"},
+]
+coverage-badge = [
+    {file = "coverage-badge-1.0.2.tar.gz", hash = "sha256:5060b991040cc0a5d7e3e45f96dd75db9ed7bc921f1802df6eec8706fc79b2ec"},
+    {file = "coverage_badge-1.0.2-py2.py3-none-any.whl", hash = "sha256:76aa88a7d78e9b48931226a2b3275118dfbf3d40975f0170a19abe1aa26521d3"},
+]
+decorator = [
+    {file = "decorator-5.1.0-py3-none-any.whl", hash = "sha256:7b12e7c3c6ab203a29e157335e9122cb03de9ab7264b137594103fd4a683b374"},
+    {file = "decorator-5.1.0.tar.gz", hash = "sha256:e59913af105b9860aa2c8d3272d9de5a56a4e608db9a2f167a8480b323d529a7"},
+]
+dill = [
+    {file = "dill-0.3.4-py2.py3-none-any.whl", hash = "sha256:7e40e4a70304fd9ceab3535d36e58791d9c4a776b38ec7f7ec9afc8d3dca4d4f"},
+    {file = "dill-0.3.4.zip", hash = "sha256:9f9734205146b2b353ab3fec9af0070237b6ddae78452af83d2fca84d739e675"},
+]
+docutils = [
+    {file = "docutils-0.17.1-py2.py3-none-any.whl", hash = "sha256:cf316c8370a737a022b72b56874f6602acf974a37a9fba42ec2876387549fc61"},
+    {file = "docutils-0.17.1.tar.gz", hash = "sha256:686577d2e4c32380bb50cbb22f575ed742d58168cee37e99117a854bcd88f125"},
+]
+drill = [
+    {file = "drill-1.2.0-py2.py3-none-any.whl", hash = "sha256:fb5a1eae68993076d033034dd446a832f2b9757222478c47f0cf129bafe70a74"},
+    {file = "drill-1.2.0.tar.gz", hash = "sha256:d2645ed6d3cfc925bd7bf5328982d8a5aff7cda9c7e56107c7a74482f7037b7d"},
+]
+hypothesis = [
+    {file = "hypothesis-6.23.2-py3-none-any.whl", hash = "sha256:ffe81bf1e3122edfcdbf21b31a0b8db3759fac6b87cdc2f9ae32cd360311ccf4"},
+    {file = "hypothesis-6.23.2.tar.gz", hash = "sha256:b71b257916c91484716a10220ed2b9a0cf82acc3ed8ef421bb2aa0a671761053"},
+]
+idna = [
+    {file = "idna-3.3-py3-none-any.whl", hash = "sha256:84d9dd047ffa80596e0f246e2eab0b391788b0503584e8945f2368256d2735ff"},
+    {file = "idna-3.3.tar.gz", hash = "sha256:9d643ff0a55b762d5cdb124b8eaa99c66322e2157b69160bc32796e824360e6d"},
+]
+imagesize = [
+    {file = "imagesize-1.2.0-py2.py3-none-any.whl", hash = "sha256:6965f19a6a2039c7d48bca7dba2473069ff854c36ae6f19d2cde309d998228a1"},
+    {file = "imagesize-1.2.0.tar.gz", hash = "sha256:b1f6b5a4eab1f73479a50fb79fcf729514a900c341d8503d62a62dbc4127a2b1"},
+]
+iniconfig = [
+    {file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"},
+    {file = "iniconfig-1.1.1.tar.gz", hash = "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"},
+]
+ipython = [
+    {file = "ipython-7.28.0-py3-none-any.whl", hash = "sha256:f16148f9163e1e526f1008d7c8d966d9c15600ca20d1a754287cf96d00ba6f1d"},
+    {file = "ipython-7.28.0.tar.gz", hash = "sha256:2097be5c814d1b974aea57673176a924c4c8c9583890e7a5f082f547b9975b11"},
+]
+isort = [
+    {file = "isort-5.9.3-py3-none-any.whl", hash = "sha256:e17d6e2b81095c9db0a03a8025a957f334d6ea30b26f9ec70805411e5c7c81f2"},
+    {file = "isort-5.9.3.tar.gz", hash = "sha256:9c2ea1e62d871267b78307fe511c0838ba0da28698c5732d54e2790bf3ba9899"},
+]
+jedi = [
+    {file = "jedi-0.18.0-py2.py3-none-any.whl", hash = "sha256:18456d83f65f400ab0c2d3319e48520420ef43b23a086fdc05dff34132f0fb93"},
+    {file = "jedi-0.18.0.tar.gz", hash = "sha256:92550a404bad8afed881a137ec9a461fed49eca661414be45059329614ed0707"},
+]
+jinja2 = [
+    {file = "Jinja2-3.0.2-py3-none-any.whl", hash = "sha256:8569982d3f0889eed11dd620c706d39b60c36d6d25843961f33f77fb6bc6b20c"},
+    {file = "Jinja2-3.0.2.tar.gz", hash = "sha256:827a0e32839ab1600d4eb1c4c33ec5a8edfbc5cb42dafa13b81f182f97784b45"},
+]
+lazy-object-proxy = [
+    {file = "lazy-object-proxy-1.6.0.tar.gz", hash = "sha256:489000d368377571c6f982fba6497f2aa13c6d1facc40660963da62f5c379726"},
+    {file = "lazy_object_proxy-1.6.0-cp27-cp27m-macosx_10_14_x86_64.whl", hash = "sha256:c6938967f8528b3668622a9ed3b31d145fab161a32f5891ea7b84f6b790be05b"},
+    {file = "lazy_object_proxy-1.6.0-cp27-cp27m-win32.whl", hash = "sha256:ebfd274dcd5133e0afae738e6d9da4323c3eb021b3e13052d8cbd0e457b1256e"},
+    {file = "lazy_object_proxy-1.6.0-cp27-cp27m-win_amd64.whl", hash = "sha256:ed361bb83436f117f9917d282a456f9e5009ea12fd6de8742d1a4752c3017e93"},
+    {file = "lazy_object_proxy-1.6.0-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:d900d949b707778696fdf01036f58c9876a0d8bfe116e8d220cfd4b15f14e741"},
+    {file = "lazy_object_proxy-1.6.0-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:5743a5ab42ae40caa8421b320ebf3a998f89c85cdc8376d6b2e00bd12bd1b587"},
+    {file = "lazy_object_proxy-1.6.0-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:bf34e368e8dd976423396555078def5cfc3039ebc6fc06d1ae2c5a65eebbcde4"},
+    {file = "lazy_object_proxy-1.6.0-cp36-cp36m-win32.whl", hash = "sha256:b579f8acbf2bdd9ea200b1d5dea36abd93cabf56cf626ab9c744a432e15c815f"},
+    {file = "lazy_object_proxy-1.6.0-cp36-cp36m-win_amd64.whl", hash = "sha256:4f60460e9f1eb632584c9685bccea152f4ac2130e299784dbaf9fae9f49891b3"},
+    {file = "lazy_object_proxy-1.6.0-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:d7124f52f3bd259f510651450e18e0fd081ed82f3c08541dffc7b94b883aa981"},
+    {file = "lazy_object_proxy-1.6.0-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:22ddd618cefe54305df49e4c069fa65715be4ad0e78e8d252a33debf00f6ede2"},
+    {file = "lazy_object_proxy-1.6.0-cp37-cp37m-win32.whl", hash = "sha256:9d397bf41caad3f489e10774667310d73cb9c4258e9aed94b9ec734b34b495fd"},
+    {file = "lazy_object_proxy-1.6.0-cp37-cp37m-win_amd64.whl", hash = "sha256:24a5045889cc2729033b3e604d496c2b6f588c754f7a62027ad4437a7ecc4837"},
+    {file = "lazy_object_proxy-1.6.0-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:17e0967ba374fc24141738c69736da90e94419338fd4c7c7bef01ee26b339653"},
+    {file = "lazy_object_proxy-1.6.0-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:410283732af311b51b837894fa2f24f2c0039aa7f220135192b38fcc42bd43d3"},
+    {file = "lazy_object_proxy-1.6.0-cp38-cp38-win32.whl", hash = "sha256:85fb7608121fd5621cc4377a8961d0b32ccf84a7285b4f1d21988b2eae2868e8"},
+    {file = "lazy_object_proxy-1.6.0-cp38-cp38-win_amd64.whl", hash = "sha256:d1c2676e3d840852a2de7c7d5d76407c772927addff8d742b9808fe0afccebdf"},
+    {file = "lazy_object_proxy-1.6.0-cp39-cp39-macosx_10_14_x86_64.whl", hash = "sha256:b865b01a2e7f96db0c5d12cfea590f98d8c5ba64ad222300d93ce6ff9138bcad"},
+    {file = "lazy_object_proxy-1.6.0-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:4732c765372bd78a2d6b2150a6e99d00a78ec963375f236979c0626b97ed8e43"},
+    {file = "lazy_object_proxy-1.6.0-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:9698110e36e2df951c7c36b6729e96429c9c32b3331989ef19976592c5f3c77a"},
+    {file = "lazy_object_proxy-1.6.0-cp39-cp39-win32.whl", hash = "sha256:1fee665d2638491f4d6e55bd483e15ef21f6c8c2095f235fef72601021e64f61"},
+    {file = "lazy_object_proxy-1.6.0-cp39-cp39-win_amd64.whl", hash = "sha256:f5144c75445ae3ca2057faac03fda5a902eff196702b0a24daf1d6ce0650514b"},
+]
+markupsafe = [
+    {file = "MarkupSafe-2.0.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d8446c54dc28c01e5a2dbac5a25f071f6653e6e40f3a8818e8b45d790fe6ef53"},
+    {file = "MarkupSafe-2.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:36bc903cbb393720fad60fc28c10de6acf10dc6cc883f3e24ee4012371399a38"},
+    {file = "MarkupSafe-2.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2d7d807855b419fc2ed3e631034685db6079889a1f01d5d9dac950f764da3dad"},
+    {file = "MarkupSafe-2.0.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:add36cb2dbb8b736611303cd3bfcee00afd96471b09cda130da3581cbdc56a6d"},
+    {file = "MarkupSafe-2.0.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:168cd0a3642de83558a5153c8bd34f175a9a6e7f6dc6384b9655d2697312a646"},
+    {file = "MarkupSafe-2.0.1-cp310-cp310-win32.whl", hash = "sha256:99df47edb6bda1249d3e80fdabb1dab8c08ef3975f69aed437cb69d0a5de1e28"},
+    {file = "MarkupSafe-2.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:e0f138900af21926a02425cf736db95be9f4af72ba1bb21453432a07f6082134"},
+    {file = "MarkupSafe-2.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:f9081981fe268bd86831e5c75f7de206ef275defcb82bc70740ae6dc507aee51"},
+    {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:0955295dd5eec6cb6cc2fe1698f4c6d84af2e92de33fbcac4111913cd100a6ff"},
+    {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:0446679737af14f45767963a1a9ef7620189912317d095f2d9ffa183a4d25d2b"},
+    {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:f826e31d18b516f653fe296d967d700fddad5901ae07c622bb3705955e1faa94"},
+    {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:fa130dd50c57d53368c9d59395cb5526eda596d3ffe36666cd81a44d56e48872"},
+    {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:905fec760bd2fa1388bb5b489ee8ee5f7291d692638ea5f67982d968366bef9f"},
+    {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bf5d821ffabf0ef3533c39c518f3357b171a1651c1ff6827325e4489b0e46c3c"},
+    {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:0d4b31cc67ab36e3392bbf3862cfbadac3db12bdd8b02a2731f509ed5b829724"},
+    {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:baa1a4e8f868845af802979fcdbf0bb11f94f1cb7ced4c4b8a351bb60d108145"},
+    {file = "MarkupSafe-2.0.1-cp36-cp36m-win32.whl", hash = "sha256:6c4ca60fa24e85fe25b912b01e62cb969d69a23a5d5867682dd3e80b5b02581d"},
+    {file = "MarkupSafe-2.0.1-cp36-cp36m-win_amd64.whl", hash = "sha256:b2f4bf27480f5e5e8ce285a8c8fd176c0b03e93dcc6646477d4630e83440c6a9"},
+    {file = "MarkupSafe-2.0.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:0717a7390a68be14b8c793ba258e075c6f4ca819f15edfc2a3a027c823718567"},
+    {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:6557b31b5e2c9ddf0de32a691f2312a32f77cd7681d8af66c2692efdbef84c18"},
+    {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:49e3ceeabbfb9d66c3aef5af3a60cc43b85c33df25ce03d0031a608b0a8b2e3f"},
+    {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:d7f9850398e85aba693bb640262d3611788b1f29a79f0c93c565694658f4071f"},
+    {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:6a7fae0dd14cf60ad5ff42baa2e95727c3d81ded453457771d02b7d2b3f9c0c2"},
+    {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:b7f2d075102dc8c794cbde1947378051c4e5180d52d276987b8d28a3bd58c17d"},
+    {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e9936f0b261d4df76ad22f8fee3ae83b60d7c3e871292cd42f40b81b70afae85"},
+    {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:2a7d351cbd8cfeb19ca00de495e224dea7e7d919659c2841bbb7f420ad03e2d6"},
+    {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:60bf42e36abfaf9aff1f50f52644b336d4f0a3fd6d8a60ca0d054ac9f713a864"},
+    {file = "MarkupSafe-2.0.1-cp37-cp37m-win32.whl", hash = "sha256:a30e67a65b53ea0a5e62fe23682cfe22712e01f453b95233b25502f7c61cb415"},
+    {file = "MarkupSafe-2.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:611d1ad9a4288cf3e3c16014564df047fe08410e628f89805e475368bd304914"},
+    {file = "MarkupSafe-2.0.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:5bb28c636d87e840583ee3adeb78172efc47c8b26127267f54a9c0ec251d41a9"},
+    {file = "MarkupSafe-2.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:be98f628055368795d818ebf93da628541e10b75b41c559fdf36d104c5787066"},
+    {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:1d609f577dc6e1aa17d746f8bd3c31aa4d258f4070d61b2aa5c4166c1539de35"},
+    {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:7d91275b0245b1da4d4cfa07e0faedd5b0812efc15b702576d103293e252af1b"},
+    {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:01a9b8ea66f1658938f65b93a85ebe8bc016e6769611be228d797c9d998dd298"},
+    {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:47ab1e7b91c098ab893b828deafa1203de86d0bc6ab587b160f78fe6c4011f75"},
+    {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:97383d78eb34da7e1fa37dd273c20ad4320929af65d156e35a5e2d89566d9dfb"},
+    {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6fcf051089389abe060c9cd7caa212c707e58153afa2c649f00346ce6d260f1b"},
+    {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:5855f8438a7d1d458206a2466bf82b0f104a3724bf96a1c781ab731e4201731a"},
+    {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:3dd007d54ee88b46be476e293f48c85048603f5f516008bee124ddd891398ed6"},
+    {file = "MarkupSafe-2.0.1-cp38-cp38-win32.whl", hash = "sha256:023cb26ec21ece8dc3907c0e8320058b2e0cb3c55cf9564da612bc325bed5e64"},
+    {file = "MarkupSafe-2.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:984d76483eb32f1bcb536dc27e4ad56bba4baa70be32fa87152832cdd9db0833"},
+    {file = "MarkupSafe-2.0.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:2ef54abee730b502252bcdf31b10dacb0a416229b72c18b19e24a4509f273d26"},
+    {file = "MarkupSafe-2.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3c112550557578c26af18a1ccc9e090bfe03832ae994343cfdacd287db6a6ae7"},
+    {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux1_i686.whl", hash = "sha256:53edb4da6925ad13c07b6d26c2a852bd81e364f95301c66e930ab2aef5b5ddd8"},
+    {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:f5653a225f31e113b152e56f154ccbe59eeb1c7487b39b9d9f9cdb58e6c79dc5"},
+    {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:4efca8f86c54b22348a5467704e3fec767b2db12fc39c6d963168ab1d3fc9135"},
+    {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:ab3ef638ace319fa26553db0624c4699e31a28bb2a835c5faca8f8acf6a5a902"},
+    {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:f8ba0e8349a38d3001fae7eadded3f6606f0da5d748ee53cc1dab1d6527b9509"},
+    {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c47adbc92fc1bb2b3274c4b3a43ae0e4573d9fbff4f54cd484555edbf030baf1"},
+    {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:37205cac2a79194e3750b0af2a5720d95f786a55ce7df90c3af697bfa100eaac"},
+    {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:1f2ade76b9903f39aa442b4aadd2177decb66525062db244b35d71d0ee8599b6"},
+    {file = "MarkupSafe-2.0.1-cp39-cp39-win32.whl", hash = "sha256:10f82115e21dc0dfec9ab5c0223652f7197feb168c940f3ef61563fc2d6beb74"},
+    {file = "MarkupSafe-2.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:693ce3f9e70a6cf7d2fb9e6c9d8b204b6b39897a2c4a1aa65728d5ac97dcc1d8"},
+    {file = "MarkupSafe-2.0.1.tar.gz", hash = "sha256:594c67807fb16238b30c44bdf74f36c02cdf22d1c8cda91ef8a0ed8dabf5620a"},
+]
+matplotlib-inline = [
+    {file = "matplotlib-inline-0.1.3.tar.gz", hash = "sha256:a04bfba22e0d1395479f866853ec1ee28eea1485c1d69a6faf00dc3e24ff34ee"},
+    {file = "matplotlib_inline-0.1.3-py3-none-any.whl", hash = "sha256:aed605ba3b72462d64d475a21a9296f400a19c4f74a31b59103d2a99ffd5aa5c"},
+]
+mccabe = [
+    {file = "mccabe-0.6.1-py2.py3-none-any.whl", hash = "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42"},
+    {file = "mccabe-0.6.1.tar.gz", hash = "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"},
+]
+multiprocess = [
+    {file = "multiprocess-0.70.12.2-cp27-cp27m-macosx_10_12_x86_64.whl", hash = "sha256:35d41e410ca2a32977a483ae1f40f86b193b45cecf85567c2fae402fb8bf172e"},
+    {file = "multiprocess-0.70.12.2-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:9a02237eae21975155c816883479f72e239d16823a6bc063173d59acec9bcf41"},
+    {file = "multiprocess-0.70.12.2-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:f12a939cd2f01d0a900e7ef2aaee3c351a49fd2297d7f760b537af22727561b8"},
+    {file = "multiprocess-0.70.12.2-cp27-cp27m-win32.whl", hash = "sha256:be3ad3eaf204abc646d85e70e41244f66d88200628a0ab867c8fc206b97cedbf"},
+    {file = "multiprocess-0.70.12.2-cp27-cp27m-win_amd64.whl", hash = "sha256:c85ffc38c50c5a4f32f3f3c1a284725b7b5040188f254eba6e572c53d3da525b"},
+    {file = "multiprocess-0.70.12.2-pp27-none-any.whl", hash = "sha256:a9f58945edb234591684c0a181b744a3231643814ef3a8f47cea9a2073b4b2bb"},
+    {file = "multiprocess-0.70.12.2-pp36-none-any.whl", hash = "sha256:0e0a5ae4bd84e4c22baddf824d3b8168214f8c1cce51e2cb080421cb1f7b04d1"},
+    {file = "multiprocess-0.70.12.2-pp37-none-any.whl", hash = "sha256:916a314a1e0f3454033d59672ba6181fa45948ab1091d68cdd479258576e7b27"},
+    {file = "multiprocess-0.70.12.2-py36-none-any.whl", hash = "sha256:b3f866f7d9c7acc1a9cb1b6063a29f5cb140ff545b35b71fd4bfdac6f19d75fa"},
+    {file = "multiprocess-0.70.12.2-py37-none-any.whl", hash = "sha256:6aa67e805e50b6e9dfc56dd0f0c85ac3409e6791d4ec5405c5f9bc0a47d745a4"},
+    {file = "multiprocess-0.70.12.2-py38-none-any.whl", hash = "sha256:85941e650c277af44fc82e3e97faacb920e5ce3615238b540cbad4012d6f60e9"},
+    {file = "multiprocess-0.70.12.2-py39-none-any.whl", hash = "sha256:6f812a1d3f198b7cacd63983f60e2dc1338bd4450893f90c435067b5a3127e6f"},
+    {file = "multiprocess-0.70.12.2.zip", hash = "sha256:206bb9b97b73f87fec1ed15a19f8762950256aa84225450abc7150d02855a083"},
+]
+packaging = [
+    {file = "packaging-21.0-py3-none-any.whl", hash = "sha256:c86254f9220d55e31cc94d69bade760f0847da8000def4dfe1c6b872fd14ff14"},
+    {file = "packaging-21.0.tar.gz", hash = "sha256:7dc96269f53a4ccec5c0670940a4281106dd0bb343f47b7471f779df49c2fbe7"},
+]
+parso = [
+    {file = "parso-0.8.2-py2.py3-none-any.whl", hash = "sha256:a8c4922db71e4fdb90e0d0bc6e50f9b273d3397925e5e60a717e719201778d22"},
+    {file = "parso-0.8.2.tar.gz", hash = "sha256:12b83492c6239ce32ff5eed6d3639d6a536170723c6f3f1506869f1ace413398"},
+]
+pathos = [
+    {file = "pathos-0.2.8-py2.py3-none-any.whl", hash = "sha256:789ae53487e5f9393fcc2b9703188a1b97f20206c429654134a7152f591bafe7"},
+    {file = "pathos-0.2.8.zip", hash = "sha256:1f0f27a90f7ab66c423ba796529000fde9360d17b2d8e50097641ff405fc6f15"},
+]
+pexpect = [
+    {file = "pexpect-4.8.0-py2.py3-none-any.whl", hash = "sha256:0b48a55dcb3c05f3329815901ea4fc1537514d6ba867a152b581d69ae3710937"},
+    {file = "pexpect-4.8.0.tar.gz", hash = "sha256:fc65a43959d153d0114afe13997d439c22823a27cefceb5ff35c2178c6784c0c"},
+]
+pickleshare = [
+    {file = "pickleshare-0.7.5-py2.py3-none-any.whl", hash = "sha256:9649af414d74d4df115d5d718f82acb59c9d418196b7b4290ed47a12ce62df56"},
+    {file = "pickleshare-0.7.5.tar.gz", hash = "sha256:87683d47965c1da65cdacaf31c8441d12b8044cdec9aca500cd78fc2c683afca"},
+]
+platformdirs = [
+    {file = "platformdirs-2.4.0-py3-none-any.whl", hash = "sha256:8868bbe3c3c80d42f20156f22e7131d2fb321f5bc86a2a345375c6481a67021d"},
+    {file = "platformdirs-2.4.0.tar.gz", hash = "sha256:367a5e80b3d04d2428ffa76d33f124cf11e8fff2acdaa9b43d545f5c7d661ef2"},
+]
+pluggy = [
+    {file = "pluggy-0.13.1-py2.py3-none-any.whl", hash = "sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d"},
+    {file = "pluggy-0.13.1.tar.gz", hash = "sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0"},
+]
+pox = [
+    {file = "pox-0.3.0-py2.py3-none-any.whl", hash = "sha256:97a389cd1c6ed44dc6b934659f315d43336f7bde946a7977e770fd7e350a5395"},
+    {file = "pox-0.3.0.zip", hash = "sha256:cb968350b186466bb4905a21084587ec3aa6fd7aa0ef55d416ee0d523e2abe31"},
+]
+ppft = [
+    {file = "ppft-1.6.6.4-py2-none-any.whl", hash = "sha256:cbe543bbd4a661a21fda3fed3602d01c1dcfb8f0ac46816f1b7564996c5d86df"},
+    {file = "ppft-1.6.6.4-py3-none-any.whl", hash = "sha256:63d12035786dfd52e7a0fedcd75a498b68b80e6ac54720cd8a4282512608131c"},
+    {file = "ppft-1.6.6.4.zip", hash = "sha256:473442cc6731856990bd25bd6b454bb98720007de4523a73c560bdd0060463d2"},
+]
+prompt-toolkit = [
+    {file = "prompt_toolkit-3.0.20-py3-none-any.whl", hash = "sha256:6076e46efae19b1e0ca1ec003ed37a933dc94b4d20f486235d436e64771dcd5c"},
+    {file = "prompt_toolkit-3.0.20.tar.gz", hash = "sha256:eb71d5a6b72ce6db177af4a7d4d7085b99756bf656d98ffcc4fecd36850eea6c"},
+]
+ptyprocess = [
+    {file = "ptyprocess-0.7.0-py2.py3-none-any.whl", hash = "sha256:4b41f3967fce3af57cc7e94b888626c18bf37a083e3651ca8feeb66d492fef35"},
+    {file = "ptyprocess-0.7.0.tar.gz", hash = "sha256:5c5d0a3b48ceee0b48485e0c26037c0acd7d29765ca3fbb5cb3831d347423220"},
+]
+py = [
+    {file = "py-1.10.0-py2.py3-none-any.whl", hash = "sha256:3b80836aa6d1feeaa108e046da6423ab8f6ceda6468545ae8d02d9d58d18818a"},
+    {file = "py-1.10.0.tar.gz", hash = "sha256:21b81bda15b66ef5e1a777a21c4dcd9c20ad3efd0b3f817e7a809035269e1bd3"},
+]
+pygments = [
+    {file = "Pygments-2.10.0-py3-none-any.whl", hash = "sha256:b8e67fe6af78f492b3c4b3e2970c0624cbf08beb1e493b2c99b9fa1b67a20380"},
+    {file = "Pygments-2.10.0.tar.gz", hash = "sha256:f398865f7eb6874156579fdf36bc840a03cab64d1cde9e93d68f46a425ec52c6"},
+]
+pylint = [
+    {file = "pylint-2.11.1-py3-none-any.whl", hash = "sha256:0f358e221c45cbd4dad2a1e4b883e75d28acdcccd29d40c76eb72b307269b126"},
+    {file = "pylint-2.11.1.tar.gz", hash = "sha256:2c9843fff1a88ca0ad98a256806c82c5a8f86086e7ccbdb93297d86c3f90c436"},
+]
+pyparsing = [
+    {file = "pyparsing-2.4.7-py2.py3-none-any.whl", hash = "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b"},
+    {file = "pyparsing-2.4.7.tar.gz", hash = "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1"},
+]
+pytest = [
+    {file = "pytest-6.1.1-py3-none-any.whl", hash = "sha256:7a8190790c17d79a11f847fba0b004ee9a8122582ebff4729a082c109e81a4c9"},
+    {file = "pytest-6.1.1.tar.gz", hash = "sha256:8f593023c1a0f916110285b6efd7f99db07d59546e3d8c36fc60e2ab05d3be92"},
+]
+pytest-cov = [
+    {file = "pytest-cov-3.0.0.tar.gz", hash = "sha256:e7f0f5b1617d2210a2cabc266dfe2f4c75a8d32fb89eafb7ad9d06f6d076d470"},
+    {file = "pytest_cov-3.0.0-py3-none-any.whl", hash = "sha256:578d5d15ac4a25e5f961c938b85a05b09fdaae9deef3bb6de9a6e766622ca7a6"},
+]
+pytest-cover = [
+    {file = "pytest-cover-3.0.0.tar.gz", hash = "sha256:5bdb6c1cc3dd75583bb7bc2c57f5e1034a1bfcb79d27c71aceb0b16af981dbf4"},
+    {file = "pytest_cover-3.0.0-py2.py3-none-any.whl", hash = "sha256:578249955eb3b5f3991209df6e532bb770b647743b7392d3d97698dc02f39ebb"},
+]
+pytest-coverage = [
+    {file = "pytest-coverage-0.0.tar.gz", hash = "sha256:db6af2cbd7e458c7c9fd2b4207cee75258243c8a81cad31a7ee8cfad5be93c05"},
+    {file = "pytest_coverage-0.0-py2.py3-none-any.whl", hash = "sha256:dedd084c5e74d8e669355325916dc011539b190355021b037242514dee546368"},
+]
+pytest-html = [
+    {file = "pytest-html-3.1.1.tar.gz", hash = "sha256:3ee1cf319c913d19fe53aeb0bc400e7b0bc2dbeb477553733db1dad12eb75ee3"},
+    {file = "pytest_html-3.1.1-py3-none-any.whl", hash = "sha256:b7f82f123936a3f4d2950bc993c2c1ca09ce262c9ae12f9ac763a2401380b455"},
+]
+pytest-metadata = [
+    {file = "pytest-metadata-1.11.0.tar.gz", hash = "sha256:71b506d49d34e539cc3cfdb7ce2c5f072bea5c953320002c95968e0238f8ecf1"},
+    {file = "pytest_metadata-1.11.0-py2.py3-none-any.whl", hash = "sha256:576055b8336dd4a9006dd2a47615f76f2f8c30ab12b1b1c039d99e834583523f"},
+]
+pytest-sugar = [
+    {file = "pytest-sugar-0.9.4.tar.gz", hash = "sha256:b1b2186b0a72aada6859bea2a5764145e3aaa2c1cfbb23c3a19b5f7b697563d3"},
+]
+pytz = [
+    {file = "pytz-2021.3-py2.py3-none-any.whl", hash = "sha256:3672058bc3453457b622aab7a1c3bfd5ab0bdae451512f6cf25f64ed37f5b87c"},
+    {file = "pytz-2021.3.tar.gz", hash = "sha256:acad2d8b20a1af07d4e4c9d2e9285c5ed9104354062f275f3fcd88dcef4f1326"},
+]
+requests = [
+    {file = "requests-2.26.0-py2.py3-none-any.whl", hash = "sha256:6c1246513ecd5ecd4528a0906f910e8f0f9c6b8ec72030dc9fd154dc1a6efd24"},
+    {file = "requests-2.26.0.tar.gz", hash = "sha256:b8aa58f8cf793ffd8782d3d8cb19e66ef36f7aba4353eec859e74678b01b07a7"},
+]
+snowballstemmer = [
+    {file = "snowballstemmer-2.1.0-py2.py3-none-any.whl", hash = "sha256:b51b447bea85f9968c13b650126a888aabd4cb4463fca868ec596826325dedc2"},
+    {file = "snowballstemmer-2.1.0.tar.gz", hash = "sha256:e997baa4f2e9139951b6f4c631bad912dfd3c792467e2f03d7239464af90e914"},
+]
+sortedcontainers = [
+    {file = "sortedcontainers-2.4.0-py2.py3-none-any.whl", hash = "sha256:a163dcaede0f1c021485e957a39245190e74249897e2ae4b2aa38595db237ee0"},
+    {file = "sortedcontainers-2.4.0.tar.gz", hash = "sha256:25caa5a06cc30b6b83d11423433f65d1f9d76c4c6a0c90e3379eaa43b9bfdb88"},
+]
+sphinx = [
+    {file = "Sphinx-4.2.0-py3-none-any.whl", hash = "sha256:98a535c62a4fcfcc362528592f69b26f7caec587d32cd55688db580be0287ae0"},
+    {file = "Sphinx-4.2.0.tar.gz", hash = "sha256:94078db9184491e15bce0a56d9186e0aec95f16ac20b12d00e06d4e36f1058a6"},
+]
+sphinx-rtd-theme = [
+    {file = "sphinx_rtd_theme-1.0.0-py2.py3-none-any.whl", hash = "sha256:4d35a56f4508cfee4c4fb604373ede6feae2a306731d533f409ef5c3496fdbd8"},
+    {file = "sphinx_rtd_theme-1.0.0.tar.gz", hash = "sha256:eec6d497e4c2195fa0e8b2016b337532b8a699a68bcb22a512870e16925c6a5c"},
+]
+sphinxcontrib-applehelp = [
+    {file = "sphinxcontrib-applehelp-1.0.2.tar.gz", hash = "sha256:a072735ec80e7675e3f432fcae8610ecf509c5f1869d17e2eecff44389cdbc58"},
+    {file = "sphinxcontrib_applehelp-1.0.2-py2.py3-none-any.whl", hash = "sha256:806111e5e962be97c29ec4c1e7fe277bfd19e9652fb1a4392105b43e01af885a"},
+]
+sphinxcontrib-devhelp = [
+    {file = "sphinxcontrib-devhelp-1.0.2.tar.gz", hash = "sha256:ff7f1afa7b9642e7060379360a67e9c41e8f3121f2ce9164266f61b9f4b338e4"},
+    {file = "sphinxcontrib_devhelp-1.0.2-py2.py3-none-any.whl", hash = "sha256:8165223f9a335cc1af7ffe1ed31d2871f325254c0423bc0c4c7cd1c1e4734a2e"},
+]
+sphinxcontrib-htmlhelp = [
+    {file = "sphinxcontrib-htmlhelp-2.0.0.tar.gz", hash = "sha256:f5f8bb2d0d629f398bf47d0d69c07bc13b65f75a81ad9e2f71a63d4b7a2f6db2"},
+    {file = "sphinxcontrib_htmlhelp-2.0.0-py2.py3-none-any.whl", hash = "sha256:d412243dfb797ae3ec2b59eca0e52dac12e75a241bf0e4eb861e450d06c6ed07"},
+]
+sphinxcontrib-jsmath = [
+    {file = "sphinxcontrib-jsmath-1.0.1.tar.gz", hash = "sha256:a9925e4a4587247ed2191a22df5f6970656cb8ca2bd6284309578f2153e0c4b8"},
+    {file = "sphinxcontrib_jsmath-1.0.1-py2.py3-none-any.whl", hash = "sha256:2ec2eaebfb78f3f2078e73666b1415417a116cc848b72e5172e596c871103178"},
+]
+sphinxcontrib-mermaid = [
+    {file = "sphinxcontrib-mermaid-0.7.1.tar.gz", hash = "sha256:aa8a40b50ec86ad12824b62180240ca52a9bda8424455d7eb252eae9aa5d293c"},
+    {file = "sphinxcontrib_mermaid-0.7.1-py2.py3-none-any.whl", hash = "sha256:3e20de1937c30dfa807e446bf99983d73d0dd3dc5c6524addda59800fe928762"},
+]
+sphinxcontrib-qthelp = [
+    {file = "sphinxcontrib-qthelp-1.0.3.tar.gz", hash = "sha256:4c33767ee058b70dba89a6fc5c1892c0d57a54be67ddd3e7875a18d14cba5a72"},
+    {file = "sphinxcontrib_qthelp-1.0.3-py2.py3-none-any.whl", hash = "sha256:bd9fc24bcb748a8d51fd4ecaade681350aa63009a347a8c14e637895444dfab6"},
+]
+sphinxcontrib-serializinghtml = [
+    {file = "sphinxcontrib-serializinghtml-1.1.5.tar.gz", hash = "sha256:aa5f6de5dfdf809ef505c4895e51ef5c9eac17d0f287933eb49ec495280b6952"},
+    {file = "sphinxcontrib_serializinghtml-1.1.5-py2.py3-none-any.whl", hash = "sha256:352a9a00ae864471d3a7ead8d7d79f5fc0b57e8b3f95e9867eb9eb28999b92fd"},
+]
+sphinxemoji = [
+    {file = "sphinxemoji-0.1.9.tar.gz", hash = "sha256:faf788e5b33a4e309b721b9fa1dba4f12fec74193e5aa30040f9d4d36736444b"},
+]
+termcolor = [
+    {file = "termcolor-1.1.0.tar.gz", hash = "sha256:1d6d69ce66211143803fbc56652b41d73b4a400a2891d7bf7a1cdf4c02de613b"},
+]
+toml = [
+    {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"},
+    {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"},
+]
+traitlets = [
+    {file = "traitlets-5.1.0-py3-none-any.whl", hash = "sha256:03f172516916220b58c9f19d7f854734136dd9528103d04e9bf139a92c9f54c4"},
+    {file = "traitlets-5.1.0.tar.gz", hash = "sha256:bd382d7ea181fbbcce157c133db9a829ce06edffe097bcf3ab945b435452b46d"},
+]
+urllib3 = [
+    {file = "urllib3-1.26.7-py2.py3-none-any.whl", hash = "sha256:c4fdf4019605b6e5423637e01bc9fe4daef873709a7973e195ceba0a62bbc844"},
+    {file = "urllib3-1.26.7.tar.gz", hash = "sha256:4987c65554f7a2dbf30c18fd48778ef124af6fab771a377103da0585e2336ece"},
+]
+wcwidth = [
+    {file = "wcwidth-0.2.5-py2.py3-none-any.whl", hash = "sha256:beb4802a9cebb9144e99086eff703a642a13d6a0052920003a230f3294bbe784"},
+    {file = "wcwidth-0.2.5.tar.gz", hash = "sha256:c4d647b99872929fdb7bdcaa4fbe7f01413ed3d98077df798530e5b04f116c83"},
+]
+wrapt = [
+    {file = "wrapt-1.13.2-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:3de7b4d3066cc610054e7aa2c005645e308df2f92be730aae3a47d42e910566a"},
+    {file = "wrapt-1.13.2-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:8164069f775c698d15582bf6320a4f308c50d048c1c10cf7d7a341feaccf5df7"},
+    {file = "wrapt-1.13.2-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:9adee1891253670575028279de8365c3a02d3489a74a66d774c321472939a0b1"},
+    {file = "wrapt-1.13.2-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:a70d876c9aba12d3bd7f8f1b05b419322c6789beb717044eea2c8690d35cb91b"},
+    {file = "wrapt-1.13.2-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:3f87042623530bcffea038f824b63084180513c21e2e977291a9a7e65a66f13b"},
+    {file = "wrapt-1.13.2-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:e634136f700a21e1fcead0c137f433dde928979538c14907640607d43537d468"},
+    {file = "wrapt-1.13.2-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:3e33c138d1e3620b1e0cc6fd21e46c266393ed5dae0d595b7ed5a6b73ed57aa0"},
+    {file = "wrapt-1.13.2-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:283e402e5357e104ac1e3fba5791220648e9af6fb14ad7d9cc059091af2b31d2"},
+    {file = "wrapt-1.13.2-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:ccb34ce599cab7f36a4c90318697ead18312c67a9a76327b3f4f902af8f68ea1"},
+    {file = "wrapt-1.13.2-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:fbad5ba74c46517e6488149514b2e2348d40df88cd6b52a83855b7a8bf04723f"},
+    {file = "wrapt-1.13.2-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:724ed2bc9c91a2b9026e5adce310fa60c6e7c8760b03391445730b9789b9d108"},
+    {file = "wrapt-1.13.2-cp35-cp35m-manylinux2010_i686.whl", hash = "sha256:83f2793ec6f3ef513ad8d5b9586f5ee6081cad132e6eae2ecb7eac1cc3decae0"},
+    {file = "wrapt-1.13.2-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:0473d1558b93e314e84313cc611f6c86be779369f9d3734302bf185a4d2625b1"},
+    {file = "wrapt-1.13.2-cp35-cp35m-win32.whl", hash = "sha256:15eee0e6fd07f48af2f66d0e6f2ff1916ffe9732d464d5e2390695296872cad9"},
+    {file = "wrapt-1.13.2-cp35-cp35m-win_amd64.whl", hash = "sha256:bc85d17d90201afd88e3d25421da805e4e135012b5d1f149e4de2981394b2a52"},
+    {file = "wrapt-1.13.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:c6ee5f8734820c21b9b8bf705e99faba87f21566d20626568eeb0d62cbeaf23c"},
+    {file = "wrapt-1.13.2-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:53c6706a1bcfb6436f1625511b95b812798a6d2ccc51359cd791e33722b5ea32"},
+    {file = "wrapt-1.13.2-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:fbe6aebc9559fed7ea27de51c2bf5c25ba2a4156cf0017556f72883f2496ee9a"},
+    {file = "wrapt-1.13.2-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:0582180566e7a13030f896c2f1ac6a56134ab5f3c3f4c5538086f758b1caf3f2"},
+    {file = "wrapt-1.13.2-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:bff0a59387a0a2951cb869251257b6553663329a1b5525b5226cab8c88dcbe7e"},
+    {file = "wrapt-1.13.2-cp36-cp36m-win32.whl", hash = "sha256:df3eae297a5f1594d1feb790338120f717dac1fa7d6feed7b411f87e0f2401c7"},
+    {file = "wrapt-1.13.2-cp36-cp36m-win_amd64.whl", hash = "sha256:1eb657ed84f4d3e6ad648483c8a80a0cf0a78922ef94caa87d327e2e1ad49b48"},
+    {file = "wrapt-1.13.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:a0cdedf681db878416c05e1831ec69691b0e6577ac7dca9d4f815632e3549580"},
+    {file = "wrapt-1.13.2-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:87ee3c73bdfb4367b26c57259995935501829f00c7b3eed373e2ad19ec21e4e4"},
+    {file = "wrapt-1.13.2-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:3e0d16eedc242d01a6f8cf0623e9cdc3b869329da3f97a15961d8864111d8cf0"},
+    {file = "wrapt-1.13.2-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:8318088860968c07e741537030b1abdd8908ee2c71fbe4facdaade624a09e006"},
+    {file = "wrapt-1.13.2-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:d90520616fce71c05dedeac3a0fe9991605f0acacd276e5f821842e454485a70"},
+    {file = "wrapt-1.13.2-cp37-cp37m-win32.whl", hash = "sha256:22142afab65daffc95863d78effcbd31c19a8003eca73de59f321ee77f73cadb"},
+    {file = "wrapt-1.13.2-cp37-cp37m-win_amd64.whl", hash = "sha256:d0d717e10f952df7ea41200c507cc7e24458f4c45b56c36ad418d2e79dacd1d4"},
+    {file = "wrapt-1.13.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:593cb049ce1c391e0288523b30426c4430b26e74c7e6f6e2844bd99ac7ecc831"},
+    {file = "wrapt-1.13.2-cp38-cp38-manylinux1_i686.whl", hash = "sha256:8860c8011a6961a651b1b9f46fdbc589ab63b0a50d645f7d92659618a3655867"},
+    {file = "wrapt-1.13.2-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:ada5e29e59e2feb710589ca1c79fd989b1dd94d27079dc1d199ec954a6ecc724"},
+    {file = "wrapt-1.13.2-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:fdede980273aeca591ad354608778365a3a310e0ecdd7a3587b38bc5be9b1808"},
+    {file = "wrapt-1.13.2-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:af9480de8e63c5f959a092047aaf3d7077422ded84695b3398f5d49254af3e90"},
+    {file = "wrapt-1.13.2-cp38-cp38-win32.whl", hash = "sha256:c65e623ea7556e39c4f0818200a046cbba7575a6b570ff36122c276fdd30ab0a"},
+    {file = "wrapt-1.13.2-cp38-cp38-win_amd64.whl", hash = "sha256:b20703356cae1799080d0ad15085dc3213c1ac3f45e95afb9f12769b98231528"},
+    {file = "wrapt-1.13.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:1c5c4cf188b5643a97e87e2110bbd4f5bc491d54a5b90633837b34d5df6a03fe"},
+    {file = "wrapt-1.13.2-cp39-cp39-manylinux1_i686.whl", hash = "sha256:82223f72eba6f63eafca87a0f614495ae5aa0126fe54947e2b8c023969e9f2d7"},
+    {file = "wrapt-1.13.2-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:81a4cf257263b299263472d669692785f9c647e7dca01c18286b8f116dbf6b38"},
+    {file = "wrapt-1.13.2-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:728e2d9b7a99dd955d3426f237b940fc74017c4a39b125fec913f575619ddfe9"},
+    {file = "wrapt-1.13.2-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:7574de567dcd4858a2ffdf403088d6df8738b0e1eabea220553abf7c9048f59e"},
+    {file = "wrapt-1.13.2-cp39-cp39-win32.whl", hash = "sha256:c7ac2c7a8e34bd06710605b21dd1f3576764443d68e069d2afba9b116014d072"},
+    {file = "wrapt-1.13.2-cp39-cp39-win_amd64.whl", hash = "sha256:6e6d1a8eeef415d7fb29fe017de0e48f45e45efd2d1bfda28fc50b7b330859ef"},
+    {file = "wrapt-1.13.2.tar.gz", hash = "sha256:dca56cc5963a5fd7c2aa8607017753f534ee514e09103a6c55d2db70b50e7447"},
+]
diff --git a/_site/poetry_scripts.py b/_site/poetry_scripts.py
new file mode 100644
index 0000000..4b94a6c
--- /dev/null
+++ b/_site/poetry_scripts.py
@@ -0,0 +1,41 @@
+import os
+from fppy import __version__
+
+def docs():
+    """生成DOC
+    """
+    build_dir = 'docs/build/'+ __version__
+    source_dir = 'docs/source'
+    build_main_dir = 'docs/build/main'
+    os.system((
+        f'sphinx-build -b html {source_dir} {build_dir}'
+        ))
+    os.system((
+        f'sphinx-build -b html {source_dir} {build_main_dir}'
+        ))
+
+def test_with_badge():
+    """运行测试
+
+    Examples:
+        ```python
+        ## 在代码行种使用如下命令即可执行测试
+        poetry run test
+        ```
+    """
+    os.system((
+        'pytest -v '
+        '--html=dist/report.html '
+        '--cov-report html:cov_html '
+        '--cov-report xml:cov.xml '
+        '--cov-report annotate:cov_annotate '
+        '--cov-report= '
+        '--cov=fppy '
+        'tests/'))
+    if os.path.isfile('badge/cov-badge.svg'):
+        os.remove('badge/cov-badge.svg')
+    os.system('coverage-badge -o badge/cov-badge.svg')
+
+if __name__ == "__main__":
+    docs()
+    test_with_badge()
\ No newline at end of file
diff --git a/_site/pyproject.toml b/_site/pyproject.toml
new file mode 100644
index 0000000..4870f74
--- /dev/null
+++ b/_site/pyproject.toml
@@ -0,0 +1,59 @@
+[tool.poetry]
+name = "fppy"
+version = "0.2.0"
+description = "A implements of Functional Programming in Python."
+authors = ["huangbaochen <huangbaochenwo@live.com>"]
+
+[tool.poetry.dependencies]
+python = "^3.10"
+pathos = "^0.2.8"
+drill = "^1.2.0"
+
+[tool.poetry.dev-dependencies]
+pytest = "6.1.1"
+pytest-sugar = "^0.9.4"
+pytest-coverage = "^0.0"
+ipython = "^7.18.1"
+Sphinx = "^4.2.0"
+sphinx-rtd-theme = "^1.0.0"
+sphinxemoji = "^0.1.9"
+sphinxcontrib-mermaid = "^0.7.1"
+coverage-badge = "^1.0.2"
+pytest-html = "^3.1.1"
+hypothesis = "^6.23.2"
+pylint = "^2.11.1"
+
+[build-system]
+requires = ["poetry-core>=1.0.0"]
+build-backend = "poetry.core.masonry.api"
+
+[tool.pytest.ini_options]
+markers = [
+    "Function: 测试一个更强大的函数对象",
+    "PartialFunction: 测试偏函数",
+    "LazyList: 惰性列表对象",
+    "base: 基本函数",
+    "combinator: 组合子",
+    "option: Option方法",
+    "algebra: 代数学",
+    "either: either方法",
+    "io: io方法",
+    "state: state方法",
+    "lens: 光学元件",
+    "property: property-based testing",
+    "const: 常量",
+    "lazy: 惰性值",
+    "cons_base: 用二元元组定义的list",
+    "cons_list: 用类实现的list",
+    "functor: 函子",
+    "lazy_list_base: 惰性列表类实现",
+    "lazy_list_lambda: 从lambda实现的惰性列表",
+]
+addopts = [
+    "--cov-config=.coveragerc"
+]
+
+[tool.poetry.scripts]
+docs = "poetry_scripts:docs"
+tests = "poetry_scripts:test_with_badge"
+
diff --git a/fppy/abstract_algebra.py b/fppy/abstract_algebra.py
index d99ef7e..e53c4bb 100644
--- a/fppy/abstract_algebra.py
+++ b/fppy/abstract_algebra.py
@@ -4,6 +4,7 @@
 from abc import abstractmethod, ABC
 from functools import reduce
 from typing import Iterable
+from math import floor
 from pathos.multiprocessing import ProcessPool
 from pathos.abstract_launcher import AbstractWorkerPool
 from pathos.threading import ThreadPool
@@ -60,6 +61,19 @@ def mreduce(self, ls: Iterable):
         """
         return reduce(self.join, ls, self.unit)
 
+    def dfs_reduce(self, it: Iterable):
+        """深度优先二分reduce
+        """
+        len_of_iter = len(it)
+        if len_of_iter == 2:
+            return self.join(it[0], it[1])
+        if len_of_iter == 1:
+            return self.join(self.unit, it[0])
+        return self.join(
+            self.dfs_reduce(iter[:floor(len_of_iter/2)]),
+            self.dfs_reduce(iter[floor(len_of_iter/2):])
+        )
+
     @classmethod
     def _split_list(cls, l, n):
         """切分列表
diff --git a/fppy/gt.py b/fppy/gt.py
index c704ef5..320bc3c 100644
--- a/fppy/gt.py
+++ b/fppy/gt.py
@@ -1,9 +1,18 @@
+'''
+Author: huangbaochen<huangbaochenwo@live.com>
+Date: 2021-12-11 15:00:48
+LastEditTime: 2021-12-11 21:23:16
+LastEditors: huangbaochen<huangbaochenwo@live.com>
+Description: 基本类型
+No MERCY
+'''
 from typing import Generic, TypeVar
 
 S0 = TypeVar("S0")
 S = TypeVar("S")
 T = TypeVar("T")
 T1 = TypeVar("T1")
+U = TypeVar("U")
 
 class GenericTypeVar(Generic[S]):
     """泛型类
diff --git a/fppy/try_monad.py b/fppy/try_monad.py
index 73d937e..9092f2c 100644
--- a/fppy/try_monad.py
+++ b/fppy/try_monad.py
@@ -1,11 +1,142 @@
+'''
+Author: huangbaochen<huangbaochenwo@live.com>
+Date: 2021-12-11 15:00:15
+LastEditTime: 2021-12-11 21:28:16
+LastEditors: huangbaochen<huangbaochenwo@live.com>
+Description: Try单子
+No MERCY
+'''
+from __future__ import annotations
 from dataclasses import dataclass
-from typing import Generic, List
+from typing import Callable, Generic
+
+from fppy.option import Just, Nothing
 from .gt import S, T
 
+
+class _Try(Generic[S, T]):
+    """Try 单子抽象类
+    """
+
 @dataclass
-class Try(Generic[S]):
-    value: None | S = None
-    state_list: List = []
-    
-    def of(self, value):
-        return ()
\ No newline at end of file
+class Fail(_Try[S, T]):
+    """失败类
+    """
+    error: Exception
+    value: S | None = None
+
+    @classmethod
+    def unapply(cls, x: Fail[S, T]) -> Nothing:
+        """unapply方法，转为option对象
+        """
+        if not isinstance(x, Fail):
+            raise TypeError(
+                'Fail.unapply需使用Fail为参数，实际是',
+                f'{type(x)}'
+                )
+        else:
+            return Nothing()
+
+    # pylint: disable=unused-argument
+    def map(self, func: Callable[[S], T]) -> Success[S, T] | Fail[S, T]:
+        """map方法
+        """
+        return self
+
+    # pylint: disable=unused-argument
+    def flat_map(
+        self,
+        func: Callable[[S], _Try]) -> _Try:
+        """flat_map方法
+        """
+        return self
+
+    def __eq__(self, __o: object) -> bool:
+        """重写等于逻辑
+        """
+        return (
+            self.error.args == __o.error.args
+        ) and (
+            self.value == __o.value
+        )
+
+    def get(self):
+        """获取错误类型
+        """
+        return self.error
+
+    def get_or_else(self, other: S) -> S:
+        """获取值
+        """
+        return other
+
+    def get_error_input(self) -> T:
+        """获取报错时的输入
+        """
+        return self.value
+
+@dataclass
+class Success(_Try[S, T]):
+    """成功类
+    """
+    value: S | None = None
+
+    @classmethod
+    def unapply(cls, x: Success[S, T]) -> Just:
+        """unapply方法，转为option对象
+        """
+        if not isinstance(x, Success):
+            raise TypeError(
+                'Success.unapply需使用Success为参数，实际是',
+                f'{type(x)}'
+            )
+        else:
+            return Just(x.value)
+
+    def map(self, func: Callable[[S], T]) -> Success[T] | Fail[S]:
+        """map方法
+        """
+        try:
+            return Success(func(self.value))
+        # pylint: disable=broad-except
+        except Exception as e:
+            return Fail(e, self.value)
+
+    def flat_map(
+        self,
+        func: Callable[[S], _Try[S, T]]) -> _Try[S, T]:
+        """flat_map方法
+        """
+        res = func(self.value)
+        if not isinstance(res, _Try):
+            raise TypeError(
+                'flat_map需返回一个Try对象，但实际上返回了',
+                f'{type(res)}'
+            )
+        else:
+            return res
+
+    def get(self):
+        """获取值
+        """
+        return self.value
+
+    # pylint: disable=unused-argument
+    def get_or_else(self, other: S):
+        """获取值
+        """
+        return self.value
+
+class Try(_Try[S, T]):
+    """Try同名类（为了实现apply）
+    """
+    def __new__(cls, x: S) -> Success[S, T]:
+        """应用
+        """
+        return Success(x)
+
+    @classmethod
+    def apply(cls, x: S) -> Success[S, T]:
+        """应用
+        """
+        return Success(x)
diff --git a/poetry.lock b/poetry.lock
index 4ae71ad..668f1dd 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -1,166 +1,168 @@
 [[package]]
-name = "alabaster"
-version = "0.7.12"
-description = "A configurable sidebar-enabled Sphinx theme"
 category = "dev"
+description = "A configurable sidebar-enabled Sphinx theme"
+name = "alabaster"
 optional = false
 python-versions = "*"
+version = "0.7.12"
 
 [[package]]
-name = "appnope"
-version = "0.1.2"
-description = "Disable App Nap on macOS >= 10.9"
 category = "dev"
+description = "Disable App Nap on macOS >= 10.9"
+marker = "sys_platform == \"darwin\""
+name = "appnope"
 optional = false
 python-versions = "*"
+version = "0.1.2"
 
 [[package]]
-name = "astroid"
-version = "2.8.3"
-description = "An abstract syntax tree for Python with inference support."
 category = "dev"
+description = "An abstract syntax tree for Python with inference support."
+name = "astroid"
 optional = false
 python-versions = "~=3.6"
+version = "2.8.3"
 
 [package.dependencies]
 lazy-object-proxy = ">=1.4.0"
+setuptools = ">=20.0"
 wrapt = ">=1.11,<1.14"
 
 [[package]]
-name = "atomicwrites"
-version = "1.4.0"
-description = "Atomic file writes."
 category = "dev"
+description = "Atomic file writes."
+marker = "sys_platform == \"win32\""
+name = "atomicwrites"
 optional = false
 python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
+version = "1.4.0"
 
 [[package]]
-name = "attrs"
-version = "21.2.0"
-description = "Classes Without Boilerplate"
 category = "dev"
+description = "Classes Without Boilerplate"
+name = "attrs"
 optional = false
 python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
+version = "21.2.0"
 
 [package.extras]
-dev = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface", "furo", "sphinx", "sphinx-notfound-page", "pre-commit"]
+dev = ["coverage (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface", "furo", "sphinx", "sphinx-notfound-page", "pre-commit"]
 docs = ["furo", "sphinx", "zope.interface", "sphinx-notfound-page"]
-tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface"]
-tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins"]
+tests = ["coverage (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface"]
+tests_no_zope = ["coverage (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins"]
 
 [[package]]
-name = "babel"
-version = "2.9.1"
-description = "Internationalization utilities"
 category = "dev"
+description = "Internationalization utilities"
+name = "babel"
 optional = false
 python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
+version = "2.9.1"
 
 [package.dependencies]
 pytz = ">=2015.7"
 
 [[package]]
-name = "backcall"
-version = "0.2.0"
-description = "Specifications for callback functions passed in to an API"
 category = "dev"
+description = "Specifications for callback functions passed in to an API"
+name = "backcall"
 optional = false
 python-versions = "*"
+version = "0.2.0"
 
 [[package]]
-name = "certifi"
-version = "2021.10.8"
-description = "Python package for providing Mozilla's CA Bundle."
 category = "dev"
+description = "Python package for providing Mozilla's CA Bundle."
+name = "certifi"
 optional = false
 python-versions = "*"
+version = "2021.10.8"
 
 [[package]]
-name = "charset-normalizer"
-version = "2.0.7"
-description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet."
 category = "dev"
+description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet."
+marker = "python_version >= \"3\""
+name = "charset-normalizer"
 optional = false
 python-versions = ">=3.5.0"
+version = "2.0.7"
 
 [package.extras]
 unicode_backport = ["unicodedata2"]
 
 [[package]]
-name = "colorama"
-version = "0.4.4"
-description = "Cross-platform colored terminal text."
 category = "dev"
+description = "Cross-platform colored terminal text."
+marker = "sys_platform == \"win32\""
+name = "colorama"
 optional = false
 python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
+version = "0.4.4"
 
 [[package]]
-name = "coverage"
-version = "5.5"
-description = "Code coverage measurement for Python"
 category = "dev"
+description = "Code coverage measurement for Python"
+name = "coverage"
 optional = false
 python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4"
-
-[package.dependencies]
-toml = {version = "*", optional = true, markers = "extra == \"toml\""}
+version = "5.5"
 
 [package.extras]
 toml = ["toml"]
 
 [[package]]
-name = "coverage-badge"
-version = "1.0.2"
-description = "Generate coverage badges for Coverage.py."
 category = "dev"
+description = "Generate coverage badges for Coverage.py."
+name = "coverage-badge"
 optional = false
 python-versions = "*"
+version = "1.0.2"
 
 [package.dependencies]
 coverage = ">=5.0.0,<6.0.0"
 
 [[package]]
-name = "decorator"
-version = "5.1.0"
-description = "Decorators for Humans"
 category = "dev"
+description = "Decorators for Humans"
+name = "decorator"
 optional = false
 python-versions = ">=3.5"
+version = "5.1.0"
 
 [[package]]
-name = "dill"
-version = "0.3.4"
-description = "serialize all of python"
 category = "main"
+description = "serialize all of python"
+name = "dill"
 optional = false
 python-versions = ">=2.7, !=3.0.*"
+version = "0.3.4"
 
 [package.extras]
 graph = ["objgraph (>=1.7.2)"]
 
 [[package]]
-name = "docutils"
-version = "0.17.1"
-description = "Docutils -- Python Documentation Utilities"
 category = "dev"
+description = "Docutils -- Python Documentation Utilities"
+name = "docutils"
 optional = false
 python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
+version = "0.17.1"
 
 [[package]]
-name = "drill"
-version = "1.2.0"
-description = "A small python library for quickly traversing XML data."
 category = "main"
+description = "A small python library for quickly traversing XML data."
+name = "drill"
 optional = false
 python-versions = "*"
+version = "1.2.0"
 
 [[package]]
-name = "hypothesis"
-version = "6.23.2"
-description = "A library for property-based testing"
 category = "dev"
+description = "A library for property-based testing"
+name = "hypothesis"
 optional = false
 python-versions = ">=3.6"
+version = "6.23.2"
 
 [package.dependencies]
 attrs = ">=19.2.0"
@@ -183,48 +185,50 @@ redis = ["redis (>=3.0.0)"]
 zoneinfo = ["importlib-resources (>=3.3.0)", "backports.zoneinfo (>=0.2.1)", "tzdata (>=2020.4)"]
 
 [[package]]
-name = "idna"
-version = "3.3"
-description = "Internationalized Domain Names in Applications (IDNA)"
 category = "dev"
+description = "Internationalized Domain Names in Applications (IDNA)"
+marker = "python_version >= \"3\""
+name = "idna"
 optional = false
 python-versions = ">=3.5"
+version = "3.3"
 
 [[package]]
-name = "imagesize"
-version = "1.2.0"
-description = "Getting image size from png/jpeg/jpeg2000/gif file"
 category = "dev"
+description = "Getting image size from png/jpeg/jpeg2000/gif file"
+name = "imagesize"
 optional = false
 python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
+version = "1.2.0"
 
 [[package]]
-name = "iniconfig"
-version = "1.1.1"
-description = "iniconfig: brain-dead simple config-ini parsing"
 category = "dev"
+description = "iniconfig: brain-dead simple config-ini parsing"
+name = "iniconfig"
 optional = false
 python-versions = "*"
+version = "1.1.1"
 
 [[package]]
-name = "ipython"
-version = "7.28.0"
-description = "IPython: Productive Interactive Computing"
 category = "dev"
+description = "IPython: Productive Interactive Computing"
+name = "ipython"
 optional = false
 python-versions = ">=3.7"
+version = "7.28.0"
 
 [package.dependencies]
-appnope = {version = "*", markers = "sys_platform == \"darwin\""}
+appnope = "*"
 backcall = "*"
-colorama = {version = "*", markers = "sys_platform == \"win32\""}
+colorama = "*"
 decorator = "*"
 jedi = ">=0.16"
 matplotlib-inline = "*"
-pexpect = {version = ">4.3", markers = "sys_platform != \"win32\""}
+pexpect = ">4.3"
 pickleshare = "*"
 prompt-toolkit = ">=2.0.0,<3.0.0 || >3.0.0,<3.0.1 || >3.0.1,<3.1.0"
 pygments = "*"
+setuptools = ">=18.5"
 traitlets = ">=4.2"
 
 [package.extras]
@@ -239,41 +243,41 @@ qtconsole = ["qtconsole"]
 test = ["nose (>=0.10.1)", "requests", "testpath", "pygments", "nbformat", "ipykernel", "numpy (>=1.17)"]
 
 [[package]]
-name = "isort"
-version = "5.9.3"
-description = "A Python utility / library to sort Python imports."
 category = "dev"
+description = "A Python utility / library to sort Python imports."
+name = "isort"
 optional = false
 python-versions = ">=3.6.1,<4.0"
+version = "5.9.3"
 
 [package.extras]
-pipfile_deprecated_finder = ["pipreqs", "requirementslib"]
-requirements_deprecated_finder = ["pipreqs", "pip-api"]
 colors = ["colorama (>=0.4.3,<0.5.0)"]
+pipfile_deprecated_finder = ["pipreqs", "requirementslib"]
 plugins = ["setuptools"]
+requirements_deprecated_finder = ["pipreqs", "pip-api"]
 
 [[package]]
-name = "jedi"
-version = "0.18.0"
-description = "An autocompletion tool for Python that can be used for text editors."
 category = "dev"
+description = "An autocompletion tool for Python that can be used for text editors."
+name = "jedi"
 optional = false
 python-versions = ">=3.6"
+version = "0.18.0"
 
 [package.dependencies]
 parso = ">=0.8.0,<0.9.0"
 
 [package.extras]
-qa = ["flake8 (==3.8.3)", "mypy (==0.782)"]
+qa = ["flake8 (3.8.3)", "mypy (0.782)"]
 testing = ["Django (<3.1)", "colorama", "docopt", "pytest (<6.0.0)"]
 
 [[package]]
-name = "jinja2"
-version = "3.0.2"
-description = "A very fast and expressive template engine."
 category = "dev"
+description = "A very fast and expressive template engine."
+name = "jinja2"
 optional = false
 python-versions = ">=3.6"
+version = "3.0.2"
 
 [package.dependencies]
 MarkupSafe = ">=2.0"
@@ -282,81 +286,106 @@ MarkupSafe = ">=2.0"
 i18n = ["Babel (>=2.7)"]
 
 [[package]]
-name = "lazy-object-proxy"
-version = "1.6.0"
-description = "A fast and thorough lazy object proxy."
 category = "dev"
+description = "A fast and thorough lazy object proxy."
+name = "lazy-object-proxy"
 optional = false
 python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*"
+version = "1.6.0"
 
 [[package]]
-name = "markupsafe"
-version = "2.0.1"
-description = "Safely add untrusted strings to HTML/XML markup."
 category = "dev"
+description = "Safely add untrusted strings to HTML/XML markup."
+name = "markupsafe"
 optional = false
 python-versions = ">=3.6"
+version = "2.0.1"
 
 [[package]]
-name = "matplotlib-inline"
-version = "0.1.3"
-description = "Inline Matplotlib backend for Jupyter"
 category = "dev"
+description = "Inline Matplotlib backend for Jupyter"
+name = "matplotlib-inline"
 optional = false
 python-versions = ">=3.5"
+version = "0.1.3"
 
 [package.dependencies]
 traitlets = "*"
 
 [[package]]
-name = "mccabe"
-version = "0.6.1"
-description = "McCabe checker, plugin for flake8"
 category = "dev"
+description = "McCabe checker, plugin for flake8"
+name = "mccabe"
 optional = false
 python-versions = "*"
+version = "0.6.1"
 
 [[package]]
-name = "multiprocess"
-version = "0.70.12.2"
-description = "better multiprocessing and multithreading in python"
 category = "main"
+description = "better multiprocessing and multithreading in python"
+name = "multiprocess"
 optional = false
 python-versions = "*"
+version = "0.70.12.2"
 
 [package.dependencies]
 dill = ">=0.3.4"
 
 [[package]]
-name = "packaging"
-version = "21.0"
-description = "Core utilities for Python packages"
 category = "dev"
+description = "Optional static typing for Python"
+name = "mypy"
+optional = false
+python-versions = ">=3.5"
+version = "0.910"
+
+[package.dependencies]
+mypy-extensions = ">=0.4.3,<0.5.0"
+toml = "*"
+typing-extensions = ">=3.7.4"
+
+[package.extras]
+dmypy = ["psutil (>=4.0)"]
+python2 = ["typed-ast (>=1.4.0,<1.5.0)"]
+
+[[package]]
+category = "dev"
+description = "Experimental type system extensions for programs checked with the mypy typechecker."
+name = "mypy-extensions"
+optional = false
+python-versions = "*"
+version = "0.4.3"
+
+[[package]]
+category = "dev"
+description = "Core utilities for Python packages"
+name = "packaging"
 optional = false
 python-versions = ">=3.6"
+version = "21.0"
 
 [package.dependencies]
 pyparsing = ">=2.0.2"
 
 [[package]]
-name = "parso"
-version = "0.8.2"
-description = "A Python Parser"
 category = "dev"
+description = "A Python Parser"
+name = "parso"
 optional = false
 python-versions = ">=3.6"
+version = "0.8.2"
 
 [package.extras]
-qa = ["flake8 (==3.8.3)", "mypy (==0.782)"]
+qa = ["flake8 (3.8.3)", "mypy (0.782)"]
 testing = ["docopt", "pytest (<6.0.0)"]
 
 [[package]]
-name = "pathos"
-version = "0.2.8"
-description = "parallel graph management and execution in heterogeneous computing"
 category = "main"
+description = "parallel graph management and execution in heterogeneous computing"
+name = "pathos"
 optional = false
 python-versions = "*"
+version = "0.2.8"
 
 [package.dependencies]
 dill = ">=0.3.4"
@@ -365,137 +394,139 @@ pox = ">=0.3.0"
 ppft = ">=1.6.6.4"
 
 [[package]]
-name = "pexpect"
-version = "4.8.0"
-description = "Pexpect allows easy control of interactive console applications."
 category = "dev"
+description = "Pexpect allows easy control of interactive console applications."
+marker = "sys_platform != \"win32\""
+name = "pexpect"
 optional = false
 python-versions = "*"
+version = "4.8.0"
 
 [package.dependencies]
 ptyprocess = ">=0.5"
 
 [[package]]
-name = "pickleshare"
-version = "0.7.5"
-description = "Tiny 'shelve'-like database with concurrency support"
 category = "dev"
+description = "Tiny 'shelve'-like database with concurrency support"
+name = "pickleshare"
 optional = false
 python-versions = "*"
+version = "0.7.5"
 
 [[package]]
-name = "platformdirs"
-version = "2.4.0"
-description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"."
 category = "dev"
+description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"."
+name = "platformdirs"
 optional = false
 python-versions = ">=3.6"
+version = "2.4.0"
 
 [package.extras]
 docs = ["Sphinx (>=4)", "furo (>=2021.7.5b38)", "proselint (>=0.10.2)", "sphinx-autodoc-typehints (>=1.12)"]
-test = ["appdirs (==1.4.4)", "pytest (>=6)", "pytest-cov (>=2.7)", "pytest-mock (>=3.6)"]
+test = ["appdirs (1.4.4)", "pytest (>=6)", "pytest-cov (>=2.7)", "pytest-mock (>=3.6)"]
 
 [[package]]
-name = "pluggy"
-version = "0.13.1"
-description = "plugin and hook calling mechanisms for python"
 category = "dev"
+description = "plugin and hook calling mechanisms for python"
+name = "pluggy"
 optional = false
 python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
+version = "0.13.1"
 
 [package.extras]
 dev = ["pre-commit", "tox"]
 
 [[package]]
-name = "pox"
-version = "0.3.0"
-description = "utilities for filesystem exploration and automated builds"
 category = "main"
+description = "utilities for filesystem exploration and automated builds"
+name = "pox"
 optional = false
 python-versions = "*"
+version = "0.3.0"
 
 [[package]]
-name = "ppft"
-version = "1.6.6.4"
-description = "distributed and parallel python"
 category = "main"
+description = "distributed and parallel python"
+name = "ppft"
 optional = false
 python-versions = "*"
+version = "1.6.6.4"
 
 [package.extras]
 dill = ["dill (>=0.3.4)"]
 
 [[package]]
-name = "prompt-toolkit"
-version = "3.0.20"
-description = "Library for building powerful interactive command lines in Python"
 category = "dev"
+description = "Library for building powerful interactive command lines in Python"
+name = "prompt-toolkit"
 optional = false
 python-versions = ">=3.6.2"
+version = "3.0.20"
 
 [package.dependencies]
 wcwidth = "*"
 
 [[package]]
-name = "ptyprocess"
-version = "0.7.0"
-description = "Run a subprocess in a pseudo terminal"
 category = "dev"
+description = "Run a subprocess in a pseudo terminal"
+marker = "sys_platform != \"win32\""
+name = "ptyprocess"
 optional = false
 python-versions = "*"
+version = "0.7.0"
 
 [[package]]
-name = "py"
-version = "1.10.0"
-description = "library with cross-python path, ini-parsing, io, code, log facilities"
 category = "dev"
+description = "library with cross-python path, ini-parsing, io, code, log facilities"
+name = "py"
 optional = false
 python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
+version = "1.10.0"
 
 [[package]]
-name = "pygments"
-version = "2.10.0"
-description = "Pygments is a syntax highlighting package written in Python."
 category = "dev"
+description = "Pygments is a syntax highlighting package written in Python."
+name = "pygments"
 optional = false
 python-versions = ">=3.5"
+version = "2.10.0"
 
 [[package]]
-name = "pylint"
-version = "2.11.1"
-description = "python code static checker"
 category = "dev"
+description = "python code static checker"
+name = "pylint"
 optional = false
 python-versions = "~=3.6"
+version = "2.11.1"
 
 [package.dependencies]
 astroid = ">=2.8.0,<2.9"
-colorama = {version = "*", markers = "sys_platform == \"win32\""}
+colorama = "*"
 isort = ">=4.2.5,<6"
 mccabe = ">=0.6,<0.7"
 platformdirs = ">=2.2.0"
 toml = ">=0.7.1"
 
 [[package]]
-name = "pyparsing"
-version = "2.4.7"
-description = "Python parsing module"
 category = "dev"
+description = "Python parsing module"
+name = "pyparsing"
 optional = false
 python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*"
+version = "2.4.7"
 
 [[package]]
-name = "pytest"
-version = "6.1.1"
-description = "pytest: simple powerful testing with Python"
 category = "dev"
+description = "pytest: simple powerful testing with Python"
+name = "pytest"
 optional = false
 python-versions = ">=3.5"
+version = "6.1.1"
 
 [package.dependencies]
-atomicwrites = {version = ">=1.0", markers = "sys_platform == \"win32\""}
+atomicwrites = ">=1.0"
 attrs = ">=17.4.0"
-colorama = {version = "*", markers = "sys_platform == \"win32\""}
+colorama = "*"
 iniconfig = "*"
 packaging = "*"
 pluggy = ">=0.12,<1.0"
@@ -503,76 +534,79 @@ py = ">=1.8.2"
 toml = "*"
 
 [package.extras]
-checkqa_mypy = ["mypy (==0.780)"]
+checkqa_mypy = ["mypy (0.780)"]
 testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "requests", "xmlschema"]
 
 [[package]]
-name = "pytest-cov"
-version = "3.0.0"
-description = "Pytest plugin for measuring coverage."
 category = "dev"
+description = "Pytest plugin for measuring coverage."
+name = "pytest-cov"
 optional = false
 python-versions = ">=3.6"
+version = "3.0.0"
 
 [package.dependencies]
-coverage = {version = ">=5.2.1", extras = ["toml"]}
 pytest = ">=4.6"
 
+[package.dependencies.coverage]
+extras = ["toml"]
+version = ">=5.2.1"
+
 [package.extras]
 testing = ["fields", "hunter", "process-tests", "six", "pytest-xdist", "virtualenv"]
 
 [[package]]
-name = "pytest-cover"
-version = "3.0.0"
-description = "Pytest plugin for measuring coverage. Forked from `pytest-cov`."
 category = "dev"
+description = "Pytest plugin for measuring coverage. Forked from `pytest-cov`."
+name = "pytest-cover"
 optional = false
 python-versions = "*"
+version = "3.0.0"
 
 [package.dependencies]
 pytest-cov = ">=2.0"
 
 [[package]]
-name = "pytest-coverage"
-version = "0.0"
-description = "Pytest plugin for measuring coverage. Forked from `pytest-cov`."
 category = "dev"
+description = "Pytest plugin for measuring coverage. Forked from `pytest-cov`."
+name = "pytest-coverage"
 optional = false
 python-versions = "*"
+version = "0.0"
 
 [package.dependencies]
 pytest-cover = "*"
 
 [[package]]
-name = "pytest-html"
-version = "3.1.1"
-description = "pytest plugin for generating HTML reports"
 category = "dev"
+description = "pytest plugin for generating HTML reports"
+name = "pytest-html"
 optional = false
 python-versions = ">=3.6"
+version = "3.1.1"
 
 [package.dependencies]
 pytest = ">=5.0,<6.0.0 || >6.0.0"
 pytest-metadata = "*"
 
 [[package]]
-name = "pytest-metadata"
-version = "1.11.0"
-description = "pytest plugin for test session metadata"
 category = "dev"
+description = "pytest plugin for test session metadata"
+name = "pytest-metadata"
 optional = false
 python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*"
+version = "1.11.0"
 
 [package.dependencies]
 pytest = ">=2.9.0"
 
 [[package]]
-name = "pytest-sugar"
-version = "0.9.4"
-description = "pytest-sugar is a plugin for pytest that changes the default look and feel of pytest (e.g. progressbar, show tests that fail instantly)."
 category = "dev"
+description = "pytest-sugar is a plugin for pytest that changes the default look and feel of pytest (e.g. progressbar, show tests that fail instantly)."
+name = "pytest-sugar"
 optional = false
 python-versions = "*"
+version = "0.9.4"
 
 [package.dependencies]
 packaging = ">=14.1"
@@ -580,65 +614,72 @@ pytest = ">=2.9"
 termcolor = ">=1.1.0"
 
 [[package]]
-name = "pytz"
-version = "2021.3"
-description = "World timezone definitions, modern and historical"
 category = "dev"
+description = "World timezone definitions, modern and historical"
+name = "pytz"
 optional = false
 python-versions = "*"
+version = "2021.3"
 
 [[package]]
-name = "requests"
-version = "2.26.0"
-description = "Python HTTP for Humans."
 category = "dev"
+description = "Python HTTP for Humans."
+name = "requests"
 optional = false
 python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*"
+version = "2.26.0"
 
 [package.dependencies]
 certifi = ">=2017.4.17"
-charset-normalizer = {version = ">=2.0.0,<2.1.0", markers = "python_version >= \"3\""}
-idna = {version = ">=2.5,<4", markers = "python_version >= \"3\""}
 urllib3 = ">=1.21.1,<1.27"
 
+[package.dependencies.charset-normalizer]
+python = ">=3"
+version = ">=2.0.0,<2.1.0"
+
+[package.dependencies.idna]
+python = ">=3"
+version = ">=2.5,<4"
+
 [package.extras]
-socks = ["PySocks (>=1.5.6,!=1.5.7)", "win-inet-pton"]
+socks = ["PySocks (>=1.5.6,<1.5.7 || >1.5.7)", "win-inet-pton"]
 use_chardet_on_py3 = ["chardet (>=3.0.2,<5)"]
 
 [[package]]
-name = "snowballstemmer"
-version = "2.1.0"
-description = "This package provides 29 stemmers for 28 languages generated from Snowball algorithms."
 category = "dev"
+description = "This package provides 29 stemmers for 28 languages generated from Snowball algorithms."
+name = "snowballstemmer"
 optional = false
 python-versions = "*"
+version = "2.1.0"
 
 [[package]]
-name = "sortedcontainers"
-version = "2.4.0"
-description = "Sorted Containers -- Sorted List, Sorted Dict, Sorted Set"
 category = "dev"
+description = "Sorted Containers -- Sorted List, Sorted Dict, Sorted Set"
+name = "sortedcontainers"
 optional = false
 python-versions = "*"
+version = "2.4.0"
 
 [[package]]
-name = "sphinx"
-version = "4.2.0"
-description = "Python documentation generator"
 category = "dev"
+description = "Python documentation generator"
+name = "sphinx"
 optional = false
 python-versions = ">=3.6"
+version = "4.2.0"
 
 [package.dependencies]
+Jinja2 = ">=2.3"
+Pygments = ">=2.0"
 alabaster = ">=0.7,<0.8"
 babel = ">=1.3"
-colorama = {version = ">=0.3.5", markers = "sys_platform == \"win32\""}
+colorama = ">=0.3.5"
 docutils = ">=0.14,<0.18"
 imagesize = "*"
-Jinja2 = ">=2.3"
 packaging = "*"
-Pygments = ">=2.0"
 requests = ">=2.5.0"
+setuptools = "*"
 snowballstemmer = ">=1.1"
 sphinxcontrib-applehelp = "*"
 sphinxcontrib-devhelp = "*"
@@ -653,12 +694,12 @@ lint = ["flake8 (>=3.5.0)", "isort", "mypy (>=0.900)", "docutils-stubs", "types-
 test = ["pytest", "pytest-cov", "html5lib", "cython", "typed-ast"]
 
 [[package]]
-name = "sphinx-rtd-theme"
-version = "1.0.0"
-description = "Read the Docs theme for Sphinx"
 category = "dev"
+description = "Read the Docs theme for Sphinx"
+name = "sphinx-rtd-theme"
 optional = false
 python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*"
+version = "1.0.0"
 
 [package.dependencies]
 docutils = "<0.18"
@@ -668,155 +709,163 @@ sphinx = ">=1.6"
 dev = ["transifex-client", "sphinxcontrib-httpdomain", "bump2version"]
 
 [[package]]
-name = "sphinxcontrib-applehelp"
-version = "1.0.2"
-description = "sphinxcontrib-applehelp is a sphinx extension which outputs Apple help books"
 category = "dev"
+description = "sphinxcontrib-applehelp is a sphinx extension which outputs Apple help books"
+name = "sphinxcontrib-applehelp"
 optional = false
 python-versions = ">=3.5"
+version = "1.0.2"
 
 [package.extras]
 lint = ["flake8", "mypy", "docutils-stubs"]
 test = ["pytest"]
 
 [[package]]
-name = "sphinxcontrib-devhelp"
-version = "1.0.2"
-description = "sphinxcontrib-devhelp is a sphinx extension which outputs Devhelp document."
 category = "dev"
+description = "sphinxcontrib-devhelp is a sphinx extension which outputs Devhelp document."
+name = "sphinxcontrib-devhelp"
 optional = false
 python-versions = ">=3.5"
+version = "1.0.2"
 
 [package.extras]
 lint = ["flake8", "mypy", "docutils-stubs"]
 test = ["pytest"]
 
 [[package]]
-name = "sphinxcontrib-htmlhelp"
-version = "2.0.0"
-description = "sphinxcontrib-htmlhelp is a sphinx extension which renders HTML help files"
 category = "dev"
+description = "sphinxcontrib-htmlhelp is a sphinx extension which renders HTML help files"
+name = "sphinxcontrib-htmlhelp"
 optional = false
 python-versions = ">=3.6"
+version = "2.0.0"
 
 [package.extras]
 lint = ["flake8", "mypy", "docutils-stubs"]
 test = ["pytest", "html5lib"]
 
 [[package]]
-name = "sphinxcontrib-jsmath"
-version = "1.0.1"
-description = "A sphinx extension which renders display math in HTML via JavaScript"
 category = "dev"
+description = "A sphinx extension which renders display math in HTML via JavaScript"
+name = "sphinxcontrib-jsmath"
 optional = false
 python-versions = ">=3.5"
+version = "1.0.1"
 
 [package.extras]
 test = ["pytest", "flake8", "mypy"]
 
 [[package]]
-name = "sphinxcontrib-mermaid"
-version = "0.7.1"
-description = "Mermaid diagrams in yours Sphinx powered docs"
 category = "dev"
+description = "Mermaid diagrams in yours Sphinx powered docs"
+name = "sphinxcontrib-mermaid"
 optional = false
 python-versions = "*"
+version = "0.7.1"
 
 [[package]]
-name = "sphinxcontrib-qthelp"
-version = "1.0.3"
-description = "sphinxcontrib-qthelp is a sphinx extension which outputs QtHelp document."
 category = "dev"
+description = "sphinxcontrib-qthelp is a sphinx extension which outputs QtHelp document."
+name = "sphinxcontrib-qthelp"
 optional = false
 python-versions = ">=3.5"
+version = "1.0.3"
 
 [package.extras]
 lint = ["flake8", "mypy", "docutils-stubs"]
 test = ["pytest"]
 
 [[package]]
-name = "sphinxcontrib-serializinghtml"
-version = "1.1.5"
-description = "sphinxcontrib-serializinghtml is a sphinx extension which outputs \"serialized\" HTML files (json and pickle)."
 category = "dev"
+description = "sphinxcontrib-serializinghtml is a sphinx extension which outputs \"serialized\" HTML files (json and pickle)."
+name = "sphinxcontrib-serializinghtml"
 optional = false
 python-versions = ">=3.5"
+version = "1.1.5"
 
 [package.extras]
 lint = ["flake8", "mypy", "docutils-stubs"]
 test = ["pytest"]
 
 [[package]]
-name = "sphinxemoji"
-version = "0.1.9"
-description = "An extension to use emoji codes in your Sphinx documentation"
 category = "dev"
+description = "An extension to use emoji codes in your Sphinx documentation"
+name = "sphinxemoji"
 optional = false
 python-versions = "*"
+version = "0.1.9"
 
 [package.dependencies]
 sphinx = ">=1.8"
 
 [[package]]
-name = "termcolor"
-version = "1.1.0"
-description = "ANSII Color formatting for output in terminal."
 category = "dev"
+description = "ANSII Color formatting for output in terminal."
+name = "termcolor"
 optional = false
 python-versions = "*"
+version = "1.1.0"
 
 [[package]]
-name = "toml"
-version = "0.10.2"
-description = "Python Library for Tom's Obvious, Minimal Language"
 category = "dev"
+description = "Python Library for Tom's Obvious, Minimal Language"
+name = "toml"
 optional = false
 python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*"
+version = "0.10.2"
 
 [[package]]
-name = "traitlets"
-version = "5.1.0"
-description = "Traitlets Python configuration system"
 category = "dev"
+description = "Traitlets Python configuration system"
+name = "traitlets"
 optional = false
 python-versions = ">=3.7"
+version = "5.1.0"
 
 [package.extras]
 test = ["pytest"]
 
 [[package]]
-name = "urllib3"
-version = "1.26.7"
-description = "HTTP library with thread-safe connection pooling, file post, and more."
 category = "dev"
+description = "Backported and Experimental Type Hints for Python 3.6+"
+name = "typing-extensions"
+optional = false
+python-versions = ">=3.6"
+version = "4.0.1"
+
+[[package]]
+category = "dev"
+description = "HTTP library with thread-safe connection pooling, file post, and more."
+name = "urllib3"
 optional = false
 python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4"
+version = "1.26.7"
 
 [package.extras]
 brotli = ["brotlipy (>=0.6.0)"]
 secure = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "certifi", "ipaddress"]
-socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"]
+socks = ["PySocks (>=1.5.6,<1.5.7 || >1.5.7,<2.0)"]
 
 [[package]]
-name = "wcwidth"
-version = "0.2.5"
-description = "Measures the displayed width of unicode strings in a terminal"
 category = "dev"
+description = "Measures the displayed width of unicode strings in a terminal"
+name = "wcwidth"
 optional = false
 python-versions = "*"
+version = "0.2.5"
 
 [[package]]
-name = "wrapt"
-version = "1.13.2"
-description = "Module for decorators, wrappers and monkey patching."
 category = "dev"
+description = "Module for decorators, wrappers and monkey patching."
+name = "wrapt"
 optional = false
 python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7"
+version = "1.13.2"
 
 [metadata]
-lock-version = "1.1"
+content-hash = "ff943e506b1c38e30b83dbd6c49b7f572471402bf5f1eb0a07c4b3b1ca8a3062"
+lock-version = "1.0"
 python-versions = "^3.10"
-content-hash = "f20515cc27d4506029ba27cc011bce7feb0866417f3d126492bee2d5f5658349"
 
 [metadata.files]
 alabaster = [
@@ -995,6 +1044,9 @@ markupsafe = [
     {file = "MarkupSafe-2.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2d7d807855b419fc2ed3e631034685db6079889a1f01d5d9dac950f764da3dad"},
     {file = "MarkupSafe-2.0.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:add36cb2dbb8b736611303cd3bfcee00afd96471b09cda130da3581cbdc56a6d"},
     {file = "MarkupSafe-2.0.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:168cd0a3642de83558a5153c8bd34f175a9a6e7f6dc6384b9655d2697312a646"},
+    {file = "MarkupSafe-2.0.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:4dc8f9fb58f7364b63fd9f85013b780ef83c11857ae79f2feda41e270468dd9b"},
+    {file = "MarkupSafe-2.0.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:20dca64a3ef2d6e4d5d615a3fd418ad3bde77a47ec8a23d984a12b5b4c74491a"},
+    {file = "MarkupSafe-2.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:cdfba22ea2f0029c9261a4bd07e830a8da012291fbe44dc794e488b6c9bb353a"},
     {file = "MarkupSafe-2.0.1-cp310-cp310-win32.whl", hash = "sha256:99df47edb6bda1249d3e80fdabb1dab8c08ef3975f69aed437cb69d0a5de1e28"},
     {file = "MarkupSafe-2.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:e0f138900af21926a02425cf736db95be9f4af72ba1bb21453432a07f6082134"},
     {file = "MarkupSafe-2.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:f9081981fe268bd86831e5c75f7de206ef275defcb82bc70740ae6dc507aee51"},
@@ -1006,6 +1058,9 @@ markupsafe = [
     {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bf5d821ffabf0ef3533c39c518f3357b171a1651c1ff6827325e4489b0e46c3c"},
     {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:0d4b31cc67ab36e3392bbf3862cfbadac3db12bdd8b02a2731f509ed5b829724"},
     {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:baa1a4e8f868845af802979fcdbf0bb11f94f1cb7ced4c4b8a351bb60d108145"},
+    {file = "MarkupSafe-2.0.1-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:deb993cacb280823246a026e3b2d81c493c53de6acfd5e6bfe31ab3402bb37dd"},
+    {file = "MarkupSafe-2.0.1-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:63f3268ba69ace99cab4e3e3b5840b03340efed0948ab8f78d2fd87ee5442a4f"},
+    {file = "MarkupSafe-2.0.1-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:8d206346619592c6200148b01a2142798c989edcb9c896f9ac9722a99d4e77e6"},
     {file = "MarkupSafe-2.0.1-cp36-cp36m-win32.whl", hash = "sha256:6c4ca60fa24e85fe25b912b01e62cb969d69a23a5d5867682dd3e80b5b02581d"},
     {file = "MarkupSafe-2.0.1-cp36-cp36m-win_amd64.whl", hash = "sha256:b2f4bf27480f5e5e8ce285a8c8fd176c0b03e93dcc6646477d4630e83440c6a9"},
     {file = "MarkupSafe-2.0.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:0717a7390a68be14b8c793ba258e075c6f4ca819f15edfc2a3a027c823718567"},
@@ -1017,6 +1072,9 @@ markupsafe = [
     {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e9936f0b261d4df76ad22f8fee3ae83b60d7c3e871292cd42f40b81b70afae85"},
     {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:2a7d351cbd8cfeb19ca00de495e224dea7e7d919659c2841bbb7f420ad03e2d6"},
     {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:60bf42e36abfaf9aff1f50f52644b336d4f0a3fd6d8a60ca0d054ac9f713a864"},
+    {file = "MarkupSafe-2.0.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:d6c7ebd4e944c85e2c3421e612a7057a2f48d478d79e61800d81468a8d842207"},
+    {file = "MarkupSafe-2.0.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:f0567c4dc99f264f49fe27da5f735f414c4e7e7dd850cfd8e69f0862d7c74ea9"},
+    {file = "MarkupSafe-2.0.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:89c687013cb1cd489a0f0ac24febe8c7a666e6e221b783e53ac50ebf68e45d86"},
     {file = "MarkupSafe-2.0.1-cp37-cp37m-win32.whl", hash = "sha256:a30e67a65b53ea0a5e62fe23682cfe22712e01f453b95233b25502f7c61cb415"},
     {file = "MarkupSafe-2.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:611d1ad9a4288cf3e3c16014564df047fe08410e628f89805e475368bd304914"},
     {file = "MarkupSafe-2.0.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:5bb28c636d87e840583ee3adeb78172efc47c8b26127267f54a9c0ec251d41a9"},
@@ -1029,6 +1087,9 @@ markupsafe = [
     {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6fcf051089389abe060c9cd7caa212c707e58153afa2c649f00346ce6d260f1b"},
     {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:5855f8438a7d1d458206a2466bf82b0f104a3724bf96a1c781ab731e4201731a"},
     {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:3dd007d54ee88b46be476e293f48c85048603f5f516008bee124ddd891398ed6"},
+    {file = "MarkupSafe-2.0.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:aca6377c0cb8a8253e493c6b451565ac77e98c2951c45f913e0b52facdcff83f"},
+    {file = "MarkupSafe-2.0.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:04635854b943835a6ea959e948d19dcd311762c5c0c6e1f0e16ee57022669194"},
+    {file = "MarkupSafe-2.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6300b8454aa6930a24b9618fbb54b5a68135092bc666f7b06901f897fa5c2fee"},
     {file = "MarkupSafe-2.0.1-cp38-cp38-win32.whl", hash = "sha256:023cb26ec21ece8dc3907c0e8320058b2e0cb3c55cf9564da612bc325bed5e64"},
     {file = "MarkupSafe-2.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:984d76483eb32f1bcb536dc27e4ad56bba4baa70be32fa87152832cdd9db0833"},
     {file = "MarkupSafe-2.0.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:2ef54abee730b502252bcdf31b10dacb0a416229b72c18b19e24a4509f273d26"},
@@ -1041,6 +1102,9 @@ markupsafe = [
     {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c47adbc92fc1bb2b3274c4b3a43ae0e4573d9fbff4f54cd484555edbf030baf1"},
     {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:37205cac2a79194e3750b0af2a5720d95f786a55ce7df90c3af697bfa100eaac"},
     {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:1f2ade76b9903f39aa442b4aadd2177decb66525062db244b35d71d0ee8599b6"},
+    {file = "MarkupSafe-2.0.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:4296f2b1ce8c86a6aea78613c34bb1a672ea0e3de9c6ba08a960efe0b0a09047"},
+    {file = "MarkupSafe-2.0.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9f02365d4e99430a12647f09b6cc8bab61a6564363f313126f775eb4f6ef798e"},
+    {file = "MarkupSafe-2.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5b6d930f030f8ed98e3e6c98ffa0652bdb82601e7a016ec2ab5d7ff23baa78d1"},
     {file = "MarkupSafe-2.0.1-cp39-cp39-win32.whl", hash = "sha256:10f82115e21dc0dfec9ab5c0223652f7197feb168c940f3ef61563fc2d6beb74"},
     {file = "MarkupSafe-2.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:693ce3f9e70a6cf7d2fb9e6c9d8b204b6b39897a2c4a1aa65728d5ac97dcc1d8"},
     {file = "MarkupSafe-2.0.1.tar.gz", hash = "sha256:594c67807fb16238b30c44bdf74f36c02cdf22d1c8cda91ef8a0ed8dabf5620a"},
@@ -1068,6 +1132,35 @@ multiprocess = [
     {file = "multiprocess-0.70.12.2-py39-none-any.whl", hash = "sha256:6f812a1d3f198b7cacd63983f60e2dc1338bd4450893f90c435067b5a3127e6f"},
     {file = "multiprocess-0.70.12.2.zip", hash = "sha256:206bb9b97b73f87fec1ed15a19f8762950256aa84225450abc7150d02855a083"},
 ]
+mypy = [
+    {file = "mypy-0.910-cp35-cp35m-macosx_10_9_x86_64.whl", hash = "sha256:a155d80ea6cee511a3694b108c4494a39f42de11ee4e61e72bc424c490e46457"},
+    {file = "mypy-0.910-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:b94e4b785e304a04ea0828759172a15add27088520dc7e49ceade7834275bedb"},
+    {file = "mypy-0.910-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:088cd9c7904b4ad80bec811053272986611b84221835e079be5bcad029e79dd9"},
+    {file = "mypy-0.910-cp35-cp35m-win_amd64.whl", hash = "sha256:adaeee09bfde366d2c13fe6093a7df5df83c9a2ba98638c7d76b010694db760e"},
+    {file = "mypy-0.910-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:ecd2c3fe726758037234c93df7e98deb257fd15c24c9180dacf1ef829da5f921"},
+    {file = "mypy-0.910-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:d9dd839eb0dc1bbe866a288ba3c1afc33a202015d2ad83b31e875b5905a079b6"},
+    {file = "mypy-0.910-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:3e382b29f8e0ccf19a2df2b29a167591245df90c0b5a2542249873b5c1d78212"},
+    {file = "mypy-0.910-cp36-cp36m-win_amd64.whl", hash = "sha256:53fd2eb27a8ee2892614370896956af2ff61254c275aaee4c230ae771cadd885"},
+    {file = "mypy-0.910-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b6fb13123aeef4a3abbcfd7e71773ff3ff1526a7d3dc538f3929a49b42be03f0"},
+    {file = "mypy-0.910-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:e4dab234478e3bd3ce83bac4193b2ecd9cf94e720ddd95ce69840273bf44f6de"},
+    {file = "mypy-0.910-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:7df1ead20c81371ccd6091fa3e2878559b5c4d4caadaf1a484cf88d93ca06703"},
+    {file = "mypy-0.910-cp37-cp37m-win_amd64.whl", hash = "sha256:0aadfb2d3935988ec3815952e44058a3100499f5be5b28c34ac9d79f002a4a9a"},
+    {file = "mypy-0.910-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ec4e0cd079db280b6bdabdc807047ff3e199f334050db5cbb91ba3e959a67504"},
+    {file = "mypy-0.910-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:119bed3832d961f3a880787bf621634ba042cb8dc850a7429f643508eeac97b9"},
+    {file = "mypy-0.910-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:866c41f28cee548475f146aa4d39a51cf3b6a84246969f3759cb3e9c742fc072"},
+    {file = "mypy-0.910-cp38-cp38-win_amd64.whl", hash = "sha256:ceb6e0a6e27fb364fb3853389607cf7eb3a126ad335790fa1e14ed02fba50811"},
+    {file = "mypy-0.910-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:1a85e280d4d217150ce8cb1a6dddffd14e753a4e0c3cf90baabb32cefa41b59e"},
+    {file = "mypy-0.910-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:42c266ced41b65ed40a282c575705325fa7991af370036d3f134518336636f5b"},
+    {file = "mypy-0.910-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:3c4b8ca36877fc75339253721f69603a9c7fdb5d4d5a95a1a1b899d8b86a4de2"},
+    {file = "mypy-0.910-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:c0df2d30ed496a08de5daed2a9ea807d07c21ae0ab23acf541ab88c24b26ab97"},
+    {file = "mypy-0.910-cp39-cp39-win_amd64.whl", hash = "sha256:c6c2602dffb74867498f86e6129fd52a2770c48b7cd3ece77ada4fa38f94eba8"},
+    {file = "mypy-0.910-py3-none-any.whl", hash = "sha256:ef565033fa5a958e62796867b1df10c40263ea9ded87164d67572834e57a174d"},
+    {file = "mypy-0.910.tar.gz", hash = "sha256:704098302473cb31a218f1775a873b376b30b4c18229421e9e9dc8916fd16150"},
+]
+mypy-extensions = [
+    {file = "mypy_extensions-0.4.3-py2.py3-none-any.whl", hash = "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d"},
+    {file = "mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"},
+]
 packaging = [
     {file = "packaging-21.0-py3-none-any.whl", hash = "sha256:c86254f9220d55e31cc94d69bade760f0847da8000def4dfe1c6b872fd14ff14"},
     {file = "packaging-21.0.tar.gz", hash = "sha256:7dc96269f53a4ccec5c0670940a4281106dd0bb343f47b7471f779df49c2fbe7"},
@@ -1222,6 +1315,10 @@ traitlets = [
     {file = "traitlets-5.1.0-py3-none-any.whl", hash = "sha256:03f172516916220b58c9f19d7f854734136dd9528103d04e9bf139a92c9f54c4"},
     {file = "traitlets-5.1.0.tar.gz", hash = "sha256:bd382d7ea181fbbcce157c133db9a829ce06edffe097bcf3ab945b435452b46d"},
 ]
+typing-extensions = [
+    {file = "typing_extensions-4.0.1-py3-none-any.whl", hash = "sha256:7f001e5ac290a0c0401508864c7ec868be4e701886d5b573a9528ed3973d9d3b"},
+    {file = "typing_extensions-4.0.1.tar.gz", hash = "sha256:4ca091dea149f945ec56afb48dae714f21e8692ef22a395223bcd328961b6a0e"},
+]
 urllib3 = [
     {file = "urllib3-1.26.7-py2.py3-none-any.whl", hash = "sha256:c4fdf4019605b6e5423637e01bc9fe4daef873709a7973e195ceba0a62bbc844"},
     {file = "urllib3-1.26.7.tar.gz", hash = "sha256:4987c65554f7a2dbf30c18fd48778ef124af6fab771a377103da0585e2336ece"},
diff --git a/pyproject.toml b/pyproject.toml
index 4870f74..e118155 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -22,10 +22,7 @@ coverage-badge = "^1.0.2"
 pytest-html = "^3.1.1"
 hypothesis = "^6.23.2"
 pylint = "^2.11.1"
-
-[build-system]
-requires = ["poetry-core>=1.0.0"]
-build-backend = "poetry.core.masonry.api"
+mypy = "^0.910"
 
 [tool.pytest.ini_options]
 markers = [
@@ -45,6 +42,7 @@ markers = [
     "lazy: 惰性值",
     "cons_base: 用二元元组定义的list",
     "cons_list: 用类实现的list",
+    "try_monad: Try单子",
     "functor: 函子",
     "lazy_list_base: 惰性列表类实现",
     "lazy_list_lambda: 从lambda实现的惰性列表",
@@ -57,3 +55,7 @@ addopts = [
 docs = "poetry_scripts:docs"
 tests = "poetry_scripts:test_with_badge"
 
+[build-system]
+requires = ["poetry-core>=1.0.0"]
+build-backend = "poetry.core.masonry.api"
+


Test patch:
diff --git a/_site/fppy/property_based_testing_tools.py b/_site/fppy/property_based_testing_tools.py
new file mode 100644
index 0000000..139e316
--- /dev/null
+++ b/_site/fppy/property_based_testing_tools.py
@@ -0,0 +1,31 @@
+from itertools import chain
+from random import randint, choices
+from string import ascii_letters, digits, punctuation, whitespace
+
+all_chars = ascii_letters + digits + punctuation + whitespace
+
+def int_generator(
+        num=200,
+        max_v=65535,
+        min_v=-65535,
+        must_have=[0]
+    ):
+    return chain(
+        (randint(min_v, max_v) for _ in range(num - len(must_have))),
+        must_have
+    )
+    
+def str_generator(
+        num=200,
+        min_length=0,
+        max_length=320,
+        must_have=["", digits, punctuation, whitespace],
+        choice_chars=all_chars
+    ):
+    return chain(
+        (
+            "".join(choices(choice_chars, k=randint(min_length, max_length)))
+            for _ in range(num - len(must_have))
+        ),
+        must_have
+    )
\ No newline at end of file
diff --git a/_site/tests/test_abstract_algebra.py b/_site/tests/test_abstract_algebra.py
new file mode 100644
index 0000000..90777cb
--- /dev/null
+++ b/_site/tests/test_abstract_algebra.py
@@ -0,0 +1,76 @@
+"""抽象代数测试
+"""
+import pytest
+from pathos.threading import ThreadPool
+from fppy.abstract_algebra import (
+    Monoid,
+    SemiGroup
+)
+
+@pytest.fixture(scope="module")
+def SM():
+    """新建测试类
+    """
+    class SumMonoid(Monoid):
+        """求和幺半群
+        """
+        def join(self, x, y):
+            return x + y
+
+        @property
+        def unit(self):
+            return 0
+
+    return SumMonoid()
+
+@pytest.mark.algebra
+def test_sum(SM):
+    """测试求和
+    """
+    m_sum = SM.mreduce
+    assert m_sum([1, 2, 3]) == 6
+    assert m_sum([]) == 0
+    assert SM.join(1, 2) == 3
+    assert SM.unit == 0
+
+@pytest.mark.algebra
+def test_thread_reduce_sum(SM):
+    """测试线程reduce
+    """
+    m_sum = SM.thread_reduce
+    assert m_sum([1, 2, 3]) == 6
+    assert m_sum([]) == 0
+
+@pytest.mark.algebra
+# @pytest.mark.skip(reason="cannot pickle abc")
+def test_process_reduce_sum(SM):
+    """测试进程reduce
+
+    TODO: Pickle失败，需要完善
+    """
+    with pytest.raises(Exception):
+        m_sum = SM.process_reduce
+        assert m_sum([1, 2, 3]) == 6
+        assert m_sum([]) == 0
+
+@pytest.mark.algebra
+#@pytest.mark.skip(reason="cannot pickle abc")
+def test_parrellel_reduce_sum(SM):
+    """测试并行的reduce
+    """
+    m_sum = SM.parrellel_reduce(ThreadPool)
+    assert m_sum([1, 2, 3]) == 6
+    assert m_sum([]) == 0
+
+@pytest.mark.algebra
+def test_semi_group():
+    """测试半群
+    """
+    class AddSemiGroup(SemiGroup):
+        """加法半群
+        """
+        def join(self, x, y):
+            return x + y
+
+    add = AddSemiGroup()
+    assert add.join(1, 2) == 3
diff --git a/_site/tests/test_base.py b/_site/tests/test_base.py
new file mode 100644
index 0000000..b27d773
--- /dev/null
+++ b/_site/tests/test_base.py
@@ -0,0 +1,88 @@
+"""基本函数测试
+"""
+import pytest
+from fppy.base import (
+    F_,
+    compose,
+    and_then,
+    I,
+)
+
+@pytest.mark.base
+@pytest.mark.parametrize(
+    "args, expected",
+    [
+        (1, 1),
+        ("a", "a")
+    ])
+def test_identity(args, expected):
+    """测试Identity
+    """
+    assert I(args) == expected
+    assert I(1) == 1
+    assert I(lambda x: x + 1)(1) == (lambda x: x + 1)(1)
+
+@pytest.mark.Function
+def test_function_attribute():
+    """测试函数属性
+    """
+    @F_
+    def f(x):
+        return x + 1
+    assert f.name == 'f'
+
+    g = F_(lambda x: x * 2)
+    assert g.name == '<lambda>'
+
+@pytest.mark.Function
+def test_apply():
+    """测试函数应用
+    """
+    @F_
+    def f(x):
+        return x + 1
+
+    assert f(1) == 2
+    assert f.apply(1) == 2
+
+@pytest.mark.Function
+def test_function_and_then():
+    """测试and_then函数
+    """
+    f = F_(lambda x: x + 1)
+
+    assert f.and_then(lambda x: x * 2)(2) == 6
+
+@pytest.mark.Function
+def test_function_compose():
+    """测试compose
+    """
+    f = F_(lambda x: x + 1)
+
+    assert f.compose(lambda x: x * 2)(2) == 5
+
+@pytest.mark.Function
+def test_function_map():
+    """测试map
+    """
+    f = F_(lambda x: x + 1)
+
+    assert f.map([1, 2, 3]) == [2, 3, 4]
+    assert f.map([1, 2, 3], True) != [2, 3, 4]
+    assert list(f.map([1, 2, 3], True)) == [2, 3, 4]
+
+@pytest.mark.base
+def test_compose_and_then():
+    """测试compose和and_then
+    """
+    f = lambda x: x + 1
+    g = lambda x: x * 2
+    h = lambda x: x ** 3
+
+    assert compose(f, g, h)(1) == 3
+    assert and_then(f, g, h)(1) == 64
+    assert compose(f, I)(1) == f(1)
+    assert compose(I, f, g)(1) == compose(f, g)(1)
+    assert and_then(f, g, I, h)(1) == \
+        and_then(f, g, h)(1) == \
+        and_then(I, f, g, h)(1)
diff --git a/_site/tests/test_combinator.py b/_site/tests/test_combinator.py
new file mode 100644
index 0000000..862dfae
--- /dev/null
+++ b/_site/tests/test_combinator.py
@@ -0,0 +1,20 @@
+"""测试组合子
+"""
+import pytest
+from fppy.combinator import Y, Z
+
+@pytest.mark.combinator
+def test_Y_combinator():
+    """测试Y组合子
+    """
+    power_of_2 = Y(lambda f: lambda x: 1 if (x == 0) else 2 * f(x - 1))
+    assert power_of_2(3) == 8
+    assert power_of_2(1) == 2
+
+@pytest.mark.combinator
+def test_Z_combinator():
+    """测试Z组合子
+    """
+    power = Z(lambda f: lambda x, n: 1 if (n == 0) else x * f(x, n - 1))
+    assert power(2, 3) == 8
+    assert power(2, 1) == 2
diff --git a/_site/tests/test_cons_base.py b/_site/tests/test_cons_base.py
new file mode 100644
index 0000000..22b6c86
--- /dev/null
+++ b/_site/tests/test_cons_base.py
@@ -0,0 +1,127 @@
+"""测试基于函数实现的列表
+"""
+import pytest
+from hypothesis import strategies as st, given
+from fppy.cons_list_base import *
+from fppy.base import and_then
+
+
+@pytest.fixture(scope="session")
+def ls1():
+    """测试例ls1
+    """
+    return cons(1, cons(2, cons(3, ())))
+
+
+@pytest.fixture(scope="session")
+def ls2():
+    """测试例ls2
+    """
+    return cons(1, cons(2, ()))
+
+
+@pytest.fixture(scope="session")
+def ls3():
+    """测试例ls3
+    """
+    return cons(1, ())
+
+
+@pytest.fixture(scope="session")
+def ls4():
+    """测试例ls4
+    """
+    return ()
+
+@pytest.mark.cons_base
+def test_cons_equal(ls1, ls2, ls3, ls4):
+    assert equal_cons(
+        cons_apply(1, 2, 3),
+        ls1
+    )
+    assert not equal_cons(
+        cons_apply(1, 2, 3),
+        ls2
+    )
+    assert not equal_cons(
+        cons_apply(1, 2, 3),
+        ls4
+    )
+    assert equal_cons(
+        cons_apply(1),
+        ls3
+    )
+    assert not equal_cons(
+        ls2,
+        cons_apply(1, 2, 3)
+    )
+
+
+@pytest.mark.cons_base
+def test_cons_apply(ls1, ls2, ls3, ls4):
+    assert print_cons(cons_apply(1, 2, 3)) == print_cons(ls1)
+    assert print_cons(cons_apply(1, 2)) == print_cons(ls2)
+    assert print_cons(cons_apply(1)) == print_cons(ls3)
+    assert print_cons(cons_apply()) == print_cons(ls4)
+    assert print_cons(cons_apply(1, 2, 3, 4)) ==\
+        ", ".join(list("1234")) + ", nil"
+
+
+@pytest.mark.cons_base
+def test_cons(ls1):
+    assert head(ls1) == 1
+    assert and_then(tail, tail, tail)(ls1) == ()
+    assert and_then(tail, head)(ls1) == 2
+
+
+@pytest.mark.cons_base
+def test_fold_left_cons_curry(ls1, ls2, ls3, ls4):
+    sum_cons = fold_left_cons_curry(lambda x, y: x + y)(0)
+    assert sum_cons(ls4) == 0
+    assert sum_cons(ls3) == 1
+    assert sum_cons(ls2) == 3
+    assert sum_cons(ls1) == 6
+
+
+@pytest.mark.cons_base
+def test_map_cons_base(ls1, ls2, ls3, ls4):
+    add1 = map_cons_curry(lambda x: x + 1)
+    assert print_cons(add1(ls1)) == "2, 3, 4, nil"
+    assert print_cons(add1(ls2)) == "2, 3, nil"
+    assert print_cons(add1(ls3)) == "2, nil"
+    assert print_cons(add1(ls4)) == "nil"
+
+
+@pytest.mark.cons_base
+def test_filter_cons_base(ls1, ls2, ls3, ls4):
+    filter_even = filter_cons_curry(lambda x: x % 2 == 1)
+    assert print_cons(filter_even(ls1)) == "1, 3, nil"
+    assert print_cons(filter_even(ls2)) == "1, nil"
+    assert print_cons(filter_even(ls3)) == "1, nil"
+    assert print_cons(filter_even(ls4)) == "nil"
+
+
+@pytest.mark.cons_base
+def test_collection_cons_base(ls1):
+    assert and_then(
+        map_cons_curry(lambda x: x + 1),
+        filter_cons_curry(lambda x: x % 2 == 0),
+        fold_left_cons_curry(lambda x, y: x + y)(0)
+        )(ls1) == 6
+
+@pytest.mark.cons_base
+@given(
+    xs1 = st.lists(st.integers()),
+    xs2 = st.lists(st.text()),
+)
+def test_cons_print(xs1, xs2):
+    assert print_cons(cons_apply(*xs1)) ==\
+        ', '.join(str(i) for i in xs1) + ', nil' if xs1 != [] else "nil"
+    assert print_cons(cons_apply(*xs2)) ==\
+        ', '.join(str(i) for i in xs2) + ', nil' if xs2 != [] else "nil"
+    assert print_cons(cons_apply(*[])) ==\
+        'nil'
+    assert print_cons_with_brackets(cons_apply(*[])) ==\
+        "()"
+    assert print_cons_with_brackets(cons_apply(1, 2, 3)) ==\
+        "(1, (2, (3, ())))"
diff --git a/_site/tests/test_cons_list.py b/_site/tests/test_cons_list.py
new file mode 100644
index 0000000..8c2b403
--- /dev/null
+++ b/_site/tests/test_cons_list.py
@@ -0,0 +1,55 @@
+"""基于类实现的列表测试
+"""
+import pytest
+from hypothesis import strategies as st, given
+from fppy.cons_list import Empty, Cons
+
+
+@pytest.fixture(scope="session")
+def cons_list_1():
+    """测试用例
+    """
+    return Cons.maker(1, 2, 3, 4)
+
+@pytest.mark.cons_list
+def test_cons_maker():
+    """测试列表创建器
+    """
+    assert Cons.maker(1, 2, 3) == Cons(1, Cons(2, Cons(3, Empty())))
+    assert Cons.maker() == Empty()
+
+@pytest.mark.cons_list
+@given(cons_list_given=st.lists(st.integers()))
+def test_cons_map(cons_list_given):
+    """测试列表map
+    """
+    assert Cons\
+        .maker(*cons_list_given)\
+        .map(lambda x: x + 1) == Cons.maker(
+           *[i + 1 for i in cons_list_given]
+        )
+
+@pytest.mark.cons_list
+@given(cons_list_given=st.lists(st.integers()))
+def test_cons_filter(cons_list_given):
+    """测试列表filter
+    """
+    assert Cons\
+        .maker(*cons_list_given)\
+        .filter(lambda x: x % 2 == 0) == Cons.maker(
+           *[i for i in cons_list_given if i % 2 == 0]
+        )
+
+@pytest.mark.cons_list
+@given(cons_list_given=st.lists(st.integers()))
+def test_cons_fold_left(cons_list_given):
+    """测试列表fold_left
+    """
+    assert Cons\
+        .maker(*cons_list_given)\
+        .fold_left(lambda x, y: x + y, 0) == sum(cons_list_given)
+
+    assert Cons\
+        .maker(*cons_list_given)\
+        .fold_left(lambda x, y: x + str(y), "") == \
+        "".join([str(i) for i in cons_list_given])
diff --git a/_site/tests/test_const.py b/_site/tests/test_const.py
new file mode 100644
index 0000000..dff3a8e
--- /dev/null
+++ b/_site/tests/test_const.py
@@ -0,0 +1,17 @@
+"""测试常量
+"""
+import pytest
+from fppy import const
+from fppy.errors import ConstError
+
+
+@pytest.mark.const
+def test_const():
+    """测试常量
+    """
+    const.a = 1
+    assert const.a == 1
+
+    with pytest.raises(ConstError):
+        const.b = 1
+        const.b = 2
diff --git a/_site/tests/test_either.py b/_site/tests/test_either.py
new file mode 100644
index 0000000..ed0a4bd
--- /dev/null
+++ b/_site/tests/test_either.py
@@ -0,0 +1,81 @@
+"""测试
+"""
+import pytest
+from fppy.either import Either, Left, Right
+from fppy.option import Just
+
+@pytest.mark.either
+def test_either_cond():
+    assert Either.cond(True, 1, "2") == Right(1)
+    assert Either.cond(False, 1, "2") == Left("2")
+
+
+@pytest.mark.either
+def test_either_unapply():
+    assert Right.unapply(Right(1)) == Just(1)
+    with pytest.raises(TypeError):
+        Right.unapply(Left(1))
+    with pytest.raises(TypeError):
+        Left.unapply(Right(1)) # require Left
+    assert Left.unapply(Left(1)) == Just(1)
+
+
+@pytest.mark.either
+def test_either_exists():
+    assert Right(1).exists(lambda x: x > 0)
+    assert not Right(1).exists(lambda x: x < 0)
+    assert not Left(1).exists(lambda _: True)
+
+
+@pytest.mark.either
+def test_either_contains():
+    assert Right(1).contains(2) is False
+    assert Left(1).contains(2) is False
+    assert Right(1).contains(1) is True
+    assert Left(1).contains(1) is True
+
+@pytest.mark.either
+def test_either_filter_or_else():
+    assert Right(1).filter_or_else(lambda x: x > 0, 3) == Right(1)
+    assert Right(1).filter_or_else(lambda x: x < 0, 3) == Left(3)
+    assert Left(1).filter_or_else(lambda x: x > 0, 3) == Left(1)
+
+
+@pytest.mark.either
+def test_either_flat_map():
+    assert Right(1).flat_map(lambda x: Right(x + 1)) == Right(2)
+    assert Right(1).flat_map(lambda x: Left(x + 1)) == Left(2)
+    assert Left(1).flat_map(lambda x: Right(x + 1)) == Left(1)
+    with pytest.raises(TypeError):
+        Right(1).flat_map(lambda x: x + 1)
+
+@pytest.mark.either
+def test_either_map():
+    assert Right(1).map(str) == Right("1")
+    assert Left(1).map(lambda x: x + 1) == Left(1)
+
+@pytest.mark.either
+def test_either_check_right():
+    assert Left(1).is_right is False
+    assert Right(1).is_left is False
+    assert Left(1).is_left is True
+    assert Right(1).is_right is True
+
+@pytest.mark.either
+def test_either_for_each():
+    assert Right(1).for_each(print) == Right(1)
+    assert Left(1).for_each(print) == Left(1)
+
+
+@pytest.mark.either
+def test_either_for_all():
+    assert Right(1).for_all(lambda x: x > 0)
+    assert not Right(1).for_all(lambda x: x < 0)
+    assert Left(1).for_all(lambda x: x < 0)
+
+@pytest.mark.either
+def test_either_get_or_else():
+    assert Right(1).get_or_else(2) == 1
+    assert Left(1).get_or_else(2) == 2
+    assert Right(1).or_else(Left(1)) == Right(1)
+    assert Left(1).or_else(Left(2)) == Left(2)
diff --git a/_site/tests/test_functor.py b/_site/tests/test_functor.py
new file mode 100644
index 0000000..7bcd84a
--- /dev/null
+++ b/_site/tests/test_functor.py
@@ -0,0 +1,14 @@
+"""测试函子
+"""
+import pytest
+from hypothesis import strategies as st, given
+from fppy.functor import BoxFunction, BoxValue
+
+
+@pytest.mark.functor
+@given(x=st.integers())
+def test_box(x):
+    assert BoxValue.of(1).map(lambda x: x + 1) == BoxValue(2)
+    assert BoxFunction(lambda x: x + 1).fmap(1) == BoxValue(2)
+    assert BoxFunction(lambda x: x + 1).fmap(x) == BoxValue(x + 1)
+    assert BoxValue.of(x).map(lambda x: x + 1) == BoxValue(x + 1)
diff --git a/_site/tests/test_lazy.py b/_site/tests/test_lazy.py
new file mode 100644
index 0000000..73ea875
--- /dev/null
+++ b/_site/tests/test_lazy.py
@@ -0,0 +1,53 @@
+from fppy.lazy_evaluate import lazy_val, lazy_property
+from fppy.errors import NotVoidFunctionError
+from dataclasses import dataclass
+import pytest
+
+@pytest.mark.lazy
+def test_lazy_property(capfd):
+
+    @dataclass
+    class Circle:
+        x: float
+        y: float
+        r: float
+
+        @lazy_property
+        def area(self):
+            print("area compute")
+            return self.r ** 2 * 3.14
+
+    cir = Circle(0, 0, 1)
+    cir.area
+    stout1, _ = capfd.readouterr()
+    assert stout1 == "area compute\n"
+    cir.area
+    stout2, _ = capfd.readouterr()
+    assert stout2 == ""
+
+@pytest.mark.lazy
+def test_lazy_val(capfd):
+    def f():
+        print("f compute")
+        return 1
+
+    lazy_val.f = f
+    stout1, _ = capfd.readouterr()
+    assert stout1 == ""
+    lazy_val.f
+    stout2, _ = capfd.readouterr()
+    assert stout2 == "f compute\n"
+    lazy_val.f
+    stout3, _ = capfd.readouterr()
+    assert stout3 == ""
+
+@pytest.mark.lazy
+def test_lazy_val_not_valid_function():
+    with pytest.raises(NotVoidFunctionError):
+        lazy_val.g = 1
+    
+    def g(x):
+        return x + 1
+    with pytest.raises(NotVoidFunctionError):
+        lazy_val.g = g
+    
diff --git a/_site/tests/test_lazy_list.py b/_site/tests/test_lazy_list.py
new file mode 100644
index 0000000..ab6d48d
--- /dev/null
+++ b/_site/tests/test_lazy_list.py
@@ -0,0 +1,130 @@
+from typing import TypeVar
+from fppy.lazy_list import LazyList, Nil
+from hypothesis import strategies as st, given
+import pytest
+
+@pytest.mark.LazyList
+def test_lazy_list_collect():
+    assert LazyList([1, 2, 3, 4]).collect() == [1, 2, 3, 4]
+    
+@pytest.mark.LazyList
+def test_lazy_list_map_collect():
+    assert (
+        LazyList([1, 2, 3, 4])
+        .map(lambda x: x + 1)
+        .collect()
+    ) == [2, 3, 4, 5]
+    
+@pytest.mark.LazyList
+def test_lazy_list_take():
+    assert (
+        LazyList([1, 2, 3, 4])
+        .take(3)
+        .collect()
+    ) == [1, 2, 3]
+
+    (
+        LazyList([1, 2, 3, 4])
+        .takewhile(lambda x: x < 4)
+        .collect()
+    ) == [1, 2, 3]
+
+@pytest.mark.LazyList
+def test_lazy_list_find():
+    assert (
+        LazyList([1, 2, 3, 4])
+        .find(lambda x: x > 2)
+    ) == 3
+
+    assert (
+        LazyList([1, 2, 3, 4])
+        .find(lambda x: x > 10)
+    ) == Nil()
+
+@pytest.mark.LazyList
+def test_lazy_list_reduce():
+    assert (
+        LazyList([1, 2, 3, 4])
+        .reduce(lambda x, y: x + y)
+    ) == 10
+    assert (
+        LazyList([1, 2, 3, 4])
+        .foldleft(lambda x, y: x + y, 1)
+    ) == 11
+    
+@pytest.mark.LazyList
+def test_lazy_list_len():
+    assert (
+        LazyList([1, 2, 3, 4])
+        .len()
+    ) == 4
+
+@pytest.mark.LazyList
+def test_lazy_list_scan_left():
+    assert (
+        LazyList([1, 2, 3, 4])
+        .scan_left(lambda x, y: x + str(y), "0")
+        .collect()
+    ) == ["0", "01", "012", "0123", "01234"]
+    assert (
+        LazyList([1, 2, 3, 4])
+        .scan_left(lambda x, y: x + y, 0)
+        .collect()
+    ) == [0, 1, 3, 6, 10]
+
+@pytest.mark.LazyList
+def test_lazy_list_filter():
+    assert (
+        LazyList([1, 2, 3, 4])
+        .filter(lambda x: x % 2 == 0)
+        .collect()
+    ) == [2, 4]
+
+@pytest.mark.LazyList
+def test_lazy_list_split_head():
+    hd, tl = LazyList([1, 2, 3, 4]).split_head()
+    assert hd == 1
+    assert tl.collect() == [2, 3, 4]
+
+    hd, tl = LazyList([]).split_head()
+    assert hd == Nil()
+
+@pytest.mark.LazyList
+def test_lazy_list_from():
+    assert LazyList.from_iter(1)(lambda x: x + 1).take(4).collect() == [1, 2, 3, 4]
+
+
+@pytest.mark.LazyList
+@given(ls1=st.lists(st.integers()), ls2=st.lists(st.text()))
+def test_lazy_list_zip(ls1, ls2):
+    assert LazyList([1, 2, 3]).zip_with([2, 3, 4]).collect() == [
+        (1, 2),
+        (2, 3),
+        (3, 4)
+    ]
+    assert LazyList(ls1).zip_with(LazyList(ls2)).collect() == list(
+        zip(ls1, ls2)
+    )
+    with pytest.raises(TypeError):
+        LazyList(ls1).zip_with(1)
+
+@pytest.mark.LazyList
+@given(ls1=st.lists(st.integers()), ls2=st.lists(st.integers()))
+def test_lazy_list_concat(ls1, ls2):
+    assert (LazyList(ls1) + LazyList(ls2)).collect() == ls1 + ls2
+    assert (LazyList(ls1) + ls2).collect() == ls1 + ls2
+    with pytest.raises(TypeError):
+        LazyList(ls1) + 1
+   
+@pytest.mark.LazyList
+def test_lazy_list_for_all():
+    assert LazyList([1, 2, 3]).for_all(lambda x: x > 0) == True
+    assert LazyList([1, 2, 3]).for_all(lambda x: x == 2) == False
+
+@pytest.mark.LazyList
+def test_lazy_list_drop():
+    assert LazyList([1, 2, 3]).drop(2).collect() == [3]
+
+@pytest.mark.LazyList
+def test_lazy_list_last():
+    assert LazyList([1, 2, 3]).last == 3
\ No newline at end of file
diff --git a/_site/tests/test_lazy_list_base.py b/_site/tests/test_lazy_list_base.py
new file mode 100644
index 0000000..7b5b295
--- /dev/null
+++ b/_site/tests/test_lazy_list_base.py
@@ -0,0 +1,36 @@
+"""测试从头实现的惰性列表
+"""
+import pytest
+from hypothesis import strategies as st, given
+from fppy.lazy_list_base import LazyCons, LazyEmpty
+
+
+@pytest.mark.lazy_list_base
+def test_lazy_list_base_maker():
+    assert LazyCons(1, LazyCons(2, LazyCons(3, LazyEmpty()))).collect() == \
+        LazyCons.maker(1, 2, 3).collect()
+
+
+@pytest.mark.lazy_list_base
+def test_lazy_list_base_from_iter():
+    assert LazyCons.from_iter(1)(lambda x: x + 1).take(3).collect() == \
+        LazyCons.maker(1, 2, 3).collect()
+    assert LazyCons.from_iter(1)(lambda x: x + 1)\
+        .filter(lambda x: x % 2 == 0)\
+        .take(3)\
+        .collect() ==\
+        LazyCons.maker(2, 4, 6).collect()
+
+
+@pytest.mark.lazy_list_base
+@given(ll=st.lists(st.integers()))
+def test_lazy_list_base_map(ll):
+    assert LazyCons.maker(*ll).map(lambda x: x + 1).collect() == \
+        LazyCons.maker(*[i + 1 for i in ll]).collect()
+
+
+@pytest.mark.lazy_list_base
+@given(ll=st.lists(st.integers()))
+def test_lazy_list_base_filter(ll):
+    assert LazyCons.maker(*ll).filter(lambda x: x % 2 == 0).collect() == \
+        LazyCons.maker(*[i for i in ll if i % 2 == 0]).collect()
diff --git a/_site/tests/test_lazy_list_lambda.py b/_site/tests/test_lazy_list_lambda.py
new file mode 100644
index 0000000..b030bfb
--- /dev/null
+++ b/_site/tests/test_lazy_list_lambda.py
@@ -0,0 +1,59 @@
+"""测试lazy_list_lambda
+"""
+import pytest
+from hypothesis import strategies as st, given
+from fppy.lazy_list_lambda import *
+from fppy.base import and_then
+
+@pytest.mark.lazy_list_lambda
+def test_lazy_list_lamdba_maker():
+    assert collect_lazy_cons(make_lazy_cons(1, 2, 3)) == (1, (2, (3, ())))
+    assert collect_lazy_cons(make_lazy_cons(1, 2, 3)) == collect_lazy_cons(
+        lazy_cons(
+            lambda: 1,
+            lazy_cons(
+                lambda: 2,
+                lazy_cons(
+                    lambda: 3,
+                    ()
+                )
+            )
+        )
+    )
+
+@pytest.mark.lazy_list_lambda
+@given(xs=st.lists(st.integers()))
+def test_lazy_list_lambda_map(xs):
+    assert and_then(
+        map_lazy_cons(lambda x: x + 1),
+        collect_lazy_cons,
+    )(make_lazy_cons(*xs)) == \
+        collect_lazy_cons(make_lazy_cons(*[x + 1 for x in xs]))
+
+
+@pytest.mark.lazy_list_lambda
+@given(xs=st.lists(st.integers()))
+def test_lazy_list_lambda_filter(xs):
+    assert and_then(
+        filter_lazy_cons(lambda x: x % 2 == 0),
+        collect_lazy_cons,
+    )(make_lazy_cons(*xs)) == collect_lazy_cons(
+        make_lazy_cons(*[x for x in xs if x % 2 == 0]))
+
+
+@pytest.mark.lazy_list_lambda
+def test_lazy_list_lambda_take_and_iterate():
+    assert and_then(
+        take_lazy_cons(5),
+        collect_lazy_cons
+    )(iterate_lazy_cons(1)(lambda x: x + 1)) == collect_lazy_cons(
+        make_lazy_cons(1, 2, 3, 4, 5)
+    )
+
+
+@pytest.mark.lazy_list_lambda
+def test_lazy_list_lambda_fold_left():
+    assert and_then(
+        take_lazy_cons(5),
+        fold_left_lazy_cons(lambda x, y: x + y)(0)
+    )(iterate_lazy_cons(1)(lambda x: x + 1)) == 15
diff --git a/_site/tests/test_option.py b/_site/tests/test_option.py
new file mode 100644
index 0000000..b380979
--- /dev/null
+++ b/_site/tests/test_option.py
@@ -0,0 +1,51 @@
+"""测试Option
+"""
+import pytest
+from fppy.errors import NothingHasNoSuchMethod
+from fppy.option import Just, Nothing
+
+
+@pytest.mark.option
+def test_option_map():
+    assert Just(1).map(lambda x: x + 1) == Just(2)
+
+    with pytest.raises(Exception):
+        None.map(lambda x: x + 1)
+
+@pytest.mark.option
+def test_option_flat_map():
+    assert Just(1).flat_map(lambda x: Just(x + 1)) == Just(2)
+    assert Just(1).flat_map(lambda _: Nothing()) == Nothing()
+
+    with pytest.raises(TypeError):
+        assert Just(1).flat_map(lambda x: x + 1)
+
+
+@pytest.mark.option
+def test_option_get():
+    assert Just(1).get == 1
+
+    with pytest.raises(NothingHasNoSuchMethod):
+        assert Nothing().get
+
+@pytest.mark.option
+def test_option_get_or_else():
+    assert Just(1).get_or_else(2) == 1
+    assert Nothing().get_or_else(2) == 2
+
+@pytest.mark.option
+def test_option_collect():
+    from fppy.partial_function import PartialFunction # pylint: disable=import-outside-toplevel
+
+    f = PartialFunction\
+        .case(lambda x: x > 0)\
+        .then(lambda x: x + 1)
+
+    assert Just(1).collect(f) == Just(2)
+    assert Nothing().collect(f) == Nothing()
+
+@pytest.mark.option
+def test_option_filter():
+    assert Just(1).filter(lambda x: x > 0) == Just(1)
+    assert Just(1).filter(lambda x: x < -1) == Nothing()
+    assert Just(1).filter(lambda x: x < 0).filter(lambda x: x > 0) == Nothing()
diff --git a/_site/tests/test_partial_function.py b/_site/tests/test_partial_function.py
new file mode 100644
index 0000000..ca0f55e
--- /dev/null
+++ b/_site/tests/test_partial_function.py
@@ -0,0 +1,72 @@
+from fppy.partial_function import PartialFunction
+from fppy.errors import NoOtherCaseError
+import pytest
+
+
+@pytest.mark.PartialFunction
+def test_partial_function_init():
+    (
+        PartialFunction
+        .case(lambda x: x > 1)
+        .then(lambda x: x + 1)
+    )
+    
+@pytest.mark.PartialFunction
+def test_partial_function_apply():
+    f = (
+        PartialFunction
+        .case(lambda x: x > 1)
+        .then(lambda x: x + 1)
+    )
+    assert f.apply(2) == 3
+    assert f(2) == 3
+
+    with pytest.raises(NoOtherCaseError):
+        f.apply(1)
+
+@pytest.mark.PartialFunction
+def test_partial_function_or_else():
+    """测试偏函数的or_else功能
+    """
+    f = (
+        PartialFunction
+        .case(lambda x: x > 1)
+        .then(lambda x: x + 1)
+    )
+    g = (
+        PartialFunction
+        .case(lambda x: x > -1)
+        .then(lambda x: x + 2)
+    )
+    assert f.or_else(g).apply(1) == 3
+    assert f.or_else(g).apply(2) == 3
+    with pytest.raises(NoOtherCaseError):
+        f.or_else(g).apply(-1)
+
+@pytest.mark.PartialFunction
+def test_partial_function_is_defined_at():
+    """测试偏函数的is_defined_at功能
+    """
+    f = (
+        PartialFunction
+        .case(lambda x: x > 1)
+        .then(lambda x: x + 1)
+        .case(lambda x: x > 0)
+        .then(lambda x: x * 2)
+    )
+    assert f.is_defined_at(1) == True
+    assert f.is_defined_at(-1) == False
+
+
+@pytest.mark.PartialFunction
+def test_partial_function_lift():
+    from fppy.option import Nothing, Just
+    f = (
+        PartialFunction
+        .case(lambda x: x > 1)
+        .then(lambda x: x + 1)
+        .case(lambda x: x > 0)
+        .then(lambda x: x * 2)
+    )
+    assert f.lift(1) == Just(2)
+    assert f.lift(0) == Nothing
\ No newline at end of file
diff --git a/_site/tests/test_property_testing.py b/_site/tests/test_property_testing.py
new file mode 100644
index 0000000..e934e10
--- /dev/null
+++ b/_site/tests/test_property_testing.py
@@ -0,0 +1,29 @@
+from fppy.property_based_testing_tools import (
+    int_generator,
+    str_generator
+)
+import pytest
+
+@pytest.mark.property
+def test_int_generator():
+    def add(x, y): return x + y
+    # add(x, y) == add(y, x)
+    # add(x, -y) == -add(-x, y)
+    # add(x - 1, y) == add(x, y - 1) == add(x, y) - 1
+
+    for i, j in zip(int_generator(), int_generator()):
+        assert add(i, j) == add(j, i)
+        assert add(i, -j) == -add(-i, j)
+        assert add(i - 1, j) == add(i, j - 1) == add(i, j) - 1
+
+@pytest.mark.property
+def test_str_generator():
+    def join_and_reverse(x, y): return (x + y)[::-1]
+    # join_and_reverse(x, y) == join_and_reverse(y[::-1], x[::-1])[::-1]
+    # join_and_reverse(x, y)[0] == y[-1] if len(y) > 0
+    # join_and_reverse(x, y)[-1] == x[0] if len(x) > 0
+
+    for x, y in zip(str_generator(), str_generator()):
+        assert join_and_reverse(x, y) == join_and_reverse(y[::-1], x[::-1])[::-1]
+        assert join_and_reverse(x, y)[0] == y[-1] if len(y) > 0 else True
+        assert join_and_reverse(x, y)[-1] == x[0] if len(x) > 0 else True
\ No newline at end of file
diff --git a/tests/test_try.py b/tests/test_try.py
new file mode 100644
index 0000000..a0d1778
--- /dev/null
+++ b/tests/test_try.py
@@ -0,0 +1,85 @@
+'''
+Author: huangbaochen<huangbaochenwo@live.com>
+Date: 2021-12-11 20:04:19
+LastEditTime: 2021-12-11 21:30:17
+LastEditors: huangbaochen<huangbaochenwo@live.com>
+Description: 测试Try单子
+No MERCY
+'''
+import pytest
+from fppy.try_monad import Try, Success, Fail
+from fppy.option import Just, Nothing
+
+@pytest.mark.try_monad
+def test_try_apply():
+    assert Try.apply(1) == Success(1)
+    assert Try(1) == Success(1)
+
+@pytest.mark.try_monad
+def test_try_unapply():
+    assert Success.unapply(Success(1)) == Just(1)
+    assert Fail.unapply(Fail(TypeError(), 1)) == Nothing()
+
+    with pytest.raises(TypeError):
+        Fail.unapply(1)
+
+    with pytest.raises(TypeError):
+        Fail.unapply(Success(1))
+
+    with pytest.raises(TypeError):
+        Success.unapply(1)
+
+    with pytest.raises(TypeError):
+        Success.unapply(Fail(Exception(), 1))
+
+def test_try_monad_map():
+    assert Success(1).map(lambda x: x + 1) == Success(2)
+    assert Success(1).map(lambda x: x / 0) ==\
+        Fail(ZeroDivisionError('division by zero'), 1)
+    assert Fail(ZeroDivisionError('division by zero'),  1)\
+        .map(lambda x: x + 1) ==\
+        Fail(ZeroDivisionError('division by zero'),  1)
+
+@pytest.mark.try_monad
+def test_try_monad_flat_map():
+    assert Success(1).flat_map(lambda x: Success(2)) == Success(2)
+    assert Fail(ZeroDivisionError('division by zero'), 1)\
+        .map(lambda x: Success(1)) ==\
+        Fail(ZeroDivisionError('division by zero'), 1)
+
+    with pytest.raises(TypeError):
+        Success(1).flat_map(lambda x: x + 1)
+
+@pytest.mark.try_monad
+def test_try_monad_eq():
+    assert Fail(ZeroDivisionError('division by zero'), 1) ==\
+        Fail(ZeroDivisionError('division by zero'), 1)
+
+    assert Fail(ZeroDivisionError('division by'), 1) !=\
+        Fail(ZeroDivisionError('division by zero'), 1)
+
+    assert Fail(ZeroDivisionError('division by zero'), 0) !=\
+        Fail(ZeroDivisionError('division by zero'), 1)
+
+@pytest.mark.try_monad
+def test_try_monad_get():
+    assert Fail(ZeroDivisionError('division by zero'), 1)\
+        .get().args ==\
+        ZeroDivisionError('division by zero').args
+
+    assert Success(1).get() == 1
+
+    # pylint: disable=no-member
+    assert Try("s").get() == "s"
+
+@pytest.mark.try_monad
+def test_try_monad_get_or_else():
+    assert Fail(ZeroDivisionError('division by zero'),  1)\
+        .get_or_else(2) == 2
+
+    assert Success(1).get_or_else(2) == 1
+
+@pytest.mark.try_monad
+def test_try_monad_get_error_input():
+    assert Fail(ZeroDivisionError('division by zero'),  1)\
+        .get_error_input() == 1