Josh Sueresh撰写的新曼宁着作“Scala in Depth”的第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方法将命令行参数作为 包含String的Option并返回引用的File对象 我们应该使用的临时目录。我们做的第一件事就是使用 如果有的话,选项上的map方法创建一个java.io.File 参数。接下来,我们确保这个新构造的文件对象 是一个目录。为此,我们使用过滤方法。这将检查 Option中的值是否遵循某些谓词,如果不是, 转换为无。最后,我们检查一下我们是否有值 选项;否则,我们返回默认的临时目录。
所以,对于我来自Java和学习Scala,代码语法让我很困惑。我不知道map(...)函数调用后面有一个点。似乎有很多类型推断发生,我在某处遗漏某些东西而没有看到类型。
对我来说,学习Scala,能够以某种方式查看所有推断的类型,以取消(或取消应用)所有减少,即看起来像Java 6之前的过于冗长的版本,这将非常有用。对于集合类,必须在equals的两侧显示类型。
是否有任何工具可以使用Scala代码片段并显示不同的东西(可能作为标志;一个用于类型,另一个用于隐含,另一个用于括号,另一个用于分号)。我只需要一些东西让我从完全简洁的代码转到更接近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")));
}
我并没有特别坚持上述内容。我只是无法跟踪链接函数调用如何根据类型推断实际发生的事情。对此的任何帮助将不胜感激。
答案 0 :(得分:11)
嗯,你确实让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
答案 1 :(得分: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")))
}
答案 2 :(得分:1)
明确:
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]
的{{1}}方法具有签名map
。由于map[B](f: A => B): Option[B]
是tmpArg
,编译器知道参数的类型是Option[String]
。现在它可以推断String => B
是name
。检查函数,可以看到函数返回String
。现在,编译器可以推断出参数的类型为File
,并且此String => File
调用返回map
。
Option[File]
的{{1}}方法具有签名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
。
答案 3 :(得分:1)
另一个资源以及REPL是使用IDE来分解和/或注释链接表达式。
例如在IntelliJ中我可以将鼠标悬停在方法调用上并查看它的类型签名,如果我有源可用,我可以点击查看实现。
然而,重述丹尼尔的建议,像Scala中的Programming这样的书将是一个更容易的起点,涵盖类型推断以及语言的语法规则(这可能更多地造成了这个例子中的混淆)。