tags:

views:

360

answers:

5

I've been doing a lot of work with tuples and lists of tuples recently and I've been wondering if I'm being sensible.

Things feel awkward and clunky which for me signals that I'm doing something wrong.

For example I've written three convenience functions for getting the first, second and third value in a tuple of 3 values.

Is there a better way I'm missing?

Are there more general functions that allow you to compose and manipulate tuple data?

Here are some things I am trying to do that feel should be generalisable.

Extracting values: Do I need to create a version of fst,snd,etc... for tuples of size two, three, four and five, etc...?

fst3(x,_,_) = x
fst4(x,_,_,_) = x

Manipulating values: Can you increment the last value of every tuple in a list of pairs and then use that same function to increment the last value of every tuple in a list of triples?

Zipping and Unzipping values: There is a zip and a zip3. Do I also need a zip4? or is there some way of creating a general zip function?

Sorry if this seems subjective, I honestly don't know if this is even possible or if I'm wasting my time writing 3 extra functions every time I need a general solution.

Thank you for any help you can give!

+1  A: 

There is no way to generalise over tuple-size without using extensions like Template Haskell. So if we just consider plain haskell: Yes, you do need to write versions of fst etc. for each tuple-size and no, you can't write a general zip method.

sepp2k
This is because each size of tuple is a different type. There is no way in the type system to say just "a tuple" because there is no such type — there are pairs and 3-tuples and 4-tuples and they're all different things.
Chuck
+5  A: 

Extracting values

Yes, you need to write fstN yourself. But why not extract it in pattern matching?

Zipping and Unzipping values

Data.List already provides up to zip7. For general zipN, use a ZipList.

See http://stackoverflow.com/questions/2468226/how-to-zip-multiple-lists-in-haskell.

Manipulating values

Not without extensions. Since all tuples are of different types, you have to create a type class, for example:

{-# LANGUAGE MultiParamTypeClasses, FlexibleInstances, FunctionalDependencies #-}

class Firstable a b c | a -> b, a -> c where
    firstOf :: a -> b
    restOf :: a -> c
    concatenate :: b -> c -> a

instance Firstable [a] a [a] where
    firstOf = head
    restOf = tail
    concatenate = (:)

instance Firstable (a,b) a b where
    firstOf = fst
    restOf = snd
    concatenate = (,)

instance Firstable (a,b,c) a (b,c) where
    firstOf (x,_,_) = x
    restOf (_,x,y) = (x,y)
    concatenate x (y,z) = (x,y,z)

instance Firstable (a,b,c,d) a (b,c,d) where
    firstOf (x,_,_,_) = x
    restOf (_,x,y,z) = (x,y,z)
    concatenate x (y,z,w) = (x,y,z,w)

instance Firstable (a,b,c,d,e) a (b,c,d,e) where
    firstOf (x,_,_,_,_) = x
    restOf (_,x,y,z,w) = (x,y,z,w)
    concatenate x (y,z,w,t) = (x,y,z,w,t)

Then you could use

incFirst :: (Num b, Firstable a b c) => a -> a
incFirst x = (1 + firstOf x) `concatenate` restOf x

main = do
    print $ map incFirst [(1,2),(3,4),(5,6)]
    print $ map incFirst [(1,3,6,7),(2,5,-2,4)]

(lastOf is similar.)

But why not use separate functions?

KennyTM
Well creating 5+ functions that do the exact same thing seems wasteful to me. (Not that I have any problem doing it if necessary mind). Maybe I just like things very DRY. Could be taking it too far I suppose.
toofarsideways
Shameless plug: `Firstable`, `Secondable`, `Thirdable`, ... `Nthable`! http://hackage.haskell.org/package/nthable
Porges
A: 

Haskell's built-in fst and snd only support tuple pairs, so you're right to define your own. If you want to increment the last value in a list, reverse the list, work from that end, and reverse it back. If you want that increment to work for lists and lists of tuples, just define a new function that increments for those data types and call that within your increment function for the list. @KennyTM has answered the zipN question.

Working with lists and tuples in Haskell is a little different than a lot of languages, but after a while of they feel especially natural and powerful in comparison.

sczizzo
Sorry I was not clear on what I meant(I have corrected the question). I did not mean: Can you increment the last value of a list of pairs and then use that same function to increment the last value a list of triples? I meant: Can you increment the last value of every tuple in a list of pairs and then use that same function to increment the last value of every tuple in a list of triples? Sorry for the confusion!
toofarsideways
+3  A: 

Once tuples get more than size 3 or so, and/or the same tuple type gets used widely, it's best to use a record.

Ganesh Sittampalam
+6  A: 

When I start to have big tuples, I use Haskell's pitiful excuse for record syntax to give each element a name, e.g.,

data LatticeOperations a = LO { bot :: a
                              , top :: a
                              , glb :: a
                              , lub :: a
                              , le  :: a
                              }

This is a five-tuple, but the names turn into functions that select the individual elements.

For changing tuples, you have record-update syntax. In the example I've just given, it makes no sense to replace just one element, but I might, for example, refine the partial order and replace three elements

lattice { le = le', glb = glb', lub = lub' }

And of course if you have a big record and are just trying to increment you can do something like

data FatRecord = FR { count :: Int, ... }

fat = fat { count = count fat + 1 }

I don't think record syntax helps with zip and unzip.

Norman Ramsey