Find the nth element in a tuple using the function below
Asked Answered
S

5

6

"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!

Sibelle answered 8/10, 2021 at 16:56 Comment(5)
there's actually an easy fix, you'll just have to change two symbols: nth (a,b,c,d,e) x = [a,b,c,d,e] !! x. It might not be an efficient implementation but I'll argue n-tuples are not really supposed to be a homogeneous container type, nor efficient random access with Int anyway. Plus it's easy to type :)Volt
@Volt of course it's not homogeneous so the list trick just won't work at all -- you can't put different type values in one list.Goldfilled
@WillNess which part of it doesn't work? The question wants something of type nth :: (a,a,a,a,a) -> Int -> a and I see no correctness issue.Volt
@Volt you're right, I wasn't paying attention to this part and assumed a more general type. which wouldn't make any sense either, since we must return one type. d'oh! (I blame sleep deprivation ;) ) ----- your answer is missing. :)Goldfilled
@WillNess I originally just wanted to leave it as a comment as I myself considered that to be just a trick rather than "the proper way", but yeah I think I can convert it to an answer to elaborate a bit more.Volt
F
6

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).

Flageolet answered 8/10, 2021 at 17:1 Comment(0)
L
5

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
Lurcher answered 8/10, 2021 at 17:21 Comment(3)
I guess the go is there to avoid repeating nth (a, b, c, d, e) lots of times? If so, you could nth (a, b, c, d, e) n = case n of .... If you must have the "memoization" behavior there's LambdaCase: nth (a, b, c, d, e) = \case ....Outcry
@DanielWagner Yes, just to avoid repeating the tuple five times. Using case is a good suggestion - I guess I was being pointlessly point-free. I don't really like to suggest language extensions to newcomers. I'm curious what memoization behavior you're referring to: what different operational semantics does the LambdaCase solution have from case/of?Lurcher
For example, map (nth (1,2,3,4,5)) will re-pattern-match once for each list element with f x y = case y of but only once with f x = \case (or f x = \y -> case y of).Outcry
V
3

(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.

Volt answered 9/10, 2021 at 21:10 Comment(0)
A
2

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!

Ahem answered 8/10, 2021 at 17:1 Comment(0)
C
2

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
Cecilycecity answered 8/10, 2021 at 17:1 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.