Last active
June 26, 2020 01:16
-
-
Save derrickturk/ab31086dabf8e0e44ed37930a7f72282 to your computer and use it in GitHub Desktop.
Base functors and a type-level fixed point in Python, for no good reason
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
from typing import Callable, Dict, Generic, List, TypeVar, Union | |
_T = TypeVar('_T') | |
JsonF = Union[str, Dict[str, Union[str, _T]], List[_T]] | |
class Fix(Generic[_T]): | |
def __init__(self, val: 'Union[_T, Fix[_T]]'): | |
self.val = val | |
# this is naughty, because it doesn't round-trip, but it shows the | |
# structure without the Fix wrappers | |
def __repr__(self): | |
return repr(self.val) | |
Json = Fix[JsonF] | |
ex: Json | |
ex = Json({ | |
'x': 'yyy', | |
'y': Json('zzz'), | |
'z': Json([Json('a'), Json('b'), Json('c')]) | |
}) | |
print(ex) | |
_U = TypeVar('_U') | |
# you have a hell of a time generalizing these while preserving typing, | |
# because mypy lacks higher-kinded types, i.e. you can't write | |
# _F = TypeVar('_F', bound=Generic[_T]) | |
def fmap_json(f: Callable[[_T], _U], x: JsonF[_T]) -> JsonF[_U]: | |
if isinstance(x, str): | |
return x | |
if isinstance(x, dict): | |
return { k: v if isinstance(v, str) else f(v) for k, v in x.items() } | |
return [f(y) for y in x] | |
# types omitted, but would be | |
# cata : forall F : (* -> *), | |
# T: * . | |
# (forall T : *, U : * . (T -> U) -> F[T] -> F[U]) -> | |
# (F[T] -> T) -> | |
# Fix[F] -> | |
# T | |
def cata(fmap, alg, x): | |
return alg(fmap(lambda x: cata(fmap, alg, x), x.val)) | |
# so we can use that now to, say, concat all strings in a Json | |
# all we have to do is write the function that runs at each level - | |
# the recursion is implicit in the catamorphism! | |
def concat_json1(x: JsonF[str]) -> str: | |
if isinstance(x, str): | |
return x | |
if isinstance(x, dict): | |
return ''.join(x.values()) | |
return ''.join(x) | |
def concat_json(json: Json) -> str: | |
return cata(fmap_json, concat_json1, json) | |
print(concat_json(ex)) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
from typing import Dict, Generic, List, TypeVar, Union | |
_T = TypeVar('_T') | |
JsonF = Union[str, Dict[str, Union[str, _T]], List[_T]] | |
class Fix(Generic[_T]): | |
def __init__(self, val: 'Union[_T, Fix[_T]]'): | |
self.val = val | |
# this is naughty, because it doesn't round-trip, but it shows the | |
# structure without the Fix wrappers | |
def __repr__(self): | |
return repr(self.val) | |
Json = Fix[JsonF] | |
ex: Json | |
ex = Json({ | |
'x': 'yyy', | |
'y': Json('zzz'), | |
'z': Json([Json('a'), Json('b'), Json('c')]) | |
}) | |
print(ex) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment