Skip to content

Instantly share code, notes, and snippets.

@bigsnarfdude
Created November 21, 2025 23:57
Show Gist options
  • Select an option

  • Save bigsnarfdude/f01eec386fff89381cf4a2d3e9bcdf82 to your computer and use it in GitHub Desktop.

Select an option

Save bigsnarfdude/f01eec386fff89381cf4a2d3e9bcdf82 to your computer and use it in GitHub Desktop.
Fizz Buzz using python monads - AlgeSnake
"""
FizzBuzz using Monoid patterns inspired by algesnake
Demonstrates how abstract algebra makes the solution composable and elegant
"""
from abc import ABC, abstractmethod
from typing import TypeVar, Generic, Callable
from functools import reduce
T = TypeVar('T')
class Monoid(ABC, Generic[T]):
"""Abstract Monoid interface following algesnake pattern"""
@property
@abstractmethod
def zero(self) -> T:
"""Identity element"""
pass
@abstractmethod
def combine(self, a: T, b: T) -> T:
"""Associative binary operation"""
pass
def combine_all(self, items: list[T]) -> T:
"""Combine multiple items, returns zero for empty list"""
if not items:
return self.zero
return reduce(self.combine, items)
class StringMonoid(Monoid[str]):
"""String concatenation monoid - the foundation of FizzBuzz"""
@property
def zero(self) -> str:
return ""
def combine(self, a: str, b: str) -> str:
return a + b
class FizzBuzzMonoid(Monoid[str]):
"""
FizzBuzz-specific monoid that handles the logic:
- If we have a non-empty string, keep it
- If empty, fall back to the number
"""
def __init__(self, n: int):
self.n = n
@property
def zero(self) -> str:
return str(self.n) # Identity is the number itself
def combine(self, a: str, b: str) -> str:
# If we've accumulated any fizz/buzz, keep building
# Otherwise return the number
result = a + b
return result if result else str(self.n)
def fizz_check(n: int) -> str:
"""Conditionally returns 'Fizz' - a monoid element"""
return "Fizz" if n % 3 == 0 else ""
def buzz_check(n: int) -> str:
"""Conditionally returns 'Buzz' - a monoid element"""
return "Buzz" if n % 5 == 0 else ""
def fizzbuzz_monoid_style(n: int) -> str:
"""
FizzBuzz using monoid composition
The key insight: Strings form a monoid under concatenation,
and we can compose the fizz and buzz checks using the monoid structure
"""
string_monoid = StringMonoid()
# Compose fizz and buzz checks
fizz = fizz_check(n)
buzz = buzz_check(n)
# Combine using monoid operation
result = string_monoid.combine(fizz, buzz)
# Return result or number if empty (identity element behavior)
return result if result else str(n)
# Alternative: Using MonoidWrapper pattern from algesnake
class MonoidWrapper(Generic[T]):
"""Wrapper to create monoids from functions (algesnake pattern)"""
def __init__(self, combine_fn: Callable[[T, T], T], zero_value: T):
self.combine_fn = combine_fn
self.zero_value = zero_value
def combine(self, a: T, b: T) -> T:
return self.combine_fn(a, b)
@property
def zero(self) -> T:
return self.zero_value
def combine_all(self, items: list[T]) -> T:
if not items:
return self.zero
return reduce(self.combine_fn, items)
def fizzbuzz_wrapper_style(n: int) -> str:
"""FizzBuzz using MonoidWrapper (algesnake style)"""
# Create a string concatenation monoid
string_monoid = MonoidWrapper(
combine_fn=lambda a, b: a + b,
zero_value=""
)
# Build list of checks
checks = [fizz_check(n), buzz_check(n)]
# Combine using monoid
result = string_monoid.combine_all(checks)
return result if result else str(n)
# Advanced: Using Option monoid for Maybe-style computation
class Option(Generic[T]):
"""Option/Maybe type for handling presence/absence"""
pass
class Some(Option[T]):
def __init__(self, value: T):
self.value = value
def __repr__(self):
return f"Some({self.value})"
class Nothing(Option[T]):
def __repr__(self):
return "Nothing"
class OptionMonoid(Monoid[Option[str]]):
"""
Option monoid:
- Nothing + Nothing = Nothing
- Nothing + Some(x) = Some(x)
- Some(x) + Nothing = Some(x)
- Some(x) + Some(y) = Some(x + y) [using string concatenation]
"""
@property
def zero(self) -> Option[str]:
return Nothing()
def combine(self, a: Option[str], b: Option[str]) -> Option[str]:
if isinstance(a, Nothing):
return b
if isinstance(b, Nothing):
return a
# Both are Some
return Some(a.value + b.value)
def fizz_option(n: int) -> Option[str]:
"""Returns Some('Fizz') or Nothing"""
return Some("Fizz") if n % 3 == 0 else Nothing()
def buzz_option(n: int) -> Option[str]:
"""Returns Some('Buzz') or Nothing"""
return Some("Buzz") if n % 5 == 0 else Nothing()
def fizzbuzz_option_style(n: int) -> str:
"""FizzBuzz using Option monoid"""
option_monoid = OptionMonoid()
# Combine fizz and buzz using monoid
result = option_monoid.combine(fizz_option(n), buzz_option(n))
# Extract value or return number
if isinstance(result, Some):
return result.value
else:
return str(n)
# Decorator pattern from algesnake for operator overloading
def provides_monoid(cls):
"""
Decorator to add operator overloading to monoid classes
Enables natural syntax like: Max(5) + Max(3)
"""
original_init = cls.__init__
def new_init(self, value):
self.value = value
original_init(self, value)
def __add__(self, other):
if not isinstance(other, cls):
return NotImplemented
return cls(self.combine(other).value)
def __radd__(self, other):
# Support sum() by handling 0 + object
if other == 0:
return self
return self.__add__(other)
cls.__init__ = new_init
cls.__add__ = __add__
cls.__radd__ = __radd__
return cls
@provides_monoid
class FizzBuzzValue:
"""
FizzBuzz as a monoid with operator overloading
This is the most algesnake-style approach!
"""
def __init__(self, text: str):
pass # Value set by decorator
def combine(self, other):
"""Combine two FizzBuzz values"""
combined = self.value + other.value
return FizzBuzzValue(combined)
@property
def zero(self):
return FizzBuzzValue("")
def __repr__(self):
return f"FizzBuzzValue('{self.value}')"
def fizzbuzz_operator_overload(n: int) -> str:
"""FizzBuzz using operator overloading (most elegant!)"""
fizz = FizzBuzzValue("Fizz" if n % 3 == 0 else "")
buzz = FizzBuzzValue("Buzz" if n % 5 == 0 else "")
# Use natural + operator!
result = fizz + buzz
return result.value if result.value else str(n)
def main():
"""Demonstrate all FizzBuzz monoid implementations"""
print("=== Basic Monoid Style ===")
for i in range(1, 16):
print(f"{i}: {fizzbuzz_monoid_style(i)}")
print("\n=== MonoidWrapper Style (algesnake pattern) ===")
for i in range(1, 16):
print(f"{i}: {fizzbuzz_wrapper_style(i)}")
print("\n=== Option Monoid Style ===")
for i in range(1, 16):
print(f"{i}: {fizzbuzz_option_style(i)}")
print("\n=== Operator Overload Style (most algesnake!) ===")
for i in range(1, 16):
print(f"{i}: {fizzbuzz_operator_overload(i)}")
print("\n=== Full FizzBuzz 1-100 (Operator Style) ===")
results = [fizzbuzz_operator_overload(i) for i in range(1, 101)]
print(", ".join(results[:20]) + "...")
if __name__ == "__main__":
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment