Functional programming in Scala

Functional programming in Scala

We came up with two implementations of the rainfall problem in Python. The second one looked like this:

def list_before(l: list, item) -> list:
  result = []
  for element in l:
    if element == item:
      return result
    result.append(element)
  return result

def average_rainfall(readings: lst) -> float:
  readings_in_period = list_before(readings, -999)
  good_readings = list(filter(lambda x: x >= 0, readings_in_period))
  return sum(good_readings) / len(good_readings)

This version is written in a more “functional” style–rather than storing mutable state in variables, we create a list of the readings we want and then compute its sum and length.

We can write a version like this in Scala, too. We’ll need to be able to do the following things:

  • get all of the elements in a list before a particular element
  • filter a list
  • get the sum of a list
  • get the length of a list

In Python, we had to write a function to do the first task and the rest were functions built in to Python. In Scala, we can do everything with methods on lists. We can find the methods we want in the documentation for Scala lists.

object Rainfall extends App {

  def averageRainfall2(readings: List[Double]): Double = {
    val readingsInPeriod = readings.takeWhile((x: Double) => x != -999)
    val goodReadings = readingsInPeriod.filter((x: Double) => x >= 0)
    goodReadings.sum.toDouble / goodReadings.length
  }

  println(averageRainfall2(List(1, 2, -1, -999, 4)))
}

A few things to notice here:

  • lst.takeWhile(f) returns a prefix of lst containing only members that satisfy f. so List(3, 5, 1, 9).takeWhile((x) => x > 1) returns List(3, 5). What’s the difference between takeWhile and filter?
  • (x: Double) => x != -999 is the Scala version of lambda x: x != -99.
  • Are sum and length fields or methods? From the outside, we actually can’t tell. Scala has a feature called parameterless methods, which lets us write methods that look like fields from the outside. For now, don’t worry too much about what that means–just know that for certain methods on the list, you should leave off the parentheses!
  • We’ve declared the names readingsInPeriod and goodReadings using val instead of var. val declares “Pyret-style” names, while var declares “Python-style” names. We can see what this means in the Scala console:
scala> var x = 2
scala> val y = 3
scala> x += 3
scala> x
5
scala> y += 3
ERROR

In general, if you’re not planning on changing the value of a name, you should use val; otherwise, you’ll need to use var.

Data structures

We saw several built-in data structures in Python: lists, hashtables, and sets. Scala has analogues of all three:

scala> val lyrics = List("Happy", "birthday", "to")
scala> val songs = Map("Beatles" -> List("Yellow Sub", "Help"), "Britney" -> List("Toxic", "Oops"))
scala> val genres = Set("country", "rock", "rap")
scala> lyrics(2)
"to"
scala> songs("Beatles")(1)
"Help"
scala> genres.contains("bluegrass")
false
scala> genres("bluegrass") // same thing as above!
false

All three work pretty much like their counterparts in Python, with one big difference: all three are immutable. This means that once we’ve built a List, Map, or Set, we can’t change its contents! So we can do operations like this:

scala> lyrics + "you"
List("Happy", "birthday", "to", "you")
scala> songs + ("Cardi B" -> List("I Like It"))
Map("Beatles" -> List("Yellow Sub", "Help"), "Britney" -> List("Toxic", "Oops"),
    "Cardi B" -> List("I Like It"))
scala> genres + "bluegrass"
Set("country", "rock", "rap", "bluegrass")

But we can’t do operations like this:

scala> lyrics(0) = "Sad"
ERROR
scala> songs("Beatles") = ("Sgt Pepper")
ERROR
scala> genres.add("folk")
ERROR

We’ll talk abut mutable data structures next time.