-- | Haskell has built-in syntax for tuples, so you can define 2D points like
-- this:
--
-- > origin :: (Float, Float)
-- > origin =
-- >   (0, 0)
-- >
-- > position :: (Float, Float)
-- > position =
-- >   (3, 4)
--
-- This module is a bunch of helpers for working with 2-tuples.
--
-- __Note 1:__ For more complex data, it is best to switch to records. So
-- instead of representing a 3D point as @(3,4,5)@ and not having any helper
-- functions, represent it as @Coords { x = 3, y = 4, z = 5 }@ and use all the
-- built-in record syntax!
--
-- __Note 2:__ If your record contains a bunch of @Bool@ and @Maybe@ values, you may want to upgrade to union types. Check out [Joël’s post][https://robots.thoughtbot.com/modeling-with-union-types] for more info on this. (Picking appropriate data structures is super important in Haskell!)
module Tuple
  ( -- * Create
    pair,

    -- * Access
    first,
    second,

    -- * Map
    mapFirst,
    mapSecond,
    mapBoth,
  )
where

-- CREATE

-- | Create a 2-tuple.
--
-- > -- pair 3 4 == (3, 4)
-- >
-- > zip :: List a -> List b -> List (a, b)
-- > zip xs ys =
-- >   List.map2 Tuple.pair xs ys
pair :: a -> b -> (a, b)
pair a b =
  (a, b)

-- ACCESS

-- | Extract the first value from a tuple.
--
-- > first (3, 4) == 3
-- > first ("john", "doe") == "john"
first :: (a, b) -> a
first (x, _) =
  x

-- | Extract the second value from a tuple.
--
-- > second (3, 4) == 4
-- > second ("john", "doe") == "doe"
second :: (a, b) -> b
second (_, y) =
  y

-- MAP

-- | Transform the first value in a tuple.
--
-- > import String
-- >
-- > mapFirst String.reverse ("stressed", 16) == ("desserts", 16)
-- > mapFirst String.length  ("stressed", 16) == (8, 16)
mapFirst :: (a -> x) -> (a, b) -> (x, b)
mapFirst func (x, y) =
  (func x, y)

-- | Transform the second value in a tuple.
--
-- > mapSecond sqrt   ("stressed", 16) == ("stressed", 4)
-- > mapSecond negate ("stressed", 16) == ("stressed", -16)
mapSecond :: (b -> y) -> (a, b) -> (a, y)
mapSecond func (x, y) =
  (x, func y)

-- | Transform both parts of a tuple.
--
-- > import String
-- >
-- > mapBoth String.reverse sqrt  ("stressed", 16) == ("desserts", 4)
-- > mapBoth String.length negate ("stressed", 16) == (8, -16)
mapBoth :: (a -> x) -> (b -> y) -> (a, b) -> (x, y)
mapBoth funcA funcB (x, y) =
  (funcA x, funcB y)