Imitating tuple in golang

367 views
Skip to first unread message

lijh8

unread,
Aug 8, 2024, 3:35:31 PMAug 8
to golang-nuts
Hi community,

I try to use slice of any to imitate the tuple type,
and use this function to compare two slices: a, b.

How can I improve it?

Thanks

```

package tuple2

func Cmp(a, b []any) (int, bool) {
for i := 0; i != min(len(a), len(b)); i++ {
if a[i] == nil && b[i] != nil {
return -1, true
}
if a[i] != nil && b[i] == nil {
return 1, true
}

a_bool, a_bool_ok := a[i].(bool)
a_string, a_string_ok := a[i].(string)
a_int, a_int_ok := a[i].(int)
a_float64, a_float64_ok := a[i].(float64)

b_bool, b_bool_ok := b[i].(bool)
b_string, b_string_ok := b[i].(string)
b_int, b_int_ok := b[i].(int)
b_float64, b_float64_ok := b[i].(float64)

one_side_bool := a_bool_ok != b_bool_ok
one_side_string := a_string_ok != b_string_ok
one_side_int := a_int_ok != b_int_ok
one_side_float64 := a_float64_ok != b_float64_ok

both_side_diff := one_side_bool || one_side_string || one_side_int || one_side_float64
both_side_numeric := (a_int_ok || a_float64_ok) && (b_int_ok || b_float64_ok)

if both_side_diff && !both_side_numeric {
return 0, false
}

if both_side_numeric {
if a_int_ok && b_int_ok {
if a_int < b_int {
return -1, true
}
if a_int > b_int {
return 1, true
}
}
if a_float64_ok && b_float64_ok {
if a_float64 < b_float64 {
return -1, true
}
if a_float64 > b_float64 {
return 1, true
}
}
if a_int_ok && b_float64_ok {
if float64(a_int) < b_float64 {
return -1, true
}
if float64(a_int) > b_float64 {
return 1, true
}
}
if a_float64_ok && b_int_ok {
if a_float64 < float64(b_int) {
return -1, true
}
if a_float64 > float64(b_int) {
return 1, true
}
}
}

if a_bool_ok && b_bool_ok {
if !a_bool && b_bool {
return -1, true
}
if a_bool && !b_bool {
return 1, true
}
}
if a_string_ok && b_string_ok {
if a_string < b_string {
return -1, true
}
if a_string > b_string {
return 1, true
}
}
}

if len(a) < len(b) {
return -1, true
}
if len(a) > len(b) {
return 1, true
}

return 0, true
}

/*
func main() {
a := []any{1, "hello", 3.14, true}
b := []any{1, "hello", 3.14, true}
c, ok := tuple2.Cmp(a, b)
println(c, ok)
}
*/

```

Is it possible for golang to have tuple in golang 2.
With tuple golang will be even more modern language.

It is very convenient to implement custom comparison operators
for user types with tuple, like this in c++:

```

struct T{
std::string name;
int num;
};

bool operator== (const T &a, const T &b){
return std::tuple{a.name, a.num} == std::tuple{b.name, b.num};
}

bool operator< (T &a, T &b){
return std::tuple{a.name, a.num} < std::tuple{b.name, b.num};
}

int main() {
T a{"aaa", 100}, b{"bbb", 200};
std::cout << (a == b) << "\n";
std::cout << (a < b) << "\n";

return 0;
}

```

Jan Mercl

unread,
Aug 8, 2024, 3:41:00 PMAug 8
to lijh8, golang-nuts
On Thu, Aug 8, 2024 at 9:35 PM 'lijh8' via golang-nuts
<golan...@googlegroups.com> wrote:

> I try to use slice of any to imitate the tuple type,
> and use this function to compare two slices: a, b.
>
> How can I improve it?

Take a look at https://pkg.go.dev/slices#Compare if it can fit your use case.

Jolyon Direnko-Smith

unread,
Aug 8, 2024, 8:19:22 PMAug 8
to golang-nuts
My $0.02 on this

First, to address the use cases in your question, Golang does not have custom operators so the value of tuples in assisting with their implementation is moot. (and, IMHO, should resist incorporating them; someone should not have to consult the source for a type to understand whether what should be a standard operator works in an expected and intuitive way or implements some exotic behaviour for the convenience of a "clever" implementation).

Second, you don't need to implement a custom == operator to compare custom struct types; the implementation of == in Golang already covers the comparison of structured value types in a logical, intuitive and type-safe manner.

[]any could be a perfectly good representation of a tuple in some cases, but context is king.  Personally, I would resist the temptation to create a "tuple type/package" to cater for all possible use cases.

If a project requires tuples of any type with support for equality testing then implement a tuple type (in that project) to support what that project needs (and nothing more); it will be trivial (type tuple []any, with an Equals(tuple) bool method to perform the equality tests).  If a different project requires tuples which support a mix of strings and ints and "greater-than"/"less-than" comparison support then implement an appropriate tuple type for that use case.  The Equals() method for the latter will be slightly more complicated than the first, but still far simpler to implement than a fully versatile tuple type.

If a project requires different tuple capabilities in different use cases then implement appropriate, distinct tuple types for each use case.  A bonus feature of this approach: you won't be able to inadvertently use "a tuple of any type supporting equality", where your code requires "a tuple of string/int supporting gt/lt comparison".

ymmv

Gergely Brautigam

unread,
Aug 9, 2024, 1:57:33 AMAug 9
to golang-nuts
Hello.

I actually implemented a Tupke logic here https://github.con/Skarlso/tuple

I find this a much better way to deal with tuples instead of the indexing logic. And it’s using generics as well. Feel free to borrow stuff. 😊

Message has been deleted

Gergely Brautigam

unread,
Aug 9, 2024, 2:26:04 AMAug 9
to golang-nuts
My apologies the link is incorrect. It should be https://github.com/Skarlso/tuple

Hopefully it’s somewhat of a help in pursuing this topic. 

lijh8

unread,
Aug 9, 2024, 7:08:31 PMAug 9
to Jolyon Direnko-Smith, golang-nuts
Hi community,

I have got this now.
It handles only built-in types: string, int, float64.
The tuples (slices) should be the same size.
Type of elements in same position should be the same.

I still need help on this:

The part involves cmp.Compare repeates three times for string, int, float64.
I wonder if I can call the type assertion with a type ` T ` like: a[i].(T), 
where T is from reflect. so, i can do the part with three calls, 
not repeat the part three times. 
I tried but a[i].(T) will not take T as reflect.TypeOf.

s := []any{"", 0, 0.0}
for _, element := s {
    t := reflect.TypeOf(element)
    v, ok := element.(t) // .(string), .(int), .(float64)
}

Is this ability same as c++ template meta programming or rust macro.
How can i do it in Go?

Thanks


```

package tuple2

import (
    "cmp"
    "reflect"
)

func Cmp(a, b []any) (int, bool) {
    if len(a) != len(b) {
        return 0, false
    }

    for i := range a {
        if a[i] == nil || b[i] == nil {
            return 0, false
        }

        if _, boolean := a[i].(bool); boolean {
            return 0, false
        }
        if _, boolean := b[i].(bool); boolean {
            return 0, false
        }

        if a, b := reflect.TypeOf(a[i]), reflect.TypeOf(b[i]); a != b {
            return 0, false
        }

        if a, aOk := a[i].(string); aOk {
            if b, bOk := b[i].(string); bOk {
                if c := cmp.Compare(a, b); c != 0 {
                    return c, true
                }
            }
        }
        if a, aOk := a[i].(int); aOk {
            if b, bOk := b[i].(int); bOk {
                if c := cmp.Compare(a, b); c != 0 {
                    return c, true
                }
            }
        }
        if a, aOk := a[i].(float64); aOk {
            if b, bOk := b[i].(float64); bOk {
                if c := cmp.Compare(a, b); c != 0 {
                    return c, true
                }
            }
        }
    }
    return 0, true
}

/*
func main() {
    a := []any{"abc", 123, 3.14}
    b := []any{"abc", 123, 3.14}
    c, ok := tuple2.Cmp(a, b)
    fmt.Println(c, ok)
}
*/

```

Brian Candler

unread,
Aug 10, 2024, 12:03:34 AMAug 10
to golang-nuts
On Friday 9 August 2024 at 06:19:22 UTC+6 Jolyon Direnko-Smith wrote:
Second, you don't need to implement a custom == operator to compare custom struct types; the implementation of == in Golang already covers the comparison of structured value types in a logical, intuitive and type-safe manner.

I would agree, except there is no == operator defined on slices, and I'm not really sure why that is. The semantics I would expect are straightforward: i.e. length is the same, and the first 'len' slice elements compare as equal, recursively if necessary. (*)

Apart from that, I see no particular advantage of tuples over structs.  They might be a bit more verbose in some cases, but literals can look like tuples already:

Being able to access elements by name rather than position is also a benefit IMO.

(*) Aside: having == defined on all types would be a nice feature which I think would lead to simplification overall - like getting rid of the issue where comparing interface values can panic when they are the same type.

AFAICS, only maps might be problematic, but even then the potential semantics seem straightforward.  If they are the same map then they are trivially equal, and if they are different lengths they are trivially unequal. Otherwise you would iterate over all keys in A, and compare values with corresponding elements in B.  But I'm sure this must have been discussed to death already.

lijh8

unread,
Aug 10, 2024, 1:35:00 PMAug 10
to golang-nuts
I add nested tuple (slice) support:

    a := []any{"abc", 123, 3.14, 100, []any{"abc", 123, 3.14, 100}}
    b := []any{"abc", 123, 3.14, 100, []any{"abc", 123, 3.14, 100}}
    c, ok := tuple2.Cmp(a, b)
    fmt.Println(c, ok)


        if a, aOk := a[i].([]any); aOk {
            if b, bOk := b[i].([]any); bOk {
                if c, ok := Cmp(a, b); ok && c != 0 {
                    return c, true
                } else if !ok {
                    return 0, false

p...@morth.org

unread,
Aug 10, 2024, 3:26:29 PMAug 10
to golang-nuts
I think when discussing tuples one should consider the [...]any type as an alternative to []any. Arrays supports comparison, aren't boxed and are generally more tuple-like. They can be used as map keys.
Of course, they're still not really type-safe. Structs solve that and also support access by name instead of by index, so they're better in general.
I have to say that whenever I've used tuples (in C++ and Python) or arrays in Go (usually [2]string), I've ended up regretting it and switching to a struct/class later.

> I tried but a[i].(T) will not take T as reflect.TypeOf.

I think this is typically written as switch reflect.TypeOf(a[i]).Kind(), which gives you the underlying type (e.g. int instead of MyInt). You can then use reflect.ValueOf(a[i]).Int() and the sibling functions to access the value.
Doing switch a[i].(type) is also possible but that doesn't support custom types.

> I would agree, except there is no == operator defined on slices, and I'm not really sure why that is. The semantics I would expect are straightforward: i.e. length is the same, and the first 'len' slice elements compare as equal, recursively if necessary.

Implementing it this way would require dereferencing the internal pointer, which could cause infinite loops if the array contains a reference to itself. For example, this code panics because it runs out of stack (it's hard to tell in the playground though): https://go.dev/play/p/zyHA7khq-d8

Dan Kortschak

unread,
Aug 10, 2024, 6:13:05 PMAug 10
to golan...@googlegroups.com
On Sat, 2024-08-10 at 12:26 -0700, p...@morth.org wrote:
> I think when discussing tuples one should consider the [...]any type
> as an alternative to []any. Arrays supports comparison, aren't boxed
> and are generally more tuple-like. They can be used as map keys.

You may be interested in this discussion which shows how a linked list
of `type K[T any] struct { val T; rest any }` can be used as a
hashable/equalable key equivalent to a []T for maps.

https://groups.google.com/g/golang-nuts/c/BLVg5yILmKQ/m/fbOAmoHesQkJ

Original implementation due to Brad: https://go.dev/play/p/HLPCw0vM27

glenn stevenson

unread,
Aug 10, 2024, 6:40:04 PMAug 10
to golang-nuts
@Gergely Brautigam its .com not .con as in your post , pls edit 

Ian Lance Taylor

unread,
Aug 10, 2024, 7:08:26 PMAug 10
to Brian Candler, golang-nuts
On Fri, Aug 9, 2024 at 9:03 PM 'Brian Candler' via golang-nuts
<golan...@googlegroups.com> wrote:
>
> I would agree, except there is no == operator defined on slices, and I'm not really sure why that is. The semantics I would expect are straightforward: i.e. length is the same, and the first 'len' slice elements compare as equal, recursively if necessary. (*)

There is no == operator on slice types because there are two possible meanings:
1) Two slices are equal if they have the same length and refer to the
same underlying array.
2) Two slices are equal if they have the same length and each
individual element is itself equal.

Because it's not clear which definition of equality people might mean,
the language doesn't define ==, and forces people to choose which
definition they want in any given situation.

Ian

Brian Candler

unread,
Aug 11, 2024, 8:34:33 AMAug 11
to golang-nuts
Are there many instances where interpretation (1) is useful?

I would also observe: if Go implemented case (2) for "==", this doesn't prevent the user from implementing case (1) using a separate function, as today.

Supplementary question: does the Go standard library even expose a function for case (1), or would you have to do it yourself using e.g. unsafe.SliceData?  Clearly case (2) is supported via bytes.Equal, slices.Equal etc.

Jochen Voss

unread,
Aug 11, 2024, 9:05:34 AMAug 11
to golang-nuts
Hi Ian,

Just out of interest: if I choose that I mean equality 1 (same length and same underlying storage), how would I perform the test for equality?  Does this require unsafe.Pointer, or is there a more proper way?

All the best,
Jochen

Jochen Voss

unread,
Aug 11, 2024, 9:12:58 AMAug 11
to golang-nuts
Hi Brian,

One instance where (1) would be useful is, when trying to implement a PostScript interpreter.  The Postscript "eq" operator has to test whether two PostScript arrays share the same storage, rather than whether they contain the same values [*].  If you can do (1), you can use Go slices to implement PostScript arrays in a straightforward way.


All the best,
Jochen

Ian Lance Taylor

unread,
Aug 12, 2024, 12:29:12 AMAug 12
to Brian Candler, golang-nuts
On Sun, Aug 11, 2024 at 5:35 AM 'Brian Candler' via golang-nuts
<golan...@googlegroups.com> wrote:
>
> Are there many instances where interpretation (1) is useful?

Yes.

> I would also observe: if Go implemented case (2) for "==", this doesn't prevent the user from implementing case (1) using a separate function, as today.

Of course. But it means that people who expect interpretation 1 will
be confused. We want to avoid confusion where possible.

> Supplementary question: does the Go standard library even expose a function for case (1), or would you have to do it yourself using e.g. unsafe.SliceData? Clearly case (2) is supported via bytes.Equal, slices.Equal etc.

if len(a) == 0 {
return (a == nil) == (b == nil)
} else {
return len(a) == len(b) && &a[0] == &b[0]
}

Ian

> On Sunday 11 August 2024 at 05:08:26 UTC+6 Ian Lance Taylor wrote:
>>
>> On Fri, Aug 9, 2024 at 9:03 PM 'Brian Candler' via golang-nuts
>> <golan...@googlegroups.com> wrote:
>> >
>> > I would agree, except there is no == operator defined on slices, and I'm not really sure why that is. The semantics I would expect are straightforward: i.e. length is the same, and the first 'len' slice elements compare as equal, recursively if necessary. (*)
>>
>> There is no == operator on slice types because there are two possible meanings:
>> 1) Two slices are equal if they have the same length and refer to the
>> same underlying array.
>> 2) Two slices are equal if they have the same length and each
>> individual element is itself equal.
>>
>> Because it's not clear which definition of equality people might mean,
>> the language doesn't define ==, and forces people to choose which
>> definition they want in any given situation.
>>
>> Ian
>
> --
> You received this message because you are subscribed to the Google Groups "golang-nuts" group.
> To unsubscribe from this group and stop receiving emails from it, send an email to golang-nuts...@googlegroups.com.
> To view this discussion on the web visit https://groups.google.com/d/msgid/golang-nuts/62959bba-5170-4f1a-b8b4-f023a22848cen%40googlegroups.com.
Reply all
Reply to author
Forward
0 new messages