Как использовать MonadError с типом эффекта IO? - PullRequest
1 голос
/ 21 июня 2020

Я пытаюсь написать тесты для моей алгебры без тегов, чтобы он использовал MonadError.

Вот алгебра без тегов с интерпретатором:

trait DbConnector[F[_]] {
  def read(url: DbUrl, user: DbUser, pw: DbPw): F[DbParams]
}


object DbConnector {

  def apply[F[_]](implicit dc: DbConnector[F]): dc.type = dc

  def impl[F[_] : MonadError[*[_], Throwable]](env: Environment[F])
  : DbConnector[F] =
    new LiveDbConnector[F](env)

}

и тестовый класс с MonadError экземпляр Either:

class DbConnectorSpec extends munit.FunSuite {
  test("Environment variables are not set") {

    val monadError = MonadError[IO[Either[DbError, DbParams]], DbError]
    implicit val badEnv = DbConnector.impl(LiveBadEnvironment())


  }
}

код не компилируется из-за сообщения об ошибке:

cats.effect.IO[Either[io.databaker.db.DbError,io.databaker.db.DbParams]] takes no type parameters, expected: 1
[error]     val monadErr = MonadError[IO[Either[DbError, DbParams]], DbError]

MonadError является новым для меня, и это первый раз пытаюсь использовать. Я прочитал концепцию MonadError на https://www.scalawithcats.com/dist/scala-with-cats.html.

Как заставить его работать?

1 Ответ

2 голосов
/ 21 июня 2020

Рассмотрим предложение параметра типа MonadError

trait MonadError[F[_], E]

Обратите внимание, что F и E имеют разные формы (или типы):

F - a type constructor of * -> * kind
E - a proper type of * kind

разница между конструктором типа и правильным типом аналогична разнице между List и List[Int], то есть конструктору типа List требуется аргумент типа * от Int до конструкция правильный тип List[Int].

Теперь рассмотрим вид IO[Either[Throwable,Int]]

scala> :kind -v IO[Either[Throwable, Int]]
cats.effect.IO[Either[Throwable,Int]]'s kind is A
*
This is a proper type.

Мы видим, что он имеет форму правильного типа, поэтому он не подходит вместо F

scala> MonadError[IO[Either[Throwable, Int]], Throwable]
<console>:25: error: cats.effect.IO[Either[Throwable,Int]] takes no type parameters, expected: one
       MonadError[IO[Either[Throwable, Int]], Throwable]

Теперь рассмотрим вид IO

scala> :kind -v IO
cats.effect.IO's kind is F[+A]
* -(+)-> *
This is a type constructor: a 1st-order-kinded type.

Мы видим, что это конструктор типа формы * -> *, который соответствует форме конструктора типа F. . Поэтому мы можем написать

scala> MonadError[IO, Throwable]
res2: cats.MonadError[cats.effect.IO,Throwable] = cats.effect.IOLowPriorityInstances$IOEffect@75c81e89

Здесь - еще несколько примеров

import cats._
import cats.data._
import cats.effect.IO
import cats.instances.either._
import cats.instances.try_._
import cats.instances.future._
import scala.concurrent.Future
import scala.concurrent.ExecutionContext.Implicits.global
import scala.util.Try

MonadError[Future, Throwable]
MonadError[Try, Throwable]
MonadError[IO, Throwable]
MonadError[Either[String, *], String]
MonadError[EitherT[IO, String, *], String]
MonadError[EitherT[Future, String, *], String]

Обратите внимание, что синтаксис * в Either[String, *] происходит от kind- проектор и является альтернативой использованию псевдонима типа для преобразования, например, * -> * -> * вида в требуемый * -> * вид

scala> :kind -v Either[String, *]
scala.util.Either[String,?]'s kind is F[+A]
* -(+)-> *
This is a type constructor: a 1st-order-kinded type.

scala> type MyError[+A] = Either[String, A]
defined type alias MyError

scala> :kind -v MyError
MyError's kind is F[+A]
* -(+)-> *
This is a type constructor: a 1st-order-kinded type.
...