Module 4: Functions
Note: Scala Language Specification can be used as an authoritative reference in case of doubts.
Function types
The following is an example of a simple function:
val triple = (n: Int) => n * 3
The type of this function literal is: Int => Int
. We could have used
the type annotation to define triple:
val triple: Int => Int = n => n * 3
That can be read like:
triple
is a function that accepts anInt
parameter and returns anInt
In general, to declare a function type, write:
(A, B, ...) => C
where
A
,B
, … are the types of the input parameters; andC
is the type of the result.
Naming conventions
Scala names are case sensitive. See Naming conventions for constants, values, variables and methods.
Can you tell why the following would not be a good name?
val Triple = (n: Float) => n * 3.0f
According to Function Types:
- Function types associate to the right, e.g.
S => T => U
is the same asS => (T => U)
. - An argument type of the form
=> T
represents a call-by-name parameter of typeT
.
Relationship between methods and functions: eta–expansion
There is a close relationship between methods and functions, by means of the Eta–expansion. For example,
def f1(n: Int): Int = n * 2
# Placeholder syntax
val f2: Int => Int = f1 _
# eta-expansion: equivalent to placeholder syntax
val f3: Int => Int = n => f1(n)
Given this close relationship, you will find methods referred to as “functions”, although they are not strictly the same.
Return value of a block
The last expression of a block becomes the value that the function
returns. For example, the following method returns the value of r
after the for
loop (no need for the return
keyword):
def fac(n: Int) = {
var r = 1
for (i <- 1 to n)
r = r * i
r
}
Recursive functions/methods must specify the return type
def fac(n: Int): Int =
if (n <= 0) 1
else n * fac(n - 1)
Varargs syntax
The term varargs is often used to refer to methods with a variable number of parameters. A common
example from the Java world is System.out.printf
:
// Two parameters
System.out.printf("%d", n);
// Three parameters, but same method signature
System.out.printf("%d %s", n, widgets);
In Java, printf
method is defined like this:
public class PrintStream {
public PrintStream printf(String fmt, Object... args) {
return format(fmt, args);
}
}
The elipsis ...
is part of the Java code, and it denotes that the method can receive an arbitrary
number of objects (in addition to the fmt
parameter).
In Scala, you can write varargs methods too. For example:
def sum(args: Int*): Int = {
var result = 0
for (arg <- args)
result += arg
result
}
val s = sum(1 to 5: _*)
Notice that you use a *
after the type you are allowing to appear several times.
Note: Scala also supports the @varargs
annotation that lets you call a Scala variable-argument
method from Java. See Scala varargs annotation scaladoc.
def foo(args: String*): Unit
is translated by the Scala compiler to
def foo(args: Seq[String]): Unit
But if you add the @varargs
annotation
@varargs def foo(args: String*): Unit
then the following (equivalent) Java method signature will be generated:
void foo(String... args)
Procedures have Unit
return value
// Compiles, but discouraged
def box(s : String) { // Look carefully: no =
// contents elided
}
Procedure syntax is discouraged. See Procedure Syntax.
// (Equivalent) Explicit return type
def box(s : String): Unit = {
// contents elided
}
Scaladoc for Function1
Open Current Scala Standard Library Scaladoc and search documentation for trait Function1
.
You should see documentation for important methods like apply
, andThen
and
compose
.
apply
In the following example (taken from Function1
scaladoc), the
definition of succ
is a shorthand for the anonymous class definition
anonfun1
:
object Main extends App {
val succ = (x: Int) => x + 1
val anonfun1 = new Function1[Int, Int] {
def apply(x: Int): Int = x + 1
}
assert(succ(0) == anonfun1(0))
}
compose
It has the following signature
def compose[A](g: (A) => T1): (A) => R
It models the mathematical function composition. For example, if (f(x) = x + 1) and (g(x) = 2x),
Using Scala:
val f: Int => Int = x => x + 1
val g: Int => Int = x => 2 * x
// f "after" g, or g "then" f
// fg(x) = 2x + 1
val fg: Int => Int = f compose g
// g "after" f, or f "then" g
// gf1(x) = 2(x + 1)
val gf1: Int => Int = f andThen g
gf1(3)
// equivalently
val gf2: Int => Int = g compose f
gf2(3)
Scala “Universal” toString
method
Scala creates a toString
method automatically for all classes.
Functions also get a default (useless, and pretty arbitrary)
implementation for toString
method.
Notice that FunctionN
traits are not sealed, so you could override
it’s implementation to get a
more meaningful implementation.
Composition
We have reviewed composition in the mathematical sense. This may seem
too theoretical, but is a wonderful tool to get complex solutions from
smaller building blocks. We are going to use atto
, a parsing library
that uses andThen
to build a new parser from smaller parsers.
Spend some time studying the section on basic parsers.
Exercises
- Discuss your understanding on Basic Parsers with others. Study first.
- Following tutorial, replicate the example given in your development machine.
Currying
Let’s take a look at Functions2
trait scaladoc. It is used for
functions that accept two arguments. For example:
scala> val add1: (Int, Int) => Int = _ + _
add1: (Int, Int) => Int = $$Lambda$4246/65203040@1a1497d3
A curried version of the above is:
scala> val add2: Int => Int => Int = a => b => a + b
add2: Int => (Int => Int) = $$Lambda$4299/874187625@770686b
From Scala for the Impatient, 2nd Edition:
Currying (named after logician Haskell Brooks Curry) is the process of turning a function that takes two arguments into a function that takes one argument. That function returns a function that consumes the second argument.
You can get a curried version of a FunctionN
by calling curried
:
scala> add1.curried // returns an equivalent of add2
res0: Int => (Int => Int) = scala.Function2$$Lambda$4310/375850982@55e50fc9
On the other hand, you can call Function.uncurried
to “uncurry”:
scala> Function.uncurried(add2) // returns an equivalent of add1
res1: (Int, Int) => Int = scala.Function$$$Lambda$4326/963248854@3af9f63e
Currying buys us (at least) 2 things in Scala:
- Partial application
- Better type inference
Partial application is the ability to apply only some of a function’s arguments. For example
scala> val addTen: Int => Int = add2(10)
addTen: Int => Int = $$Lambda$4333/1213289156@6d5d371f
Here is an example from the Scala Standard Library to explain the second
point. Take a look at List.foldRight()
’s type signature:
def foldRight[B](z: B)(op: (A, B) => B): B
The z
and op
values are separate curried parameters. The type
inferencer can figure out what B
is from the type of z
, and then it
can use that information when analyzing the function that is passed for
op
.
Notice that one way to achieve currying in Scala is by using multiple parameter lists.
Generics and parametric polymorphism
Generics is a powerful tool from modern programming languages. Back in early 2000’s even some product team members from Microsoft considered that “generics [was] for academics only”.
At the moment of writing, several mainstream languages feature generic collections in their Standard API/library. See for example:
Scala has higher level of support for generic programming, than Java and C#. See:
Oliveira, Bruno CdS, Adriaan Moors, and Martin Odersky.
“Type Classes as Objects and Implicits.”
In ACM Sigplan Notices, 45:341–360. ACM, 2010.
Now, let’s review some of the basics.
Polymorphism
“Polymorphic” means “made of many forms”, in contrast with “monomorphic”, “made of one form”. In Scala, you can use several kinds of polymorphism:
- Subtyping polymorphism (reviewed in “Objects and classes” Module)
- Parametric polymorphism
- Ad hoc (or constrained) polymorphism
Subtyping/dynamic polymorphism
The following snippet illustrates subtyping polymorphism, and how we can
constraint type parameters with the upper type bound T <: Shape
.
sealed trait Shape
case class Square(l: Double) extends Shape
case class Circle(radius: Double) extends Shape
// Constraining T to be a subtype of Shape
// area is still a generic method
def area[T <: Shape](t: T) = ???
Take a look at lower type bounds for documentation on lower type bounds.
Parametric polymorphism
Parametric polymorphism refers to type variables, or parameters, that are fully polymorphic. When unconstrained by a typeclass, their final, concrete type could be anything.
Let’s use List.map
as an example:
final def map[B](f: (A) => B): List[B]
List[A]
is a polymorphic/generic collection. It can hold elements of
the same arbitrary type A
. In turn, List.map
is a polymorphic
method, that accepts any function that transforms an A
into an
arbitrary type B
, and produces a List
of elements of type B
.
Making type A = Int
, type B = String
:
val ns = List(1, 2, 3, 4)
val ss = ns.map(_.toString)
Classes can be parametric also, for example:
case class Pair[A, B](first: A, second: B) {
def swap: Pair[B, A] = Pair(second, first)
}
Ad hoc polymorphism
Prerequisite: Study Context Bounds.
Types can be constrained in generic functions, by using typeclasses.
Let’s start by reviewing Ordering[T]
from the Scala Standard Library.
// Type class Constraint
import scala.math.Ordering.Implicits._
def myMax[T : Ordering](x: T, y: T): T =
if (x > y) x else y;
The following (more advanced) example is an excerpt from an implementation of the k-means algorithm (Spire GitHub repository):
/**
* Returns a collection of k points which are the centers of k clusters of
* `points0`.
*/
def kMeans[V, @sp(Double) A, CC[V] <: Iterable[V]](points0: CC[V], k: Int)(implicit
vs: NormedVectorSpace[V, A], order: Order[A],
cbf: CanBuildFrom[Nothing, V, CC[V]], ct: ClassTag[V]): CC[V] = {
// code elided
// This is the main loop of the k-means algorithm. Given a new clustering
// and some previous assignments mapping each point to a cluster, we
// determine if the new clustering will cause any points to switch
// clusters. If so, we re-assign all points to their closest center in the
// clustering, then find new centers using the centroids of the points
// assigned to each cluster, rinse, repeat.
@tailrec
def loop(assignments0: Array[Int], clusters0: Array[V]): Array[V] = {
val assignments = assign(clusters0)
if (assignments === assignments0) {
clusters0
} else {
val clusters = Array.fill[V](clusters0.length)(vs.zero)
val counts = new Array[Int](clusters0.length)
cfor(0)(_ < points.length, _ + 1) { i =>
val idx = assignments(i)
clusters(idx) = clusters(idx) + points(i)
counts(idx) += 1
}
cfor(0)(_ < clusters.length, _ + 1) { j =>
clusters(j) = clusters(j) :/ vs.scalar.fromInt(counts(j))
}
loop(assignments, clusters)
}
}
}
Notice there are several advanced uses of Scala features, including ad hoc polymorphism, specialization and implicits.
Functions and Dependency Injection
Prerequisite: Dead-Simple Dependency Injection
The following abstraction (Reader[A]
monad), can be used to implement
dependency injection in a functional way.
object Reader {
implicit def reader[A, B](f: A => B): Reader[A, B] = Reader(f)
def pure[C, A](a: A): Reader[C, A] =
(_: C) => a
}
case class Reader[C, A](g: C => A) {
// Pulling in implicit conversion reader
import Reader._
def apply(c: C): A = g(c) // or "run"
def map[B](f: A => B): Reader[C, B] =
(c: C) => f(g(c))
def flatMap[B](f: A => Reader[C, B]): Reader[C, B] =
(c: C) => f(g(c))(c)
}
Instead of implementing our own Reader[A]
abstraction, we will use
cats library implementation (to pull a more robust/complete
implementation). Still, we will be using the same ideas/patterns.
import java.sql.{Connection, DriverManager, PreparedStatement}
import cats.data.Reader
object DB {
type DB[A] = Reader[Connection, A]
type UserId = String
type Pwd = String
implicit def reader[A](g: Connection => A): DB[A] = Reader(g)
def pure[A](a: A): DB[A] = Reader(_ => a)
// Code elided
}
Some primitive operations that are delaying the definition of a
Connection
database:
def setUserPwd(id: UserId,
pwd: Pwd): Connection => Unit =
c => {
val stmt: PreparedStatement =
c.prepareStatement("update users set pwd = ? where id = ?")
stmt.setString(1, pwd)
stmt.setString(2, id)
stmt.executeUpdate()
stmt.close()
}
def getUserPwd(id: UserId): Connection => Pwd =
c => {
val stmt: PreparedStatement =
c.prepareStatement("select pwd where id = ?")
stmt.setString(1, id)
val resultSet = stmt.executeQuery()
val pwd = resultSet.getString(0)
stmt.close()
pwd
}
Now we are able to express database “actions”, without referring to a
database Connection
at all:
def changePwd(userId: UserId,
oldPwd: Pwd,
newPwd: Pwd): DB[Boolean] =
for {
pwd <- getUserPwd(userId)
eq <- if (pwd == oldPwd) for {
_ <- setUserPwd(userId, newPwd)
} yield true
else pure(false)
} yield eq
And we can define different database connection providers: one for testing and some other for production purposes:
abstract class ConnProvider {
def apply[A](f: DB[A]): A
}
lazy val sqliteTestDB: ConnProvider =
mkProvider("org.sqlite.JDBC", "jdbc:sqlite::memory:")
lazy val mysqlProdDB: ConnProvider =
mkProvider(
"org.gjt.mm.mysql.Driver",
"jdbc:mysql://prod:3306/?user=one&password=two")
We can define interpreters to process our DB[A]
actions:
def mkProvider(driver: String, url: String): ConnProvider =
new ConnProvider {
override def apply[A](f: DB[A]): A = {
//Class.forName(driver)
val conn = DriverManager.getConnection(url)
try {
f(conn)
}
finally {
conn.close()
}
}
}
And “programs” or descriptions of the computations on our DB[A]
actions. For example, next one describes how to change a user password,
but without fixing the connection provider that eventually will be used:
def myProgram(userId: UserId): ConnProvider => Unit =
r => {
println("Enter old password")
val oldPwd = readLine()
println("Ender new password")
val newPwd = readLine()
r(changePwd(userId, oldPwd, newPwd))
}
And finally, the actual “injection” of different implementations
(Testing Vs. Production) of a ConnectionProvider
:
def runInTest[A](f: ConnProvider => A): A =
f(sqliteTestDB)
def runInProduction[A](f: ConnProvider => A): A =
f(mysqlProdDB)
def main(args: Array[String]) =
runInTest(myProgram(args(0)))