对“Scala in Depth”选项示例感到困惑

Posted

技术标签:

【中文标题】对“Scala in Depth”选项示例感到困惑【英文标题】:Confused by "Scala in Depth" Option example 【发布时间】:2012-04-04 08:30:15 【问题描述】:

由 Josh Sueresh 撰写的 Manning 新书“深入 Scala”的第 2 章发布在 here。在阅读文章时,我遇到了这段代码:

def getTemporaryDirectory(tmpArg : Option[String]) : java.io.File = 

   tmpArg.map(name => new java.io.File(name)).

          filter(_.isDirectory).

       getOrElse(new java.io.File(System.getProperty("java.io.tmpdir")))


解释上述代码的后续文字如下:

getTemporaryDirectory 方法将命令行参数作为 一个包含字符串的选项并返回一个文件对象引用 我们应该使用的临时目录。我们做的第一件事是使用 选项上的 map 方法来创建一个 java.io.File 如果有 范围。接下来,我们确保这个新构造的文件对象 是一个目录。为此,我们使用过滤器方法。这将检查 Option 中的值是否遵守某个谓词,如果不是, 转换为无。最后,我们检查一下我们是否有一个值 选项;否则,我们返回默认的临时目录。

所以,对于来自 Java 并学习 Scala 的我来说,代码语法让我感到困惑。我不明白 map(...) 函数调用之后如何有一个点。似乎发生了太多类型推断,我在某处遗漏了一些东西并且没有看到类型。

这对我学习 Scala 非常有帮助,能够以某种方式查看所有推断类型,取消推断(或取消应用)所有缩减,即看起来像 Java 6 之前的过于冗长的版本对于集合类,类型必须在等号的两边都显式。

是否有任何工具可以使用 Scala 代码 sn-p 并做出明确的不同事物(可能作为标志;一个用于类型,另一个用于隐式,另一个用于大括号,另一个用于分号)。我只需要一些东西来将我从完全简洁的代码引导到更接近 Java 的东西,这样我就可以自信地培养我阅读(并最终编写)更简洁的 Scala 的技能。

这是我要找的东西:

def getTemporaryDirectory(tmpArg : Option[String]) : java.io.File = 

   ContainerType1[Type1] t1 = tmpArg.map(name => new java.io.File(name));
   ContainerType2[Type2] t2 = t1.filter(_.isDirectory);
   return t2.getOrElse(new java.io.File(System.getProperty("java.io.tmpdir")));


我并没有特别拘泥于上述内容。由于类型推断,我只是无法理解链式函数调用是如何工作的。对此的任何帮助将不胜感激。

【问题讨论】:

这篇文章提出了一个类似于我上面遇到的挑战的观点:zeroturnaround.com/blog/scala-sink-or-swim-part-1 在这个级别上,您将从 Odersky 等人的 Programming in Scala 中受益比从 Suereth 的 Scala in Depth 中受益更多。只是说。 This post 和漂亮的动画可能会给你更多的感觉 @Daniel 根据您的建议,我现在正在重读 Odersky 的书(第 2 版)。事实证明,这对我来说是一个很好的建议。我第一次从中得到了很多。现在一年后重新阅读它,我看到我第一次错过了多少。我现在正在使用 REPL 来玩本书中的示例。我认为我需要在 REPL 中真正“玩”一些更“抽象”的东西,而这些东西我不会仅仅“阅读”。感谢电子阅读器。我的手机、笔记本电脑、平板电脑和工作计算机上都有它。 :) @chaotic3quilibrium 很高兴能帮上忙。 【参考方案1】:

好吧,您确实有 REPL 可以一一尝试链式命令并检查其结果类型,但我不确定看到签名是否会对您有很大帮助:

scala> Some("c:\\users\\paolo")
res0: Some[java.lang.String] = Some(c:\users\paolo)

scala> res0.map(name => new java.io.File(name))
res1: Option[java.io.File] = Some(c:\users\paolo)

scala> res1.filter(_.isDirectory)
res2: Option[java.io.File] = Some(c:\users\paolo)

scala> res2.getOrElse(new java.io.File(System.getProperty("java.io.tmpdir")))
res3: java.io.File = c:\users\paolo

现在让我们再试一次,从无开始。

scala> None:Option[String]
res6: Option[String] = None

scala> res6.map(name => new java.io.File(name))
res7: Option[java.io.File] = None

scala> res7.filter(_.isDirectory)
res8: Option[java.io.File] = None

scala> res8.getOrElse(new java.io.File(System.getProperty("java.io.tmpdir")))
res9: java.io.File = C:\Users\paolo\AppData\Local\Temp

因此,使用Option 帮助我们“传播”None,而无需像在 java 中那样在每一步都检查空值。

如您所见,这里没有发生很多类型推断。 我认为您困惑的根源可能是 mapfilter(以及其他)通常与某种集合相关联,因此可能很难理解它们在 Option 上所做的事情,这与收藏。 为此,我推荐您参考经典的scala.Option cheat sheet

【讨论】:

"scala> res0.map(name => new java.io.File(name))"的第三行,变量名从何而来?我猜应该是 res0 吧? @chaotic3quilibrium scala REPL 自动创建一个 val 来分配您输入的每个表达式的值,并将它们命名为 res0、res1、res2 等等。在第一行中我输入了Some("c:\\users\\paolo"),所以scala 自动将其解释为val res0 = Some("c:\\users\\paolo") 并回复res0 的类型为Some[java.lang.String] 和值Some(c:\users\paolo)。然后我在res0上使用了map,它又返回了一个res1,类型为Option[java.io.File],值Some(c:\users\paolo)...等等 没关系,我重读了一遍,现在得到 res0.map,这个名字就是 map 函数的参数名。 @Paulo 感谢您使用 REPL。我真的很感激。这是弄清楚如何拆分方法/功能的好方法。我以后会这样做的。 @chaotic3quilibrium:哦,是的,对不起……我没有得到你的问题。【参考方案2】:

尾随点简单地链接到下一行的方法filter。没有推理在起作用。

这可以重写为

import java.io._

def getTemporaryDirectory(tmpArg : Option[String]) : File = 
    tmpArg.map(name => new File(name)).filter(_.isDirectory).getOrElse(new File(System.getProperty("java.io.tmpdir")))

【讨论】:

我不是这个意思。我知道这是一系列方法调用。我没有得到的是调用下一个方法所返回的类型。类型推断隐藏了(从我没有经验的角度来看)每个函数返回的类型。 @chaotic - 但是在这个例子中没有必要进行真正的类型推断,除了用作方法参数的函数的类型。带有链式调用的 Java 版本看起来与 return tmpArg.map(...).filter(...).getOrElse(new File()) 完全一样,只是 ... 必须是一些糟糕的函数对象实例化。 @Michal 我知道我可以在 Java 中做到这一点。但是,一般来说,我知道大多数可以在这样的 Java 序列中发生的活动。 Scala 中有各种各样的东西我不明白,这降低了我阅读和理解序列的信心。我的差距是 filter() 返回了最后一个点正在使用的 Option[File]。 其实有推论:你不必写map(name: String => ....)而是写map(name => ...)【参考方案3】:

明确地说:

def getTemporaryDirectory(tmpArg : Option[String]) : java.io.File = 
  val v1: Option[java.io.File] = tmpArg.map(name => new java.io.File(name))
  val v2: Option[java.io.File] = v1.filter(_.isDirectory)
  val v3: File = v2.getOrElse(new java.io.File(System.getProperty("java.io.tmpdir")))

这里还有很多类型推断,Daniel Spiewak 有an excellent presentation on type inference。它确实让您了解 Scala 能够推断出哪些类型。

更具体地说是这个例子。

Option[A]map 方法具有签名map[B](f: A => B): Option[B]。因为tmpArgOption[String],编译器就知道参数的类型是String => B。现在它可以推断出nameString。检查函数,可以看到函数返回一个File。现在编译器可以推断出参数的类型是String => File,并且这个map调用返回Option[File]

Option[A]filter 方法具有签名filter (p: A => Boolean): Option[A]。函数文字_.isDirectory 只是x => x.isDirectory 的简写。鉴于A 现在是File,编译器可以推断_ 也是File。结果是Option[File]

最后,我们有了Option[A]getOrElse 方法,签名为getOrElse[B >: A](default: => B): B。语法B >: A 指定类型参数B 被限制为与A 相同或超类型。语法=> B 将参数指定为按名称/惰性参数,仅在需要时才对其进行评估。传入的参数是File类型,也就是说B就是File,而这个getOrElse返回File

【讨论】:

这就是我在特定问题中寻找的内容。所以,.map 和 .filter 都返回 Option[File]。关于我可以用来“分解”这样的 Scala 代码的 sn-p 的工具的任何想法,以便我可以学习所有关键点来开始自己推断类型? @chaotic3quilibrium 如果您将鼠标悬停在 Eclipse 中的术语上,它会告诉您它们的类型。 太棒了!我也会尽快尝试的。特维姆。 @chaotic3quilibrium 我添加了更多信息,希望能帮助您掌握推理的窍门。我强烈推荐观看 Daniel 的演讲;信息量很大。 @dave 我现在已经观看了 Daniel 的演示文稿。 Tyvm 用于发布它的链接。虽然其中一些超出了我的想象,但我确实了解的内容帮助我更深入地了解了 Scala 和反射。【参考方案4】:

与 REPL 一起的另一个资源是使用您的 IDE 来分解和/或注释链式表达式。

例如在 IntelliJ 中,我可以将鼠标悬停在方法调用上并查看它的类型签名,如果我有可用的源,我可以单击并查看实现。

但是重申 Daniel 的建议,像 Programming in Scala 这样的书会是一个更容易的起点,涵盖类型推断和语言的语法规则(这可能会导致本示例中的混乱更多)。

【讨论】:

我一直在使用 Eclipse IDE。但是,当我将鼠标悬停在一段 sn-p 代码上时,我没有注意到它会显示类型。这很有帮助。

以上是关于对“Scala in Depth”选项示例感到困惑的主要内容,如果未能解决你的问题,请参考以下文章

对分配伪 TTY 的 Docker -t 选项感到困惑

对 MutationObserver 感到困惑

对服务与工厂感到困惑

如果没有前向引用,对如何创建引用感到困惑?

对@output 装饰器行为感到困惑

对访问数组中的可变字典感到困惑