Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Tuple1SemigroupalOps methods have different names from other TupleNSemigroupalOps classes #4555

Open
m50d opened this issue Jan 28, 2024 · 3 comments

Comments

@m50d
Copy link
Contributor

m50d commented Jan 28, 2024

I don't know whether this is a deliberate design decision, but it seems wrong to me: the TupleNSemigroupalOps classes offer a consistent family of methods like mapN and traverseN, but for Tuple1SemigroupalOps these get generated as map and traverse instead. I'd like to be able to work consistently with tuples of any size, including 1, so I think it would make more sense if Tuple1SemigroupalOps (and friends) generated the same mapN/traverseN/etc. methods that are present on all the other TupleNSemigroupalOps classes.

@satorg
Copy link
Contributor

satorg commented Jan 29, 2024

Although I don't know for sure the reasoning behind such naming conventions, my guess is that despite Tuple1SemigroupalOps name its methods do not require the Semigroupal typeclass actually – but only Functor, Traverse, etc. Whereas all the other TupleNSemigrioupalOps methods for N >= 2 do require the Semigroupal typeclass.

In other words, we can assume that Tuple1[N] is isomorphic to Id[N] and therefore do not need mapN semantic.

@m50d
Copy link
Contributor Author

m50d commented Jan 29, 2024

mapN on a Tuple1 in isolation is kind of silly - but so is any use of Tuple1 in isolation - as you say, Tuple1[N] is isomorphic to Id[N]. The only real reason for having Tuple1 (and therefore Tuple1SemigroupalOps) at all is to operate consistently on tuples of all sizes - if you have to special-case Tuple1 anyway then you might as well just operate on the value inside.

@satorg
Copy link
Contributor

satorg commented Jan 29, 2024

I've just realized there's even a broader issue with Tuple1: depending on imports used, the map method for Tuple1 can change its semantic dramatically. Check this out:

import cats.syntax.functor._

scala> val t1 = Tuple1(List("A", "B"))
val t1: (List[String],) = (List(A, B),)

scala> t1.map(s => s"$s!")
val res0: (String,) = (List(A, B)!,)

However, if I use another syntax import:

scala> import cats.syntax.apply._

scala> val t1 = Tuple1(List("A", "B"))
val t1: (List[String],) = (List(A, B),)

scala> t1.map(s => s"$s!")
val res0: List[String] = List(A!, B!)

I.e. the same t1.map(s => s"$s!") call for exactly the same Tuple1(List("A", "B")) value works differently and produces results of different types depending on whether import cats.syntax.functor._ or import cats.syntax.apply._ is used.

I think it is somewhat confusing.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants