[PYTHON] Algebraic data types and FizzBuzz

The title is unclear.

[PyAlgebraicDataTypes] I played with (https://github.com/benanhalt/PyAlgebraicDataTypes). Inspired by the language Racket [Algebraic Data Types](http://en.wikipedia.org/wiki/%E4%BB%A3%E6%95 It seems to be a library that expresses% B0% E7% 9A% 84% E3% 83% 87% E3% 83% BC% E3% 82% BF% E5% 9E% 8B) in Python. It looked good because it was small for learning, so I tried it.

3.4


>>> from adt import ADT, Require
>>> class List(ADT): pass
... 
>>> class Nil(List): pass
... 
>>> class Cons(List):
...   car = Require(int)
...   cdr = Require(List)

3.4


>>> Nil()
Nil()

A list with elements

3.4


>>> Cons(1, Cons(2, Nil()))
Cons(car=1, cdr=Cons(car=2, cdr=Nil()))

It can be expressed by passing * Cons * to * Cons.cdr *. If there is no element, you can express the end by using * Nil *.

It is troublesome to write this * List * data by hand, so let's define a * range_ * function that has the element specified by the argument.

3.4


>>> def range_(until, value=0):
...     return Nil() if value == until else Cons(value, range_(until, value + 1))
... 
>>> range_(5)
Cons(car=0, cdr=Cons(car=1, cdr=Cons(car=2, cdr=Cons(car=3, cdr=Cons(car=4, cdr=Nil())))))

I was able to define my own * List * type with a list data structure.

Now that I know how to use it, let's define * FizzBuzzList * with a data structure like Fizz Buzz.

3.4


class FizzBuzzList(List):
    pass

class Value(FizzBuzzList):
    value = Require(int)

class Fizz(FizzBuzzList):
    value = Require(int)

class Buzz(FizzBuzzList):
    value = Require(int)

class FizzBuzz(FizzBuzzList):
    value = Require(int)
    fizz = Require(Fizz)
    buzz = Require(Buzz)

3.4


def fbrange(until, value=0):
    """
    >>> fbrange(6, 1)  # doctest: +NORMALIZE_WHITESPACE
    Cons(car=Value(value=1), cdr=Cons(car=Value(value=2),
         cdr=Cons(car=Fizz(value=3), cdr=Cons(car=Value(value=4),
         cdr=Cons(car=Buzz(value=5), cdr=Nil())))))
    """
    def make_cons(obj):
        return Cons(obj, fbrange(until, obj.value + 1))

    if value == until:
        return Nil()
    elif value % 15 == 0:
        return make_cons(FizzBuzz(value, Fizz(value), Buzz(value)))
    elif value % 5 == 0:
        return make_cons(Buzz(value))
    elif value % 3 == 0:
        return make_cons(Fizz(value))
    return make_cons(Value(value))

When you think of Fizz Buzz as a data structure, the logic comes out when you create the data. Here, it is generated by the logic of Fizz Buzz, which is generally called, but you can also think about the data structure of Fizz Buzz with your own logic.

[MatchCases] You can express pattern matching by creating a matcher that inherits (https://github.com/benanhalt/PyAlgebraicDataTypes#match-cases). Let's pattern match this data structure and output Fizz Buzz.

3.4


from adt import MatchCases

class FizzBuzzMatch(MatchCases):
    def value(match: Value):
        return value
    def fizz(match: Fizz):
        return 'fizz'
    def buzz(match: Buzz):
        return 'buzz'
    def fizzbuzz(match: FizzBuzz):
        return FizzBuzzMatch(fizz) + FizzBuzzMatch(buzz)
    def cons(match: Cons):
        return '{}, {}'.format(FizzBuzzMatch(car), FizzBuzzMatch(cdr))
    def nil(match: Nil):
        return None

Like this.

3.4


>>> FizzBuzzMatch(fbrange(16, 1))
'1, 2, fizz, 4, buzz, fizz, 7, 8, fizz, buzz, 11, fizz, 13, 14, fizzbuzz, None'

Since Python is dynamically typed, it may not make a big difference in this example even if it is implemented with * isinstance * as follows, in the sense that even if it is written like a pattern match, the actual error can be detected only at runtime. ..

3.4


def match_fizzbuzz(fblist):
    if isinstance(fblist, Value):
        return fblist.value
    elif isinstance(fblist, Fizz):
        return 'fizz'
    elif isinstance(fblist, Buzz):
        return 'buzz'
    elif isinstance(fblist, FizzBuzz):
        return match_fizzbuzz(fblist.fizz) + match_fizzbuzz(fblist.buzz)
    elif isinstance(fblist, Cons):
        return '{}, {}'.format(
                match_fizzbuzz(fblist.car),
                match_fizzbuzz(fblist.cdr))
    elif isinstance(fblist, Nil):
        return None

However, the if statement creates a dependency on the order, so when the data type is derived and complicated (for example, when the FizzBuzz type inherits from the Fizz and Buzz types). I feel that the strictness of type matching is effective.

Python does not support algebraic data types (direct sum types) as a language

I don't think it's normal to think about it, so it was interesting to try it and realize the difference in thinking.

One more thing, the logic of Fizz Buzz came out when the data was generated. I noticed that changing the data generation method makes it possible to change the output result without changing the output side (at the pattern matching).

Recommended Posts

Algebraic data types and FizzBuzz
Algebraic data types and pattern matching
Algebraic data types and object-oriented programming
Understanding data types and beginning linear regression
Python variables and data types learned in chemoinformatics
cv2 functions and data types (OpenCV python bindings)
FizzBuzz problems here and there
Extract csv data and calculate
Notes on Python and dictionary types
Memo "Chapter 5 – Dictionaries and Structuring Data"
Hashing data in R and Python
About time series data and overfitting
Extract Pokemon GO Pokemon data and skill data