Skip to content

Instantly share code, notes, and snippets.

@derrickturk
Last active June 26, 2020 01:16
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save derrickturk/ab31086dabf8e0e44ed37930a7f72282 to your computer and use it in GitHub Desktop.
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
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))
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