python/응용
functools.singledispatch 로 typeclass 흉내 내기
wefree
2024. 1. 24. 18:10
- https://docs.python.org/3/library/functools.html#functools.singledispatch
- https://news.ycombinator.com/item?id=22682219
- functools.singledispatch 를 이용해 typeclass 처럼 만들어 보자
코드
import dataclasses
import functools
import math
from typing import Any
@dataclasses.dataclass
class Circle:
radius: float
@dataclasses.dataclass
class Rectangle:
width: float
height: float
@functools.singledispatch
def area(shape: Any) -> float:
raise TypeError(f"{type(shape)} is not allowed")
@area.register
def _(shape: Circle) -> float:
return math.pi * shape.radius ** 2
@area.register
def _(shape: Rectangle) -> float:
return shape.width * shape.height
result = area(Circle(radius=2))
print(result)
singledispatch 한계
https://dev.to/wemake-services/typeclasses-in-python-3ma6
- mypy 로 타입 체크가 안됨
- 특정 타입에 대해 구현이 안되어 있을 경우 실행해 보기 전에 체크할 수 없다..
- Protocol 지원 안됨
생각해 볼 문제
Overloading
python 에서 아래처럼 구현이 허용되면 좋겠지만, 기본적으로 type 으로 function 을 구분할 수 없으므로 불가능하다.
def area(shape: Circle) -> float:
return math.pi * shape.radius ** 2
def area(shape: Rectangle) -> float:
return shape.width * shape.height
하지만 https://github.com/mrocklin/multipledispatch/ 를 이용하면 비슷하게 코딩할 수 있어 보이기도 하다.
(사용해 보니 약간 불안정해 보임 - Circle 과 Retangle member 를 혼용?)
from multipledispatch import dispatch
@dispatch(Circle)
def area(shape) -> float:
return math.pi * shape.radius ** 2
@dispatch(Rectangle)
def area(shape) -> float:
return shape.width * shape.height
typeclass 흉내를 냈지만
singledispatch 를 사용한 코드는, 결국 아래랑 다를게 없어 보인다.
def area(shape: Any) -> float:
if isinstance(shape, Circle):
return math.pi * shape.radius ** 2
elif isinstance(shape, Rectangle):
return shape.width * shape.height
else:
raise TypeError(f"{type(shape)} is not allowed")
참고
- 아래처럼 singledispatch 에서 Union type 을 사용할 수 있는데, python 3.11 이상 부터 가능한 듯
@area.register
def _(shape: Circle | Rectangle) -> float:
...
- class method 대상으로는 singledispatch 대신에 singledispatchmethod 를 사용한다.
- typeclass vs overloading: https://stackoverflow.com/questions/50373097/scala-type-class-pattern-vs-pattern-matching-or-overloading