Scala 与IO单子相比,自由单子的优势是什么?

Scala 与IO单子相比,自由单子的优势是什么?,scala,error-handling,functional-programming,traits,free-monad,Scala,Error Handling,Functional Programming,Traits,Free Monad,所以我一直在深入研究FP概念,我喜欢IO单子中包含的纯度概念。然后我读到,并认为IO单子确实不像使用自由单子那样解耦(?) 所以我开始用这些概念来做我的工作,然后我意识到traits达到了将结构与执行分离的相同目的。更糟糕的是,使用自由单子有很多限制,比如错误处理和将上下文边界和隐式参数传递到解释器/实现中 所以我的问题是:使用它们有什么好处?如何解决我刚才提到的问题(隐式参数和错误处理)?免费单子的使用是局限于学术领域,还是可以用于工业领域 编辑:一个例子来解释我的疑问 import ca

所以我一直在深入研究FP概念,我喜欢IO单子中包含的纯度概念。然后我读到,并认为IO单子确实不像使用自由单子那样解耦(?)

所以我开始用这些概念来做我的工作,然后我意识到
traits
达到了将结构与执行分离的相同目的。更糟糕的是,使用自由单子有很多限制,比如错误处理和将上下文边界和隐式参数传递到解释器/实现中

所以我的问题是:使用它们有什么好处?如何解决我刚才提到的问题(隐式参数和错误处理)?免费单子的使用是局限于学术领域,还是可以用于工业领域

编辑:一个例子来解释我的疑问

  import cats.free.Free._
  import cats.free.Free
  import cats.{Id, ~>}

  import scala.concurrent.Future

  sealed trait AppOpF[+A]

  case class Put[T](key: String, value: T) extends AppOpF[Unit]

  case class Delete(key: String) extends AppOpF[Unit]
  //I purposely had this extend AppOpF[T] and not AppOpF[Option[T]]
  case class Get[T](key: String) extends AppOpF[T]

  object AppOpF {
    type AppOp[T] = Free[AppOpF, T]

    def put[T](key: String, value: T): AppOp[Unit] = liftF[AppOpF, Unit](Put(key, value))

    def delete(key: String): AppOp[Unit] = liftF[AppOpF, Unit](Delete(key))

    def get[T](key: String): AppOp[T] = liftF[AppOpF, T](Get(key))

    def update[T](key: String, func: T => T): Free[AppOpF, Unit] = for {
      //How do I manage the error here, if there's nothing saved in that key?
      t <- get[T](key)
      _ <- put[T](key, func(t))
    } yield ()


  }

  object AppOpInterpreter1 extends (AppOpF ~> Id) {
    override def apply[A](fa: AppOpF[A]) = {
      fa match {
        case Put(key,value)=>
          ???
        case Delete(key)=>
          ???
        case Get(key) =>
          ???
      }
    }
  }
  //Another implementation, with a different context monad, ok, that's good
  object AppOpInterpreter2 extends (AppOpF ~> Future) {
    override def apply[A](fa: AppOpF[A]) = {
      fa match {
        case a@Put(key,value)=>
          //What if I need a Json Writes or a ClassTag here??
          ???
        case a@Delete(key)=>
          ???
        case a@Get(key) =>
          ???
      }
    }
  }
导入cats.free.free_
进口猫。免费。免费
导入猫。{Id,~>}
导入scala.concurrent.Future
密封式空气过滤器[A]
case类Put[T](键:字符串,值:T)扩展AppOpF[Unit]
案例类删除(键:字符串)扩展APOPPF[单位]
//我故意让这个扩展AppOpF[T],而不是AppOpF[Option[T]]
case类Get[T](键:String)扩展了AppOpF[T]
对象AppOpF{
类型AppOp[T]=自由[AppOpF,T]
def put[T](键:字符串,值:T):AppOp[Unit]=liftF[AppOpF,Unit](put(键,值))
def delete(键:字符串):AppOp[Unit]=liftF[AppOpF,Unit](删除(键))
def get[T](键:字符串):AppOp[T]=liftF[AppOpF,T](get(键))
def update[T](键:字符串,函数:T=>T):自由[APOPPF,单位]=for{
//如果密钥中没有保存任何内容,我如何管理此处的错误?
T
???
案例删除(关键)=>
???
案例获取(关键)=>
???
}
}
}
//另一个实现,使用不同的上下文monad,很好
对象AppOpInterpreter2扩展(AppOpF~>Future){
覆盖def应用[A](fa:APOPPF[A])={
足总杯比赛{
案例a@Put(键,值)=>
//如果我在这里需要一个Json写入或类标记呢??
???
案例a@Delete(键)=>
???
案例a@Get(键)=>
???
}
}
}

使用IO monad的自由代数具有相同的目的——将程序构建为纯数据结构。如果将Free与IO的一些具体实现进行比较,IO可能会获胜。它将有更多的功能和专门的特点,这将帮助您快速移动和快速开发您的程序。但这也意味着您将在一个IO实现上锁定主要供应商。无论您选择哪种IO,它都是一个具体的IO库,可能存在性能问题、bug或支持问题——谁知道呢。由于程序和实现之间的紧密耦合,将程序从一个供应商更改为另一个供应商将花费大量成本

另一方面,自由代数允许您表达您的程序,而无需谈论程序的实现。它将您的需求从实现中分离出来,这样您就可以轻松地测试并独立地更改它们。另一个好处是,Free允许您完全不使用IO。您可以将标准Futures、java的标准CompletableFuture或任何其他第三方并发原语封装在其中,您的程序仍然是纯的。为此,Free需要额外的样板文件(正如您在示例中所示)和更少的灵活性。因此,选择的是你的


还有另一种方法-最终无标签。这种方法试图平衡双方的优点,提供较少的供应商锁定,但仍然不像免费代数那样冗长。值得一看。

如果您提供一个这样的替换示例,会更容易。当然,它不是解决这个问题的唯一方法。但我想您即将重新发明最终的无标记方法。Free monad/Free Applicationive允许您将程序构建为纯数据结构,可以将其视为一个值,并且可以轻松地组合、重用、传递和以不同方式解释,就像任何其他普通值一样。我不确定你如何通过简单的旧特征来实现这一点-你可能想更新你的例子来展示你的简单的旧特征是如何工作的。你是对的,我说的是IOmonads&traits