Idiomatic tuples

What is the idiomatic way of representing tuples in golang? Do people just use a struct or is there something lighter weight?

How could something be lighter than a struct?

A tuple isn’t really much more that a generic 2 field struct.

As soon as 1.18 is out you can even define it yourself.

struct Tuple[A, B] {
   fst A
   snd B
}

Or at least something close to that… Haven’t had time to play much with Golangs generics yet…

2 Likes

the same way you do most other things: either write the code yourself or find some external package.

There’s some good discussion on this topic here. Also @NobbZ I think your example isn’t quite a complete tuple. Your Tuple[A, B] example is a 2-tuple, AKA pair but a tuple can have n values.

Back to the original question: I would want to know more about your specific use case. Many years ago I built a multi-year WPF project with a team. I found we used tuples often when we were being lazy and didn’t want to be too specific about return types. And it always resulted in hard-to-read code because you have to just keep it in your head which value represents what based on its’ order. The codebase would have almost always been better served with some sort of class/struct with descriptive values (or projecting into anonymous types in the case of .NET). What’s your use case?

In Go’s case I find the idea of tuples far less interesting since Go has built-in support for multiple return values. If, for example, I wanted to define something in a language like C# that returns Tuple<string, int, int> I would instead just return (string, int, int). No Tuple needed.

Declaring something inline and want to declare multiple values? Let’s take a look at a tuple:

// c# using tuple
var myTuple = new Tuple<string, int>("Jack", 78);
// Yikes. You have to just know that item 1 is name and item 2 is ID...
Console.WriteLine("Name {0} ID {1}", myTuple.Item1, myTuple.Item2);

… vs just declaring multiple values:

name, id := "Jack", 78
// Named variables? We are truly living in the future...
fmt.Println("Name:", name, ", ID:", id)

In summary: if you want simple inline variable storage, declare muple variables at once. If you want to return a tuple, just return multiple values. If you have some other use case, let me know!

3 Likes

A typical use case is that tuples are used as a quick-and-dirty struct to keep values together and store them in containers. In Go a struct is a better approach on so many levels. The only downside is that you must make the small effort to declare the struct type, but even so, anonymous structs can alleviate some the “burden” 10 things you (probably) don't know about Go

I hesitate to mention it because someone may take it is good approach, but the closest type in Go to tuples in dynamic languages is an array of interface{}. An array is fixed length, and interface{} can hold a value of any type. The nuisance of actually using it should dissuade any one from resorting to this as a tuple replacement. Use a struct instead.

I was just preparing for a screening interview and one of the problems (language agnostic) used in the past used tuples as input. I wanted to be prepared to implement a solution to such a problem.

Also, I was just curious. I agree that in most languages, whenever I wanted to use a tuple, a struct has almost always worked better.

Interesting. So you’re talking about an interview that’s generic but you’re writing code and your language of choice is go? I would just create a quick struct with an explanation that there isn’t really an idiomatic way of representing tuples in go. Or if their example is using the same type of objects you could solve it with a variadic function. Add generics and you could re-use the function with different types:

func doStuff[T any](input ...T) {
	for _, v := range input {
		fmt.Println(v)
	}
}

// Works
doStuff("Hello", "world")
// Also works
doStuff(1, 2, 3)
// Doesn't work with multiple types
doStuff("Hello", 1)

You COULD do what @mje mentioned and use interface{} for something more generic:

func dontDoThis(input ...interface{}) {
	for _, v := range input {
		fmt.Println(v)
	}
}

// Works but at what cost?
dontDoThis("Hello", 1)

… but that is generally frowned upon as more specific types are usually preferred. I mean - how useful is a function if you know nothing about the input? What can you reasonably do with the input at that point?

i would just go with array of unnamed struct type for this:

Technical phone screen (LC Meeting Rooms): given a list of appointments represented as tuples (start, end), write a function that returns True if all the appointments can be handled by one doctor with no overlap and False if not.

You can solve this with anonymous structs if you can assume you can create the array or slice using a literal, rather than accepting it as a function argument. If the struct must be specified in a function or method argument or return, fields or other structs or in a host of other constructs, it can’t be anonymous.

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.