Scala Multiple Inheritance: Differentiate between Iterable and PartialFunction in method arguments

和自甴很熟 提交于 2019-12-23 02:55:15

问题


I want to be able to define a method with the same name that has a different implementation if the argument is an Iterable[T1] vs a function: T1 => T2

However, many classes that implement Iterable also implement PartialFunction

For example:

object FunList {
  def foo(itr: Iterable[Int]) = println("hello")
  def foo(f: (Int => Int)) = println("Goodbye")
}

scala> FunList.foo(List(1))
<console>:9: error: ambiguous reference to overloaded definition,
both method foo in object FunList of type (f: Int => Int)Unit
and  method foo in object FunList of type (itr: Iterable[Int])Unit
match argument types (List[Int])
          FunList.foo(List(1))

currently my solution looks like this, but it does not match subclasses of Iterable which are not also subclasses of PartialFunction

case class SeqOrFun[T1, T2](f: (T1 => T2))
implicit def seqOrFun[T1, T2](f: (T1 => T2)) = SeqOrFun(f)

def unfurl[T1: Numeric, T2: Numeric](x: SeqOrFun[T1, T2], y: SeqOrFun[T1, T2]) = {
  (x.f, y.f) match {
    case (xs: Iterable[T1], ys: Iterable[T2]) => (xs, ys)
    case (xf: (T2 => T1), ys: Iterable[T2]) => (ys.map(xf), ys)
    case (xs: Iterable[T1], yf: (T1 => T2)) => (xs, xs.map(yf))
  }
}

回答1:


Since List is both Iterable[Int] and Int => Int, as you've said, what you're writing is inherently abmiguous. Perhaps I've misread, but I don't see any place at all where you've specified whether you want FunList.foo(List(1)) to print "hello" or "Goodbye".

First, you can use a cast at the call site. I assume you already know this, but just to be explicit for the sake of discussion:

FunList.foo(List(1): Int => Int) // "hello"
FunList.foo(List(1): Iterable[Int]) // "Goodbye"

If our objective here is to allow the caller to simply write FunList.foo(List(1)), you could use the magnet pattern. This means you don't use method overloading at all; instead you write a single method, and the dispatch is done with implicit conversions.

sealed trait FooMagnet
case class HelloMagnet(itr: Iterable[Int]) extends FooMagnet
case class GoodbyeMagnet(f: Int => Int) extends FooMagnet

def foo(x: FooMagnet): Unit = x match {
  case HelloMagnet(itr) => println("hello")
  case GoodbyeMagnet(f) => println("Goodbye")
}

def a(): Unit = {
  implicit def listIsHelloMagnet(x: List[Int]): FooMagnet = HelloMagnet(x)
  FunList.foo(List(1)) // "hello"
}

def b(): Unit = {
  implicit def listIsGoodbyeMagnet(x: List[Int]): FooMagnet = GoodbyeMagnet(x)
  FunList.foo(List(1)) // "Goodbye"
}

The fun advantage you get here is that the dispatch decision is decoupled from the foo implementation, since it's determined by those implicits which you can define wherever you like.

You can also use this to resolve the ambiguity problem! Start with the two implicits from earlier:

implicit def iterableIsHelloMagnet(x: Iterable[Int]): FooMagnet = HelloMagnet(x)
implicit def functionIsGoodbyeMagnet(x: Int => Int): FooMagnet = GoodbyeMagnet(x)

And then add another implicit specifically for List[Int].

implicit def listIsHelloMagnet(x: List[Int]): FooMagnet = HelloMagnet(x)

Scala is clever enough to see that this third conversion is more specific than the first two, so it will use that one for List[Int] even though they all apply. Thus we can now write:

FunList.foo(Set(1)) // "hello"
FunList.foo((_: Int) + 1) // "Goodbye"
FunList.foo(List(1)) // "hello"

And you can move the definitions of those implicits into the called library, so all the caller has to do is import them.



来源:https://stackoverflow.com/questions/27261371/scala-multiple-inheritance-differentiate-between-iterable-and-partialfunction-i

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!