【问题标题】:Scala - function map is a pattern matching or an interationScala - 函数映射是模式匹配或交互
【发布时间】:2019-10-23 04:16:10
【问题描述】:

我花了数周时间试图理解 scala 中“提升”背后的想法。

最初来自《Scala 中的函数式编程》一书第 4 章相关的示例

然后我发现下面的主题“How map work on Options in Scala?

所选答案指定:

def map[B](f: A => B): Option[B] = this match (Let's considered this as (*) )

所以,从上面的代码中,我假设函数“map”是从函数匹配派生的。因此,“地图”背后的机制 是一种模式匹配,用于提供 Some 和 None 之间的大小写选择

然后,我使用 Seq、Option 和 Map 的函数映射创建了以下示例(让我们将以下示例视为 (**))

Example 1: map for Seq
  val xs = Seq(1, 2, 3)
  xs.map(println)

Example 2: map for Option
val a:Option[Int] = Some(5)
  a.map(println)
val b:Option[Int] = None
  b.map(println)

Example 3: map for Map
val capitals = Map("France" -> "Paris", "Japan" -> "Tokyo")
capitals.map(println)

从 (*) 和 (**),我无法知道“map”是模式匹配还是迭代,或两者兼而有之。

感谢您帮助我理解这一点。

【问题讨论】:

    标签: scala


    【解决方案1】:

    @Jwvh 提供了更多基于编程的答案,但我想更深入地挖掘。

    我当然感谢您尝试了解 Scala 中的工作原理,但是如果您真的想深入挖掘,恐怕您需要获得一些类别理论的基本知识,因为在 scala 中提升背后没有“想法” ” 但只是“提升背后的想法”

    这也是为什么像“map”这样的函数会非常混乱的原因。从本质上讲,程序员被教导 map 等作为对集合的操作,而实际上它们是 Functors 和 Natural Transformations 附带的操作(这通常在 Category Theory 和 Haskell 中称为 fmap)。

    在我继续之前,简短的回答是它是您提供的示例中的模式匹配,并且在 一些 中两者都是。 Map是专门针对case定义的,唯一的条件是它保持functoriality

    注意:我不会在下面定义每个术语,因为我需要写一本书来建立以下一些定义,欢迎有兴趣的读者自己研究它们。按照类型应该可以有一些基本的了解

    让我们将它们视为 Functor,其定义大致如下:

    简而言之,我们将类型视为我们语言类别中的对象。这些类型(类型构造函数)之间的函数是该类别中类型之间的态射。这些转换的集合称为 E​​ndo-Functors(将我们从 Scala 的类别中带回到 Scala 的类别中)。函子必须有一个多态的(实际上在范畴论中有一个完全不同的(额外的)定义)映射函数,它将获取一些对象 A,通过一些类型构造函数将其转换为对象 B。

    implicit val option: Functor[Option] = new Functor[Option] {
        override def map[A,B](optA: Option[A])(f: (A) => B): Option[B] = optA match{
            case Some(a) => Some(f(a))
            case _ => None
        }
    }
    
    
    implicit val seq: Functor[Seq[_]] = new Functor[Seq[_]] {
      override def map[A,B](sA: Seq[A])(f: (A) => B): Seq[B] = sA match{
         case a :: tail => Seq(f(a), map(tail)(f))
         case Nil => Nil
      }
    }
    

    正如您在第二种情况下看到的那样,两者都有一点点(更多的是递归而不是迭代,但仍然如此)。

    现在在我被互联网炸毁之前,我会说你不能在 Scala 中的 Seq 上进行模式匹配。它在这里工作是因为默认的 Seq 也是一个列表。我只是提供了这个例子,因为它更容易理解。基本定义与此类似。

    现在等一下。如果您查看这些类型,您会发现它们还定义了 flatMap。这意味着它们比普通的 Functor 更特别。他们是单子。因此,除了满足功能性之外,它们还遵守一元法则。

    原来 Monad 在核心 scala 中具有不同的含义,更多内容请参见:What exactly makes Option a monad in Scala?

    但是又非常非常短,这意味着我们现在处于一个类别中,我们之前类别中的内函子是对象,它们之间的映射是态射(自然变换),这稍微更准确,因为如果你考虑一下当您采用类型并对其进行转换时,您会采用(继承)它的所有内部类型构造函数(2-cell 或内部态射),您不仅会采用这种类型的唯一概念而没有它的功能。

    implicit val optionMonad: Monad[Option] = new Monad[Option] {
     override def flatMap[A, B](optA: Option[A])(f: (A) => Option[B]): Option[B] =  optA match{
       case Some(a) => f(a) 
       case _ => None 
     } 
    
     def pure[A](a: A): Option[A] = Some(a)
    
       //You can define map using pure and flatmap
    
    }
    
    implicit val seqMonad: Monad[Seq[_]] = new Monad[Seq[_]] {
       override def flatMap[A, B](sA: Seq[A])(f: (A) => Seq[B]): Seq[B] = sA match{
          case x :: xs => f(a).append(flatMap(tail)(f))
          case Nil => Nil
       } 
       override def pure[A](a: A): Seq[A] = Seq(a) 
       //Same warning as above, also you can implement map with the above 2 funcs 
     }  
    

    您始终可以依靠的一件事是 map 具有模式匹配(或某些 if 语句)。为什么? 为了满足恒等律,我们需要某种“基本情况”,一个单元对象,并且在许多情况下(例如列表),这些类型将是我们所说的产品或副产品。

    希望这不会让您更加困惑。我希望我能深入了解这方面的每一个细节,但它只需要几页,我强烈建议进入类别以充分了解这些来自何处。

    【讨论】:

    • 谢谢。我试图在没有范畴理论的情况下掌握这个概念,但似乎理解 CT 是关键。
    • @datn 非常欢迎您。我知道这可能令人生畏,但我保证一旦你掌握了基础知识,它就会改变你对程序的看法。我强烈推荐 Bartosz Milewski 的资源。免费在线书籍:bartoszmilewski.com/2014/10/28/… Youtube 讲座:youtube.com/… 这几乎涵盖了您编程所需的所有 CT。他的 youtube 讲座在他的频道中也有第 2 部分和第 3 部分,这建立在镜头上,这是一个非常有用的编程概念
    • @datn 除了他解释的内容之外,还有很多 ct 本身并未用于编程。如果您掌握了所有这些并想要更多,我很乐意提供有关更高级主题的资源,例如更高类别理论和拓扑理论
    【解决方案2】:

    ScalaDocs 页面我们可以看到标准库map() 方法的类型配置文件有点不同。

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

    因此,标准库map() 是从A 类型的元素集合转换到同一集合但元素为B 类型的方法。 (AB 可能是同一类型。它们不需要不同。)

    所以,是的,它遍历集合应用函数f() 到每个元素A 以创建每个新元素B。而函数f() 可能会在其代码中使用模式匹配,但它不是必须的。

    现在考虑a.map(println)a 的每个元素都发送到 println,后者返回 Unit。所以如果aList[Int] 那么a.map(println) 的结果是List[Unit],这并不是非常有用。

    当我们想要的只是向 StdOut 发送信息的副作用时,我们使用foreach(),它不会创建新集合:a.foreach(println)

    【讨论】:

    • 感谢您的回答。从您的回答中,您能否解释一下为什么我们将 A 放在括号中作为 (A),即:(f: (A) => B)
    • 当您有多个输入时,括号是必需的:(A,A) => B(A,B) => C 等。只有一个输入时,括号是可选的。 ScalaDocs 喜欢把它们留在里面。
    【解决方案3】:

    Option 的函数 map 与模式匹配无关。您引用的链接中使用的match/case 只是定义函数的众多方法之一。它可以使用if/else 定义。事实上,这就是它在 class Option 的 Scala 2.13 源代码中的定义方式:

    sealed abstract class Option[+A] extends IterableOnce[A] with Product with Serializable {
      self =>
        ...
        final def map[B](f: A => B): Option[B] =
          if (isEmpty) None else Some(f(this.get))
        ...
      }
    

    如果您将 Option 视为一个元素 (Some(x)) 或没有元素 (None) 的“集合”,则可能更容易看出 map 如何转换 Option 的相似之处与 List 相比:

    val f: Int => Int = _ + 1
    
    List(42).map(f)
    // res1: List[Int] = List(43)
    
    List.empty[Int].map(f)
    // res2: List[Int] = List()
    
    Some(42).map(f)
    // res3: Option[Int] = Some(43)
    
    None.map(f)
    // res4: Option[Int] = None
    

    【讨论】:

    • @Leo C:我知道定义函数的方法有很多种。就像您提到的“如果您将 Option 视为一个元素(Some(x))或没有元素(None)的“集合”......“。这就是我想了解的,在哪种机制下,我们可以把 Option 当作一个 Collection。正如@sinanspd 指出的那样,我能够理解更多。
    猜你喜欢
    • 1970-01-01
    • 2021-06-22
    • 1970-01-01
    • 2023-03-19
    • 1970-01-01
    • 2018-01-09
    • 2023-03-05
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多