The first thing that fascinates me is Martin Odersky's pronouncement that functional programming (FP) and object-oriented programming (OOP) are orthogonal concerns. The idea is that you can do OOP in imperative as well as in functional languages; there's no need for a great divide between the functional and OO realms. So where functional languages like F#, OCaml, and Haskell normally avoid the use of objects, Scala embraces them. That's pretty wild!
This attitude fits pretty well with the history of OO in languages like Lisp. Going further, Odersky even considers Smalltalk, as purebred an OO language as it gets, to be a member of the broader family of functional languages, thanks to its support for blocks and hence arbitrary control structures.
So the big idea behind Scala is that while functional programming is generally preferable to imperative programming, since FP isn't always feasible, we might as well have a nice language for both. And while we're at it, make it part of the JVM ecosystem.
From a Haskeller's perspective
We can't always rely on the functional style. That's true. Some data structures and algorithms are known only in imperative forms; and for others, the functional equivalents have unacceptable complexity. However, every practical functional language has some form of reference type to support operations that require mutation.
OOP
You can do OOP in Haskell, but the ways I know of are either slow or have the occasional strange edge-case. And error messages may be a touch on the obscure side.
Signalling purity
Haskell's do
notation, reference types, and associated monads provide a
different and highly principled way of mixing functional and imperative-like
programming. What you lose in flexibility, namely the ability to have
side-effects anywhere (ignoring Haskell's unsafe forms), you gain in code
clarity. One look at a function's type signature tells you whether it can have
side-effects or not. That clarity is missing in Scala.
In Haskell, it's obvious when a function is pure. In Scala, you have to check.
Scala uses val
for immutable values and var
for mutable references. But
the object captured by the immutable form can itself by mutable. So val
and
var
really don't tell you that much. (As an aside, it would have been nice
had var
been called ref
instead. As is, the two are similar visually,
and depending where you're from they even sound the same. I guess the
similarity is intentional.)
Scala has a neat shorthand for procedure-style functions, those functions that are executed purely for their side-effects:
def doThis(a: Int) { println(a) }
Skipping the =
indicates the return type is Unit
, which fulfills the same
role as Haskell's ()
. But while a Unit
return type signals a side-effecting
function, a non-Unit
return doesn't signal a pure function.
REPL
Well, the Scala Repl kind of sucks. Apparently it's the JVM's fault. The JVM
doesn't make reloading stuff easy at all, so there's no :reload
.
There is a :replay
which reruns the Repl session from the top. But it doesn't
reload any imported modules either.
On the plus side, there are worksheets.
Modules
Scala modules are first-class, which is to say, supremely awesome.
Return a smile :)
If you follow the spacing convention of <typedSomething: Type>
everywhere,
you end up with little sad faces in every function definition:
def fun(arg: TypeA): TypeB = {...}
Can you see the frown? Oh well.
Type annotations
Scala is part of the inlined type annotation tradition. While Haskell's
fun :: TypeA -> TypeB
fun arg = ...
is more verbose, it's great for reading and reviewing code. First get a handle on the types, then look at the particulars.
Type inference
For type inference, Scala's type system isn't as complete as Haskell's.
Here's an error I ran into:
for {
size <- Gen.choose(1,30)
set = DisjointSet(size)
numU <- Gen.choose(0,2*size)
pair = for { a <- Gen.choose(0,size-1)
b <- Gen.choose(0,size-1) } yield (a,b)
unions <- Gen.containerOfN(numU, pair)
} yield unions.toArray.foldLeft(set) {
case (s,(i,j)) => s.union(i,j)
}
Find the compile-time error and its cause!
Error:
No implicit view available from C[(Int, Int)] => Traversable[(Int, Int)].
unions <- Gen.containerOfN(numU, pair)
^
The cause is a missing type annotation:
Gen.containerOfN[List,(Int,Int)](numU, pair)
The type inference being a little more limited means we need a few more annotations here and there. But the compiler error pointed to the right spot, so that's pretty good.
Advanced stuff
If you love obscure features both Haskell and Scala have you covered. With
Haskell's language extensions you can program in a different language every day
of the week and yet ghc
knows how to compile them all. Scala is a pretty
large language. For example, it supports implicit parameters, implicit classes
to extend existing types, using objects as modules, and abstract classes.