我必须承认我只掌握Python的基本知识,目前正在学习Haskell。
我想知道类型类的概念是否存在/在Python或Clojure(或其他动态强类型语言)中是否有意义?
换句话说,如果我有一个函数名f
,那么根据提供给f
的运行时参数,将调用一个不同的函数实现(如类型的==
函数属于Haskell中的Eq
类型类。这样的概念是否存在于动态语言中,例如Clojure / Python?
答案 0 :(得分:4)
多个分派(example in Julia language)与类型类似。多分派提供编译时多态(就像类型类一样),而典型动态语言(即Python)中的对象接口通常仅限于运行时多态。多个调度提供了比动态面向对象语言中常见接口更好的性能,因此它在动态语言中非常有用。
Python有一些multiple dispatch implementations,但我不确定它们是否提供编译时多态性。
答案 1 :(得分:4)
Multimethods似乎在Clojure中做到了。例如,让我们定义一个plus
函数来添加数字,但是连接其他任何东西的字符串表示。
(defmulti plus (fn [& xs] (every? number? xs)))
(defmethod plus true [& xs] (apply + xs))
(defmethod plus false [& xs] (apply str xs))
(plus 1 8) ;9
(plus 1 \8) ;"18"
多方法是函数((ifn? plus)
是true
),所以你想要的是第一类:
(let [z (partial plus 5)] (z \3)) ;"53"
答案 2 :(得分:4)
你可以使用clojure中的多方法或协议,或者使用python中的简单成员函数(类方法)来接近这一点。但是,haskell中存在的每个缺失都有一个重要特征:返回型多态。
编译器知道你"期望"一个返回的函数,可以相应地调度到不同的实现。这意味着,在相同参数上调用的相同函数可以完全不同,具体取决于对其返回值的处理方式。例如:
Prelude> read "[5,6,7]" :: [Int]
[5,6,7]
Prelude> read "[5,6,7]" :: [Double]
[5.0,6.0,7.0]
同样,你甚至可以拥有多态常量,它们对每个类型类实例都有不同的值:
Prelude Data.Word> (minBound, minBound, minBound) :: (Int, Bool, Word8)
(-9223372036854775808,False,0)
你不能用动态语言真正做到这一点,因为没有类型推断。您可以通过传递代表"我想要的结果类型的对象来假装它,并将它们用作您的调度程序,但它并不完全相同。
答案 3 :(得分:2)
您无法在Python中定义多个具有相同名称的函数(在同一范围内)。如果这样做,第二个定义将覆盖第一个并且是唯一一个被调用的(至少当两者都在同一个范围内时 - 显然你可以在不同的类中拥有共享名称的类方法)。参数列表也是类型无知的,因此即使你可以定义两次函数,解释器也只能根据参数的数量来区分它们,而不是类型。你需要做的是编写一个可以处理几个不同参数列表的函数,然后在必要时检查它们在该函数中的类型。
最简单的方法是使用默认参数和关键字参数。
假设你有这样的功能:
def BakePie(crust, filling="apple", oventemp=(375,F), universe=1):
...
您可以使用位置参数调用此函数,如下所示:
BakePie("graham cracker")
BakePie("buttery", "cherry")
BakePie("fluffy", "lemon meringue", (400,F))
BakePie("fluffy", "key lime", (350,F), 7)
这些都会奏效,但您可能并不总是希望更改每个默认值。如果您想在不同的宇宙中烘烤标准的苹果派怎么办?好吧,你可以使用关键字参数调用它:
BakePie("buttery", universe=42)
在这种情况下,将使用fill和oventemp的默认参数,并且只会更改Universe(和crust,因为没有默认值而必须始终给出)的参数。这里的一条规则是,在调用函数时,任何关键字参数都必须位于任何位置参数的右侧。关键字参数的顺序无关紧要,例如这也有效:
BakePie("fluffy", oventemp=(200, C), filling="pecan")
但这不会:
BakePie(filling="boysenberry", "crumb")
现在,如果函数的行为完全不同,具体取决于传递的参数,该怎么办?例如,你有一个乘法函数,它接受两个整数,或者它取一个整数和一个整数列表,或者两个整数列表,并将它们相乘。这种情况是调用者只想使用关键字参数的情况。您可以像这样设置函数定义:
def GenericMultiply(int1=False, int2=False, ilist1=False, ilist2=False):
# check which parameters have values, then do stuff
(或使用None代替False。)
当你需要乘以两个整数时,请这样调用:
GenericMultiply(int1=6, int2=7)
注意:您也可以只使用两个位置参数执行上述操作,并通过使用type()函数或使用try:except:blocks等手动检查函数内部的类型。调用仅适用于每个参数的列表或整数的方法。
在Python中还有很多其他方法可以解决这个问题,我只是想描述一个最简单的方法。如果你想了解更多,我推荐官方Python教程的Defining Functions部分和下一部分,更多关于定义函数(这将详细介绍位置和关键字参数,以及* args和** kwargs语法,允许您使用可变长度参数列表定义函数,而无需使用默认值。)
答案 4 :(得分:1)
在某种程度上,此功能由类方法复制。例如,__repr__
方法与Haskell中的Show
类型类大致相同:
$ ghci
>>> x = 1
>>> show x
"1"
>>> x = [1,2,3]
>>> show x
"[1,2,3]"
或在Python中
$ python
>>> x = 1
>>> x.__repr__()
'1'
>>> x = [1,2,3]
>>> x.__repr__()
'[1,2,3]'
显然,在每种情况下都会调用一个不同的函数,具体取决于show
/ __repr__
的类型(在Haskell的情况下)或类(在Python的情况下)被应用于/被召唤。
对于支持它们的语言,更接近的是接口 - 抽象类及其所有方法也是抽象的(它们在Java中称为接口,在C ++中称为虚拟类)。您不会倾向于在动态类型语言中看到它们,因为接口的要点是声明一组实现类必须符合的方法及其相关类型。如果没有静态类型,那么就没有必要声明类型应该是什么。
答案 5 :(得分:0)
是的,Python 中存在类型类。您可以使用 dry-python/classes
为它们建模。
示例:
from classes import AssociatedType, Supports, typeclass
class Greet(AssociatedType):
"""Special type to represent that some instance can `greet`."""
@typeclass(Greet)
def greet(instance) -> str:
"""No implementation needed."""
@greet.instance(str)
def _greet_str(instance: str) -> str:
return 'Hello, {0}!'.format(instance)
def greet_and_print(instance: Supports[Greet]) -> None:
print(greet(instance))
greet_and_print('world')
# Hello, world!
它还具有完整的 mypy
支持。