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 oflst
containing only members that satisfyf
. soList(3, 5, 1, 9).takeWhile((x) => x > 1)
returnsList(3, 5)
. What’s the difference betweentakeWhile
andfilter
?(x: Double) => x != -999
is the Scala version oflambda x: x != -99
.- Are
sum
andlength
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
andgoodReadings
usingval
instead ofvar
.val
declares “Pyret-style” names, whilevar
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.