Find the nth element in a tuple using the function below

Viewed 254

"Use the below function with two parameters:

nth :: (a,a,a,a,a) -> Int -> a

where the Int value should return the Int-th value of the five-element tuple." I tried:

nth (a,b,c,d,e) x = (a,b,c,d,e) !! x

But GHC gave me an error message:

file.hs:11:21: error:
* Couldn't match expected type `[a1]'
              with actual type `(a, b, c, d, e)'
* In the first argument of `(!!)', namely `(a, b, c, d, e)'
  In the expression: (a, b, c, d, e) !! x
  In an equation for `nth':
      nth (a, b, c, d, e) x = (a, b, c, d, e) !! x
* Relevant bindings include
    e :: e (bound at file.hs:11:14)
    d :: d (bound at file.hs:11:12)
    c :: c (bound at file.hs:11:10)
    b :: b (bound at file.hs:11:8)
    a :: a (bound at file.hs:11:6)
    nth :: (a, b, c, d, e) -> Int -> a1
      (bound at file.hs:11:1)

What should I do? How should I write the tuple part of this equation? Thanks for your answers in advance!

5 Answers

You can not use (!!) :: [a] -> Int -> a since, as the signature says, it works on lists, not tuples. You can work with pattern matching and implement this as:

nth :: (a, a, a, a, a) -> Int -> a
nth (a, _, _, _, _) 0 = a
nth (_, b, _, _, _) 1 = b
nth (_, _, c, _, _) 2 = c
nth (_, _, _, d, _) 3 = d
nth (_, _, _, _, e) 4 = e

There have been proposals where a 3-tuple is defined as (a, (b, c)) such that it is thus a recursive structure where an n-tuple is defined as a 2-tuple with an n-1-tuple as second item. But this is not the case (at the moment).

You can avoid some of the repetition from the existing pattern-matching solutions by separating the pattern match for the tuple from the match on n:

nth :: (a, a, a, a, a) -> Int -> a
nth (a, b, c, d, e) n = case n of
  0 -> a
  1 -> b
  2 -> c
  3 -> d
  4 -> e

You should also consider what you want to happen when n is not in the range [0, 4]. As written, you will get a non-exhaustive pattern error at runtime. It would be more honest to either return a Maybe a or take a more constrained type as input, like

data QuintupleIndex = Zero | One | Two | Three | Four

(converted from my own comments)

There's actually an easy fix that haven't been mentioned in other answers: you'll just have to change two symbols:

nth :: (a, a, a, a, a) -> Int -> a
nth (a,b,c,d,e) x = [a,b,c,d,e] !! x

-- or alternatively
nth (a,b,c,d,e) = ([a,b,c,d,e] !!)

There's still pattern matching to be done to find the n-th element, but it's now handled implicitly in !!.

This works because list is homogeneous, meaning all of its elements are of the same type. On the left hand side of nth, you have the pattern (a,b,c,d,e), so type inference gives the most general constraint a :: a, b :: b, ... as indicated in the error message. However, once [a,b,c,d,e] is seen on the right hand side, it gives type inference a clue that all of a, b, c, d, e are of the same type! So type inference can now determine that (a,b,c,d,e) :: (a,a,a,a,a) typechecks.

In Haskell, tuples are not meant to be container types that you index with Int like a list (unlike tuples in Python and some other languages), rather it's more of a structure that each field serves a specific meaning (think it as struct in C), if you do want efficient indexing into that, you'll need field selectors rather than natural numbers:

data Five a = Five { _1 :: a,  _2 :: a,  _3 :: a,  _4 :: a,  _5 :: a }

Now that you can access fields with _x, but the type of nth will be :

nth :: Five a -> (Five a -> a) -> a
nth v f = f v

-- or pointfreely:
nth = flip id

(another nice thing about this is that you have a complete function rather than one that might fail pattern matches and raise an exception)

Note that field selectors are functions rather than natural numbers - if you are dealing with list indices, it makes sense to ask "what is the next element?", but less so when it comes to a structure.

The (!!) function only works on lists.

For tuples, at least those of more than 2 elements, the only way to access the elements is by direct pattern matching:

nth :: (a,a,a,a,a) -> Int -> a
nth (a, _, _, _, _) 0 = a
nth (_, a, _, _, _) 1 = a
nth (_, _, a, _, _) 2 = a
nth (_, _, _, a, _) 3 = a
nth (_, _, _, _, a) 4 = a

(note the _s, which mean "I don't care what this value" is. You could give them a name instead - eg match on (a, b, c, d, e) on each line and have the returned value being a on the first line, b on the second and so on. But it's less visual "noise" to only name the values you care about.)

Also note that this function will crash with a "non-exhaustive patterns" error if the integer argument is anything outside 0-4. But using (!!) on a list of 5 elements has a similar flaw. You can easily add a default case to the end, although that's not easy when working with a completely generic type a because there is no value that will work as a return value of any type the function might be called with!

a simple solution using guards is as follows:

nth :: (a,a,a,a,a) -> Int -> a
nth (a, b, c, d, e) i
  | i == 0    = a
  | i == 1    = b
  | i == 2    = c
  | i == 3    = d
  | otherwise = e
Related