cats
cats copied to clipboard
`Tuple1SemigroupalOps` methods have different names from other `TupleNSemigroupalOps` classes
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.
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.
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.
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.