Scala程序员 - “应该有一种明显的方法来实现它”或“不止一种方式”吗?

时间:2010-02-05 15:11:28

标签: design-patterns scala functional-programming

我已经做了很长时间的Java并且大约6个月前开始使用Scala。我喜欢这门语言。我发现的一件事是有多种方法可以做。我不知道这是因为语言的性质还是因为它还很年轻而且不断发展,习惯用法和最佳实践还没有出现。

让我感到惊讶的是,我一直是perl人的蟒蛇:

  

“应该有一个 - 最好只有一个 - 显而易见的方法。”

对我来说比

更有意义
  

“有多种方法可以做到这一点”

我很想知道您认为Scala适合这种规模的原因以及原因?


报价:Python PEP20Perl quote

6 个答案:

答案 0 :(得分:19)

我对Scala的经验是,有多种方法可以做事,但是做事情的方法相对较少。图书馆以不同方式做事的几乎没有任何选择;当它这样做时,通常是为了在一部分案例中实现额外的表现力或紧凑性或效率。

例如,如果你想对一个整数数组求和,你可能完全是程序性的,它会产生尽可能快的代码,代价是有点笨拙 - 但如果真的是时间 - 关键代码,这是解决问题的唯一最佳方法:

val array = Array(1,2,3,4,5)
var sum = 0
var index = 0
while (index < array.length) {
  sum += array(index)
  index += 1
}
sum

在使用基元时,同样一般的功能方法会更慢(这可能会随着2.8中的@specialized而改变)但是让你减少乏味:

var sum = 0
Array(1,2,3,4,5).foreach(x => sum += x)
sum

然后有一些稍微不那么通用的功能结构就是为这类问题设计的(因为它出现了很多),如果你想要干净,紧凑的代码,这是“最好的”:

Array(1,2,3,4,5).reduceLeft( _ + _ )

有时会有非常非常规的结构来完全按照你的意愿行事;在Scala 2.8中,例如:

Array(1,2,3,4,5).sum

因此,您可以在一般功率,紧凑性,清晰度和速度方面进行权衡,从而获得连续的选择。如果你可以假设只需要熟悉Scala的人可以访问代码,并且你知道你是否需要绝对最佳的性能,那么好的选择通常是相当有限的。但是,由于语言和图书馆的表达能力,通常有许多可能的次要做法。

答案 1 :(得分:5)

Scala是multi-paradigm language包含许多不同的哲学和设计方法。其中包括

  • 对象取向
  • 功能编程
  • 特定于Java的方法
  • 面向语言的编程(扩展语法,元编程)

这允许程序员从许多不同的角度解决问题,因此在很多方面。正如您所怀疑的那样,这就是语言的本质(就像其他通用/多范式语言一样)。

使用哪一个很大程度上取决于程序员,虽然我可以给出一些一般规则/想法:

  • Scala毕竟是一种功能语言。因此,我认为遵循一种功能性的编程风格(这也将导致非常简洁和优雅的解决方案)是一种很好的做法。

  • 保持一致! 毕竟,应该有一种方式,但您可以选择哪一种。避免混合副作用和纯函数,列表推导和库函数(映射,过滤)或Java-与功能思维混合。相反,在正确使用的地方使用不同的范例。

  • 使用Scala为您提供的精彩功能(而不仅仅是将它用作具有良好语法的Java):模式匹配,延续,泛型和极其强大的类型系统,更高 - 订单功能。

答案 2 :(得分:4)

Scala绝对是一种超越单行的语言。特别是,因为它是一种高度正交的语言,尽可能少的例外,并尽可能通过库而不是语言本身提供。

正交意味着语言特征不是相互依赖的。相反,它们可以自由组合以实现任何目的。这使语言更具表现力 - 并使语言更简单 - 但它肯定不会“引导”程序员,无论是强迫一种偏好的方式,还是不允许组合可疑的价值。

很少有例外是高正交性的结果。斯卡拉的事情并没有被禁止,因为它们没有意义(从设计者的角度来看)或者不是最理想的。

最后,强调图书馆。 Scala没有列表或地图。并且仅指定数组,因为与Java的互操作性要求它这样做。从这个意义上说,它与C语言非常相似,而不是BASIC。在C中,除了数学之外你几乎所做的一切都是通过库完成的,用C本身或汇编语言编写。在BASIC中,即使打开文件或在屏幕上显示文本等命令也是语言本身的一部分。

这有两个实际后果。首先,有一种趋势是拥有众多的职责重叠的图书馆。当然,对于任何具有强大图书馆生态系统的语言来说都是如此,但在Scala中,这些库看起来就像是语言本身的一部分;

另一个实际结果是基础库需要一定的灵活性,这会在它们之间产生一些冗余。

例如,List有一个追加运算符(在Scala 2.8上),即使这对列表来说不是最理想的。它有它,因为ListSeq,这是一个人们可能想要做的一般操作。

作为最后一点,在功能和面向对象编程的结合中可以看到该语言的多方位特性。

答案 3 :(得分:2)

scala以完全不同的方式实现相同目标的最明显的地方是for理解:

val ls = List(1, 2, 3, 4)
for (l <- ls) println(l) //for comprehension
ls.foreach(println(_))   //underlying form 

这扩展到flatMapfilter等。我个人的选择是坚持方法调用,尽管在更复杂的例子中如下:

for (i <- 1 to 10;
     j <- 1 to 20 if (j % 2 == 0)) ...

我可以在for表单中看到这些更简单,更直观。对于它的价值,我重视选择你的风格的能力,因为来自不同背景的人喜欢不同的东西。

将控制结构添加到像Exceptions这样的2.8就是一个例子:来自Java背景我起初没有得到这些抽象背后的力量,但如果我从功能领域开始那么我可能会马上理解。

我在Scala中的主要问题是对选项类缺乏合理的谓词理解。即使对于某些谓词p

opt.isEmptyOr( p ) //does not exist
opt.forall( p )

是等同的,我不喜欢前者从可读性角度来看不存在的事实。同样地:

opt.notEmptyAnd( p ) //does not exist       
opt.map(p).getOrElse(false)

在我看来,第二种形式是不必要的! 重复以实现可读性肯定是积极的!

答案 4 :(得分:1)

整个概念太模糊,几乎毫无意义。但是我怀疑,如果你正在使用一种语言,其中只有一种方法可以做任何给定的事情,那么你至少有50%的时间都在哭泣并咬牙切齿。

我(模糊)的感觉是,在任何体面的通用编程语言中,总是有很多方法可以完成大多数事情。

答案 5 :(得分:1)

免责声明:我不知道Scala的作者对此有何看法,我只是表达了我对scala(以及python和ruby)开发人员的看法。

通过Scala API,您没有看到很多方法来做同样的事情。例如,对象的Mutable和Immutable版本具有相同的接口。这样的事情让Scala有了一些连贯性。

作为一种混合语言,通常有两种解决问题的方法:功能和程序方式(foreach vs for)。当然,如果你想使用程序方法,那么你只需要使用Java。

如果你看一下像Lisp和Haskell这样的函数式语言,通常有一种方法可以为你采用的每种方法做到这一点(例如,因为你可以解决递归或列表推导中的问题)。

最后,我相信任何代码都应该是显而易见的。写得很明显。你可以非常灵活(perl / ruby​​)或严格(python)。 Scala很灵活(想想在方法调用中使用“。”和括号的语法,但是你可以按照你想要的那样严格。个人而言,我喜欢严格的事情,所以我会编写自己的代码,我认为这是显而易见的方式。