Manipulating Tuples in Haskell
Asked Answered
I

2

6

I'm new to Haskell, I have a question regarding tuples. Is there not a way to traverse a tuple? I understand that traversal is very easy with lists but if the input is given as a tuple is there not a way to check the entire tuple as you do with a list? If that's not the case would it possible to just extract the values from the tuple into a list and perform traversal that way?

Indraft answered 13/10, 2020 at 13:29 Comment(3)
Yes, (,) is a Bitraversable instance.Corvine
The answer below is excellent, and essentially says "no". We can say more, but to do a good job of it, we need to know more, specifically: what is the surrounding code you have that makes you think a tuple is the right choice and a traversal is the thing you want?Hesketh
Well it was more of a general question I wanted to ask. In a case where if it was a requirement, I wanted to explore that idea in further detail.Indraft
P
8

In Haskell, it’s not considered idiomatic (nor is it really possible) to use the tuple as a general-purpose traversable container. Any tuple you deal with is going to have a fixed number of elements, with the types of these elements also being fixed. (This is quite different from how tuples are idiomatically used in, for example, Python.) You ask about a situation where “the input is given as a tuple” but if the input is going to have a flexible number of elements then it definitely won’t be given as a tuple—a list is a much more likely choice.

This makes tuples seem less flexible than in some other languages. The upside is that you can examine them using pattern matching. For example, if you want to evaluate some predicate for each element of a tuple and return True if the predicate passes for all of them, you would write something like

all2 :: (a -> Bool) -> (a, a) -> Bool
all2 predicate (x, y) = predicate x && predicate y

Or, for three-element tuples,

all3 :: (a -> Bool) -> (a, a, a) -> Bool
all3 predicate (x, y, z) = predicate x && predicate y && predicate z

You might be thinking, “Wait, you need a separate function for each tuple size?!” Yes, you do, and you can start to see why there’s not a lot of overlap between the use cases for tuples and the use cases for lists. The advantages of tuples are exactly that they are kind of inflexible: you always know how many values they contain, and what type those values have. The former is not really true for lists.

Is there not a way to traverse a tuple?

As far as I know, there’s no built-in way to do this. It would be easy enough to write down instructions for traversing a 2-tuple, traversing a 3-tuple, and so on, but this would have the big limitation that you’d only be able to deal with tuples whose elements all have the same type.

Think about the map function as a simple example. You can apply map to a list of type [a] as long as you have a function a -> b. In this case map looks at each a value in turn, passes it to the function, and assembles the list of resulting b values. But with a tuple, you might have three elements whose values are all different types. Your function for converting as to bs isn’t sufficient if the tuple consists of two a values and a c! If you try to start writing down the Foldable instance or the Traversable instance even just for two-element tuples, you quickly realize that those typeclasses aren’t designed to handle containers whose values might have different types.

Would it be possible to just extract the values from the tuple into a list?

Yes, but you would need a separate function for each possible size of the input tuple. For example,

tupleToList2 :: (a, a) -> [a]
tupleToList2 (x, y) = [x, y]

tupleToList3 :: (a, a, a) -> [a]
tupleToList3 (x, y, z) = [x, y, z]

The good news, of course, is that you’re never going to get a situation where you have to deal with tuples of arbitrary size, because that isn’t a thing that can happen in Haskell. Think about the type signature of a function that accepted a tuple of any size: how could you write that?

In any situation where you’re accepting a tuple as input, it’s probably not necessary to convert the tuple to a list first, because the pattern-matching syntax means that you can just address each element of the tuple individually—and you always know exactly how many such elements there are going to be.

Paisano answered 13/10, 2020 at 13:52 Comment(5)
I see, hmm. So for my second part of the question if you had lets say 5 elements within a tuple it wouldn't be easy to extract them all into a list? I'm aware of using fst and snd but for larger tuples I guess it would be more difficult to implement that or am I wrong.Indraft
@Indraft See the edit I just made. You always know a tuple’s size ahead of time, so you can convert it to a list if you want to, but it’s not possible to write a function that can act on tuples of arbitrary different sizes. Note that even simple functions like fst can only act on tuples of a fixed size (two elements, in the case of fst and snd).Paisano
Thank you so much. This has been extremely useful. Maybe I was looking incorrectly but it really didn't click with me at all before your answer so thanks a lot.Indraft
@Indraft Happy to help!Paisano
It might be interesting to mention things like Pair Identity Identity and Sized Vector, which are tuple-like entities (in that their size is statically known) but guarantee all the elements have the same type (and so can be and are instances of Traversable).Hesketh
W
6

If your tuple is a homogeneous tuple, and you don't mind to use the third-party package, then lens provides some functions to traverse each elements in an arbitrary tuple.

ghci> :m +Control.Lens
ghci> over each (*10) (1, 2, 3, 4, 5)   --traverse each element
(10,20,30,40,50)

Control.Lens.Tuple provides some lens to get and set the nth element up to 19th.

You can explore the lens package for more information. If you want to learn the lens package, Optics by examples by Chris Penner is a good book.

Wingding answered 13/10, 2020 at 15:35 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.