我想对某个函数的参数签定合同,以强制参数对象必须具有特定的属性。我了解python不是严格类型的语言,但是有时具有合同和接口非常有用。Python现在具有类型提示,这很好,因此我们可以这样做:
def myfunc(myparam: MyType) -> SomeType:
myparam.myprop # this should exist
Run Code Online (Sandbox Code Playgroud)
但是,如何说MyType必须具有特定的对象属性(myprop),而又不能在运行时插入断言和引发异常?我可以用abc元类定义抽象类,该类可以用作接口。
from abc import ABC, abstractmethod
class MyInterface(ABC):
@property
@abstractmethod
def myprop(self) -> int: pass
Run Code Online (Sandbox Code Playgroud)
现在在代码中的某个地方,我可以将MyType定义为:
class MyType(MyInterface):
myprop = 8
Run Code Online (Sandbox Code Playgroud)
它正在工作,但是 myprop是类属性,而不是对象属性(属性)。我当然可以这样做:
class MyType(MyInterface):
myprop = 0
def __init__(self):
self.myprop = 8
Run Code Online (Sandbox Code Playgroud)
很好,但是我必须定义一个(不必要的)类(“静态”)属性,并使用对象属性有效地将其隐藏。不太干净。而且现在我有一个myprop的默认值,这不是我想要的。但是,如果我这样做:
class MyType(MyInterface):
myprop = None # wrong type here
def __init__(self):
self.myprop = 8
Run Code Online (Sandbox Code Playgroud)
这是错误的,因为myprop 必须为 int且不能为None,这是由linter 正确捕获的。应该有一个没有类属性的对象属性。
目标是像mypy这样的静态检查器可以捕获实现错误,其中类不遵循已定义的接口或协定,而该协定或协定要求参数实例必须具有某些属性。
什么是pythonic(或不是pythonic)的实现方式?
我们假设我有一个类型
data F a = A a | B
Run Code Online (Sandbox Code Playgroud)
我实现这样的功能f :: F a -> F a -> F a:
f (A x) B = A x
f B (A _) = B
f (A _) (A x) = A x
Run Code Online (Sandbox Code Playgroud)
但是没有这样的事情在f B B逻辑上是不可能的,所以我想要:
f B B = GENERATE_HASKELL_COMPILE_ERROR
Run Code Online (Sandbox Code Playgroud)
这当然不起作用.省略定义或使用f B B = undefined不是解决方案,因为它会生成运行时异常.我想得到编译时类型错误.
编译器拥有所有信息,它应该能够推断出我犯了一个逻辑错误.如果说我声明let z = f (f B (A 1)) B应该是一个立即编译时错误,而不是一些运行时异常可以隐藏在我的代码中多年.
我找到了一些关于合同的信息,但我不知道如何在这里使用它们,我很好奇是否有任何标准方法在Haskell中执行此操作.
编辑:事实证明,我试图做的是被称为依赖类型,但在Haskell中还没有完全支持.然而,使用索引类型和几个扩展可能会生成类型错误.David Young的解决方案似乎更简单,而Jon Purdy创造性地使用类型操作符.我接受第一个,但我喜欢两个.