This is my solution to exercise from YAHT:
Exercise 4.6 Write a datatype Tuple which can hold one, two, three or four elements, depending on the constructor (that is, there should be four constructors, one for each number of arguments). Also provide functions tuple1 through tuple4 which take a tuple and return Just the value in that position, or Nothing if the number is in valid (i.e., you ask for the tuple4 on a tuple holding only two elements).
When I wrote a first line I was excited about simplicity comparing to C#
data Tuplex a b c d = Tuple1 a | Tuple2 a b | Tuple3 a b c | Tuple4 a b c d -- class Tuplex<a,b,c,d> { -- Tuplex(a p1){ _p1 = p1; } -- Tuplex(a p1, b p2){ _p1 = p1; _p2 = p2; } -- Tuplex(a p1, b p2, c p3){ _p1 = p1; _p2 = p2; _p3 = p3; } -- Tuplex(a p1, b p2, c p3, d p4){ _p1 = p1; _p2 = p2; _p3 = p3; _p4 = p4; } -- public Nullable<a> _p1; -- public Nullable<b> _p2; -- public Nullable<c> _p3; -- public Nullable<d> _p4; -- }
In C# I can access any field without problem, but here I should write an 'accessor functions', right? And amount of code here makes me sad.
Can I have shorter code here?
tuple1 ∷ Tuplex a b c d → Maybe a tuple2 ∷ Tuplex a b c d → Maybe b tuple3 ∷ Tuplex a b c d → Maybe c tuple4 ∷ Tuplex a b c d → Maybe d tuple1 (Tuple1 a) = Just a tuple1 (Tuple2 a b) = Just a tuple1 (Tuple3 a b c) = Just a tuple1 (Tuple4 a b c d) = Just a tuple2 (Tuple1 a) = Nothing tuple2 (Tuple2 a b) = Just b tuple2 (Tuple3 a b c) = Just b tuple2 (Tuple4 a b c d) = Just b tuple3 (Tuple1 a) = Nothing tuple3 (Tuple2 a b) = Nothing tuple3 (Tuple3 a b c) = Just c tuple3 (Tuple4 a b c d) = Just c tuple4 (Tuple1 a) = Nothing tuple4 (Tuple2 a b) = Nothing tuple4 (Tuple3 a b c) = Nothing tuple4 (Tuple4 a b c d) = Just d -- unit tests prop_tx1 = tuple1 (Tuple1 4) ≡ Just 4 prop_tx2 = tuple1 (Tuple2 4 'q') ≡ Just 4 prop_tx3 = tuple2 (Tuple1 4) ≡ (Nothing ∷ Maybe Char) prop_tx4 = tuple2 (Tuple2 4 'q') ≡ Just 'q'
I would try to keep it dead simple:
data Tuplex a b c d = Tuple1 a | Tuple2 a b | Tuple3 a b c | Tuple4 a b c d
toMaybes (Tuple1 p) = (Just p, Nothing, Nothing, Nothing)
toMaybes (Tuple2 p q) = (Just p, Just q, Nothing, Nothing)
toMaybes (Tuple3 p q r) = (Just p, Just q, Just r, Nothing)
toMaybes (Tuple4 p q r s) = (Just p, Just q, Just r, Just s)
tuple1 t = p where (p,_,_,_) = toMaybes t
tuple2 t = q where (_,q,_,_) = toMaybes t
tuple3 t = r where (_,_,r,_) = toMaybes t
tuple4 t = s where (_,_,_,s) = toMaybes t
Just give your tuples field names!
data Tuplex a b c d = Tuple1 { tuple1 :: a }
| Tuple2 { tuple1 :: a
, tuple2 :: b }
| Tuple3 { tuple1 :: a
, tuple2 :: b
, tuple3 :: c }
| Tuple4 { tuple1 :: a
, tuple2 :: b
, tuple3 :: c
, tuple4 :: d }
And as a result you have functions with types of:
tuple1 :: Tuplex a b c d -> a
tuple2 :: Tuplex a b c d -> b
-- etc
Using field names of records like this is actually less common than you might expect in Haskell due to the ease of pattern matching and, in at least some circles, the popularity of the RecordWildCard
extension which allows you to do things like:
function (Tuples3 {..}) =
-- now you have variables tuple1 :: a, tuple2 :: b, etc.
(when using record wild cards it might be better to name your tuple fields something a bit simpler, like tupA, tupB, tupC, tupD)
import Safe (atMay) -- from the 'safe' package
toList (Tuple1 a) = [a]
toList (Tuple2 a b) = [a, b]
toList (Tuple3 a b c) = [a, b, c]
toList (Tuple4 a b c d) = [a, b, c, d]
tuple n t = atMay (toList t) n
[tuple1, tuple2, tuple3, tuple4] = map tuple [1..4]
Edit: Vitus correctly point out that this only works for a homogeneous tuple, so this is not a correct answer. In that case I defer to Daniel's answer.
Here's one way: centralize your pattern match.
unTuplex f1 f2 f3 f4 t = case t of
Tuple1 a -> f1 a
Tuple2 a b -> f2 a b
Tuple3 a b c -> f3 a b c
Tuple4 a b c d -> f4 a b c d
tuple1 = unTuplex (\a -> Just a ) (\a _ -> Just a ) (\a _ _ -> Just a ) (\a _ _ _ -> Just a)
tuple2 = unTuplex (\_ -> Nothing) (\_ b -> Just b ) (\_ b _ -> Just b ) (\_ b _ _ -> Just b)
tuple3 = unTuplex (\_ -> Nothing) (\_ _ -> Nothing) (\_ _ c -> Just c ) (\_ _ c _ -> Just c)
tuple4 = unTuplex (\_ -> Nothing) (\_ _ -> Nothing) (\_ _ _ -> Nothing) (\_ _ _ d -> Just d)
Alternately, you could explicitly express the nested structure:
{-# LANGUAGE NoMonomorphismRestriction #-}
data DONE = DONE -- could just use (), but this is a pretty descriptive name
type Tuplex a b c d = Maybe (a, Maybe (b, Maybe (c, Maybe (d, DONE))))
tuple1 x = x >>= return . fst -- or tuple1 = fmap fst
tuple2 x = x >>= tuple1 . snd
tuple3 x = x >>= tuple2 . snd
tuple4 x = x >>= tuple3 . snd
Then tuple1
has (among others) the type Tuplex a b c d -> Maybe a
, and on up to tuple4
which has (again, among others) the type Tuplex a b c d -> Maybe d
.
edit: ...actually, this suggests an alternate continuation of the first approach.
import Control.Monad
decrement :: Tuplex a b c d -> Maybe (Tuplex b c d t)
decrement (Tuple1 a) = Nothing
decrement (Tuple2 a b) = Just (Tuple1 b)
decrement (Tuple3 a b c) = Just (Tuple2 b c)
decrement (Tuple4 a b c d) = Just (Tuple3 b c d)
zero :: Tuplex a b c d -> a
zero (Tuple1 a) = a
zero (Tuple2 a b) = a
zero (Tuple3 a b c) = a
zero (Tuple4 a b c d) = a
tuple1 = Just . zero
tuple2 = decrement >=> tuple1
tuple3 = decrement >=> tuple2
tuple4 = decrement >=> tuple3
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With