M.C. Oscar Vargas Torres
@oscarvarto
Scala Developer and
(dys)functional programmer
FUNCTIONS ALL THE WAY!
Note: The Scala Language Specification can be used as an authoritative reference in case of doubts.
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.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:
S => T => U
is the same as S => (T => U)
=> T
represents a call-by-name parameter of type T
.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.
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
}
def fac(n: Int): Int =
if (n <= 0) 1
else n * fac(n - 1)
def sum(args: Int*): Int = {
var result = 0
for (arg <- args)
result += arg
result
val s = sum(1 to 5: _*)
Unit
return valuedef box(s : String) { // Look carefully: no =
// contents elided
}
// (Equivalent) Explicit return type
def box(s : String): Unit = {
// contents elided
}
Function1
Open Current Scala Standard Library Scaladoc and search documentation for trait Function1
.
Search the following methods:
apply
andThen
andcompose
.apply
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\),
\[\begin{align} (f \cdot g)(x) &= f(g(x)) = 2x + 1 \\ (g \cdot f)(x) &= g(f(x)) = 2(x + 1) \end{align}\]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)
toString
methodScala creates a toString()
method automatically for all classes. Functions also get a useless implementation for toString()
.
Note: FunctionN
traits are not sealed: you can override
it's implementation.
A wonderful tool to get complex solutions from smaller building blocks.
Atto
, is a parsing library that uses andThen
to build a new parser from smaller parsers. Spend some time studying the section on basic parsers.
Let's take a look at Functions2
trait scaladoc. It is used for functions that accept two arguments. For example:
val add1: (Int, Int) => Int = _ + _
A curried version of the above is:
val add2: Int => Int => Int = a => b => a + b
// Convert
(A, B) => C
// into
A => (B => C)
You can get a curried version of a FunctionN
by calling curried
:
add1.curried // returns an equivalent of add2
On the other hand, you can call Function.uncurried
to "uncurry":
Function.uncurried(add2) // returns an equivalent of add1
Currying buys us (at least) 2 things in Scala:
The ability to apply only some of a function's arguments
val addTen: Int => Int = add2(10)
From the Scala Standard Library, List.foldRight()
:
def foldRight[B](z: B)(op: (A, B) => B): B
B
is from the type of z
, andop
.Generic collections in mainstream languages
Scala has higher level of support for generic programming, than Java and C#.
Oliveira, Bruno CdS, Adriaan Moors, and Martin Odersky. "Type Classes as Objects and Implicits." In ACM Sigplan Notices, 45:341–360. ACM, 2010.
Constraining 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) = ???
Documentation on lower type bounds.
Param. Polym. \(\approx\) Unconstrained type variables
From List.map
's type signature:
final def map[B](f: (A) => B): List[B]
List[A]
is a polymorphic/generic collection.List.map
is a polymorphic method.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)
}
Prerequisite: Context Bounds
Types variables can be constrained in generic functions, by using typeclasses.
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;
An excerpt from an implementation of the k-means algorithm (Spire):
/**
* 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] = {
@tailrec
def loop(assignments0: Array[Int],
clusters0: Array[V]): Array[V] = {
val assignments = assign(clusters0)
if (assignments === assignments0) {
clusters0
} else {
// ***** CODE ELIDED *****
loop(assignments, clusters)
}
}
Prerequisite: Dead-Simple Dependency Injection
Reader[A]
monad abstractionobject 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) {
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)
}
Using cats' Reader[A]
implementation:
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.
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))
}
The actual "injection" of (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)))