对“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 中那样在每一步都检查空值。
如您所见,这里没有发生很多类型推断。
我认为您困惑的根源可能是 map
和 filter
(以及其他)通常与某种集合相关联,因此可能很难理解它们在 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]
。因为tmpArg
是Option[String]
,编译器就知道参数的类型是String => B
。现在它可以推断出name
是String
。检查函数,可以看到函数返回一个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”选项示例感到困惑的主要内容,如果未能解决你的问题,请参考以下文章