【问题标题】:Calling map with flatMap(for Option data type) but the types don't match in scala使用 flatMap 调用 map(对于 Option 数据类型)但类型在 scala 中不匹配
【发布时间】:2019-01-02 17:39:47
【问题描述】:

我正在完成“Scala 中的函数式编程”练习,在第 4 章中有一段关于 Option 类的代码,我认为我很难理解。

我被要求实现 flatMap。我知道 Map 旨在将 A 转换为 B,然后将其包装在 Option 中。那么 flatMap 的目的是什么?如果我没记错的话,在列表中实现的 flatMap 意味着采用将 A 转换为 B 的列表的函数。A => List[B]。原则上,我想这也适用于此。该选项只是一个包含一个元素的列表,我将为其提供一个函数,该函数旨在获取一个 A 并将其包装在一个选项中。

我不知道如何使用 Map 实现 flatMap。明显的实现很简单:

def flatMap(f: A => Option[B]): Option[B] = map(f) getOrElse None

为什么这个实现有效? f 没有A=>B 的类型签名,但它使用这个签名调用 map 就好了。显然,调用 getOrElse(None) 是让它工作的原因,因为如果我把它拿出来,实现就会开始抱怨。但我不明白为什么 scala 解释器不在这里抱怨。我们知道f 返回一个Option[B]。是否返回调用 getOrElse 的选项,然后解包其值以便它可以具有正确的类型签名?如果是这种情况,是否有其他方法可以更清楚地说明它为什么会起作用?

def flatMap[B](f: A => Option[B]): Option[B] = map((a)=> f(a).getOrElse(None)) // doesn't work

不幸的是,这本书并没有真正提供对 scala 的深入介绍,所以我不确定这是我不理解的语法(很确定它的句法)还是逻辑。

import scala.{Option => _, Some => _, Either => _, _} // hide std library `Option`, `Some` and `Either`, since we are writing our own in this chapter

sealed trait Option[+A] {
  def map[B](f: A => B): Option[B] = this match {
      case Some(a) => Some(f(a))
      case None => None
  }

  def getOrElse[B>:A](default: => B): B = this match {
      case None => default
      case Some(b) => b
  }

  def flatMap[B](f: A => Option[B]): Option[B] = 
    map(f) getOrElse(None)


  def orElse[B>:A](ob: => Option[B]): Option[B] = ???

  def filter(f: A => Boolean): Option[A] = ???
}
case class Some[+A](get: A) extends Option[A]
case object None extends Option[Nothing]

object Option {
  def mean(xs: Seq[Double]): Option[Double] =
    if (xs.isEmpty) None
    else Some(xs.sum / xs.length)
  def variance(xs: Seq[Double]): Option[Double] = ???

  def map2[A,B,C](a: Option[A], b: Option[B])(f: (A, B) => C): Option[C] = ???

  def sequence[A](a: List[Option[A]]): Option[List[A]] = ???

  def traverse[A, B](a: List[A])(f: A => Option[B]): Option[List[B]] = ???
}

【问题讨论】:

    标签: scala types functional-programming


    【解决方案1】:

    您对flatMap 含义的直觉是完全正确的。我相信您对实现的困惑源于B每个方法定义的类型参数,因此它不必具有相同的值 em> 当一种方法调用另一种方法时。

    为了说明这一点,让我们简单地给这些方法的类型参数起不同的名称:

    def map[B](f: A => B): Option[B] = this match {
      case Some(a) => Some(f(a))
      case None => None
    }
    
    def getOrElse[C>:A](default: => C): C = this match {
      case None => default
      case Some(b) => b
    }
    
    def flatMap[D](f: A => Option[D]): Option[D] = {
      map(f) getOrElse None
    }
    

    现在:

    • flatMap 有一个类型参数D,可以是任何东西。
    • flatMap 调用map 时,它会将Option[D] 分配为map 的类型参数B (!) 的值。因此,就map 而言,它被传递了一个函数f 到某种类型的B 中,对于某种类型的D 实际上是Option[D](但map 不在乎!)李>
    • flatMap 调用getOrElse 时——类似的事情发生了,这一次getOrElse 的类型参数C 被赋值为Option[B]——同样,getOrElse 只是“做它的事”而不关心输出恰好是Option 本身

    这样,返回值为:

    • None 如果原始输入是 None(因为 getOrElse 将返回 default 它已通过)
    • None 如果f 返回None(因为getOrElse 将返回它得到的Some 中的值,它本身就是None
    • Some(v) 如果f 的输入和结果都是Some

    这正是flatMap 应该做的。

    【讨论】:

    • 好答案。实际上,我也对语法感到困惑。那么幕后发生的事情是map(f) 返回一个Option[D],这实质上是调用getOrElse 对吗?所以形式为obj function arg。在这种情况下,Option[D] getOrElse None
    • 正确! map(f) getOrElse None 等价于 map(f).getOrElse(None),请参阅此处的“中缀表示法”:docs.scala-lang.org/style/method-invocation.html
    猜你喜欢
    • 1970-01-01
    • 2018-03-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-10-31
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多