我正在使用sealed trait Op[A]
object Op {
final case class Get[T](name: String) extends Op[T]
type OpF[A] = Free[Op, A]
def get[T](name: String): OpF[T] = liftF[Op, T](Get[T](name))
}
FreeMonad。这是代数的简化版本:
Client
其中一个解释器将是第三方库的包装器,在此处称为get
,其class Client {
def get[O <: Resource](name: String)
(implicit f: Format[O], d: Definition[O]): Future[O] = ???
}
方法的签名类似于:
class FutureOp extends (Op ~> Future) {
val client = new Client()
def apply[A](fa: Op[A]): Future[A] =
fa match {
case Get(name: String) =>
client.get[A](name)
}
}
我的问题是如何在我的实现中对该要求进行编码?
apply
我尝试过向我的apply[A <: Resource : Format : Definition]
(例如FunctionK
)引入无法工作的界限。
我理解def run[F[_]: Monad, A](intp: Op ~> F, op: OpF[A]): F[A] = op.foldMap(intp)
val p: Op.OpF[Foo] = Op.get[Foo]("foo")
val i = new FutureOp()
run(i, d)
是转换一阶类型的值的类型,但无论如何我可以编码类型参数的要求吗?
我打算像以下一样使用它:
iframe.contentWindow.postMessage(message, origin);
答案 0 :(得分:4)
(我的原始答案包含相同的想法,但显然它没有提供足够的实现细节。这一次,我写了一个更详细的分步指南,讨论了每个中间步骤。每个部分都包含一个单独的可编辑代码段。)
<强> TL; DR 强>
T
中出现的每种类型get[T]
都需要隐含,因此当DSL程序构造时,必须插入并存储它们,而不是< EM>执行。这解决了隐含问题。~>
粘合自然变换trait RNT[R, F[_ <: R], G[_]]{ def apply[A <: R](x: F[A]): G[A] }
的一般策略。这解决了A <: Resource
类型绑定的问题。详情如下。在您的问题中,您有两个不同的问题:
Format
和Definition
<: Resource
- 类型绑定我希望孤立地处理这两个问题中的每一个,并为两者提供可重用的解决方案策略。然后,我将对您的问题应用这两种策略。
我的答案结构如下:
从此以后,我假设你有scalaVersion
2.12.4
,依赖
libraryDependencies += "org.typelevel" %% "cats-core" % "1.0.1"
libraryDependencies += "org.typelevel" %% "cats-free" % "1.0.1"
并插入
import scala.language.higherKinds
在适当的地方。
请注意,解决方案策略并非特定于此特定scala版本或cats
库。
本部分的目标是确保我解决正确的问题,并提供非常简单的模型定义
Resource
,Format
,Client
等等,以便此答案是自包含的
并且可编辑。
我假设你想使用Free
monad构建一个特定于域的语言。
理想情况下,您希望DSL看起来大致如此(我已将名称DslOp
用于操作,Dsl
用于生成的免费monad):
import cats.free.Free
import cats.free.Free.liftF
sealed trait DslOp[A]
case class Get[A](name: String) extends DslOp[A]
type Dsl[A] = Free[DslOp, A]
def get[A](name: String): Dsl[A] = liftF[DslOp, A](Get[A](name))
它定义了一个命令get
,可以在给定字符串的情况下获取A
类型的对象
名。
稍后,您希望使用某些get
提供的Client
方法来解释此DSL
你无法修改:
import scala.concurrent.Future
trait Resource
trait Format[A <: Resource]
trait Definition[A <: Resource]
object Client {
def get[A <: Resource](name: String)
(implicit f: Format[A], d: Definition[A]): Future[A] = ???
}
您的问题是get
的{{1}}方法有类型绑定,那就是
它需要额外的含义。
让我们首先假装客户端中的Client
- 方法需要隐含,但是
忽略现在绑定的类型:
get
在我们写下解决方案之前,让我们简要讨论为什么你不能提供所有解决方案
在import scala.concurrent.Future
trait Format[A]
trait Definition[A]
object Client {
def get[A](name: String)(implicit f: Format[A], d: Definition[A])
: Future[A] = ???
}
中调用apply
方法时必要的含义。
传递给~>
时,foldMap
的{{1}}是假设的
能够处理apply
类型的任意长程序来生成FunctionK
。
Dsl[X]
类型的任意长程序可以包含无限数量的程序
针对不同类型的Future[X]
,...,Dsl[X]
命令get[T1]
,...,get[Tn]
。
对于T1
,...,Tn
中的每一个,您必须在某处获得T1
和Tn
。
这些隐式参数必须由编译器提供。
当您解释Format[T_i]
类型的整个程序时,只有Definition[T_i]
类型而非类型Dsl[X]
,...,X
可用,
所以编译器无法在呼叫站点插入所有必要的T1
和Tn
。
因此,所有Definition
和Format
必须作为隐式参数提供给Definition
当你构建 Format
- 程序时,而不是当你解释时。
解决方案是将get[T_i]
和Dsl
添加为Format[A]
案例类的成员,
并使用Definition[A]
定义Get[A]
接受这两个额外的隐含
参数:
get[A]
现在,我们可以定义lift[DslOp, A]
- 解释器的第一个近似值,至少是这样
可以应付暗示:
import cats.free.Free
import cats.free.Free.liftF
import cats.~>
sealed trait DslOp[A]
case class Get[A](name: String, f: Format[A], d: Definition[A])
extends DslOp[A]
type Dsl[A] = Free[DslOp, A]
def get[A](name: String)(implicit f: Format[A], d: Definition[A])
: Dsl[A] = liftF[DslOp, A](Get[A](name, f, d))
现在,让我们单独处理绑定类型。假设你的~>
不需要任何暗示,但在val clientInterpreter_1: (DslOp ~> Future) = new (DslOp ~> Future) {
def apply[A](op: DslOp[A]): Future[A] = op match {
case Get(name, f, d) => Client.get(name)(f, d)
}
}
上施加了额外的限制:
Client
如果您尝试以与此处相同的方式记下A
在前面的例子中,你会注意到import scala.concurrent.Future
trait Resource
object Client {
def get[A <: Resource](name: String): Future[A] = ???
}
类型太笼统了
因此,您无法使用clientInterpreter
中A
的内容。
相反,您必须找到其他类型信息Get[A]
的范围
不会丢失。实现它的一种方法是在Client.get
本身上定义A <: Resource
方法。
这种accept
方法将取代完全一般的自然变换Get
能够使用受限域的自然转换。
这是一个模型的特征:
~>
它看起来几乎像accept
,但有一个额外的trait RestrictedNat[R, F[_ <: R], G[_]] {
def apply[A <: R](fa: F[A]): G[A]
}
限制。现在我们
可以在~>
中定义A <: R
:
accept
并写下我们的翻译的第二个近似值,没有任何 讨厌的类型转换:
Get
这个想法可以推广到任意数量的类型构造函数import cats.free.Free
import cats.free.Free.liftF
import cats.~>
sealed trait DslOp[A]
case class Get[A <: Resource](name: String) extends DslOp[A] {
def accept[G[_]](f: RestrictedNat[Resource, Get, G]): G[A] = f(this)
}
type Dsl[A] = Free[DslOp, A]
def get[A <: Resource](name: String): Dsl[A] =
liftF[DslOp, A](Get[A](name))
,...,
val clientInterpreter_2: (DslOp ~> Future) = new (DslOp ~> Future) {
def apply[A](op: DslOp[A]): Future[A] = op match {
case g @ Get(name) => {
val f = new RestrictedNat[Resource, Get, Future] {
def apply[X <: Resource](g: Get[X]): Future[X] = Client.get(g.name)
}
g.accept(f)
}
}
}
,类型限制Get_1
,...,Get_N
。一般的想法对应于
从较小的角度构造分段定义的自然变换
仅适用于某些亚型的碎片。
现在我们可以将两种常规策略合并为一种解决方案 你的具体问题:
R1
现在,RN
可以解决这两个问题:处理类型绑定问题
通过为每个案例类定义import scala.concurrent.Future
import cats.free.Free
import cats.free.Free.liftF
import cats.~>
// Client-definition with both obstacles: implicits + type bound
trait Resource
trait Format[A <: Resource]
trait Definition[A <: Resource]
object Client {
def get[A <: Resource](name: String)
(implicit fmt: Format[A], dfn: Definition[A])
: Future[A] = ???
}
// Solution:
trait RestrictedNat[R, F[_ <: R], G[_]] {
def apply[A <: R](fa: F[A]): G[A]
}
sealed trait DslOp[A]
case class Get[A <: Resource](
name: String,
fmt: Format[A],
dfn: Definition[A]
) extends DslOp[A] {
def accept[G[_]](f: RestrictedNat[Resource, Get, G]): G[A] = f(this)
}
type Dsl[A] = Free[DslOp, A]
def get[A <: Resource]
(name: String)
(implicit fmt: Format[A], dfn: Definition[A])
: Dsl[A] = liftF[DslOp, A](Get[A](name, fmt, dfn))
val clientInterpreter_3: (DslOp ~> Future) = new (DslOp ~> Future) {
def apply[A](op: DslOp[A]): Future[A] = op match {
case g: Get[A] => {
val f = new RestrictedNat[Resource, Get, Future] {
def apply[X <: Resource](g: Get[X]): Future[X] =
Client.get(g.name)(g.fmt, g.dfn)
}
g.accept(f)
}
}
}
,对其类型参数施加上限,
并且通过向DSL的clientInterpreter_3
方法添加隐式参数列表来解决implicits问题。
答案 1 :(得分:1)
我认为通过将ReaderT
monad transformer与intersection types相结合,我找到了解决问题的方法:
import scala.concurrent.Future
import cats.~>
import cats.data.ReaderT
import cats.free.Free
object FreeMonads {
sealed trait Op[A]
object Op {
final case class Get[T](name: String) extends Op[T]
type OpF[A] = Free[Op, A]
def get[T](name: String): OpF[T] = Free.liftF[Op, T](Get[T](name))
}
trait Resource
trait Format[A]
trait Definition[A]
trait Client {
def get[O <: Resource](name: String)
(implicit f: Format[O], d: Definition[O]): Future[O]
}
type Result[A] = ReaderT[
Future,
(Format[A with Resource], Definition[A with Resource]),
A,
]
class FutureOp(client: Client) extends (Op ~> Result) {
def apply[A](fa: Op[A]): Result[A] =
fa match {
case Op.Get(name: String) =>
ReaderT {
case (format, definition) =>
// The `Future[A]` type ascription makes Intellij IDEA's type
// checker accept the code.
client.get(name)(format, definition): Future[A]
}
}
}
}
背后的基本思想是,您从Reader
生成Op
,Reader
接收可用于隐式参数的值。这解决了O
类型具有Format
和Definition
实例的问题。
另一个问题是O
是Resource
的子类型。要解决这个问题,我们只是说Format
和Definition
个实例不仅仅是A
个实例,而且任何A
也恰好是Resource
。
如果您在使用FutureOp
时遇到问题,请与我们联系。