armanbilge/calico

`SignallingRef` has to be upcast to `Signal` for syntax

armanbilge opened this issue ยท 4 comments

This is annoying.

This should be fixed by #183 shouldn't it?

Unfortunately not, this is really an FS2 issue.

//> using lib "co.fs2::fs2-core::3.6.1"

import cats.effect.IO
import cats.syntax.all.*
import fs2.concurrent.{Signal, SignallingRef}

def sigRef: SignallingRef[IO, String] = ???

@main def main =

  (sigRef: Signal[IO, String]).void

  sigRef.void
[error] ./signal.scala:13:3: value void is not a member of fs2.concurrent.SignallingRef[cats.effect.IO, String].
[error] An extension method was tried, but could not be fully constructed:
[error] 
[error]     cats.syntax.all.toFunctorOps[
[error]       ([A] =>> fs2.concurrent.SignallingRef[cats.effect.IO, A])
[error]     , String](sigRef)(cats.Invariant.catsApplicativeForArrow[F, A])
[error]   sigRef.void
[error]   ^^^^^^^^^^^
Error compiling project (Scala 3.2.2, JVM)
Compilation failed

But, I just had an idea how to fix it :)

But, I just had an idea how to fix it :)

Erm, spoke too soon :) I thought the problem is in FS2, but it's not really. You can't have a Functor for SignallingRef, so the implicit search gives up quickly and with good reason.

So it turns out, this is something that would need to be fixed in Cats ๐Ÿ˜‚ the question is, if you have Foo[A] <: Bar[A], and Bar implements Functor but Foo does not, should you be able to use functor syntax on Foo? All the methods would return Bar in that case.

I have a hard time imagining such a major change like that being made in Cats.

That said, maybe the fix is to add an .asSignal method to SignallingRef in FS2 /shrug

One more datapoint: experimented with the extension-based encoding in Scala 3, and it doesn't work there either. That's even harder to fix than the implicit class syntax used in Scala 2. So I don't really think we can "fix" this, we can just make the upcast more convenient.

Edit: Ha wait, totally screwed that up. It does work! So that's very interesting ๐Ÿค”

trait Functor[F[_]]:
  extension [A](fa: F[A]) def map[B](f: A => B): F[B]

trait Foo[A]
object Foo:
  given Functor[Foo] = ???

trait Bar[A] extends Foo[A]

def foo: Foo[String] = ???
def bar: Bar[String] = ???

@main def main =
  foo.map(_.length)
  bar.map(_.length)