1

I have written this code

trait Input[F[_]] {
    def read: F[String]
    def write(str: String) : F[Unit]
    def getName : F[String] = for {
        _ <- write("What is your name")
        name <- read
    } yield name
}

This code doesn't compile obviously because the compiler has no way of knowing that the type F supports flatmap. So I change my code to

import scalaz._
import Scalaz._
trait Input[F[_] : Monad] {
    def read: F[String]
    def write(str: String) : F[Unit]
    def getName : F[String] = for {
        _ <- write("What is your name")
        name <- read
    } yield name
}

but now I get compile time error traits cannot have type parameters with context bounds.

So how can I specify constraints on my type parameter so that it always supports flatmap?

Knows Not Much
  • 30,395
  • 60
  • 197
  • 373
  • 1
    As has been noted: "traits can't have constructor parameters," but an `abstract class` can, and there are a few other reasons you might [prefer an abstract class instead of a trait](https://stackoverflow.com/a/35251513/4993128). – jwvh Nov 11 '18 at 07:35

1 Answers1

1

trait Input[F[_]: Monad] would create an implicit constructor parameter and traits can't have constructor parameters (until Scala 3). def testFunc[F[_]: Monad] would create an implicit parameter. For instance:

def testFunc[F[_]: Monad](arg: Int) = ???
class TestClass[F[_]: Monad] {}

Will work because it is translated to:

def testFunc[F[_]](arg: Int)(implicit ev: Monad[F]) = ???
class TestClass[F[_]](implicit val ev: Monad[F]) {}

I.e. [F[_]: Monad] is syntactic sugar for [F[_]] with implicit val ev: Monad[F]. And traits has no constructor to pass parameters until Scala 3.

For your case, if you really need to inside a trait to constraint F to a Monad for example, then:

trait Input[F[_]] {
  val M: Monad[F]

  def read: F[String]
  def write(str: String) : F[Unit]
  def getName : F[String] = M.flatMap(write("What is your name"))(_ => read)
}

I.e. you are saying to one who implements, that "you can implement Input, as soon as you have Monad[F]". Then you can use it like:

object Main extends App{

  class IOInput extends Input[IO] {
    override val M: Monad[IO] = Monad[IO]
    override def read: IO[String] = IO("red")
    override def write(str: String): IO[Unit] = IO(println(s"write: $str"))
  }

  val impl = new IOInput

  println(impl.getName.unsafeRunSync())
}

P.S. But for me, it seems like something is wrong. You are kinda defining effects in trait Input and using them right in the same trait. Reads weird for me at least. Probably getName should be somewhere else.

muradm
  • 1,973
  • 19
  • 30
  • 2
    The problem is not "Because constraints work on actual implementations." It's just that `trait Input[F[_]: Monad]` would create an implicit constructor parameter and traits can't have constructor parameters (until Scala 3). – Alexey Romanov Nov 11 '18 at 07:11