【问题标题】:Scala Typeclass with multiple parameters error具有多个参数错误的Scala Typeclass
【发布时间】:2017-01-26 08:30:18
【问题描述】:

我遇到了一个与此多参数类型类实现相关的奇怪错误

trait Feedtype
trait Atom extends Feedtype
trait Rss2 extends Feedtype
case object Atom extends Atom
case object Rss2 extends Rss2

trait Encoding
trait Xml extends Encoding
trait Json
case object Json extends Json
case object Xml extends Xml

trait Content
case class show[T <: Feedtype,E <: Encoding](str: String, tp: T, en: E) extends Content

trait TypeClass[C,D,E] {
  def show(c: C, d: D, e:E): String
}

trait Service{
  def print[T,C,D](t: T,c:C, d:D)(implicit s: TypeClass[T,C,D]): String
}

object Service extends Service{
  def print[T,C,D](t:T, c:C, d:D)(implicit s: TypeClass[T,C,D]): String =
    s.show(t,c,d)
}

implicit val t1 = new TypeClass[show[Atom,Xml], Atom, Xml] {
  def show(c: show[Atom,Xml], d:Atom, e:Xml) = c.str
}

implicit val t2 = new TypeClass[show[Rss2,Xml], Rss2, Xml] {
  def show(c: show[Rss2,Xml], d:Rss2, e:Xml) = "hi there " + c.str
}

val s1 = show("some show", Atom, new Xml {})

Service.print(s1, s1.tp, s1.en)

我得到的错误是

type mismatch;
 found   : A$A10.this.typeclass[A$A10.this.show[A$A10.this.atom,A$A10.this.xml],A$A10.this.atom,A$A10.this.xml]
 required: A$A10.this.typeclass[A$A10.this.show[A$A10.this.atom.type,A$A10.this.xml.type],A$A10.this.atom.type,A$A10.this.xml.type]
service.print(s1, s1.tp, s1.en)(t1);}
                                ^

我在这里错过了什么?


更新

我发现问题在于atomxml 在被用作创建s1 的值时是case objects。如果我使用new atom {}new xml {},那么执行会正常进行。但是,我想知道为什么 case objects 被认为与它们所代表的类型不同?

【问题讨论】:

标签: scala types functional-programming typeclass


【解决方案1】:

正如已经指出的问题出在:

val s1 = show("some show", Atom, new Xml {})
                //           ^
                // The most specific type of this is Atom.type

Service#print 的类型参数由传递给它的对象推断。 s1show[Atom.type, Xml],因此编译器正在为 Service#print 寻找隐含的 TypeClass[show[Atom.type, Xml], Atom.type, Xml]。编译器不会自动尝试将Atom.type(转换对象)向上转换为Atom,原因如下:

  1. 编译器不知道show[Atom.type, Xml] 也是show[Atom, Xml]show 被声明为不变量,TypeClass 也是如此)。

  2. 有一个隐含的TypeClass[show[Atom,Xml], Atom, Xml] 可用于通用Atom,但不适用于更具体的类型Atom.type


您的示例非常复杂,但不需要重现该问题。考虑一下:

trait TypeClass[A] {
    def show(a: A): String
}

trait Atom
case object Atom extends Atom

implicit val ar = new TypeClass[Atom] {
    def show(a: Atom): String = "Atom"
}

object Service {
    def print[A](a: A)(implicit tc: TypeClass[A]) = tc.show(a)
}

scala> Service.print(Atom)
<console>:18: error: could not find implicit value for parameter tc: TypeClass[Atom.type]
       Service.print(Atom)
                    ^

我们得到相同的编译错误。这是因为在上述结构中,就编译器而言,TypeClass[Atom.type]TypeClass[Atom] 不同,但不必如此。我们可以在A 上制作TypeClass 逆变,这意味着TypeClass[Atom] 可以代替TypeClass[Atom.type]。在这种情况下这样做是有意义的,因为在我的示例中,TypeClass 代表了一个类似打印机的类。如果您知道如何打印超类型,则可以打印子类型,但不一定反过来。

trait TypeClass[-A] {
    def show(a: A): String
}

这将允许隐式现在解决。如果您想保持TypeClass 不变,我们可以自己向上转换Atom

scala> Service.print(Atom: Atom)
res4: String = Atom

如何将此应用到您的示例中?

最简单的方法是向上转换Atom

val s1 = show("some show", Atom: Atom, new Xml {})

或者showTypeClass 不能是不变的。首先,使showT 上协变,这样show[Atom.type, B] 也是show[Atom, B]

case class show[+T <: Feedtype, E <: Encoding](str: String, tp: T, en: E) extends Content

然后,使TypeClassCD 上逆变,这样TypeClass[show[Atom,Xml], Atom, Xml] 也可以被视为TypeClass[show[Atom.type, Xml], Atom.type,Xml],这将允许提取隐含的t1

trait TypeClass[-C, -D, E] {
  def show(c: C, d: D, e:E): String
}

// cutting out putting it all together to save space

scala> val s1 = show("some show", Atom, new Xml {})
s1: show[Atom.type,Xml] = show(some show,Atom,$anon$1@76a4d6c)

scala> Service.print(s1, s1.tp, s1.en)
res1: String = some show

【讨论】:

  • 对于这个详细的回答,我无法表达我的感激之情。非常感谢。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-05-26
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-06-17
相关资源
最近更新 更多