昔作ったminiadtというパッケージを更新してみた
miniadt
miniadt と言う名前の通り、完全に必要な機能が揃っているというわけではないですが。昔作ったパッケージを更新してみました。 機能は限定的ですが、ある1つのsum typeに対して網羅性チェックのようなものを定義時に行いたいときに使えるかもしれません。
pythonとパターンマッチは本当に相性が悪いので、まともなものを作ることができないのですが。 単純な網羅性チェックだけでも欲しいという場合があったので作ってみました。
使い方
ADTTypeProviderというものを使って型を作成します。
型の定義
from miniadt import ADTTypeProvider Tree = ADTTypeProvider("Tree") Node = Tree("Node", "e children") Leaf = Tree("Leaf", "e")
型の対応関係
Tree型のメンバーとしてNode,Leafがいるというような感じです。 一応、型の対応関係も存在しています。
from miniadt import ADTTypeProvider, ADTType Tree = ADTTypeProvider("Tree") Node = Tree("Node", "e children") Leaf = Tree("Leaf", "e") # Tree.baseがNode, Leafの親クラス print(issubclass(Node, Tree.base)) # => True print(issubclass(Leaf, Tree.base)) # => True # Tree.base自信はADTTypeのサブクラス print(issubclass(Node, ADTType)) # => True # もちろん別の型から作成したものは親子関係にない XTree = ADTTypeProvider("X") XNode = XTree("Node", "e children") XLeaf = XTree("Leaf", "e") print(issubclass(Node, XTree.base)) # => False print(issubclass(XLeaf, Tree.base)) # => False
網羅性チェックを行う関数
関数と言ってもクラスで定義します。それにmatch()をデコレータとして使うと網羅性をチェックした関数になります。 網羅性チェックは定義時に行われるだけで、実行時には行われません。 型名と同名のメソッドを呼ぶような実装になっています
@Tree.match class depth(object): def Leaf(e): return 1 def Node(e, children): return max(depth(e) for e in children) + 1 print(depth(Leaf(e=10))) # => 10 print(depth(Node(e=10, children=[Leaf(e=20)]))) # 2
分岐の候補が足りない場合エラーになります。
@Tree.match class MissingNode(object): def Leaf(e): return "ok" # miniadt.NotComprehensive: Node is not found. expected=['Node', 'Leaf']
また、引数の数が型のメンバーの数と一致しない場合にもエラーが出ます。
@Tree.match class depth(object): def Leaf(e, x): # 1個引数が多い return 1 def Node(e, children): return max(depth(e) for e in children) + 1 # miniadt.NotComprehensive: on Tree.Leaf: expected=['e'] != actual=['e', 'x'] @Tree.match class depth(object): def Leaf(e): return 1 def Node(e): # 引数が足りない return max(depth(e) for e in children) + 1 # miniadt.NotComprehensive: on Tree.Node: expected=['e', 'children'] != actual=['e']
類縁の機能
match()と似たような機能として以下の2つがあります。
- match_instance()
- classify()
両者は共にオブジェクトとして使います。match_instance()はmatch()に引数を追加したい場合に使います。 classify()は、分岐の候補の網羅性チェックだけを行い分配束縛はしないものです。
@Tree.match_instance class Applicator(object): def __init__(self, name): self.name = name def Leaf(self, e): return self.name def Node(self, e, children): return [self.name, [self(x) for x in children]] print(Applicator("foo")(Leaf(e=10))) # => foo print(Applicator("foo")(Node(e=10, children=[Leaf(e=20)]))) # => ['foo', ['foo']] @Tree.classify class ToDict(object): def Leaf(self, leaf): return leaf.e def Node(self, node): return {"e": node.e, "children": [self(e) for e in node.children]} todict = ToDict() print(todict(Leaf(e=10))) # => 10 print(todict(Node(e=10, children=[Leaf(e=20)]))) # => {'e': 10, 'children': [20]}