最近,我一直在一个名为Sparrow的图书馆工作。该库源于需要拥有一系列工具,以使使用声明的方法更容易在Python中开发,主要是在功能的组成中。在这里,我创建了一个功能和装饰器的集合,并且还实现了Haskell启发的Typeclass。
什么是打字机?
Typeclass概念是一种非常强大的工具,该工具由Haskell之类的语言使用,使我们能够创建类型的类型。这是通过类型使用多态性的一种方式。在Haskell中,我们可以定义这样的类型类:
class Eq a where
(==) :: a -> a -> Bool
a == b = not (a /= b)
(/=) :: a -> a -> Bool
a /= b = not (a == b)
在上面的示例中,EQ定义为用于比较两个值的平等性的“类型”。这样做的好处是,例如,我们可以定义函数的参数是类型等式的参数,并且编译器将检查参数的类型是否为类型eq。因此,我们可以执行这样的通用功能:
same :: (Eq a) => a -> a -> Bool
same a b = a == b
此功能适用于具有EQ Typeclass的任何类型。
使用麻雀我们可以在python中相同:
from sparrow.kind import Kind, kind_function
class Eq(Generic[T], Kind):
@kind_function(True)
def eq(self: "Eq[T]", other: "Eq[T]"):
return not self.neq(other)
@kind_function(True)
def neq(self: "Eq[T]", other: "Eq[T]"):
return not self.eq(other)
def same(a: Eq[T], b: Eq[T]) -> bool:
return a.eq(b)
您可以看到,这种类型的定义(我将其称为“类型”)在两种语言中都使用Sparrow非常相似。主要区别在于,在Python中,我们需要使用装饰器来定义多态函数并指定它们是否具有默认实现。
如何使用它?
此库有助于数据建模。这意味着我们可以使用类型和功能对系统进行建模。我们可以利用类型系统的功能使我们的代码更安全,更易于理解。
让我们看看一个例子。
from sparrow.kind import Kind, kind_function
from sparrow.datatype import DataType
class Speaker(Kind):
@kind_function
def speak(speaker: "Speaker"):
pass
class Runner(Kind):
@kind_function(has_default=True)
def start_running(runner: "Runner") -> None:
print("I'm running")
@kind_function(has_default=True)
def stop_running(runner: "Runner") -> None:
print("I'm not running")
class Animal(Speaker, Runner, DataType):
pass
@dataclass
class Dog(Animal):
def speak(self):
print("Woof woof")
@dataclass
class Cat(Animal):
def speak(self):
print("Meow...")
def start_running(self):
print("I'm a cat, I won't run")
def stop_running(self):
print("You can't stop me")
dog = Dog()
cat = Cat()
dog.speak() # Woof woof
cat.speak() # Meow...
dog.start_running() # I'm running
dog.stop_running() # I'm not running
cat.start_running() # I'm a cat, I won't run
cat.stop_running() # You can't stop me
这并不是什么新鲜事物,我们可以使用简单的继承在Python中做同样的事情。但是区别在于,类型类是一种更有声明的方法。它通过编写类型和对其执行操作(类型和类型的总和)来使代码更可读。
错误处理
在Python,Java,C#和[插入您喜欢的OOP语言]等语言中。通常使用try-catch块来处理错误,但这可以增加不必要的复杂性来使用结果值。
def divide(a: int, b: int) -> int:
return a / b
try:
result = divide(10, 2)
except ZeroDivisionError:
do_something()
在这里,我们可以重构代码以使用称为result的函数或其他语言来处理异常。这是功能编程中非常普遍的模式。我们可以这样做:
from sparrow.datatype.result import Result, Success, Failure
def divide(a: int, b: int) -> Result[int, str]:
if b == 0:
return Failure("Division by zero")
return Success(a // b)
divide(10, 0) # Failure("Division by zero")
divide(10, 2) # Success(5)
或更好:
from sparrow.decorator.wrap import result
@result
def divide_v2(a: int, b: int) -> Result[int, Exception]:
return a // b
divide(10, 0) # Failure(ZeroDivisionError)
divide(10, 2) # Success(5)
现在,如果我们要使用结果,我们可以简单地映射值。
result = divide(10, 0) # Failure(ZeroDivisionError)
result.fmap(lambda x: x * 2) # Failure(ZeroDivisionError)
和其他值:
result = divide(10, 2) # Success(5)
result.fmap(lambda x: x * 2) # Success(10)
包容性我们可以组成更多的操作:
result = (
divide(10, 2) # Success(5)
.fmap(lambda x: x * 2) # Success(10)
.fmap(lambda x: x + 1) # Success(11)
.fmap(lambda x: x % 2) # Success(1)
.fmap(lambda x: "Even" if x == 0 else "Odd") # Success("Odd")
).value # "Odd"
结果也是一个双肢体,我们可以使用第一个,第二和bimap来映射第一个,第二或两个可能的值。
result = divide(10, 0) # Failure(ZeroDivisionError)
result.first(lambda x: x * 2) # Failure(ZeroDivisionError)
result.second(lambda x: repr(x)) # Failure("ZeroDivisionError()")
result.bimap(lambda x: x * 2, lambda x: repr(x)) # Failure("ZeroDivisionError()")
在这里,函数和双函数的定义与函数模式无关,在这种情况下,我们正在使用它们来处理错误。
或许
也许是功能编程中的另一种非常常见的模式。它用于处理可选值,并且与结果类型非常相似,但是它返回一个值或一无所有。
from sparrow.datatype.maybe import Maybe, Just, Nothing
def divide(a: int, b: int) -> Maybe[int]:
if b == 0:
return Nothing()
return Just(a // b)
divide(10, 0) # Nothing()
divide(10, 2) # Just(5)
,或者如果我们更喜欢使用装饰器:
from sparrow.decorator.wrap import maybe
@maybe
def divide(a: int, b: int) -> Optional[int]:
return a // b if b != 0 else None
divide(10, 0) # Nothing()
divide(10, 2) # Just(5)
装饰器将可选的返回值映射到可能的类型。
也许是函子,应用和单子。这意味着我们可以使用FMAP,应用和绑定。
result = divide(10, 2) # Just(5)
result.fmap(lambda x: x * 2) # Just(10)
result.apply(Just(lambda x: x * 2)) # Just(10)
result.bind(lambda x: Just(x * 2)) # Just(10)
另外,也许有一种称为pure
的方法,用于创建正义值。
from sparrow.datatype.maybe import Maybe
Maybe.pure(1) # Just(1)
pure
函数可以由实现应用类型的任何类型使用。
其他工具
麻雀还提供其他功能工具。这里有一些示例:
咖喱
咖喱是一种将具有多个参数的函数转换为具有单个参数的函数的技术。当我们要使用部分应用程序时,这非常有用。例如:
from sparrow.decorator.currify import currify
@currify
def add(a: int, b: int) -> int:
return a + b
add_one = add(1) # A function with one argument
add_one(2) # 3
作品
组成是一种结合两个或多个功能的技术。
from sparrow.decorator.compose import compose
@compose(lambda x: x * 2, lambda x: x + 1)
def add_one_and_double(x: int) -> int:
return x
add_one_and_double(1) # 4
add_one_and_double(2) # 6
这个想法是使功能管道更容易地模块化代码。
反射
反射旨在调试代码或产生副作用以完成执行。
from sparrow.decorator.reflex import reflex
@reflex(lambda x: print(f"The value is {x}"))
def add_one(x: int) -> int:
return x + 1
add_one(1)
# stdout: The value is 2
如果我们想调试,我们可以使用调试,信息,警告,错误和关键装饰器。
from sparrow.decorator.reflex import debug, info, warning, error, critical
@critical
@error
@warning
@info
@debug
def add_one(x: int) -> int:
return x + 1
add_one(1)
# stdout:
# DEBUG:root:2
# INFO:root:2
# WARNING:root:2
# ERROR:root:2
# CRITICAL:root:2
我们也可以格式化消息。
from sparrow.decorator.reflex import info
@info("The value is {0}")
def add_one(x: int) -> int:
return x + 1
add_one(1)
# stdout: INFO:root:The value is 2
之前和之后
是装饰器之前和之后执行装饰功能之前或之后执行功能的装饰。
from sparrow.decorator.before_after import before, after
@after(str)
@before(int)
def add_one(x: int) -> int:
return x + 1
add_one(1.1) # "2"
什么时候
装饰者
当条件为真时,何时是执行功能的装饰器。
from sparrow.decorator.when import when
@when(lambda x: x > 0)
def add_one(x: int) -> int:
return x + 1
add_one(1) # 2
add_one(-1) # -1
功能
当条件为真时,何时也是执行函数的函数。
from sparrow.function.when import when
def divide(a: int, b: int) -> int:
return when(
condition=b != 0,
then=lambda x: x / b,
otherwise=lambda x: 0,
value=a
)
divide(10, 2) # 5
divide(10, 0) # 0
地图何时
映射何时是在条件为真并映射结果时执行函数的函数。
from sparrow.function.when import map_when
my_list = [1, 2, 3, 4, 5]
map_when(
condition=lambda x: x % 2 == 0,
then=lambda x: x * 2,
value=my_list
) # [1, 4, 3, 8, 5]
结论
所有工具必须谨慎使用,最好与严格的类型检查一起使用。该库的目的是帮助您编写更多声明代码,并使数据建模和功能组成更加灵活。
这个图书馆仍在开发中,我愿意接受建议和贡献。该存储库可在GitHub上找到。该文档尚未可用,但我正在研究。