Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

When should Haskell functions take tuples, rather than multiple arguments?

In http://www.haskell.org/pipermail/haskell-cafe/2007-August/030096.html the typeclass method collide is defined as taking a 2-tuple as its single argument, rather than two "normal" arguments (I think I understand partial application, etc.).

{-# OPTIONS_GHC -fglasgow-exts
        -fallow-undecidable-instances
        -fallow-overlapping-instances #-}

module Collide where

class Collide a b where
    collide :: (a,b) -> String

data Solid = Solid
data Asteroid = Asteroid
data Planet = Planet
data Jupiter = Jupiter
data Earth = Earth

instance Collide Asteroid Planet where
    collide (Asteroid, Planet) = "an asteroid hit a planet"

instance Collide Asteroid Earth where
    collide (Asteroid, Earth) = "the end of the dinos"

-- Needs overlapping and undecidable instances
instance Collide a b => Collide b a where
    collide (a,b) = collide (b, a)

-- ghci output
*Collide> collide (Asteroid, Earth)
"the end of the dinos"
*Collide> collide (Earth, Asteroid)
"the end of the dinos"

What is the purpose of this?

When is it better to use a tuple argument rather than multiple arguments?

like image 512
fadedbee Avatar asked Jul 09 '14 14:07

fadedbee


People also ask

What are tuples in Haskell?

Haskell tuple is the structure by which we can manage to store or group the values inside it. The tuple in Haskell is a fixed sized structure which can hold any type of data type inside it.

Are tuples immutable in Haskell?

A tuple is a sequence of values. The values can be of any type, and they are indexed by an integer, so tuples are not like lists. Tuples are immutable which means you cannot add more elements to the tuple as the program runs.

How do you declare a tuple in Haskell?

Use parentheses and commas to create tuples. Use one comma to create a pair. Use more commas to create tuples with more components. Note that it is also possible to declare tuples using in their unsugared form.


2 Answers

I almost never write functions that take tuples as arguments. If the situation arises where variables are inherently connected (as bheklilr mentioned in a comment), I'm more likely to box that up into it's own separate data type and pattern match on it.

One common situation where you might want to define a function that takes tuples as arguments is when you have a list (or any arbitrary Functor) of tuples that you generate on the fly, but want to map over it with some function, e.g.

grid :: [(Int, Int)]
grid = (,) <$> [1..10] <*> [1..10]

You might want to, say, add the first and second values of all of the tuples in your grid (for whatever reason), which you could do by mapping a tuple-consuming function over grid:

addTuple :: (Int, Int) -> Int
addTuple (x, y) = x + y

sumPoints :: [(Int, Int)] -> [Int]
sumPoints = map addTuple

What I would rather do in this situation is just use uncurry (:: (a -> b -> c) -> (a, b) -> c) to use + just like normal:

sumPoints :: [(Int, Int)] -> [Int]
sumPoints = map (uncurry (+))

This is arguably clearer and definitely shorter; it's also extremely easy to define higher-order analogues such as uncurry3, for example:

> let uncurry3 f (a, b, c) = f a b c
> uncurry3 (\a b c -> a + b + c) (1, 2, 3)
6
like image 115
Benjamin Kovach Avatar answered Sep 29 '22 11:09

Benjamin Kovach


I would say, normally, function should be curried (so no tuples) except is the arguments themself are tuples. For example If you write a function to add two numbers, you have 2 arguments, so you should write it :

add :: Num a => a -> a -> a
add x y = x + y

Now, if for some reason your are using 2-uple as 2-D point, and you want to add two points. That's till two arguments, which appears to be tuples so you would write like that

add :: Num a => (a,a) -> (a,a) -> (a,a)
add (x,y) (x,y') = (x+x', y+y')

Writing

add :: Num a => a -> a -> a -> a -> (a,a)
add a b c d = (a+c, b+d)

Wouldn't really make sense because the entity your dealing with are tuples. You wouldn't write that way either

add :: Num a => ((a,a),(a,a)) -> (a,a)

In our example, it's probable that the collide function is always called in a context when thing to check are served as a tuple (because maybe there is stage which collect all possible collision resulting in a list of 2-uples). In that situation it might be easier then to have a uncurried function, so you can map collide over it.

like image 23
mb14 Avatar answered Sep 29 '22 12:09

mb14