0

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 ...'
}
Dmytro Mitin
  • 48,194
  • 3
  • 28
  • 66
jakstack
  • 2,143
  • 3
  • 20
  • 37

1 Answers1

3

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

Dmytro Mitin
  • 48,194
  • 3
  • 28
  • 66
  • there is so much for me to learn from your answer :( – jakstack Mar 05 '23 at 16:54
  • my ide isn't recognising ? and * in State[LoginProcessAlgebra[?], *] adding scalacOptions += "-Ykind-projector" to the sbt build file didn't help – jakstack Mar 05 '23 at 16:58
  • @jakstack IDE is irrelevant. What is relevant is whether sbt compiles. Do `sbt clean compile`. What is your Scala version? At Scastie you can switch to "Build Settings" tab and see `build.sbt`. The code compiles with Scala 3.2.2 + `-Ykind-projector` + Cats 2.9.0 https://scastie.scala-lang.org/DmytroMitin/cVmdwXy7RKSWUbn9V0TBGw/16 – Dmytro Mitin Mar 05 '23 at 17:08
  • @jakstack You can try `type LoginProcessState = [A] =>> State[LoginProcessAlgebra[?], A]` or `type LoginProcessState[A] = State[LoginProcessAlgebra[?], A]` without kind projector. – Dmytro Mitin Mar 05 '23 at 17:11
  • 1
    Using Scala 2.12.17, will try your code but I came up with this: https://scastie.scala-lang.org/james-oncloud/uCAPMvBvR7OLuEW27jyt6Q/4 , many thanks – jakstack Mar 05 '23 at 17:43
  • @jakstack Aha, so it's just `type LoginProcessState[A] = State[String, A]`. Great. – Dmytro Mitin Mar 05 '23 at 17:54
  • @jakstack In Scala 2 kind projector should be added with `addCompilerPlugin("org.typelevel" % "kind-projector" % "0.13.2" cross CrossVersion.full)`, not `-Ykind-projector` (it's for Scala 3) https://github.com/typelevel/kind-projector In Scala 2 existential types are written with `_` or `forSome`, not `?`. Type lambdas `[A] =>> ...` exist only in Scala 3, in Scala 2 they can be emulated `({ type F[A] = ... })#F`. – Dmytro Mitin Mar 05 '23 at 18:23
  • @jakstack I considered `type LoginProcessState = State[LoginProcessAlgebra[?], *]` only because you wrote `State[LoginProcessAlgebra[_], _]`. Most probably this is over-engineering. Actually it was weird. You were defining implicit `LoginProcessAlgebra[LoginProcessState]` where `LoginProcessState` depends on `LoginProcessAlgebra`. – Dmytro Mitin Mar 05 '23 at 18:23
  • Thanks Dmytro, yes I made an error in my contrived example, but that's been a blessing since I'm learning new stuff from your replies and messages, God bless :) – jakstack Mar 05 '23 at 19:38