简体   繁体   English

为什么Scala不选择此类型类中最具体的实例?

[英]Why isn't Scala picking the most specific instance in this this type class?

I am struggling to build this computation pipeline builder in Scala. 我正在努力在Scala中构建此计算管道构建器。 I want a class that has two methods, map and reduce , that receive anonymous functions in a "fluent interface". 我想要一个具有两个方法mapreduce的类,这些方法在“流畅的接口”中接收匿名函数。 These functions will be composed, so I want to type-check all of them, also having their input type inferred from the previous method call... See this related question of mine (it's all part of the puzzle). 这些函数将组成,因此我想对所有函数进行类型检查,并从先前的方法调用中推断出它们的输入类型...请参阅我的这个相关问题 (这都是难题的一部分)。

All my questions oversimplify the problem, but answers have been helpful, and I think I am almost arriving there. 我所有的问题都简化了问题,但答案很有帮助,我想我快到了。

I have managed to make everything work as long as I have as special method that I use when I register a mapper function that has a KeyVal output. 只要我注册了具有KeyVal输出的映射器函数时使用的特殊方法,就可以使所有工作正常进行。 But I wanted to use the same map name for the functions, and to simplify the architecture in general too. 但是我想为函数使用相同的map名称,并且总体上也简化了体系结构。 For that I decided to try using the type class pattern. 为此,我决定尝试使用类型类模式。 That allows me to do different things depending on the type from the function in the argument of my builder method. 这使我可以根据builder方法的参数中函数的类型来做不同的事情。 Keep in mind too that part of my problem is that if I give to the mapper method a function that outputs a KeyVal[K,V] type (pretty much a tuple), and I need to store this K and V as type parameters from my builder class, so they can be used to type-check / infer the type from the reducer method later on. 还要记住,我的问题的一部分是,如果我给mapper方法提供了一个输出KeyVal[K,V]类型(几乎是一个元组)的函数,并且我需要将此KV存储为来自我的构建器类,因此以后可以使用它们从reducer方法进行类型检查/推断类型。

This is my builder class 这是我的建造者课程

case class PipelineBuilder[A, V](commandSequence: List[MRBuildCommand]) {

  trait Foo[XA, XB, +XV] {
    def constructPB(xs: XA => XB): PipelineBuilder[XB, XV]
  }

  implicit def fooAny[XA, XB]: Foo[XA, XB, Nothing] = new Foo[XA, XB, Nothing] {
    def constructPB(ff: XA => XB) = PipelineBuilder[XB, Nothing](MapBuildCommand(ff) :: commandSequence)
  }

  implicit def fooKV[XA, XK, XV]: Foo[XA, KeyVal[XK,XV], XV] = new Foo[XA, KeyVal[XK,XV], XV] {
    def constructPB(ff: XA => KeyVal[XK,XV]) = PipelineBuilder[KeyVal[XK,XV], XV](MapBuildCommand(ff) :: commandSequence)
  }

  def innermymap[AA, FB, FV](ff: AA => FB)(implicit mapper: Foo[AA, FB, FV]) = mapper.constructPB(ff)

  def mymap[FB](ff: A => FB) = innermymap(ff)


  def rreduce[K](newFun: (V, V) => V)(implicit ev: KeyVal[K, V] =:= A) =
    PipelineBuilder[A,V](RedBuildCommand[K, V](newFun) :: commandSequence)

  def output(dest: MRWorker) = constructPipeline(dest)
  //...

}

And this is how the class is used in the main program 这就是在主程序中使用该类的方式

object PipelineLab extends App {

  val mapredPipeline = PipelineBuilder[String, Nothing](List())
    .mymap { s: String => s.toLowerCase }
    .mymap { s: String => KeyVal(s, 1) }
    .rreduce(_ + _)
    .output(OutputWorker)
  // ...
}

Note that the s: String shouldn't be necessary because if the type parameter A from the class. 请注意, s: String是不必要的,因为如果类中的类型参数A Same goes for V in the rreduce . 在减少中, V也是rreduce

I have already managed to use the type class pattern in the following simple example. 在下面的简单示例中,我已经设法使用类型类模式。 If I output a tuple of something, it does something different... Here it is. 如果我输出某些内容的元组,它会执行某些不同的操作……就在这里。

object TypeClassLab extends App {

  trait FuncAdapter[A, B] {
    def runfunc(x: A, f: A => B): B
  }

  implicit def myfunplain[X, A]: FuncAdapter[X, A] = new FuncAdapter[X, A] {
    def runfunc(x: X, f: X => A): A = {
      println("Function of something to plain, non-tuple type")
      f(x)
    }
  }

  implicit def myfuntuple[X, AA, AB]: FuncAdapter[X, (AA, AB)] = new FuncAdapter[X, (AA, AB)] {
    def runfunc(x: X, f: X => (AA, AB)): (AA, AB) = {
      println("Function from String to tuple")
      f(x)
    }
  }

  def ffuunn[A, B](x: A)(f: A => B)(implicit fa: FuncAdapter[A, B]) = {
    fa.runfunc(x, f)
  }

  println(ffuunn("obaoba") { s => s.length })
  println(ffuunn("obaobaobaobaoba") { s => s.length })
  println(ffuunn("obaoba") { s => (s.length, s.reverse) })
  println(ffuunn("obaobaobaobaoba") { s => (s.length, s.reverse) })
}
//OUTPUT:
//Function of something to plain, non-tuple type
//6
//Function of something to plain, non-tuple type
//15
//Function from String to tuple
//(6,aboabo)
//Function from String to tuple
//(15,aboaboaboaboabo)

Works like a charm. 奇迹般有效。 But then I can't adapt it to my real problem... Right now it seems the compiler is not looking for the more specific fooKV implicit, and instead always picks fooAny , and that causes an error when I try to run rreduce , because it is expecting a V <: Nothing . 但是然后我无法使其适应我的实际问题...现在看来,编译器没有在寻找更具体的fooKV隐式,而是总是选择fooAny ,这在我尝试运行rreduce时会导致错误,因为它期望V <: Nothing How do I make it work? 我该如何运作?

I'm not sure I fully understand your question. 我不确定我是否完全理解您的问题。

As far as choosing fooAny vs fooKV , the instance of Foo must be known and passed appropriately from the site where the types are known. 至于选择fooAny vs fooKV ,必须知道Foo的实例,并从已知类型的站点适当地传递它。 This would be the place where mymap is called. 这将是调用mymap的地方。 Foo is not passed as a parameter though. 虽然Foo没有作为参数传递。

def mymap[FB](ff: A => FB) = innermymap(ff)

You are requiring it be know when innermymap(ff) is called. 需要时,它可以知道innermymap(ff)被调用。 At this point, type information is lost. 此时,类型信息丢失。 The only available instance of Foo is fooAny . Foo唯一可用的实例是fooAny

This is actually an example of why a definition like fooAny should not exist. 这实际上是为什么不应该存在像fooAny这样的定义的一个示例。 You are defining a valid relationship between any XA and any XB , even if these are in fact just Any . 您正在定义任何XA和任何XB之间的有效关系,即使它们实际上只是Any The existence of this definition is causing your code to type check when it should not. 此定义的存在导致您的代码在不应该键入时进行检查。 This will most likely happen again. 这很可能会再次发生。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM