Cool scala features, compared to PHP


I worked with PHP in my day-to-day job for more than three years now. Since PHP is not really the best tool for many projects, I looked around for other languages available. There were two languages which looked interesting to me:

  • go, which is very opinionated
  • scala, which is not at all opinionated (multi-paradigm etc.)

In many ways, this two languages are contrary. One important goal for the go-lang creators was to keep the language simple, so that it can be used efficiently after a short period of learning time. Scala on the other hand is quite feature-rich, and generally needs a lot of time to learn.

While I liked the idea of a simple (but still well designed) language, I was also impressed by the possibilities that an advanced language like scala can give us. So I decided to invest time in learning scala.

In this article, I want to describe some of the features I like very much, and compare them with PHP. If you're interested in the go language, please have a look at this article, describing go for php devs. It's written by Evert Pot, which decided to go with go ...

Another thing to note: This is not intended to be an article about how bad PHP is. PHP has improved a lot in the last years, both the language itself and the way that the majority of the community writes code. In my opinion PHP may be the right tool for some projects. But from a technical perspective, I think there are better options available today.

So now, let's look at scala:

Immutable by default

Explicitly declaring that something is meant to be immutable is often useful. Even in PHP (which normally runs on a single thread) it makes sense, for example when dispatching an event in a plugin system.

Instead of just having "variables", scala provides mutable variables and immutable values:

var x = 5;
val y = 10;

x = 8; // ok
y = 8; // error: reassignment to val

In parameter lists, when you don't provide val or var keywords, it defaults to val:

// This is a class `A` with a constructor that takes two *values* (not variables).
class A(x: Int, y: Int) {

  // a method returning the sum of x and y
  def sum =
    x + y

  // that doesn't work, because `x` and `y` are immutable.
  def swap = {
    val tmp = x
    x = y
    y = x
  }
}

In PHP, we only have variables (and constants, but this is not the same...). We need to encapsulate the variables as private class members, and remember not to mutate them ourselves (from inside of the class).

Case classes

Case classes are data classes (or "value objects"). They...

  • give your data structure a type and a name
  • provide some useful additional features, e.g. destructing when using pattern matching and automatic getters.

While equivalent things can be done in PHP, there's a lot of boilerplate to write something that is similar to:

case class Address(name: String, street: String, city: String)

val myAddr = Address("Max Meier", "Karl-Marx-Allee", "Aachen")

// this is a feature called "pattern matching"
val Address(_, street, city) = myAddr 

println(s"I live in $city, $street")

Because the syntax is so simple (and because it's ok in scala to not use a separate file per class), whole sets of case classes are often defined without much behaviour, simply to define a data structure. In PHP, the same problems are solved either by writing a lot of boilerplate (classes with constructors, setters and getters), or by using a completely unstructured nested array.

In scala:

case class Name(first: String, last: String)
case class Address(street: String, number: String, city: String)
case class Person(id: Int, name: Name, addr: Address)

val me = Person(
  1,
  Name("Max", "Meier"),
  Address("Karl-Marx-Allee", "Aachen"))

In php:

$me = [
  'id' => 1,
  'name' => ["Max", "Meier"],
  'address' => ["Karl-Marx-Allee", "Aachen"]
  ];

While the php version is a bit shorter, it has no types, and no semantic. You would have to write documentation with a length similar to the length of the scala code to make this code useful (e.g. in a method accepting such a "person").

So while this looks like a "syntax" feature only, it has an impact on the code we write. And in my opinion it helps to write good code.

The syntax for lambdas / anonymous functions

It's just much more concise than in php:

// scala
val add = (x: Int, y: Int) => x + y
val increment = (x: Int) => x + 1

// mapping over a list, using the `increment` function
List(1, 2, 4).map(increment(_))

// or, we can define the function directly
List(1, 2, 4).map((x: Int) => x + 1)

// no need for the type declaration...
List(1, 2, 4).map(x => x + 1)

// even shorter (`_` references the current list item)
List(1, 2, 4).map(_ + 1)

Moreover, the functions have types. They are not just Closure or Callables (PHP), but for example (Int, Int) => Int (for the add function defined above). In PHP, I sometimes write interfaces and implementing classes to communicate the expected type that I expect in a higher order function:

interface Calculator {
  function run(int $a, int $b): int;
}

class Addition implements Calculator {
  function run(int $a, int $b): int
  {
    return $a + $b;
  }
}

// only by declaring my `Calculator` type, I can know that the provided "function"
// has the right signature for my `array_map` function.
function someHigherOrderFn(array $abTuples, Calculator $calculator): array
{
  return array_map(function($tuple) use ($calculator) {
    list($a, $b) = $tuple;
    return $calculator->run($a, $b);
  }, $abTuples);
}

$result = someHigherOrderFn([[1, 2], [5, 1], [1, 5]], new Addition());

Here is how I could define someHigherOrderFn in scala:

def someHigherOrderFn(abTuples: Seq[(Int, Int)], calculator: (Int, Int) => Int): Seq[Int] =
    abTuples.map {
      case (a, b) => calculator(a, b)
    }

No need here for the explicit Calculator interface. We have it too, but we can express it using the built-in type notations for:

  • functions: InputType => OuputType
  • tuples: (A, B)

If we don't like the long type declaration in the function signature, we can declare a type alias:

type IntPair = (Int, Int)
type Calculator = (Int, Int) => Int

def someHigherOrderFn(abTuples: Seq[IntPair], calculator: Calculator): Seq[Int] =
    abTuples.map(pair => calculator(pair._1, pair._2))

Type system in general

Scala is statically typed, i.e. names (variables, values, functions, ..) have types and the correctness of this types is checked at compile time. It has a quite sophisticated type system, which allows us to express our data very precisely. This requires us to write some more characters, but is a great help to reduce bugs.

PHP also has a type system, but it is very simple. There are some primitive types, arrays (which are sequences/lists, maps, tuples, ... and everything else you can imagine), resources (e.g. a file handle), callables ("functions"), and that's pretty much it. With php, we are very limited when we try to express on what data our functions operate. If we want we can use doc block type annotations to help us a bit, but that's not really standardized.

So PHP gives us some weak tools to declare types, and it can check them at runtime. Compared to scala, that's not much, but it is still useful. We can leverage static analyses tools to "play" compiler and check our code before runtime.

The Option type

In idiomatic scala, there is no null. Instead we can use more specific types, like Option, which represents an "optionally available value". This seems trivial at first, but when working with it you realise how useful that is:

  • By declaring a value's type as Option[Int], you explicitly state that it may be either Some[Int] or Nothing. All other code knows about it and can (and must) handle it.
  • Because Option has methods, it can simplify handling the Nothing case. For example, instead of repeatedly check for null, we can use functions like map and flatMap to compose multiple actions and only handle failure once.
// type annotated to show how `map` works for option...
val order: Option[Order]             = orderBySessionId(session)
val total: Option[Int]               = order.map(_.total);

// The `taxCalculator` takes the "value" of the option (if available), and returns
// another `Option`. `flatMap` will return this result (or `None` if the `total` Option
// is already `None`)
val tax: Option[Int]                 = total.flatMap(t => taxCalculator(t))
val grandTotal = tax.map(_ + total)

There is also syntactic sugar to make it even less painful:

val grantTotal: Option[Int] = for {
  order <- orderBySession(session)
  total <- order.total
} yield taxCalculator(total) + total

The php equivalent would look something like this (note the null checks):

function getTax($session) {
  $order = orderBySession($session);
  if ($order === null) {
    return null;
  }
  $total = $order->getTotal(); // safe, i.e. does not return null
  $tax = taxCalculator($total); // not safe, may return null
  if ($tax === null) {
    return null;
  }
  return $tax + $total;
}

In PHP, the result of every method which may return null, needs to be checked. (Side note: this is not enforced by the language and therefore leads to lots of null referencing errors).

In scala, you can use Option.flatMap instead of Option.map to run a potentially failing operation on a value that may already be None. This is just the most obvious use case, there are some other useful methods defined on the Option type. To summarize, Option lets you work on a potentially failed (not available) value. As long as there is a valid result, everything gets composed as normal. As soon as one operations returns None, the following operations are skipped and the end result is None.

Typeclasses

Typeclasses are a concept to support polymorphism. Describing how they work in detail would be too much for this article (if you're interested, there's a good tutorial about it).

The advantage compared to inheritance is higher flexibility. It's possible to provide an implementation of an interface for another class, without touching neither the interface nor the class for which you provide the implementation. This allows us to make a class of a third party library compatible with a typeclass (an interface) defined by another third party library.

Author: Claudio Kressibucher
Tags: PHP, Scala