Search code examples
scalafunctional-programmingscala-catstagless-final

Scala Tagless Final - Not compiling


This is a basic Scala Tagless Final pattern implementation of a contrived login process. It doesn't compile because as it shows near the end 'No implicits found for parameters ...'

But if I remove ': Monad: LoginProcessAlgebra[F]' from the program generic type that specific error goes away but the for-comprehension starts to complain because F[_] is no longer narrowed down to a Monad

Question: Why does Scala think State isn't a Monad?

import cats.Monad
import cats.data.State
import cats.implicits._

import java.util.UUID
import scala.language.higherKinds

case class Credentials(uid: String, pwd: String)
case class Session(sessionId: String, credentials: Credentials)

object LoginProcessTaglessFinal {

  trait LoginProcessAlgebra[F[_]] {
    def captureCredentials(name: String, password: String): F[Credentials]
    def login(credentials: Credentials): F[Session]
  }

  type LoginProcessState = State[LoginProcessAlgebra[_], _]
  type LoginProcessStateA[A] = LoginProcessState[A]

  implicit object LoginProcessInterpreterUsingState extends LoginProcessAlgebra[LoginProcessStateA] {
    override def captureCredentials(name: String, password: String): LoginProcessStateA[Credentials] =
      State(login => (login, Credentials(name, password)))
    override def login(credentials: Credentials): LoginProcessStateA[Session] =
      State(login => (login, Session(UUID.randomUUID().toString, credentials)))
  }

  def program[F[_]: Monad: LoginProcessAlgebra[F]](userName: String, password: String)
    (implicit interpreter: LoginProcessAlgebra[F]): F[Session] = for {
      credentials <-  interpreter.captureCredentials(userName, password)
      session <-  interpreter.login(credentials)
    } yield session


  val sessionState = program("someUserName", "p455w0rd")
  //compile error here 
  //due to 'No implicits found for parameters ...'
}

Solution

  • import scala.language.higherKinds is deprecated (since Scala 2.13.1).

    Compilation errors start earlier

    https://scastie.scala-lang.org/DmytroMitin/cVmdwXy7RKSWUbn9V0TBGw (Scala 2)

    https://scastie.scala-lang.org/DmytroMitin/cVmdwXy7RKSWUbn9V0TBGw/6 (Scala 3)

    You have incorrect kinds in these lines

    type LoginProcessState = State[LoginProcessAlgebra[_], _]
    type LoginProcessStateA[A] = LoginProcessState[A]
    

    Your code seems to be in Scala 3.

    I replaced

    type LoginProcessState = State[LoginProcessAlgebra[_], _]
    

    with

    // scalacOptions += "-Ykind-projector"
    
    type LoginProcessState = State[LoginProcessAlgebra[?], *]
    

    i.e. with a type lambda [A] =>> State[LoginProcessAlgebra[?], A] into existential type LoginProcessAlgebra[?]

    Polymorphic method works with type lambda, but not with type wildcard in Scala 3

    Also I replaced

    def program[F[_]: Monad: LoginProcessAlgebra[F]](userName: String, password: String)
        (implicit interpreter: LoginProcessAlgebra[F]): F[Session]
    

    with

    def program[F[_]: Monad](userName: String, password: String)
        (implicit interpreter: LoginProcessAlgebra[F]): F[Session]
    

    (it's LoginProcessAlgebra that can be a context bound, not LoginProcessAlgebra[F]; also you had both context bound LoginProcessAlgebra and the same implicit LoginProcessAlgebra[F] once again, which can't be correct).

    Now the error is Ambiguous given instances: both value catsStdInstancesForOption in trait OptionInstances and value catsStdInstancesForVector in trait VectorInstances match type cats.Monad[F] of an implicit parameter of method program in object LoginProcessTaglessFinal

    https://scastie.scala-lang.org/DmytroMitin/cVmdwXy7RKSWUbn9V0TBGw/3

    You just need to specify type parameter F=LoginProcessStateA at the call site. It can't be inferred.

    val sessionState = program[LoginProcessStateA]("someUserName", "p455w0rd")
    

    The code now compiles

    https://scastie.scala-lang.org/DmytroMitin/cVmdwXy7RKSWUbn9V0TBGw/5

    LoginProcessStateA seems to be the same as LoginProcessState. I'm removing the former.

    https://scastie.scala-lang.org/DmytroMitin/cVmdwXy7RKSWUbn9V0TBGw/13

    Also in Scala 3 it's better to use given/using instead of implicit

    https://scastie.scala-lang.org/DmytroMitin/cVmdwXy7RKSWUbn9V0TBGw/16


    In Scala 2 I can't make this compile

    type LoginProcessState[A] = State[LoginProcessAlgebra[F], A] forSome { type F[_] }
    

    https://scastie.scala-lang.org/DmytroMitin/Ov3BYdS5RjGXcXKEleoIlw/2

    scalac: 
      ?BoundedWildcardType?
         while compiling: ...
            during phase: typer
    
      last tree to typer: Apply(method apply)
           tree position: ...
                tree tpe: Credentials
                  symbol: case method apply in object Credentials
       symbol definition: case def apply(uid: String, pwd: String): Credentials (a MethodSymbol)
          symbol package: <empty>
           symbol owners: method apply -> object Credentials -> object App
               call site: method captureCredentials in object LoginProcessInterpreterUsingState in package <empty>
    

    or

    type LoginProcessAlgebraE = LoginProcessAlgebra[F] forSome { type F[_] }
    type LoginProcessState[A] = State[LoginProcessAlgebraE, A]
    

    (this should be the literal translation of Scala 3 type LoginProcessState = State[LoginProcessAlgebra[?], *])

    https://scastie.scala-lang.org/DmytroMitin/Ov3BYdS5RjGXcXKEleoIlw/3

    can't existentially abstract over parameterized type F
      State((login: LoginProcessAlgebraE) => (login, Credentials(name, password)))
      State((login: LoginProcessAlgebraE) => (login, Session(UUID.randomUUID().toString, credentials)))
    

    WeakTypeTag for higher kinded type

    "can't existentially abstract over parameterized type..."


    Finally this turned out to be Scala 2 and

    type LoginProcessState[A] = State[String, A]
    

    https://scastie.scala-lang.org/james-oncloud/uCAPMvBvR7OLuEW27jyt6Q/4