Created
November 21, 2025 23:57
-
-
Save bigsnarfdude/f01eec386fff89381cf4a2d3e9bcdf82 to your computer and use it in GitHub Desktop.
Fizz Buzz using python monads - AlgeSnake
This file contains hidden or 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
| """ | |
| 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