phasm/phasm/type3/routers.py
Johan B.W. de Vries b5f0fda133 Implements sum for Foldable types
Foldable take a TypeConstructor. The first argument must be a
NatNum.

The FunctionSignatureRouter wasn't completely on point, instead
this commit adds an TypeClassArgsRouter lookup router. This
makes sense since the only available arguments we have to find
a router is the list of type class arguments.
2025-05-12 18:36:37 +02:00

120 lines
3.7 KiB
Python

from typing import Any, Callable
from .functions import (
TypeConstructorVariable,
TypeVariable,
TypeVariableApplication_Unary,
)
from .typeclasses import Type3ClassArgs
from .types import KindArgument, Type3, TypeApplication_TypeInt, TypeConstructor_Base
class NoRouteForTypeException(Exception):
pass
class TypeApplicationRouter[S, R]:
"""
Helper class to find a method based on a constructed type
"""
__slots__ = ('by_constructor', 'by_type', )
by_constructor: dict[Any, Callable[[S, Any], R]]
"""
Contains all the added routing functions for constructed types
"""
by_type: dict[Type3, Callable[[S], R]]
"""
Contains all the added routing functions for constructed types
"""
def __init__(self) -> None:
self.by_constructor = {}
self.by_type = {}
def add_n(self, typ: Type3, helper: Callable[[S], R]) -> None:
"""
Lets you route to types that were not constructed
Also known types of kind *
"""
self.by_type[typ] = helper
def add[T](self, constructor: TypeConstructor_Base[T], helper: Callable[[S, T], R]) -> None:
self.by_constructor[constructor] = helper
def __call__(self, arg0: S, typ: Type3) -> R:
t_helper = self.by_type.get(typ)
if t_helper is not None:
return t_helper(arg0)
c_helper = self.by_constructor.get(typ.application.constructor)
if c_helper is not None:
return c_helper(arg0, typ.application.arguments)
raise NoRouteForTypeException(arg0, typ)
TypeVariableLookup = dict[TypeVariable, tuple[KindArgument, ...]]
class TypeClassArgsRouter[S, R]:
"""
Helper class to find a method based on a type class argument list
"""
__slots__ = ('args', 'data', )
args: Type3ClassArgs
data: dict[tuple[Type3 | TypeConstructor_Base[Any], ...], Callable[[S, TypeVariableLookup], R]]
def __init__(self, args: Type3ClassArgs) -> None:
self.args = args
self.data = {}
def add(
self,
tv_map: dict[TypeVariable, Type3],
tc_map: dict[TypeConstructorVariable, TypeConstructor_Base[Any]],
helper: Callable[[S, TypeVariableLookup], R],
) -> None:
key: list[Type3 | TypeConstructor_Base[Any]] = []
for tc_arg in self.args:
if isinstance(tc_arg, TypeVariable):
key.append(tv_map[tc_arg])
else:
key.append(tc_map[tc_arg])
self.data[tuple(key)] = helper
def __call__(self, arg0: S, tv_map: dict[TypeVariable, Type3]) -> R:
key: list[Type3 | TypeConstructor_Base[Any]] = []
arguments: TypeVariableLookup = {}
for tc_arg in self.args:
if isinstance(tc_arg, TypeVariable):
key.append(tv_map[tc_arg])
continue
for tvar, typ in tv_map.items():
tvar_constructor = tvar.application.constructor
if tvar_constructor != tc_arg:
continue
key.append(typ.application.constructor)
if isinstance(tvar.application, TypeVariableApplication_Unary):
# FIXME: This feels sketchy. Shouldn't the type variable
# have the exact same number as arguments?
if isinstance(typ.application, TypeApplication_TypeInt):
arguments[tvar.application.arguments] = typ.application.arguments
continue
raise NotImplementedError(tvar.application, typ.application)
t_helper = self.data.get(tuple(key))
if t_helper is not None:
return t_helper(arg0, arguments)
raise NoRouteForTypeException(arg0, tv_map)