cats icon indicating copy to clipboard operation
cats copied to clipboard

Foldable for tuples?

Open oyvindberg opened this issue 4 years ago • 1 comments

Hey there.

In https://github.com/typelevel/cats/pull/3299 (I think. at least in 2.4.x) tuples gained a Foldable (among others, naturally) instance, which only considers the right-most element.

I guess I'm opening up a discussion of whether this makes any sense?

  import cats.syntax._

  def toList[F[_]: Foldable, A](fa: F[A]): List[A] =
    Foldable[F].toList(fa)

  println(toList((1,2))) // List(2)

The issue I see is I associate Foldable with "handle all values" rather than "handle a subset of a value".

How this breaks the Text type class in doobie for tuples

Onto the discussion of how I ended up looking into this. I tried to upgrade cats to 2.4.2 from 2.3.1, and it broke our code in a strange way.

We use doobie, which among other things offers a way to stream As to postgres via a (tab-separated) textual format through the Text type class.

Let's examine a derivation rule which now causes problems:

It takes a Foldable F of As and puts the As into a postgres array, again in the textual representation. Crucially, this is prioritized before the macro which composes instances for Text for for product types, which is what we used to hit.

source

object Text {
  // ... simplified, it's really in a parent
  implicit def foldableInstance[F[_]: Foldable, A](implicit ev: Text[A]): Text[F[A]] =
    iterableInstance[List, A].contramap(_.toList)
}

Here is a minified example, where you can see the resolved implicits before and after

object Tester extends App {
  import cats.UnorderedFoldable
  import doobie.postgres.Text

  def p[A: Text](a: A): Unit =
    println(Text[A].encode(a))

  private val tuple: (Int, Long, String) = (1, 2L, "hello")

  p(tuple)

  // cats 2.3.x it picked this
  p(tuple)(Text.generic)
  // output: 1	2	hello

  // cats 2.4.x it picks this
  p(tuple)(Text.foldableInstance(UnorderedFoldable.catsUnorderedFoldableInstancesForTuple3, Text[String]))
  // output: {"hello"}
}

oyvindberg avatar Feb 22 '21 22:02 oyvindberg

Thanks for documenting this issue and I agree with you that behaviour is weird. Not sure what we can do here (except that you'll have to avoid using Foldable for now)...

jatcwang avatar Feb 22 '21 23:02 jatcwang