6

我的问题在下面的代码中表达。我正在尝试获取一些包含 .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)
    }
  }

提前致谢!干杯!

4

3 回答 3

7

首先简要介绍一下mapforeach。如果您只对对集合中的每个项目执行具有副作用的操作(例如,打印到标准输出或文件等)感兴趣,请使用foreach. 如果您有兴趣通过转换旧集合中的每个元素来创建新集合,请使用map. 当您编写xs.map(println)时,实际上您将打印集合的所有元素,但您也会得到一个(完全无用的)单元集合,并且还可能使您的代码的未来读者(包括您自己)感到困惑,他们希望foreach成为在这种情况下使用。

现在谈谈你的问题。您遇到了我认为 Scala 标准库中最丑陋的缺陷之一——名为mapand foreach(and flatMap) 的方法在语言级别上得到了神奇的处理,而与定义它们的特定类型无关。例如,我可以这样写:

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

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

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

您可以使用结构类型在您自己的代码中执行类似的操作:

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

在这里,任何xs具有适当类型foreach方法的方法(例如 anOption[Int]或 a List[Int])都将按预期编译和工作。

当您尝试使用时,结构类型会变得更加混乱mapflatMap并且在其他方​​面并不令人满意——例如,由于它们使用运行时反射,它们会带来一些难看的开销。由于这些原因,它们实际上必须在 Scala 2.10 中显式启用以避免警告。

正如 senia 的回答所指出的,Scalaz 库通过使用类型类(如Monad. 但是,在这种情况下,您不会想使用Monad:它是一个比您需要的更强大的抽象。您将使用EachtoforeachFunctorfor map。例如,在 Scalaz 7 中:

import scalaz._, Scalaz._

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

或者:

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

总而言之,你可以用一种标准的、惯用的、但可以说是丑陋的结构类型来做你想做的事,或者你可以使用 Scalaz 来获得一个更干净的解决方案,但代价是新的依赖。

于 2012-12-15T17:10:17.200 回答
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 类型专家。我尝试用 and 代替HasMapAndForeachthis.typeList这些都不起作用。

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

斯卡拉兹

因为如果你想支持结构类型是一个死胡同,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对象中有一个隐含的 for Each[TraversableOnce](List是的子类型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参数传递给printValues方法。

printValuesorincremented方法内部,编译器不知道xsmaporforeach方法,因为类型参数F没有任何上限或下限。据它所知FAnyRef满足上下文绑定(是类型类的一部分)。有foreach或的范围是什么mapMAfrom scalaz 有两种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)
}

请注意,foreachandmap方法MAEachor类型类的约束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原始方法中的类型变为 type Min MA

传递给原始调用的隐式参数然后作为隐式参数传递给foreachor map。在 的情况下foreacheach对其隐式参数调用e。在上面的示例中,隐式ev是类型Each[TraversableOnce],因为原始参数是 a List,所以e是相同的类型。 foreach调用哪些代表eachon 。eforeachTraversableOnce

所以调用顺序printValues(List(1,2,3))是:

new Each[TraversableOnce]-> printValues-> new MA-> MA.foreach-> Each.each->TraversableOnce.foreach

正如他们所说,没有任何问题无法通过额外的间接级别解决:)

于 2012-12-15T18:19:44.377 回答
2

您可以使用MA来自scalaz

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
于 2012-12-15T16:46:21.473 回答