Skip to content

Instantly share code, notes, and snippets.

@Niriel
Created January 21, 2020 12:53
Show Gist options
  • Save Niriel/83699a50914179d41c045d5d06aa8b20 to your computer and use it in GitHub Desktop.
Save Niriel/83699a50914179d41c045d5d06aa8b20 to your computer and use it in GitHub Desktop.
Python method that can't even be accessed if some condition is False
# Dunno how useful it could be. Whatever.
import types
class Monoid:
def __init__(self, xs):
self.xs = xs
@classmethod
def empty(cls):
raise NotImplementedError
def append(self, x):
raise NotImplementedError
class Mult(Monoid):
# Number multiplication form a monoid of identity 1.
@classmethod
def empty(cls):
return Mult(1)
def append(self, x):
# xs is a number that may result from previous calls to append.
# x is a number.
return Mult(self.xs * x)
class List(Monoid):
# Concatenation is the free monoid in any cartesian category.
# The identity element is the empty list.
@classmethod
def empty(cls):
return List([])
def append(self, x):
# xs is an actual (potentially empty) list of elements
# x is a new element to add to the list
return List(self.xs + [x])
class Func(Monoid):
# Function composition and identity form a monoid.
@classmethod
def empty(cls):
return Func(lambda a: a)
def append(self, x):
# xs is a function.
# x is also
# This is function composition, we run x after a.
return Func(lambda a: x(self.xs(a)))
class IfMonoid:
def __init__(self, method):
self.method = method
self.name = 'extra'
def __get__(self, instance, owner):
if instance is None:
return self
if isinstance(getattr(instance, self.name), Monoid):
return types.MethodType(self.method, instance)
raise AttributeError
class CarryExtra:
def __init__(self, extra, value):
self.extra = extra
self.value = value
def map(self, f):
# Run f on the value, pass the extra data along, unchanged.
return CarryExtra(self.extra, f(self.value))
@IfMonoid
def flatmap(self, f):
# Here, f takes a value and returns a CarryExtra.
# We need to combine that new extra with the current extra.
# For that we use a monoid.
# If extra isn't a monoid, well, we just don't have a monad at all.
y = f(self.value)
new_value = y.value
new_extra = y.extra
return CarryExtra(self.extra.append(new_extra), new_value)
def _main():
def inc(x):
return x + 1
def dbl(x):
return x * 2
def log_inc(x):
return CarryExtra('called inc on {}'.format(x), inc(x))
def log_dbl(x):
return CarryExtra('called dbl on {}'.format(x), dbl(x))
def log_str(x):
return CarryExtra('stringified {}'.format(x), str(x))
print('======== case 1: metadata is not monoid')
# Attach some stupid metadata to the value 10, then do maths with it.
metadata = {'i like': 'rabbits'}
ce0 = CarryExtra(metadata, 10)
print('Initial', ce0.extra, ce0.value)
print('Trying map...')
ce1 = ce0.map(inc)
ce2 = ce1.map(dbl)
assert ce2.value == 22
assert ce2.extra == metadata
print('Trying flatmap...')
try:
_ = ce2.flatmap
except AttributeError:
print('[ OK ] I got the AttributeError I expected because metadata is not a monoid')
else:
print('[FAIL] This should have raised AttributeError because we cannot have that monad without a monoid')
print('Final ', ce2.extra, ce2.value)
print()
print('======== case 2: metadata is a monoid')
metadata = List.empty().append('Mushrooms are pretty cool')
ce0 = CarryExtra(metadata, 10)
print('initial value: ', repr(ce0.value))
print('initial extra: ', ce0.extra.xs)
print('Trying map...')
ce1 = ce0.map(inc)
print('Trying flatmap...')
ce2 = ce1.flatmap(log_dbl)
ce3 = ce2.flatmap(log_str)
print('final value: ', repr(ce3.value))
print('final extra: ', ce3.extra.xs)
if __name__ == '__main__':
_main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment