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

时间:2022-08-25 17:05:05

I am struggling to build this computation pipeline builder in Scala. I want a class that has two methods, map and reduce, that receive anonymous functions in a "fluent interface". 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).

我正在努力在Scala中构建这个计算管道构建器。我想要一个有两个方法的类,map和reduce,它们在“流畅的界面”中接收匿名函数。这些函数将被组合,所以我想要对它们进行类型检查,同时从前一个方法调用中推断出它们的输入类型...参见我的相关问题(这是拼图的全部内容)。

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. But I wanted to use the same map name for the functions, and to simplify the architecture in general too. 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. 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.

我已经设法使一切正常工作,只要我在注册具有KeyVal输出的mapper函数时使用的特殊方法。但是我想为函数使用相同的映射名称,并且通常也要简化体系结构。为此,我决定尝试使用类型类模式。这允许我根据构建器方法的参数中的函数的类型执行不同的操作。请记住,我的问题的一部分是,如果我给mapper方法一个输出KeyVal [K,V]类型(几乎是一个元组)的函数,我需要将这个K和V作为类型参数存储起来我的构建器类,因此可以在以后使用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. Same goes for V in the rreduce.

请注意,s:String不应该是必需的,因为如果类中的类型参数为A.同样适用于rreduce中的V.

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. How do I make it work?

奇迹般有效。但后来我无法适应我的真正问题......现在似乎编译器没有寻找更具体的fooKV隐式,而是总是选择fooAny,并且当我尝试运行rreduce时会导致错误,因为它期待V <:没什么。我如何使其工作?

1 个解决方案

#1


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. This would be the place where mymap is called. Foo is not passed as a parameter though.

至于选择fooAny vs fooKV,必须知道Foo的实例并从已知类型的站点中适当地传递。这将是调用mymap的地方。但Foo不作为参数传递。

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

You are requiring it be know when innermymap(ff) is called. At this point, type information is lost. The only available instance of Foo is fooAny.

你需要知道何时调用innermymap(ff)。此时,类型信息丢失。唯一可用的Foo实例是fooAny。

This is actually an example of why a definition like fooAny should not exist. You are defining a valid relationship between any XA and any XB, even if these are in fact just Any. The existence of this definition is causing your code to type check when it should not. This will most likely happen again.

这实际上是为什么不应该存在像fooAny这样的定义的例子。您正在定义任何XA和任何XB之间的有效关系,即使这些实际上只是Any。此定义的存在导致您的代码在不应该的情况下键入check。这很可能会再次发生。

#1


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. This would be the place where mymap is called. Foo is not passed as a parameter though.

至于选择fooAny vs fooKV,必须知道Foo的实例并从已知类型的站点中适当地传递。这将是调用mymap的地方。但Foo不作为参数传递。

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

You are requiring it be know when innermymap(ff) is called. At this point, type information is lost. The only available instance of Foo is fooAny.

你需要知道何时调用innermymap(ff)。此时,类型信息丢失。唯一可用的Foo实例是fooAny。

This is actually an example of why a definition like fooAny should not exist. You are defining a valid relationship between any XA and any XB, even if these are in fact just Any. The existence of this definition is causing your code to type check when it should not. This will most likely happen again.

这实际上是为什么不应该存在像fooAny这样的定义的例子。您正在定义任何XA和任何XB之间的有效关系,即使这些实际上只是Any。此定义的存在导致您的代码在不应该的情况下键入check。这很可能会再次发生。