-
-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Description
A follow-up to #4830.
Consider the snippet:
//> using scala 3.8.2
//> using options -Xkind-projector
//> using dep org.typelevel::cats-core:2.13.0
import cats.Applicative
import cats.data.OneAnd
import cats.data.ZipLazyList
val s1 = LazyList(1, 2, 3)
val s2 = LazyList(4, 5)
val sR = Applicative[LazyList].product(s1, s2)
println("- LasyList: " + sR.mkString(","))
val ns1 = OneAnd(s1.head, s1.tail)
val ns2 = OneAnd(s2.head, s2.tail)
val nsR = Applicative[OneAnd[LazyList, *]].product(ns1, ns2)
println("- NeLasyList: " + (nsR.head #:: nsR.tail).mkString(","))
val z1 = ZipLazyList(s1)
val z2 = ZipLazyList(s2)
val zR = Applicative[ZipLazyList].product(z1, z2)
println("- ZipLasyList: " + zR.value.mkString(","))
val nz1 = OneAnd(s1.head, ZipLazyList(s1.tail))
val nz2 = OneAnd(s2.head, ZipLazyList(s2.tail))
val nzR = Applicative[OneAnd[ZipLazyList, *]].product(nz1, nz2)
println("- NeZipLasyList: " + (nzR.head #:: nzR.tail.value).mkString(","))It prints the following:
- LasyList: (1,4),(1,5),(2,4),(2,5),(3,4),(3,5)
- NeLasyList: (1,4),(1,5),(2,4),(2,5),(3,4),(3,5)
- ZipLasyList: (1,4),(2,5)
- NeZipLasyList: (1,4),(1,5),(2,4),(3,4)
Note: it works the same way on Scala 2.13 and for Stream type on Scala 2.12
As you can see, while Applicative[F].product produces identical results for both LazyList and OneAnd[LazyList, *],
for ZipLazyList and OneAnd[ZipLazyList, *] it doesn't. Moreover, the latter is neither the Cartesian product nor element-wise composition – it's something different.
I would argue that OneAnd for a collection type should behave as the non-empty variant of that collection. That is not the case for ZipLazyList, apparently.
The reason appears to be the Alternative[ZipLazyList] instance discussed in #4830 – it seems to be unlawful. The problem for OneAnd is that its Applicative instance requires an Alternative for the underlying type F:
| implicit def catsDataApplicativeForOneAnd[F[_]](implicit F: Alternative[F]): Applicative[OneAnd[F, *]] = |
There's something off with OneAnd:
- if we assume that
Alternative[ZipLazyList]is incorrect indeed, then the currentApplicativeimplementation forOneAndimplies that it cannot work correctly forZip*collections. - if we still believe that
Alternative[ZipLazyList]is good enough forOneAnd, thenApplicativeforOneAndis not correctly implemented. - or, perhaps, we cannot treat
OneAndas just a non-empty "drop-off" for the underlyingF.