7

我很难弄清楚如何从 Scala 高阶函数定义跳转到提供的示例。它在幻灯片81上的幻灯片中提供。

这是高阶函数定义:

trait X[A] { def map[B](f: A => B): X[B] }

以下是提供的示例:

(1 to 10) map { x => x * 2 } // evaluates to Vector(2, 4, ..., 20)
(1 to 10) map { _ * 2 }      // shorthand!

咦?!我在这里缺少一些步骤。我知道这些示例可能同时利用了函数定义和一些 Scala 细节。我只是没有足够的阅读 Scala 和做出连接假设的经验。

我的背景是 Java OO。我现在正在学习 Scala 和函数式编程。这不是我不理解的第一个这样的例子。这只是我觉得我有勇气发帖的第一个,因为我知道我看起来很无知。

我确实试图研究这个。首先,我查阅了 Scala“圣经”、“Scala 2nd Edition 编程”,并试图从那里理解 if(第 165-9 页)。然后,我在 StackOverflow 上进行了搜索。我发现了几个在该地区讨论的链接。但是,没有任何东西真正地一步一步地向我展示了 Scala 高阶函数定义和提供的示例之间的联系,这种联系映射到这张幻灯片中的特定实例。

这是我在 StackOverflow 上找到的:

  1. Scala:研讨会建议
  2. 更多关于通用 Scala 函数的信息
  3. Scala:如何定义“通用”函数参数?

我现在才意识到我跳过了 Google,直接来到了 StackOverflow。嗯。如果你用谷歌搜索并找到正确的链接,我很乐意看到它。我已经没有时间筛选所有使用猴子单子、胚胚等术语的 Google 链接,同时让我更加困惑并且不太可能尝试解决这个问题。

4

7 回答 7

6

高阶函数(或方法)是一个函数/方法,它要么将函数作为其参数,要么将函数作为其结果,或两者兼而有之。

在这种情况下,它是一种map在列表、数组以及许多其他类型的容器上定义的方法。当调用一个1 to 10从 1 到 10 的数字范围(Range[Int]在 Scala 中由 这个函数的结果被累积在一个新的容器中——Vector[Int]在这种情况下,它作为map方法的结果返回。

所以(1 to 10) map { x => x * 2 }顺便说一句,语法糖(1 to 10).map(x => x * 2)适用x => x * 2于来自1 to 10. 您可以将其视为回调函数。你也可以这样写:

(1 to 10).map( new Function1[Int, Int] {
   override def apply(x: Int) = x * 2
})
于 2012-02-26T16:26:39.823 回答
6

我认为以下只是为了显示 Scala 的一些集合属性而提供的示例签名。特别是它没有显示任何实现,因此您无法真正连接所有点。而且它实际上与示例不一致......所以,这可能是令人困惑的。

trait X[A] { def map[B](f: A => B): X[B] }

我将其解读为:给定X类型元素的集合类A

  • 它有一个map在类型上参数化的函数B
  • map函数采用一个函数f将单曲转换A为单曲B
  • mapX在 type的元素上返回相同类型的集合B

然后跳转到示例以说明使用:

(1 to 10) map { x => x * 2 }

所以,连接点:

  • 集合X是(1到10)的类型,这里是Range
  • f: A => Bisx => x * 2被推断为一个函数,该函数接受 anInt并返回 and Int
  • 给定签名,您会认为它会返回 a Rangeover Int,但实际上返回的是 a IndexedSeq

一个更好的例子可能是:

List(1, 2, 3).map(i => i + "!") // a List[Int]
// returns a List[String]: List("1!", "2!", "3!") 
于 2012-02-26T16:47:26.327 回答
5

让我们用 map 方法定义一个数据类型,一个单链表。

sealed abstract class MyList[+A] {
  def map[B](f: A => B): MyList[B]  // higher order function declaration.
  def head: A
  def tail: MyList[A]
}
case class Cons[A](head: A, tail: MyList[A]) extends MyList[A] {
  def map[B](f: A => B): MyList[B] = Cons[B](f(head), tail.map(f))
}
case object Nil extends MyList[Nothing] {
  def map[B](f: Nothing => B): MyList[B] = this
  def head = sys.error("head on empty list")
  def tail = sys.error("tail on empty list")
}

一个列表要么是空的,要么是一个单一的值 (the head) 与列表的其余部分 (the ) 配对tail。我们将这两种情况表示为一个类层次结构,从一个密封的父类扩展而来MyList

注意 的实现Cons#map,我们使用了f两次参数,第一次用 执行函数head,第二次传递给递归调用tail.map

语法f(head)是 的简写f.apply(head),值是定义方法f的类的实例。Function1apply

到现在为止还挺好。让我们构建一个列表。

val list: MyList[Int] = Cons(1, Cons(2, Nil))

现在,我们想通过转换每个元素来将列表转换Ints为 s 的新列表。String在 Java 中,您将显式匿名子类化Function1,如下所示。

// longhand:
val stringList1: MyList[String] = list.map[String](new Function1[Int, String] {
  def apply(a: Int): String = a.toString
})

这在 Scala 中是合法的,但信噪比不是很好。让我们改用匿名函数语法。

val stringList2: MyList[String] = list.map[String]((a: Int) => a.toString)

我们可以更进一步,省略显式类型注释,编译器有足够的信息来推断它们。

a首先,让我们根据 的元素类型来推断参数的类型list

val stringList3: MyList[String] = list.map[String](a => a.toString)

像这样真正简单的函数也可以用占位符语法来表示。无需声明参数,只需编写代码并_用于任何未知数量。这样做,并且还允许stringList4推断类型:

val stringList4 = list.map(_.toString)
于 2012-02-26T16:25:56.687 回答
3

让我们专注于在您的 trait X 上定义的map方法

def map[B](f: A => B): X[B]

好的,所以这是一个带有一个参数的方法,f. f(冒号后的位)的类型是A => B. 这是一个函数类型;它是 trait 的简写,Function1[A, B]但我宁愿根本不考虑这些特征,而只是将 B 视为给定 A 值的东西。

所以:函数A => B是接受一个 type 实例A并产生一个 type 实例的东西B。以下是一些声明它们的示例

val f = (i: Int) => i.toString //Int => String
val g = (_ : String).length    //String => Int, using placeholder syntax

所以现在想想是什么X[A];它可以是集合类型,例如List. 如果您拥有 aList[String]和上面的String => Int函数g,那么您显然可以List[Int]通过将函数应用于列表中的每个元素来获取 a in,并使用结果构造一个新列表。

所以现在,你可以说:

strings map g //strings is a List[String]

但 Scala 允许您匿名声明函数。这意味着什么?好吧,这意味着您可以在使用时内联声明它,而不必将其声明为 val 或 var。通常这些被称为“lambdas”。您感到困惑的语法是此类函数的两个选项。

strings map { (x: String) => x.length }
strings map { x => x.length }
strings map { _.length }
strings map ( _.length )

这些基本上都是一样的。第一个明确声明传递给 map 的函数。因为 scala 有类型推断,你可以在这个用例中省略函数输入的类型。占位符语法 _ 用于代替标识符x,并且在您只需要引用一次输入的情况下是一个很好的糖。在许多情况下,您可以使用括号代替大括号,但多语句函数除外。

于 2012-02-26T17:06:29.770 回答
2

您的示例参考了 Scala 集合框架,它本身对类型系统进行了一些复杂的使用,以在转换集合时产生最具体的类型。现在,实现这一点的机制很难掌握,并且与示例并不真正相关。高阶函数只是一个以其他函数为参数(或返回)的函数或方法。通过添加类型参数并且没有提及 Scala 集合框架对隐式的使用,该示例有些模糊。

于 2012-02-26T16:41:17.517 回答
2

不确定您到底没有得到什么,但要解释一下示例:

trait X[A] { def map[B](f: A => B): X[B] }

Atrait就像一个 Java 接口,它也可以有具体的方法。

X是特征名称并且[A]是类型参数 - 想想 Java 泛型<A>。(通常A, Betc 用于集合和T其他地方的元素类型,但这只是一个约定。)

该特征指定了一个名为 的成员map,它是一个带有另一个类型参数的方法[B],并采用类型为函数的参数,A => B返回类型为X[B]。这里是抽象的,因为没有方法体。

您可能缺少的一点A => BFunction1[A, B]. Function1是采用 1 个参数的函数对象的类型。(A, B) => CFunction2[A, B, C]etc 的缩写。您可以在 Java 中创建自己的Function类型 - 这是一个有趣的练习。函数对象本质上只是一个具有apply方法的对象,它从一些参数产生结果。

(1 to 10) map { x => x * 2 }    

这些包括无点符号,其中a.method(b)写为a method b. 获取和产生.to的方法也是如此。是一种采用 Function1 参数的方法(请记住,函数只是类型的对象)。RichIntIntRangemapRangeFunction1

=>也用于编写函数本身(除了在类型级别,如上所述)。所以以下是相同的,所有类型的对象Int => Int

(x: Int) => x + 1
new Function1[Int, Int] { def apply(x: Int) = x + 1 }
  // note Function1 is a trait, not a class, 
  // so this the same as `new Object with Function[Int, Int]`
new (Int => Int) { def apply(x: Int) = x + 1 }

Scala 使用类型推断,因此如果上下文期望有特定的函数类型(或任何其他参数化类型),您不需要自己添加所有类型,例如

val f : Int => Int  =  x => x + 1
val f : Int => Int  =  _ + 1

希望你能明白这个下划线符号的含义。下划线很有用,否则总会有一些重复,因为函数定义的 RHS 必须使用 LHS 的参数。另一个例子可能是将 a 映射String到其长度的函数:

val f: String => Int = _.length

由于通常会推断出 val 的类型,因此您只能提供必要的类型注释

val f = (_: String).length

这可能有点令人困惑,因为语法糖和类型推断意味着有几种方法可以编写相同的东西,但是一旦你得到它,你会发现它可以让你的生活更轻松并减少噪音。如果您还没有,请在 REPL 中好好利用这些。

于 2012-02-26T16:50:37.987 回答
1

     Scala:(1 to 10) map { x => x * 2 }
     英语:取值,从 1 到 10,然后将每个值乘以 2。

需要注意的一些事项:

  • (1 到 10),Scala 识别这是一个整数的集合,特别是 Range[Int]。它可以转换为另一种集合类型,例如。(1 to 10).toList

  • 地图,是小写的。思考动词,从事物映射到另一个事物。

  • {x => x * 2},被花括号包围。这意味着它是一个没有名字的函数,一个匿名函数

  • 下划线 (_) 可以替换为x => x


     Scala:trait X[A] { def map[B](f: A => B): X[B] }
     英语:
       我们定义了一个特征,我们可以将其添加到 X 的类 A中。
       它有一个方法,该方法接受一个值并将其映射到 X 的新类的另一个值。

笔记:

  • X[A]并且X[B]具有相同的集合类型,但可以具有不同类型的元素,例如。`(1 到 10).toList map { _.toSTring } 将 List[Int] 映射到 List[String]。

  • f: A => B,这意味着 map 接受一个函数作为参数,它有一个 A 类型的参数并返回 B 类型。

  • map在所有 Scala 集合类型中定义。您通常不会自己定义它。

于 2012-02-26T22:05:37.080 回答