当一个类在Scala中定义.map函数时,有没有办法通过继承(或其他方式)知道?

时间:2012-12-15 15:59:16

标签: scala

我的问题在下面的代码中表达。 我正在尝试获取一些具有.map功能的输入。我知道如果我调用.map,它会返回一个Int给我。

  // In my case, they are different representations of Ints
  // By that I mean that in the end it all boils down to Int
  val list: Seq[Int] = Seq(1,2,3,4)
  val optInt: Option[Int] = Some(1)
  // I can use a .map with a Seq, check!
  list.map {
    value => println(value)
  }
  // I can use it with an Option, check!
  optInt.map {
    value => println(value)
  }
  // Well, you're asking yourself why do I have to do it,
  // Why don't I use foreach to solve my problem. Check!
  list.foreach(println)
  optInt.foreach(println)

  // The problem is that I don't know what I'm going to get as input
  // The only thing I know is that it's "mappable" (it has the .map function)
  // And that if I were to apply .map it would return Ints to me
  // Like this:
  def printValues(genericInputThatHasMap: ???) {
    genericInputThatHasMap.map {
      value => println(value)
    }
  }

  // The point is, what do I have to do to have this functionality?
  // I'm researching right now, but I still haven't found anything.
  // That's why I'm asking it here =(

  // this works:
  def printValues(genericInputThatHasMap: Seq[Int]) {
    genericInputThatHasMap.map {
      value => println(value)
    }
  }

提前致谢!干杯!

3 个答案:

答案 0 :(得分:7)

首先快速了解mapforeach。如果您只对集合中每个项目执行带副作用的操作(例如,打印到标准输出或文件等)感兴趣,请使用foreach。如果您有兴趣通过转换旧元素中的每个元素来创建新集合,请使用map。当您编写xs.map(println)时,您实际上会打印该集合的所有元素,但您也会找回(完全无用的)单位集合,并且还可能会混淆未来的代码读者 - 包括您自己 - 期望foreach在这种情况下使用。

现在问你的问题。您已经遇到了我认为Scala标准库中最丑陋的一个问题 - 名为mapforeach(以及flatMap)的方法在此处获得了神奇的处理语言级别与定义它们的特定类型无关。例如,我可以这样写:

case class Foo(n: Int) {
  def foreach(f: Int => Unit) {
    (0 until n) foreach f
  }
}

并在for这样的循环中使用它,只是因为我已将我的方法命名为foreach

for (i <- Foo(10)) println(i)

您可以使用structural types在您自己的代码中执行类似操作:

def printValues(xs: { def foreach(f: (Int) => Unit): Unit }) {
  xs foreach println
}

此处任何具有适当类型xs方法的foreach - 例如Option[Int]List[Int] - 都会按预期编译并运行。

当您尝试使用mapflatMap时,结构类型变得更加混乱,并且在其他方​​面不满意 - 由于使用运行时反射,它们会产生一些丑陋的开销,例如。实际上他们必须explicitly enabled in Scala 2.10以避免出于这些原因发出警告。

正如Senia的回答所指出的那样,Scalaz library通过使用type classes Monad来提供更加一致的方法来解决问题。但是,在这样的情况下,您不希望使用Monad:它比您需要的更强大的抽象。您使用Eachforeach提供Functor,为map提供import scalaz._, Scalaz._ def printValues[F[_]: Each](xs: F[Int]) = xs foreach println 。例如,在Scalaz 7中:

def incremented[F[_]: Functor](xs: F[Int]) = xs map (_ + 1)

或者:

{{1}}

总而言之,您可以使用结构类型以标准的,惯用的,但可以说是丑陋的方式做您想要的事情,或者您可以使用Scalaz来获得更清晰的解决方案,但代价是新的依赖。

答案 1 :(得分:3)

我对这两种方法的看法。

结构类型

您可以为foreach使用结构类型,但对于map,您似乎无法构建一个可以跨多种类型工作的结构类型。例如:

import collection.generic.CanBuildFrom

object StructuralMap extends App {
  type HasMapAndForeach[A] = {
//    def map[B, That](f: (A) ⇒ B)(implicit bf: CanBuildFrom[List[A], B, That]): That
    def foreach[B](f: (A) ⇒ B): Unit
  }

  def printValues(xs: HasMapAndForeach[Any]) {
    xs.foreach(println _)
  }

//  def mapValues(xs: HasMapAndForeach[Any]) {
//    xs.map(_.toString).foreach(println _)
//  }

  def forComp1(xs: HasMapAndForeach[Any]) {
    for (i <- Seq(1,2,3)) println(i)
  }

  printValues(List(1,2,3))

  printValues(Some(1))

  printValues(Seq(1,2,3))

//  mapValues(List(1,2,3))
}

scala> StructuralMap.main(new Array[String](0))
1
2
3
4
5
6
7
8
9
10

请参阅上面注释的map方法,它List硬编码为CanBuildFrom隐式的类型参数。可能有一种方法可以一般地提取类型 - 我会将其作为一个问题留给那里的Scala类型大师。我尝试用HasMapAndForeachthis.type代替List,但这些都没有。

关于结构类型的常见性能警告适用。

Scalaz

如果你想支持map,结构类型是死路一条,那么让我们看一下Travis的scalaz方法,看看它是如何工作的。以下是他的方法:

def printValues[F[_]: Each](xs: F[Int]) = xs foreach println

def incremented[F[_]: Functor](xs: F[Int]) = xs map (_ + 1)

(如果我错了,请在下面纠正我,我将此作为scalaz学习经验)

类型类EachFunctor用于将F的类型分别限制为Each[F]Functor[F]可用的隐含类型。例如,在通话中

printValues(List(1,2,3))

编译器将查找满足Each[List]的隐式。 Each特征是

trait Each[-E[_]] {
  def each[A](e: E[A], f: A => Unit): Unit
}

Each对象中隐含Each[TraversableOnce]List是[{1}}的子类型,且特征是逆变的):

TraversableOnce

请注意“上下文绑定”语法

object Each {
  implicit def TraversableOnceEach[A]: Each[TraversableOnce] = new Each[TraversableOnce] {
    def each[A](e: TraversableOnce[A], f: A => Unit) = e foreach  f
  }
}

的简写
def printValues[F[_]: Each](xs: F[Int])

这两个都表示def printValues(xs: F[Int])(implicit ev: Each[F]) F类型类的成员。满足类型类的隐式作为Each参数传递给ev方法。

printValuesprintValues方法中,编译器不知道incremented具有xsmap方法,因为类型参数{{1没有任何上限或下限。至于它可以告诉foreachF并且满足上下文绑定(是类型类的一部分)。有FAnyRef的范围是什么?来自scalaz的foreachmapMA方法:

foreach

请注意,map上的trait MA[M[_], A] { def foreach(f: A => Unit)(implicit e: Each[M]): Unit = e.each(value, f) def map[B](f: A => B)(implicit t: Functor[M]): M[B] = t.fmap(value, f) } foreach方法受mapMA类型类的约束。这些是与原始方法相同的约束,因此约束得到满足,并且通过Each方法隐式转换为Functor

MA[F, Int]

原始方法中的maImplicit类型在trait MAsLow extends MABLow { implicit def maImplicit[M[_], A](a: M[A]): MA[M, A] = new MA[M, A] { val value = a } } 中变为F类型。

然后,传递给原始调用的隐式参数作为隐式参数传递到MMA。如果是foreach,则会在其隐式参数map上调用foreach。在上面的示例中,隐式each的类型为e,因为原始参数为ev,因此Each[TraversableOnce]的类型相同。 Liste上致电foreacheach代理e {/ 1}}。

因此foreach的来电顺序为:

TraversableOnce - &gt; printValues(List(1,2,3)) - &gt; new Each[TraversableOnce] - &gt; printValues - &gt; new MA - &gt; MA.foreach

正如他们所说,没有任何问题无法用额外的间接水平来解决:)

答案 2 :(得分:2)

您可以use MAscalaz

import scalaz._
import Scalaz._

def printValues[A, M[_]](ma: MA[M, A])(implicit e: Each[M]) {
  ma |>| { println _ }
}

scala> printValues(List(1, 2, 3))
1
2
3

scala> printValues(Some(1))
1