在 Scala 中只坚持函数范式的功效
我最近购买了Programming Scala,并且一直在阅读它。语言绝对不是我所期望的!具体来说,它似乎实现了我所知道的几乎所有编程语言的想法,除了 Lisp 宏和 Haskell 的类型级副作用隔离之外。
坦白说,这让我有些不知所措。虽然我认为有这么多工具可供我使用是件好事,但我实际上只是在 JVM 上寻找一种强类型函数语言。我想我可能可以那样使用 Scala,但我想如果我与任何库交互或浏览其他人的代码,我会遇到很多这种高级(对我来说)OOP 的东西——特征和“对象层次结构”线性化”,所有这些抽象和压倒一切的业务、单例、包和伴生对象、隐式转换……更不用说各种语法快捷方式和糖了。
人们经常抱怨程序员出于很多充分的理由试图将一种语言的风格硬塞到另一种语言的风格中。但并非所有语言都像 Scala 一样多范式,所以也许它的社区有不同的看法?例如,在 F# 中,编程风格和使用 OOP 的程度似乎有一些余地。但仅仅通过阅读,我不确定这对于 Scala 来说是否也是一个好的哲学。
更有经验的 Scala 程序员可以帮助我吗? 编辑清楚:基本上,我可以安全地仅(或大部分)使用 Scala 的 FP 功能,而不用担心其高级 OOP 方面吗?
抱歉问了这么漫无目的的问题!
I recently bought Programming Scala, and have been reading through it. The language definitely isn't what I expected! Specifically, it seems to implement just about every programming language idea I'm aware of, outside of Lisp macros and Haskell's type-level side-effect segregation.
Frankly, it has me somewhat overwhelmed. Though I suppose it's nice to have so many tools at my disposal, I was really just looking for a strongly-typed functional language on the JVM. I imagine I could probably use Scala that way, but I imagine if I interact with any libraries or go through anyone else's code, I'll be running into a lot of this advanced (for me) OOP stuff--traits and "object hierarchy linearization," all this abstract and overriding business, singleton, package, and companion objects, implicit conversions... not to mention the various syntactic shortcuts and sugars.
People often bemoan programmers who try to shoehorn one language's style into another, for lots of good reasons. But not all languages are as multi-paradigm as Scala, so perhaps its community has a different view? In F#, for example, there seems to be some leeway in programming styles and how much OOP you use. But just from reading I'm not sure if this is a good philosophy for Scala as well.
Can more experienced Scala programmers help me out here? Edit for clarity: Basically, can I safely use just (or mostly) the FP features of Scala without worrying about its advanced OOP side?
Sorry for the rambling question!
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(4)
您看到的一件事是,Scala 首先是 JVM 上的强类型函数式语言。Scala
不仅仅是函数式语言。它确实一开始就是一个(漏斗:http://lamp.epfl.ch/funnel/ ),但随后对其进行了扩展,使其成为一种面向对象的语言,其明确的目标是使其与 Java 类具有很强的互操作性。
抽象和覆盖以及包都是这种互操作性实际的例子。
其余部分可能不被视为新功能,而只是消除了 Java 的限制。一次一个地获取它们:
特征和对象层次结构线性化
消除了 Java 接口只能包含抽象方法的限制。线性化是 Scala 解决否则会导致的菱形继承问题的方法。
单例
Java 静态方法是 C++ 语法遗产的遗留问题,而 C++ 语法继承又添加了它们,以更好地支持与 C 互操作的过程样式需求。静态方法非常不是面向对象的(请参阅此问题: 为什么单例对象更加面向对象?)
伴生对象
允许使用单例来代替静态方法,并具有特权访问权限。
隐式转换
Java 已经做到了这一点,但它仅限于将对象和基元隐式转换为字符串,如表达式
"" + 3
中所示。 Scala 只是扩展了这个想法,并允许程序员将其用于其他转换。One of the things you're seeing is that Scala is, above all, a strongly-typed functional language on the JVM
Scala is not just a Functional language. It did start off as one (Funnel: http://lamp.epfl.ch/funnel/), but this was then extended to make it an Object-Oriented language, with the explicit goal of making it strongly interoperable with Java classes.
abstraction and overriding and packages are all examples of this interoperability in action.
The remainder might be considered not as new features, but simply as removing restrictions from Java. Taking them one at a time:
traits and object hierarchy linearization
Removes the restriction that Java interfaces can only contain abstract methods. Linearisation is how Scala resolves the diamond-inheritance problems that this would otherwise cause.
singletons
Java static methods are a hangover from it's C++ syntax heritage, which in turn added them to better support the procedural style need for interop with C. Static methods are very much NOT object oriented (see this question: Why are singleton objects more object-oriented?)
companion objects
Allow singletons to be used in lieu of static methods, with their privileged access rights.
implicit conversions
Java already does this, but it's restricted to only implicitly converting objects and primitives to strings, as in the expression
"" + 3
. Scala just extends this idea and allows the programmer to use it for other conversions.让我对凯文的回答添加一些评论。
Traits 主要用作抽象(宽松地说,与 Haskell 类型类定义抽象的方式相同)和 mixin。因此,我的代码可能使用 Map 特征,但它实际上使用的是 HashMap 等类型的实例。相反,如果我希望我的“服务”类型具有日志记录功能,我可能会混合可重用的日志记录特征。
幸运的是,除了简单的情况之外,您很少需要考虑线性化算法,例如,我可以混合拦截(包装)方法调用的特征来记录调用该方法的事实。该算法需要处理更复杂的线性化情况(就像我们在书中展示的示例),但恕我直言,这种复杂的类型实际上是一个糟糕的设计。
单例(包括特殊子集伴生对象)旨在使所有内容都成为对象,但您也可以将它们视为包装函数和可能的某些状态的命名空间。
隐式转换非常有用,尽管有点“神奇”。您可能会发现了解如何使用它们来模拟 Haskell 类型类很有用。这是 Debasish Ghosh 写的一篇很棒的文章: http:// /debasishg.blogspot.com/2010/06/scala-implicits-type-classes-here-i.html
Let me add a few comments to Kevin's answer.
Traits are primarily used as abstractions (loosely speaking, the same way that Haskell type classes define abstractions) and mixins. So, my code might use the Map trait, but it's really using an instance of type like HashMap. Conversely, if I want my "Service" type to have logging functionality, I might mix in a reusable Logging trait.
Fortunately, you rarely have to think about the linearization algorithm beyond the simple cases, e.g., I can mix in a trait that intercepts (wraps) a method call to log the fact that the method was invoked. The algorithm needs to handle the more complicated linearization cases (like the examples we show in the book), but really such complicated types are a poor design, IMHO.
Singletons, including the special subset companion objects, are designed to make everything an object, but you could also just view them as namespaces wrapping functions and possibly some state.
The implicit conversions are very useful, if a bit "magical". You might find it useful to see how they are used to simulate Haskell type-classes. Here's a great post by Debasish Ghosh: http://debasishg.blogspot.com/2010/06/scala-implicits-type-classes-here-i.html
我认为这个问题肯定有其道理。通过研究任何库如何处理异常来阐明它。
如果您与 Java 库交互,任何方法都可能引发异常。这可以通过检查异常显式地进行,也可以通过运行时异常透明地(从API角度)进行。作为一名程序员,您可能会尝试使用功能更强大的
Either
类型来指示失败(或 scalaz 的Validation< /代码>)?
我完全不清楚这将如何在 scala 中发挥作用(即解决问题并选择纯函数式方法)。当然,它可能会产生许多不同风格的编码。也就是说,scala 的功能方面有很多丰富且有用的东西可供您选择;并且当然可以在程序中让函数式代码和命令式代码的这种组合一起工作。尽管功能纯粹主义者当然可能不同意这是否是最佳情况。
无论如何,我发现在我的程序中应用函数范式极大地改进了我的代码。由于我没有尝试过 Haskell 或 F# 我无法告诉你最终结果是好还是坏。
但它与 Java 擦肩而过;从我的角度来看,在 JVM 上实现这一点(具有与我们所有 Java 库共存的实际优势)是一个杀手级应用程序。
在边缘,当您开始更多地使用 scala 的功能方面时,您会遇到许多问题。这些是最明显的:
A => B => C
和(A, B) =>等价)。 C
)M[A]
与Either[Int, A]
等价)这两个问题都使应该变得漂亮和清晰功能代码丑陋且晦涩。
I think that there is most certainly a solid point in this question. It is illuminated by looking into how any library might handle exceptions.
If you interact with a Java library, any method may throw an exception. This could be either explicitly, via a checked exception - or transparently (from an API perspective), via a runtime exception. What are you supposed to do about this, as a programmer who might be trying to use the more functional
Either
type to indicate failure (or scalaz'sValidation
)?It's not at all clear to me how this will play out in scala (i.e. approaching the problem and choosing the purely functional approach). Certainly it is likely to produce many different styles of coding. That said, there is much that is rich and useful along the functional side of scala which you can pick from; and it is certainly possible to have this mix of functional and imperative code in your program work alongside each other. Although a functional purist may of course disagree about whether this an optimal situation.
For what it's worth, I've found that the application of functional paradigms within my programs has improved my code no end. As I haven't tried either Haskell or F# I couldn't tell you whether the net result is better or worse.
But it wipes the floor with Java; and to get that on the JVM (with the practical advantage of coexisting with all our Java libraries) is a killer app, from my perspective.
At the edges, as you begin to use scala's functional side more, there are a number of issues which you will run into. These are most obviously:
A => B => C
and(A, B) => C
)M[A]
with, for example,Either[Int, A]
)Both of these issues make what should be pretty and clear functional code, ugly and obscure.
虽然我已经开始使用 scala 进行编程,但我绝对不是 scala 社区中更有经验的成员。但我已经使用另一种语言——Python 工作了很长时间,它在较小程度上也有类似的情况。它支持过程式、面向对象式和函数式风格。我发现虽然有些人倾向于坚持一种极端,但许多人结合了不同风格的结构。因此,惯用的Python将包括大量使用列表推导式、一等函数、高阶函数和部分函数,即使这些函数处理的数据是对象。然而值得注意的是,多年来,趋势已经稍微转向功能性。
我认为考虑到 scala 支持的样式的包容性,很可能没有一种正确的方法来解决问题。但如果看到更多的程序员慢慢地倾向于一种明智地使用这两种极端的风格,我不会感到惊讶,尽管他们仍然容忍那些希望坚持其中一个极端的程序员。
Though I've started programming in scala, I am definitely not a more experienced member of the scala community. But I've worked for long with another language - python, which to a lesser extent has a similar situation. It supports procedural, object oriented and functional styles. I've found that while some folks tend to stick to one extreme, many combine constructs from different styles. Thus idiomatic python will consist of heavy use of list comprehensions, first class, higher order and partial functions, even as the data these functions work on are objects. However it is to be noted that over the years, the tendency has been to shift slightly towards functional.
I think given the inclusivity of styles that scala supports it is only likely that there will be no one right way to solve a problem. But I wouldn't be surprised to see that a larger proportion of programmers slowly tend towards a style which makes judicious use of both, even as they remain tolerant of programmers wishing to stick to one of the extremes.