phasm/phasm/type3/routers.py
Johan B.W. de Vries 0e8540c611 Removes the special casing for foldl
Had to implement both functions as arguments and type
place holders (variables) for type constructors.

Had to implement functions as a type as well.

Still have to figure out how to pass functions around.
2025-05-19 21:06:40 +02:00

143 lines
4.5 KiB
Python

from typing import Any, Callable
from .functions import (
TypeConstructorVariable,
TypeVariable,
TypeVariableApplication_Nullary,
TypeVariableApplication_Unary,
)
from .typeclasses import Type3ClassArgs
from .types import (
KindArgument,
Type3,
TypeApplication_Type,
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 = tuple[
dict[TypeVariable, KindArgument],
dict[TypeConstructorVariable, TypeConstructor_Base[Any]],
]
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 = (dict(tv_map), {}, )
for tc_arg in self.args:
if isinstance(tc_arg, TypeVariable):
key.append(tv_map[tc_arg])
arguments[0][tc_arg] = 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)
arguments[1][tc_arg] = typ.application.constructor
if isinstance(tvar.application, TypeVariableApplication_Unary):
if isinstance(typ.application, TypeApplication_Type):
da_type, = typ.application.arguments
sa_type_tv = tvar.application.arguments
arguments[0][sa_type_tv] = da_type
continue
# FIXME: This feels sketchy. Shouldn't the type variable
# have the exact same number as arguments?
if isinstance(typ.application, TypeApplication_TypeInt):
sa_type, sa_len = typ.application.arguments
sa_type_tv = tvar.application.arguments
sa_len_tv = TypeVariable(sa_type_tv.name + '*', TypeVariableApplication_Nullary(None, None))
arguments[0][sa_type_tv] = sa_type
arguments[0][sa_len_tv] = sa_len
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)