为无标签代数编写定律或单元测试

Write laws or unit test for tagless algebras

我已经写了两个无标签代数,我想为其中一个写出定律。

代数如下:

@newtype case class Variable(v: String)

@newtype case class Value(v: String)

trait Environment[F[_]] {
  def get(v: Variable): F[Option[Value]]
}


@newtype case class DbUrl(v: String)

@newtype case class DbUser(v: String)

@newtype case class DbPw(v: String)

final case class DbParams(url: DbUrl, user: DbUser, pw: DbPw)

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

口译员如下:

object Environment {

  def apply[F[_]](implicit ev: Environment[F]): ev.type = ev

  def impl[F[_] : Sync]: Environment[F] = new Environment[F] {
    override def get(v: Variable): F[Option[Value]] =
      Sync[F].delay(sys.env.get(v.v).map(a => Value(a)))
  }
}



final case class LiveDbConnector[F[_] : MonadError[*[_], Throwable]](env: Environment[F]) extends DbConnector[F] {
  override def read(url: DbUrl, user: DbUser, pw: DbPw): F[DbParams] =
    (for {
      a <- OptionT(env.get(Variable(url.v)))
      b <- OptionT(env.get(Variable(user.v)))
      c <- OptionT(env.get(Variable(pw.v)))
    } yield DbParams(DbUrl(a.v), DbUser(b.v), DbPw(c.v)))
      .value
      .flatMap {
        case Some(v) => v.pure[F]
        case None => DbSettingError.raiseError[F, DbParams]
      }
}

object DbConnector {

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


函数式编程中有Monoid、Monads等规律

我的问题是:

  1. 我的代数需要规律还是编写单元测试就足够了?
  2. 规律和单元测试有什么区别?
  3. 我应该如何为 DbConnector 代数写法

您混淆了数学定律和稳健性与程序的正确性。

首先,Monad、Monoid、Fold 等不是定律。它们是来自 Category Theory 的数学“结构”。这些“结构”具有它们需要遵守的某些特性,以使其健全和正确。例如,这些属性中的一组称为 Monadic Laws

数学中的任何 Monadic 结构都需要遵守这 3 条规则:

  1. 留下身份
  2. 正确身份
  3. 关联性

Monad、flatMap 和 pure(或数学中的 join 和 return)的操作必须符合这些法则才能正常运行。即 pure(a).flatMap(f) == f(a)M.flatMap(pure) == M

在函数式编程中,Monads 是从这些数学结构和法则衍生出来的一种设计模式。他们描述了一些“可组合的计算”。在猫中,Monad 被定义为 typeclass。此结构符合上述规则。

How should I write laws for DbConnector algebra

如果你想证明定律,你可能需要使用定理证明器,在 Scala 中确实没有明确编写或测试定律的方法,但你总是可以编写单元测试来确保你的 Monad 遵守这些法,即 testFlatMapLeftIdentity(...)

Do my algebras need laws or writing the unit tests is enough?

简而言之,我想不出代数有明确规律的情况,除非你的代数描述了一些数学运算集。例如,在您的代码中,上下文绑定 MonadError[*[_], Throwable]] 需要范围内的 Monad[F] ,这需要遵守这些法律,假设您使用的是常见的 IO, Task, Future 等,猫已经做到了所以你不需要担心这些法律。如果您决定实现自己的效果类型或编写新的 Monad 实现,您将需要确保遵守这些法则,但您的代码中没有任何内容需要您担心它们。为这个代数编写良好的单元测试就足够了。