Scala 与IO单子相比,自由单子的优势是什么?
所以我一直在深入研究FP概念,我喜欢IO单子中包含的纯度概念。然后我读到,并认为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
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