Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can this haskell sample be shorter?

Tags:

haskell

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'

like image 788
Dmitry Gusarov Avatar asked Aug 28 '12 05:08

Dmitry Gusarov


4 Answers

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
like image 183
Landei Avatar answered Sep 28 '22 05:09

Landei


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)

like image 25
Thomas M. DuBuisson Avatar answered Sep 28 '22 07:09

Thomas M. DuBuisson


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.

like image 20
Gabriella Gonzalez Avatar answered Sep 28 '22 05:09

Gabriella Gonzalez


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
like image 25
Daniel Wagner Avatar answered Sep 28 '22 06:09

Daniel Wagner