# 05-Pattern-Matching-in-Functions.purs

module Syntax.Basic.PatternMatching where

import Prelude

-- Given a data type like this:
data Fruit
= Apple
| Orange
| Banana
| Cherry
| Tomato -- because why not!?

-- Pattern Matching: Basic idea and order of matching
mkString :: Fruit -> String {-
if the arg is _ = then return _ -}
mkString Apple           = "apple"

{-  else if the arg is _ = then return _ -}
mkString Orange          = "orange"

{-  else if the arg is _ = then return _ -}
mkString Banana          = "banana"

{-  else if the arg is _ = then return _ -}
mkString Cherry          = "cherry"

{-  else if the arg is _ = then return _ -}
mkString Tomato          = "tomato"

-- The above pattern match is "exhaustive" because there are no other
-- Fruit values against which one could match.

-- Pattern Matching: Literal values and catching all values

literalValue :: String -> String
literalValue "a" = "Return this string if arg is 'a'"
literalValue "b" = "Return this string if arg is 'b'"
literalValue "c" = "Return this string if arg is 'c'"
literalValue _   = "ignore input and return this default value"

-- syntax sugar for pattern-matching literal arrays
array :: Array Int -> String
array []           = "an empty array"
array [0]          = "an array with one value that is 0"
array [0, 1]       = "an array with two values, 0 and 1"
array [0, 1, a, b] = "an array with four values, starting with 0 and 1 \
\ and binding the third and fouth to names 'a' and 'b'"
array [-1, _ ]     = "an array of two values, '-1' and another value that \
\ will not be used in the body of this function."
array _            = "catchall for arrays. This is needed to make this \
\ example compile"

-- Pattern Matching: Unwrapping Data Constructors
data A_Type
= AnInt Int
| Outer A_Type -- recursive type!
| Inner Int

f :: A_Type -> String {-
-- Syntax
f patternMatch = bodyToRunIfPatternWasMatched

where 'patternMatch' is:
- literal value
- DataConstructorWithNoArgs
- (DataConstructor withArgBoundToThisBinding)
- (DataConstructor "with arg whose value is this literal value")
- bindingForEntireValue@(literalValue)
- bindingForEntireValue@(DataConstructorWithNoArgs)
- bindingForEntireValue@(DataConstructor withArgBoundToThisBinding)
- bindingForEntireValue@(DataConstructor "with arg whose value is this literal value")

-- Example

f the pattern match     = description of what was matched -}
f (Inner 0)             = "a value of type Inner whose value is 0"
f (Inner int)           = "a value of type Inner, binding its value to 'int' \
\name for usage in function body"
f (Outer (Inner int))   = "a value of type Outer, whose Inner value is bound \
\to int name for usage in function body"
f object@(AnInt 4)      = "a value of type AnInt whose value is '4', \
\binding the entire object to the object name for \
\usage in function body"
f _                     = "ignores input and matches everything; \
\acts as a default / catch all case"

-- Pattern Matching: Regular Guards
g :: Int -> Int -> String {-
g x y | condition1  = return this if condition1 is true
| condition2  = return this if condition2 is true
| ...         = ...
| conditionN  = return this if conditionN is true
| otherwise   = default case-}
g x y | x + y == 0 = "x == -y"
| x - y == 0 = "x == y"
| x * y == 0 = "x == 0 || y == 0"
| otherwise = "some other value"

-- Pattern Matching: Single and Multiple Guards
h :: Int -> Int -> String
h x y | x == 4 && y == 5 = "body"

-- ... same as...
-- | x == 4 && y == 6 = "body"
| x == 4, y == 6   = "body"

-- | condition1, condition2 = body
| x == 4, y == 6   = "body"

{-  ... or when using syntax sugar...
| condition1
, condition2 = body -}
| x == 3
, y == 2           = "body"

-- It's wise to separate mulitple guards with a blank line for readability.
| otherwise        = "default"

-- Pattern Matching: Single Pattern Guard
j :: Int -> String {-
j x | returnedValue <- function arg1 arg2 argN = body if match occurs -}
j x | (Box 2) <- toBox x = "Calling toBox x returned a Box with 2 inside of it"
| (Box y) <- toBox x = concat "The 'y' value was: " (toString y)

-- Pattern Matching: Multiple Pattern Guards
p :: Int -> Int -> String {-
p x y | returnedValue1 <- functionCall1, returnedValue2 <- functionCall2 = body -}
p x y | (Box 2) <- toBox x, (Box 3) <- toBox (x - 1) = "without syntax sugar"

{-   ... or for easier reading, there is sugar syntax:
p x y | returnedValue1 <- functionCall1
, returnedValue2 <- functionCall2 = body -}
| (Box a) <- toBox x
, (Box b) <- toBox (x * 2) = "with syntax sugar"

| otherwise = "some other value"

-- Different guards can be mixed:
q :: Int -> Int -> String
q x y | x == 3                   = "3"
| x == 5, y == 5           = "5"
| (Box 2) <- toBox x       = "2?"

| (Box 2) <- toBox x
, y == 4                   = "curious, no?"

| (Box a) <- toBox x
, (Box b) <- toBox (y * 2) = "something?"

| otherwise                = "catch-all"

-- necessary for this to compile
data Box a = Box a

toBox :: Int -> Box Int
toBox 1 = Box 2
toBox _ = Box 0

concat :: String -> String -> String
concat left right = left <> right

toString :: forall a. Show a => a -> String
toString = show
`