FunctionK的类型参数的界限

时间:2018-03-08 12:06:55

标签: scala scala-cats

我正在使用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);

2 个答案:

答案 0 :(得分:4)

(我的原始答案包含相同的想法,但显然它没有提供足够的实现细节。这一次,我写了一个更详细的分步指南,讨论了每个中间步骤。每个部分都包含一个单独的可编辑代码段。)

<强> TL; DR

  1. T中出现的每种类型get[T]都需要隐含,因此当DSL程序构造时,必须插入并存储它们,而不是< EM>执行。这解决了隐含问题。
  2. 使用模式匹配从一些受限制的自然变换 ~>粘合自然变换trait RNT[R, F[_ <: R], G[_]]{ def apply[A <: R](x: F[A]): G[A] }的一般策略。这解决了A <: Resource类型绑定的问题。详情如下。
  3. 在您的问题中,您有两个不同的问题:

    1. 隐式FormatDefinition
    2. <: Resource - 类型绑定
    3. 我希望孤立地处理这两个问题中的每一个,并为两者提供可重用的解决方案策略。然后,我将对您的问题应用这两种策略。

      我的答案结构如下:

      1. 首先,我会按照我的理解总结你的问题。
      2. 然后我将解释如何处理implicits,忽略类型绑定。
      3. 然后我将处理类型绑定,这次忽略了含义。
      4. 最后,我将这两种策略应用于您的特定问题。
      5. 从此以后,我假设你有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库。

        设置

        本部分的目标是确保我解决正确的问题,并提供非常简单的模型定义 ResourceFormatClient等等,以便此答案是自包含的 并且可编辑。

        我假设你想使用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}}方法有类型绑定,那就是 它需要额外的含义。

        为Free monad定义解释器时处理implicits

        让我们首先假装客户端中的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中的每一个,您必须在某处获得T1Tn

        • 这些隐式参数必须由编译器提供。

        • 当您解释Format[T_i]类型的整个程序时,只有Definition[T_i]类型而非类型Dsl[X],...,X可用, 所以编译器无法在呼叫站点插入所有必要的T1Tn

        • 因此,所有DefinitionFormat必须作为隐式参数提供给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))
        

        在定义DSL操作的案例类中键入边界

        现在,让我们单独处理绑定类型。假设你的~> 不需要任何暗示,但在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] = ??? } 类型太笼统了 因此,您无法使用clientInterpreterA的内容。 相反,您必须找到其他类型信息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 transformerintersection 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生成OpReader接收可用于隐式参数的值。这解决了O类型具有FormatDefinition实例的问题。

另一个问题是OResource的子类型。要解决这个问题,我们只是说FormatDefinition个实例不仅仅是A个实例,而且任何A也恰好是Resource

如果您在使用FutureOp时遇到问题,请与我们联系。