Skip to content

Instantly share code, notes, and snippets.

@dhilst
Created June 17, 2019 00:56
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 dhilst/3f3e4ca9695c82d063c35c26a33404f2 to your computer and use it in GitHub Desktop.
Save dhilst/3f3e4ca9695c82d063c35c26a33404f2 to your computer and use it in GitHub Desktop.
Functional fun with python types
from typing import *
from typing_extensions import Protocol
# Type Generics
A = TypeVar('A')
B = TypeVar('B')
C = TypeVar('C')
A_co = TypeVar('A_co', covariant=True)
class Box:
def __init__(self, contents):
self.contents = contents
class SupportsAdd(Protocol[A_co]):
def __add__(self, other): ...
class Maybe(Generic[A]):
def __init__(self, contents: Optional[SupportsAdd]) -> None:
self.contents = contents
def __add__(self, other: 'Maybe[A]'):
if self.contents and other.contents:
return Maybe(self.contents + other.contents)
return self
class MaybeString(Maybe[str]):
pass
class MaybeInt(Maybe[int]):
pass
try:
wat = MaybeInt(1) + MaybeString('Foo') # Error: Unsupported operand types MaybeInt and MaybeString
except TypeError as e:
pass
string: MaybeString = MaybeString("Hello") + MaybeString(None) + MaybeString(" world") # => "Hello world"
integer: MaybeInt = MaybeInt(1) + MaybeInt(None) + MaybeString(1) # => 2
none = MaybeInt(None) + MaybeInt(None)
print(string.contents) # => "Hello World"
print(integer.contents) # => 2
print(none.contents) # => None
print()
# Some callables
def upper(s: str) -> str:
'upcase s'
return s.upper()
def inc(i: int) -> str:
'increment i and convert to string'
return str(i+1)
def echo(message: str, times: int) -> List[str]:
'echos a message N times'
return [message] * times
def concatx(s: str) -> str:
'concatenate "x" to s'
return s + 'x'
# Functors
class Functor(Box, Generic[A]):
pass
def fmap(func: Callable[[A], B], val: A) -> 'Functor[B]':
return Functor(func(val))
hello = fmap(upper, 'Hello World')
two = fmap(inc, 1)
print(hello.contents) # => HELLO WROLD
print(two.contents) # => 2
print()
# Applicative
class Applicative(Functor[A], Box):
def __mul__(self, other):
return other.__rmul__(self)
def __rmul__(self, other):
return sequential_application(self, other)
def __add__(self, other):
return pure(other(self.contents))
def pure(a: A) -> Applicative[A]:
'lifts a'
return Applicative(a)
def sequential_application(fab_func: Applicative[Callable[[A], B]], fa: Applicative[A]) -> Applicative[B]:
'Apply fab_func to fa'
func = fab_func.contents
a_arg = fa.contents
return Applicative(func(a_arg))
def lifitA2(fabc_func: Callable[[A,B],C], fa: Applicative[A], fb: Applicative[B]) -> Applicative[C]:
'Lift binary function'
a_arg = fa.contents
b_arg = fb.contents
return Applicative(fabc_func(a_arg, b_arg))
ainc: Applicative[Callable[[int], str]] = pure(inc)
atwo: Applicative[str] = sequential_application(ainc, pure(1))
print(atwo.contents) # => '2'
aupper: Applicative[Callable[[str], str]] = pure(upper)
ahello: Applicative[str] = sequential_application(aupper, pure('hello applicative'))
print(ahello.contents) # 'HELLO APPLICATIVE'
ahellos: Applicative[List[str]] = lifitA2(echo, pure('hello'), pure(3))
print(ahellos.contents) # => ['hello', 'hello', 'hello']
ahello2: Applicative[str] = pure("fo") * pure(concatx) * pure(upper)
print(ahello2.contents) # => FOX
ahello3: Applicative[str] = pure("hi") + concatx + concatx + concatx + upper
print(ahello3.contents) # => HIXXX
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment