In the previous article, we saw how we can solve the Blackjack Kata using monads with a functional programming approach. In this article, we will solve the same problem, once again using monads but leveraging Python's Object Oriented data model. Python, being a multi-paradigm programming language is well suited for this kind of mix-and-match approach.

If you haven't read the previous article, I suggest taking a look first as it explains the problem statement.

Just like before, we start off with the multi-value monad.

``````from itertools import chain

class MultiValue:
def __init__(self, values):
self.values = set(values)

def map(self, fn):
out = {fn(val) for val in self.values}
return MultiValue(out)

def flatmap(self, fn):
out = (fn(val).values for val in self.values)
return MultiValue(chain(*out))``````

One of the key operations we need to do to solve the blackjack kata is be able to add up a set of values. Some of those values might be numbers, while others might be `MultiValue` objects.

• The class `MultiValue` above allows us to create items that can have multiple values. For example, the Ace in Blackjack game can have the value 1 or the value 11. Both are valid and you can choose the value which is most beneficial for you. We represent this as `a = MultiValue({1, 11})`
• The `map` and `flatmap` functions are used to perform operations on these values. `map` is used when the output of an operation is a regular value. Example, if I want to add the number `5` to the variable `a` above, the code will be `a.map(lambda val: val + 5)`
• `flatmap` is used when doing an operation that involves another `MultiValue`, for example `MultiValue({1, 2}) + MultiValue({3, 4})`

Again for more details, consult the multi-value monad article.

## Summing up a hand

Consider a hand of cards `[2, 5, 7, A]`. Since `A` can take the value `1` or `11`, the sum of this hand could be `15` (when `A` is `1`) or `25` (when `A` is `11`). If we represent `A` as `MultiValue({1, 11})` then the hand can be represented as `hand = [2, 5, 7, MultiValue({1, 11})]`.

Ideally, we can use the `sum()` built-in function to calculate the sum of this list, but it won't work because of the `MultiValue` object in there.

In the functional programming approach of the previous article, we wrote the functions `add`, `add_mv_and_int` and `add_multivalue` which helped us to add up the list.

We follow the same approach here, but instead of creating standalone functions, we are going to implement the `__add__` dunder method to `MultiValue` so that it supports the `+` operator directly.

Here is what the skeleton looks like

``````class MultiValue:
...

...

return self + other``````

Note that we also implement `__radd__` so that `MultiValue() + x` as well as `x + MultiValue()` works.

Just like in the previous article, there are two cases we need to consider:

1. Adding an integer to a `MultiValue`
2. Adding two `MultiValue` objects

In case we are adding `MultiValue` to an integer, then we can use the `map` method to do the operation.

``````class MultiValue:
...

match other:
case int():
return self.map(lambda a: a + other)

return self + other``````

When we are adding two `MultiValue` together, then we can use the `flatmap` method to do the operation

``````class MultiValue:
...

match other:
case int():
return self.map(lambda a: a + other)
case MultiValue():
return self.flatmap(lambda a: a + other)

return self + other``````

Check out the previous article for an in-depth explanation of how the `map` and `flatmap` work in this situation.

We can test this out

``````>>> MultiValue({2, 3}) + 10
MultiValue({12, 13})

>>> 10 + MultiValue({2, 3})
MultiValue({12, 13})

>>> MultiValue({2, 3}) + MultiValue({10, 20})
MultiValue({12, 13, 22, 23})
``````

Now that we have implemented support for the `+` operator for the `MultiValue` class, we can just use the `sum()` built-in function normally, even if we have `MultiValue` objects in the list. The code below is mostly the same as the one from the previous article, except we use the `sum` function to add up all the values instead of using `reduce`

``````def get_card_value(card):
if card == 'A':
return MultiValue({1, 11})
elif card in ('J', 'Q', 'K'):
return MultiValue({10})
else:
return MultiValue({card})

def possible_hand_values(hand: list[CardFace]) -> MultiValue:
values = [get_card_value(card) for card in hand]
return sum(values, MultiValue({0}))

def hand_value(hand: list[CardFace]) -> int | None:
try:
possible_values = possible_hand_values(hand).values
valid_values = {value for value in possible_values if value <= 21}
return max(valid_values)
except ValueError:
return None``````

Here is what this code does:

• `get_card_value` takes a card and returns the `MultiValue` representation of it
• `possible_hand_values` takes a hand of cards, converts each to its `MultiValue` representation and then adds up all the values using `sum`. Since the Ace has two possible values, this will actually give us all the possible totals for a hand of cards
• `hand_value` takes the list of all possible hand values, filters out those hands that bust, and returns the maximum of whatever is remaining. It will return `None` if every possible hand value is a bust

## Summary

That brings us to the end of this Blackjack Kata using the `MultiValue` monad. In these two articles, we saw how we can use the monad to abstract items that could take on multiple values. In the Blackjack example, that was the Ace which could take on the value of `1` or `11` whichever gave a better hand value.

Normally dealing with such variables leads to complex code, but with the monad abstraction, we barely need to think about it.

Furthermore, by hooking on to python's ability for operator overloading, we were able to make the `MultiValue` support the `+` operator, leading to some very clean code. In the end, we could use `sum` to add up all the values in the hand of cards as if it was just a list of numbers, when actually there were `MultiValue` in there.

In essence, this is the power of abstraction. It frees the developer to write the core algorithm at a high level ("Sum up the values in the hand") without having to think about the internal details ("Some cards in the hand take single values, some take on multiple values")