如何使用无形状为具有依赖类型的类型类创建实例 [英] How to create an instances for typeclass with dependent type using shapeless

本文介绍了如何使用无形状为具有依赖类型的类型类创建实例的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试为具有依赖类型的类型类派生一个元组实例.我正在使用shapeless为元组元素创建召唤类型类.我在匹配元组实例类型时遇到了麻烦:

I'm trying to derive a tuple instance for a type class with dependent type. I'm using shapeless to create summon the type class for the tuple elements. I'm having trouble matching tuple instance types:

import shapeless.the
import simulacrum.typeclass

@typeclass trait Identifiable[M] {
  type K
  def identify(id: M): K
}

object Identifiable{
  implicit def identifiableTuple[K1: Identifiable, K2: Identifiable]: Identifiable[(K1,K2)] = new Identifiable[(K1,K2)]{
     val b = the[Identifiable[K2]]
    val a = the[Identifiable[K1]]
    type K = (a.K, b.K)   
    override def identify(id: (K1, K2)): K = {
          val k1 = the[Identifiable[K1]].identify(id._1)
          val k2 = the[Identifiable[K2]].identify(id._2)
          (k1,k2)
        }
  }

我收到此错误:

type mismatch;
 found   : k1.type (with underlying type ai.fugo.cms.service.common.domain.Identifiable[K2]#K)
 required: this.a.K

type mismatch;
 found   : k2.type (with underlying type ai.fugo.cms.service.common.domain.Identifiable[K1]#K)
 required: this.b.K

推荐答案

您的代码中有几个错误.

There are several mistakes in your code.

首先,如果返回(k1,k2),则 k1 k2 [Identifiable [K1]].identify(id._1) the [Identifiable [K2]].identify(id._2)相对应,反之亦然定义它们.(错别字是固定的.)

Firstly, if you return (k1, k2) then k1, k2 should be the[Identifiable[K1]].identify(id._1), the[Identifiable[K2]].identify(id._2) correspondingly and not vice versa as you defined them. (Typo is fixed.)

第二,,您忘记了类型优化.您将 identifiableTuple 的返回类型声明为 Identifiable [(K1,K2)] ,而不是正确的 Identifiable [(K1,K2)] {类型K =(aK,bK)} (又名 Identifiable.Aux [(K1,K2),(aK,bK)] ).如果您保留 Identifiable [(K1,K2)] ,则实际上是向右上方

Secondly, you forgot type refinement. You declare return type of identifiableTuple to be Identifiable[(K1,K2)] instead of correct Identifiable[(K1,K2)] { type K = (a.K, b.K)} (aka Identifiable.Aux[(K1,K2), (a.K, b.K)]). If you keep Identifiable[(K1,K2)] you actually upcast right hand side

new Identifiable[(K1,K2)]{
  ...
  type K = (a.K, b.K)   
  ...
}

以及此隐式实例类型K =(a.K,b.K)的信息将丢失.

and information that for this implicit instance type K = (a.K, b.K) will be lost.

由于必须恢复类型细化,因此无法使用上下文范围来编写 identifiableTuple ,因此必须使用隐式块来编写它

Since you have to restore type refinement you can't write identifiableTuple with context bounds, you have to write it with implicit block

implicit def identifiableTuple[K1, K2](implicit
  a: Identifiable[K1],
  b: Identifiable[K2]
): Identifiable[(K1, K2)] {type K = (a.K, b.K)} = new Identifiable[(K1, K2)] {
  type K = (a.K, b.K)
  override def identify(id: (K1, K2)): K = {
    val k1 = a.identify(id._1)
    val k2 = b.identify(id._2)
    (k1, k2)
  }
}

您可以在编译时测试代码

You can test your code at compile time

implicit val int: Identifiable[Int] { type K = Double } = null
implicit val str: Identifiable[String] { type K = Char } = null
implicitly[Identifiable[(Int, String)] { type K = (Double, Char)}]

您可以使用 Aux 模式将其重写 type Aux [M,K0] = Identifiable [M] {type K = K0}

You can rewrite this with Aux pattern type Aux[M, K0] = Identifiable[M] { type K = K0 }

implicit def identifiableTuple[K1, K2](implicit
  a: Identifiable[K1],
  b: Identifiable[K2]
): Identifiable.Aux[(K1, K2), (a.K, b.K)] = new Identifiable[(K1, K2)] {
  type K = (a.K, b.K)
  override def identify(id: (K1, K2)): K = {
    val k1 = a.identify(id._1)
    val k2 = b.identify(id._2)
    (k1, k2)
  }
} // (*)

implicit val int: Identifiable.Aux[Int, Double] = null
implicit val str: Identifiable.Aux[String, Char] = null
implicitly[Identifiable.Aux[(Int, String), (Double, Char)]]

这类似于 @MateuszKubuszok 的答案

implicit def identifiableTuple[M1, M2, K1, K2](implicit
  a: Identifiable.Aux[M1, K1],
  b: Identifiable.Aux[M2, K2]
): Identifiable.Aux[(M1, M2), (K1, K2)] = new Identifiable[(M1, M2)] {
  type K = (K1, K2)
  override def identify(id: (M1, M2)): K = {
    val k1 = a.identify(id._1)
    val k2 = b.identify(id._2)
    (k1, k2)
  }
} // (**)

尽管后者需要两个类型参数的额外推断.

although the latter needs extra inferrence of two type parameters.

第三,您不能像

那样用隐式甚至是 the 编写(*)

And thirdly, you can't write (*) with implicitly or even the inside like

implicit def identifiableTuple[K1, K2](implicit
  a: Identifiable[K1],
  b: Identifiable[K2]
): Identifiable.Aux[(K1, K2), (a.K, b.K)] = new Identifiable[(K1, K2)] {
  type K = (a.K, b.K)
  override def identify(id: (K1, K2)): K = {
    val k1 = the[Identifiable[K1]].identify(id._1)
    val k2 = the[Identifiable[K2]].identify(id._2)
    (k1, k2)
  }
}

问题在于,在Scala中定义了与路径相关的类型,因此即使 a == a1 b == b1 键入 aK a1.K bK b1.K 不同( a1 b1 [Identifiable [K1]] [Identifiable [K2]] ).因此,您返回错误类型为(a1.K,b1.K)(k1,k2).

The thing is that path-dependent types are defined in Scala so that even when a == a1, b == b1 types a.K and a1.K, b.K and b1.K are different (a1, b1 are the[Identifiable[K1]], the[Identifiable[K2]]). So you return (k1, k2) of wrong type (a1.K,b1.K).

但是,如果您以(**)风格编写

But if you write it in (**) style

implicit def identifiableTuple[M1, M2, K1, K2](implicit
  a: Identifiable.Aux[M1, K1],
  b: Identifiable.Aux[M2, K2]
): Identifiable.Aux[(M1, M2), (K1, K2)] = new Identifiable[(M1, M2)] {
  type K = (K1, K2)
  override def identify(id: (M1, M2)): K = {
    val k1 = the[Identifiable[M1]].identify(id._1)
    val k2 = the[Identifiable[M2]].identify(id._2)
    (k1, k2)
  }
}

然后就可以了(使用 the ,但不能正确使用 ),因为编译器会推断(k1,k2)具有类型>(K1,K2).

then it will be ok (with the but not with implicitly) because compiler infers that (k1,k2) has type (K1,K2).

这篇关于如何使用无形状为具有依赖类型的类型类创建实例的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆