#!/bin/env python3
# An example of multiple-dispatch, taken from 
# Functional Programming in Python
# by David Mertz
# Copyright Â© 2015 OâReilly Media, Inc.
# URL: http://www.oreilly.com/programming/free/functional-programming-python.csp
#
# For a purely functional version in Haskell, see RockPaperSciss.hs
# -------------------------------------------------------

class Thing(object): pass
class Rock(Thing): pass
class Paper(Thing): pass
class Scissors(Thing): pass

# First a purely imperative version.

def beats(x, y):
    if isinstance(x, Rock):
        if isinstance(y, Rock):
            return None  # No winner
        elif isinstance(y, Paper):
            return y
        elif isinstance(y, Scissors):
            return x
        else:
            raise TypeError("Unknown second thing")
    elif isinstance(x, Paper):
        if isinstance(y, Rock):
            return x
        elif isinstance(y, Paper):
            return None  # No winner
        elif isinstance(y, Scissors):
            return y
        else:
            raise TypeError("Unknown second thing")
    elif isinstance(x, Scissors):
        if isinstance(y, Rock):
            return y
        elif isinstance(y, Paper):
            return x
        elif isinstance(y, Scissors):
            return None  # No winner
        else:
            raise TypeError("Unknown second thing")
    else:
        raise TypeError("Unknown first thing")


rock, paper, scissors = Rock(), Paper(), Scissors()
# >>>
print("beats(paper, rock) = ", beats(paper, rock))
# <__main__.Paper at 0x103b96b00>
# >>>
# beats(paper, 3)
# TypeError: Unknown second thing

# -----------------------------------------------------------------------------

class DuckRock(Rock):
    def beats(self, other):
        if isinstance(other, Rock):
            return None  # No winner
        elif isinstance(other, Paper):
            return other
        elif isinstance(other, Scissors):
            return self
        else:
            raise TypeError("Unknown second thing")

class DuckPaper(Paper):
    def beats(self, other):
        if isinstance(other, Rock):
            return self
        elif isinstance(other, Paper):
            return None        # No winner
        elif isinstance(other, Scissors):
            return other
        else:
            raise TypeError("Unknown second thing")

class DuckScissors(Scissors):
    def beats(self, other):
        if isinstance(other, Rock):
            return other
        elif isinstance(other, Paper):
            return self
        elif isinstance(other, Scissors):
            return None        # No winner
        else:
            raise TypeError("Unknown second thing")

def beats2(x, y):
    if hasattr(x, 'beats'):
        return x.beats(y)
    else:
        raise TypeError("Unknown first thing")

rock, paper, scissors = DuckRock(), DuckPaper(), DuckScissors()
print("beats2(paper, rock) = ", beats2(paper, rock))

# -----------------------------------------------------------------------------

# As a final try, we can express all the logic more directly using multi‐
# ple dispatch. This should be more readable, albeit there are still a
# number of cases to define:

# The version below uses the dispatch decorator to further improve the code
# However, multipledispatch is not in the standard library and needs to be installed separately

# from multipledispatch import dispatch

# @dispatch(Rock, Rock)
# def beats3(x, y): return None

# @dispatch(Rock, Paper)
# def beats3(x, y): return y

# @dispatch(Rock, Scissors)
# def beats3(x, y): return x

# @dispatch(Paper, Rock)
# def beats3(x, y): return x

# @dispatch(Paper, Paper)
# def beats3(x, y): return None

# @dispatch(Paper, Scissors)
# def beats3(x, y): return x

# @dispatch(Scissors, Rock)
# def beats3(x, y): return y

# @dispatch(Scissors, Paper)
# def beats3(x, y): return x

# @dispatch(Scissors, Scissors)
# def beats3(x, y): return None

# @dispatch(object, object)
# def beats3(x, y):
#     if not isinstance(x, (Rock, Paper, Scissors)):
#         raise TypeError("Unknown first thing")
#     else:
#         raise TypeError("Unknown second thing")

# # beats3(rock, paper)
# print("beats3(paper, rock) = ", beats3(paper, rock))

