# WEBSITE:

plus1 = x -> x + 1
assert plus1(5) == 6

assert range(10) |> map$(.**2) |> list == [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]

match [head] + tail in [0, 1, 2, 3]:
    assert head == 0
    assert tail == [1, 2, 3]

{"list": [0] + rest, **_} = {"list": [0, 1, 2, 3]}
assert rest == [1, 2, 3]

A = [1, 2;; 3, 4]
AA = [A ; A]
assert AA == [[1, 2, 1, 2], [3, 4, 3, 4]]

product = reduce$(*)
assert range(1, 5) |> product == 24

first_five_words = .split() ..> .$[:5] ..> " ".join
assert first_five_words("ab cd ef gh ij kl") == "ab cd ef gh ij"

@recursive_iterator
def fib() = (1, 1) :: map((+), fib(), fib()$[1:])
assert fib()$[:5] |> list == [1, 1, 2, 3, 5]

# can't use parallel_map here otherwise each process would have to rerun all
#  the tutorial tests since we don't guard them behind __name__ == "__main__"
assert range(100) |> concurrent_map$(.**2) |> list |> .$[-1] == 9801

def factorial(n, acc=1):
    match n:
        case 0:
            return acc
        case int(_) if n > 0:
            return factorial(n-1, acc*n)
assert factorial(100) == 93326215443944152681699238856266700490715968264381621468592963895217599993229915608941463976156518286253697920827223758251185210916864000000000000000000000000

data Empty()
data Leaf(n)
data Node(l, r)

def size(Empty()) = 0
addpattern def size(Leaf(n)) = 1
addpattern def size(Node(l, r)) = size(l) + size(r)

assert size(Node(Leaf(1), Node(Leaf(2), Empty()))) == 2


# TUTORIAL:

def factorial(n):
    """Compute n! where n is an integer >= 0."""
    if n `isinstance` int and n >= 0:
        acc = 1
        for x in range(1, n+1):
            acc *= x
        return acc
    else:
        raise TypeError("the argument to factorial must be an integer >= 0")

# Test cases:
try:
    -1 |> factorial
except TypeError:
    assert True
else:
    assert False
try:
    0.5 |> factorial
except TypeError:
    assert True
else:
    assert False
assert 0 |> factorial == 1
assert 3 |> factorial == 6

def factorial(n):
    """Compute n! where n is an integer >= 0."""
    match n:
        case 0:
            return 1
        case x `isinstance` int if x > 0:
            return x * factorial(x-1)
    else:
        raise TypeError("the argument to factorial must be an integer >= 0")

# Test cases:
try:
    -1 |> factorial
except TypeError:
    assert True
else:
    assert False
try:
    0.5 |> factorial
except TypeError:
    assert True
else:
    assert False
assert 0 |> factorial == 1
assert 3 |> factorial == 6

def factorial(n):
    """Compute n! where n is an integer >= 0."""
    try:
        # The only value that can be assigned to 0 is 0, since 0 is an
        # immutable constant; thus, this assignment fails if n is not 0.
        0 = n
    except MatchError:
        pass
    else:
        return 1
    try:
        # This attempts to assign n to x, which has been declared to be
        # an int; since only an int can be assigned to an int, this
        # fails if n is not an int.
        x `isinstance` int = n
    except MatchError:
        pass
    else: if x > 0:  # in Coconut, statements can be nested on the same line
        return x * factorial(x-1)
    raise TypeError("the argument to factorial must be an integer >= 0")

# Test cases:
try:
    -1 |> factorial
except TypeError:
    assert True
else:
    assert False
try:
    0.5 |> factorial
except TypeError:
    assert True
else:
    assert False
assert 0 |> factorial == 1
assert 3 |> factorial == 6

def factorial(n):
    """Compute n! where n is an integer >= 0."""
    match n:
        case 0:
            return 1
        case _ `isinstance` int if n > 0:
            return n * factorial(n-1)
    else:
        raise TypeError("the argument to factorial must be an integer >= 0")

# Test cases:
try:
    -1 |> factorial
except TypeError:
    assert True
else:
    assert False
try:
    0.5 |> factorial
except TypeError:
    assert True
else:
    assert False
assert 0 |> factorial == 1
assert 3 |> factorial == 6

def factorial(n):
    """Compute n! where n is an integer >= 0."""
    match n:
        case 0:
            return 1
        case int() if n > 0:
            return n * factorial(n-1)
    else:
        raise TypeError("the argument to factorial must be an integer >= 0")

# Test cases:
try:
    -1 |> factorial
except TypeError:
    assert True
else:
    assert False
try:
    0.5 |> factorial
except TypeError:
    assert True
else:
    assert False
assert 0 |> factorial == 1
assert 3 |> factorial == 6

def factorial(n, acc=1):
    """Compute n! where n is an integer >= 0."""
    match n:
        case 0:
            return acc
        case int() if n > 0:
            return factorial(n-1, acc*n)
    else:
        raise TypeError("the argument to factorial must be an integer >= 0")

# Test cases:
try:
    -1 |> factorial
except TypeError:
    assert True
else:
    assert False
try:
    0.5 |> factorial
except TypeError:
    assert True
else:
    assert False
assert 0 |> factorial == 1
assert 3 |> factorial == 6

def factorial(n):
    """Compute n! where n is an integer >= 0."""
    match n:
        case 0:
            return 1
        case int() if n > 0:
            return range(1, n+1) |> reduce$(*)
    else:
        raise TypeError("the argument to factorial must be an integer >= 0")

# Test cases:
try:
    -1 |> factorial
except TypeError:
    assert True
else:
    assert False
try:
    0.5 |> factorial
except TypeError:
    assert True
else:
    assert False
assert 0 |> factorial == 1
assert 3 |> factorial == 6

def factorial(0) = 1

addpattern def factorial(int() as n if n > 0) =
    """Compute n! where n is an integer >= 0."""
    range(1, n+1) |> reduce$(*)

# Test cases:
try:
    -1 |> factorial
except MatchError:
    assert True
else:
    assert False
try:
    0.5 |> factorial
except MatchError:
    assert True
else:
    assert False
assert 0 |> factorial == 1
assert 3 |> factorial == 6

def factorial(0) = 1

addpattern def factorial(int() as n if n > 0) =
    """Compute n! where n is an integer >= 0."""
    n * factorial(n - 1)

# Test cases:
try:
    -1 |> factorial
except MatchError:
    assert True
else:
    assert False
try:
    0.5 |> factorial
except MatchError:
    assert True
else:
    assert False
assert 0 |> factorial == 1
assert 3 |> factorial == 6

def quick_sort([]) = []

addpattern def quick_sort([head] + tail) =
    """Sort the input sequence using the quick sort algorithm."""
    quick_sort(left) + [head] + quick_sort(right) where:
        left = [x for x in tail if x < head]
        right = [x for x in tail if x >= head]

# Test cases:
assert [] |> quick_sort == []
assert [3] |> quick_sort == [3]
assert [0,1,2,3,4] |> quick_sort == [0,1,2,3,4]
assert [4,3,2,1,0] |> quick_sort == [0,1,2,3,4]
assert [3,0,4,2,1] |> quick_sort == [0,1,2,3,4]

def quick_sort(l):
    """Sort the input iterator using the quick sort algorithm."""
    match [head] :: tail in l:
        tail = reiterable(tail)
        yield from quick_sort(left) :: [head] :: quick_sort(right) where:
            left = (x for x in tail if x < head)
            right = (x for x in tail if x >= head)
    # By yielding nothing if the match falls through, we implicitly return an empty iterator.

# Test cases:
assert [] |> quick_sort |> list == []
assert [3] |> quick_sort |> list == [3]
assert [0,1,2,3,4] |> quick_sort |> list == [0,1,2,3,4]
assert [4,3,2,1,0] |> quick_sort |> list == [0,1,2,3,4]
assert [3,0,4,2,1] |> quick_sort |> list == [0,1,2,3,4]

data vector2(x, y):
    """Immutable 2-vector."""
    def __abs__(self) =
        """Return the magnitude of the 2-vector."""
        (self.x**2 + self.y**2)**0.5

# Test cases:
assert vector2(1, 2) |> str == "vector2(x=1, y=2)"
assert vector2(3, 4) |> abs == 5
assert vector2(1, 2) |> fmap$(x -> x*2) |> str == "vector2(x=2, y=4)"
v = vector2(2, 3)
try:
    v.x = 7
except AttributeError:
    assert True
else:
    assert False

data vector(*pts):
    """Immutable n-vector."""
    def __new__(cls, *pts):
        """Create a new vector from the given pts."""
        match [v `isinstance` vector] in pts:
            return v  # vector(v) where v is a vector should return v
        else:
            return pts |*> makedata$(cls)  # accesses base constructor

# Test cases:
assert vector(1, 2, 3) |> str == "vector(*pts=(1, 2, 3))"
assert vector(4, 5) |> vector |> str == "vector(*pts=(4, 5))"

data vector(*pts):
    """Immutable n-vector."""
    def __new__(cls, *pts):
        """Create a new vector from the given pts."""
        match [v `isinstance` vector] in pts:
            return v  # vector(v) where v is a vector should return v
        else:
            return pts |*> makedata$(cls)  # accesses base constructor
    def __abs__(self) =
        """Return the magnitude of the vector."""
        self.pts |> map$(.**2) |> sum |> (.**0.5)
    def __add__(self, vector(*other_pts)
                if len(other_pts) == len(self.pts)) =
        """Add two vectors together."""
        map((+), self.pts, other_pts) |*> vector
    def __sub__(self, vector(*other_pts)
                if len(other_pts) == len(self.pts)) =
        """Subtract one vector from another."""
        map((-), self.pts, other_pts) |*> vector
    def __neg__(self) =
        """Retrieve the negative of the vector."""
        self.pts |> map$(-) |*> vector
    def __mul__(self, other):
        """Scalar multiplication and dot product."""
        match vector(*other_pts) in other:
            assert len(other_pts) == len(self.pts)
            return map((*), self.pts, other_pts) |> sum  # dot product
        else:
            return self.pts |> map$(.*other) |*> vector  # scalar multiplication
    def __rmul__(self, other) =
        """Necessary to make scalar multiplication commutative."""
        self * other

# Test cases:
assert vector(1, 2, 3) |> str == "vector(*pts=(1, 2, 3))"
assert vector(4, 5) |> vector |> str == "vector(*pts=(4, 5))"
assert vector(3, 4) |> abs == 5
assert vector(1, 2) + vector(2, 3) |> str == "vector(*pts=(3, 5))"
assert vector(2, 2) - vector(0, 1) |> str == "vector(*pts=(2, 1))"
assert -vector(1, 3) |> str == "vector(*pts=(-1, -3))"
assert (vector(1, 2) == "string") is False
assert (vector(1, 2) == vector(3, 4)) is False
assert (vector(2, 4) == vector(2, 4)) is True
assert 2*vector(1, 2) |> str == "vector(*pts=(2, 4))"
assert vector(1, 2) * vector(1, 3) == 7

def diagonal_line(n) = range(n+1) |> map$(i -> (i, n-i))

assert diagonal_line(0) `isinstance` (list, tuple) is False
assert diagonal_line(0) |> list == [(0, 0)]
assert diagonal_line(1) |> list == [(0, 1), (1, 0)]

def linearized_plane(n=0) = diagonal_line(n) :: linearized_plane(n+1)

# Note: these tests use $[] notation, which we haven't introduced yet
#  but will introduce later in this case study; for now, just run the
#  tests, and make sure you get the same result as is in the comment
assert linearized_plane()$[0] == (0, 0)
assert linearized_plane()$[:3] |> list == [(0, 0), (0, 1), (1, 0)]

def vector_field() = linearized_plane() |> starmap$(vector)

# You'll need to bring in the vector class from earlier to make these work
assert vector_field()$[0] |> str == "vector(*pts=(0, 0))"
assert vector_field()$[2:3] |> list |> str == "[vector(*pts=(1, 0))]"

data vector(*pts):
    """Immutable n-vector."""
    def __new__(cls, *pts):
        """Create a new vector from the given pts."""
        match [v `isinstance` vector] in pts:
            return v  # vector(v) where v is a vector should return v
        else:
            return pts |*> makedata$(cls)  # accesses base constructor
    def __abs__(self) =
        """Return the magnitude of the vector."""
        self.pts |> map$(.**2) |> sum |> (.**0.5)
    def __add__(self, vector(*other_pts)
                if len(other_pts) == len(self.pts)) =
        """Add two vectors together."""
        map((+), self.pts, other_pts) |*> vector
    def __sub__(self, vector(*other_pts)
                if len(other_pts) == len(self.pts)) =
        """Subtract one vector from another."""
        map((-), self.pts, other_pts) |*> vector
    def __neg__(self) =
        """Retrieve the negative of the vector."""
        self.pts |> map$(-) |*> vector
    def __mul__(self, other):
        """Scalar multiplication and dot product."""
        match vector(*other_pts) in other:
            assert len(other_pts) == len(self.pts)
            return map((*), self.pts, other_pts) |> sum  # dot product
        else:
            return self.pts |> map$(.*other) |*> vector  # scalar multiplication
    def __rmul__(self, other) =
        """Necessary to make scalar multiplication commutative."""
        self * other

def diagonal_line(n) = range(n+1) |> map$(i -> (i, n-i))
def linearized_plane(n=0) = diagonal_line(n) :: linearized_plane(n+1)
def vector_field() = linearized_plane() |> starmap$(vector)

# Test cases:
assert diagonal_line(0) `isinstance` (list, tuple) is False
assert diagonal_line(0) |> list == [(0, 0)]
assert diagonal_line(1) |> list == [(0, 1), (1, 0)]
assert linearized_plane()$[0] == (0, 0)
assert linearized_plane()$[:3] |> list == [(0, 0), (0, 1), (1, 0)]
assert vector_field()$[0] |> str == "vector(*pts=(0, 0))"
assert vector_field()$[2:3] |> list |> str == "[vector(*pts=(1, 0))]"

import math  # necessary for math.acos in .angle

data vector(*pts):
    """Immutable n-vector."""
    def __new__(cls, *pts):
        """Create a new vector from the given pts."""
        match [v `isinstance` vector] in pts:
            return v  # vector(v) where v is a vector should return v
        else:
            return pts |*> makedata$(cls)  # accesses base constructor
    def __abs__(self) =
        """Return the magnitude of the vector."""
        self.pts |> map$(.**2) |> sum |> (.**0.5)
    def __add__(self, vector(*other_pts)
                if len(other_pts) == len(self.pts)) =
        """Add two vectors together."""
        map((+), self.pts, other_pts) |*> vector
    def __sub__(self, vector(*other_pts)
                if len(other_pts) == len(self.pts)) =
        """Subtract one vector from another."""
        map((-), self.pts, other_pts) |*> vector
    def __neg__(self) =
        """Retrieve the negative of the vector."""
        self.pts |> map$(-) |*> vector
    def __mul__(self, other):
        """Scalar multiplication and dot product."""
        match vector(*other_pts) in other:
            assert len(other_pts) == len(self.pts)
            return map((*), self.pts, other_pts) |> sum  # dot product
        else:
            return self.pts |> map$(.*other) |*> vector  # scalar multiplication
    def __rmul__(self, other) =
        """Necessary to make scalar multiplication commutative."""
        self * other
    # New one-line functions necessary for finding the angle between vectors:
    def __truediv__(self, other) = self.pts |> map$(x -> x/other) |*> vector
    def unit(self) = self / abs(self)
    def angle(self, other `isinstance` vector) = math.acos(self.unit() * other.unit())

# Test cases:
assert vector(3, 4) / 1 |> str == "vector(*pts=(3.0, 4.0))"
assert vector(2, 4) / 2 |> str == "vector(*pts=(1.0, 2.0))"
assert vector(0, 1).unit() |> str == "vector(*pts=(0.0, 1.0))"
assert vector(5, 0).unit() |> str == "vector(*pts=(1.0, 0.0))"
assert vector(2, 0).angle(vector(3, 0)) == 0.0
assert vector(1, 0).angle(vector(0, 2)) == math.pi/2
try:
    vector(1, 2).angle(5)
except MatchError:
    assert True
else:
    assert False
