我对函数式编程很陌生。然而,我读到了关于免费Monad的文章,我正试图将其用于一个玩具项目。在这个项目中,我对股票的投资组合域进行建模。正如许多书中所建议的,我定义了PortfolioService
的代数和PortfolioRepository
的代数。
我想在PortfolioRepository
代数和解释器的定义中使用Free monad。目前,我还没有用自由单元来定义PortfolioService
代数。
然而,如果我这样做,在PortfolioService
解释器中,由于使用的单子不同,我无法使用PortfolioRepository
的代数。例如,我不能在同一中使用monadEither[List[String], Portfolio]
和Free[PortfolioRepoF, Portfolio]
进行理解:(
我怀疑,如果我开始使用自由单元来建模一个代数,那么所有需要用它组成的其他代数都必须根据自由单元定义。
这是真的吗?
我使用的是Scala和Cats 2.2.0。
99%的时间Free monad可以与Tagless final:互换
- 您可以将
Free[S, *]
作为Monad
实例传递 - 您可以使用
S ~> F
将Monad[F]
映射到F[A]
来.foldMap
Free[S, A]
唯一的区别是你什么时候解释:
- tagless立即解释,因此它要求您为
F
传递类型类实例,但由于F
是一个类型参数,它给人的印象是它被推迟了,因为它推迟了选择类型的时刻 - free monad允许您在不依赖于类型类的情况下立即创建值,您可以将它们存储为
val
s中的object
s,不存在对类型类的依赖。你所付出的代价是中间表示,一旦你能够将其解释为有用的结果,你最终想要放弃它。另一方面,它缺少了无标签的能力,即仅将操作约束到某些代数(例如,仅Functor
、仅Applicative
等,以更好地控制依赖关系中的效果(
如今,事情朝着无标签决赛的方向发展。Free monad在IO monad实现(Cats Effect IO、Monix Task、ZIO(和例如Doobie中内部使用(尽管据我所知,Doobie的作者正在考虑将其改写为无标签,或者至少后悔没有使用无标签?(。
如果你想学习如何在建模中使用它,Gabriel Volpe的一本书《Scala中的实用FP》使用了无标签的词尾,还有我自己的小项目,它使用了Cats、FS2、Tapir、无标签等,可以展示一些想法。
如果你打算使用免费,那么,有一些挑战:
sealed trait DomainA[A] extends Product with Serializable
object DomainA {
case class Service1(input1: X, input2: Y) extends DomainA[Z]
// ...
def service1(input1: X, input2: Y): Free[DomainA, Z] =
Free.liftF(Service1(input1, input2))
}
val interpreterA: DomainA ~> IO = ...
使用Free[DomainA, *]
,使用.map
、.flatMap
等组合,并使用interpretA
进行解释。
然后添加另一个域DomainB
。有趣的开始了:
- 您不能仅仅将
Free[DomainA, *]
和Free[DomainB, *]
组合在一起,因为它们是不同的类型,您需要将它们对齐才能实现这一点 - 所以,你必须把所有的代数组合成一个:
type BusinessLogic[A] = EitherK[DomainA, DomainB, A] implicit val injA: InjectK[DomainA, BusinessLogic] = ... implicit val injB: InjectK[DomainB, BusinessLogic] = ...
- 您的服务不能对一个代数进行硬编码,您必须将当前代数注入";较大的";一:
def service1[Total[_]](input1: X, input2: Y)( implicit inject: InjectK[DomainA, Total] ): Free[Total, Z] = Free.liftF(inject.inj(Service1(input1, input2)))
- 您的口译员现在也更加复杂:
val interpreterTotal: EitherK[DomainA, DomainB, *] ~> IO = new (EitherK[DomainA, DomainB, *] ~> IO) { def apply[A](fa: EitherK[DomainA, DomainB, A]) = fa.run.fold(interpreterA, interpreterB) }
- 并且随着每增加一个新代数(
EitherK[DomainA, EitherK[DomainB, ..., *], *]
(,它变得更加复杂
在无标签词尾中,总是有一个依赖项,但几乎总是依赖于一种类型——F
——许多人的经验证据表明,尽管理论上与自由monad的权力相等,但它更容易使用。但这不是一个科学的论点,所以你可以自己尝试免费的monad。例如,请参阅下面这篇关于同时使用多个DSL的文章。
无论你选择一个还是另一个,你都不会被迫在任何地方使用它-所有免费的东西都可以(应该(被解释为一个特定的实现,无标记使你将特定的实现作为参数传递,这样你就可以将其用于单个组件,即在其边缘进行解释。