Published 2014-07-03.
Last modified 2019-07-12.
Time to read: 1 minutes.
Scala provides some powerfully expressive and efficient iteration mechanisms. This lecture explores functional iteration and explains some foundational concepts.
Recall that an Option
is a collection of zero or one elements.
The None
subclass of Option
contains zero elements,
and the Some
subclass contains one element.
If you are a bit fuzzy on how Option
works, please review the lecture on
Option, Some and None of the
Introduction to Scala course before diving into this lecture.
You also might want to review the
Functions are First Class,
Closures and
More Fun With Functions
lectures of the same course.
The sample code for this lecture can be found in
courseNotes/
.
Higher-Order Functions
Let’s say we have an Option
of Int
that needs to have an operation performed upon it, such as squaring the element if present.
scala> def square(num: Int): Int = num * num square: (num: Int)Int
scala> scala> val maybeInt = Some(3) maybeInt: Some[Int] = Some(3)
scala> scala> maybeInt.map(x => square(x)) res1: Some[Int] = Some(9)
The map
method iterates over each element of the Option
, and passes each element to the square
method.
Another Option
is created from the result, and because squaring an Int
always results in another Int
, the
type of the function passed into the map method is Int => Int
.
In other words, the function passed in receives an Int
parameter and returns another Int
.
map
is called a higher-order function because it receives a function as a parameter.
A higher-order function is a function that takes one or more functions as an input parameter and/or outputs a function.
Here is another example, using the standard Scala
The type of the function argument is
Scala allows us to write this same operation more succinctly.
The collection item passed in by
Now let’s rewrite
If only one parameter is passed to a higher-order function, the underscore can also be omitted.
Using infix notation we can even do away with the parentheses.
The Partially Applied Functions
lecture has a more in-depth discussion of lifting methods to functions, also known as eta expansion.
The function passed into
We are not able to use the underscore shorthand in higher order functions if the bound variable is referenced more than once in the anonymous
function.
This higher-order function accepts an interval length in seconds, and a function value, then executes the
function periodically according to the specified interval.
Notice that the Java
The function value is shown in yellow twice in the above code; the second time it is shown the function is not invoked.
Instead, this assignment connects the
The code below uses the
You can run this program by typing.
Here is some sample output.
This next higher-order function accepts a block of code and times how long it takes to execute.
The method prints out the time it took and returns the result of the method execution.
The
The return type of the time method is
Let’s use the
You can run this code using SBT by typing.
Write a one line program using the REPL that reverses every word in a text file – any file you have access to on your computer’s hard
drive.
Hints
Executing the above results in.
The solution can be found in the
Now that we know how higher-order functions work, we can use them to read binary files.
You can read a binary file into a
Notice that I ran a bash command, "
Since we want to always ensure that the source file is closed, this incantation is preferred.
Even better,
This code fragment does a lot of character conversions, which is inefficient.
A more efficient mechanism is shown in the Immutable Collections lecture.
Output is.
You can play with this program in toDouble
method which converts an Int
to a Double
; this
map
operation therefore transforms an Option[Int]
into an Option[Double].
scala> maybeInt.map( x => x.toDouble )
res2: Some[Double] = Option(3.0)
Int => Double
.
Let’s try this same map
operation with a different collection type.
In this example, the input Array[Int]
is transformed into the output Array[Double]
.
scala> Array(1, 2, 3).map( x => x.toDouble )
res3: Array[Double] = Array(1.0, 2.0, 3.0)
Higher Order Function Shorthand
map
to the anonymous function is only referenced once, so there is really no need to give it a name.
The underscore (_
) refers to the current list item as map
iterates through the collection’s items.
scala> maybeInt.map(_.toDouble)
res4: Some[Double] = Some(3.0)
maybeInt.map(x => square(x))
using progressively terser shorthands.
First we can elide the definition and single usage of the temporary variable to _
.
scala> maybeInt.map(square _)
res5: Some[Int] = Some(9)
scala> maybeInt.map(square)
res6: Some[Int] = Some(9)
scala> maybeInt map square
res7: Some[Int] = Some(9)
Lambdas
map
could be formally defined, like square
and toDouble
, or it could be anonymous,
as we shall now see.
BTW, anonymous functions are often called lambdas.
This lambda doubles whatever is passed into it.
scala> maybeInt.map(_ * 2)
res8: Some[Int] = Some(6)
scala> maybeInt.map(x => x * x)
res8: Some[Int] = Some(9)
Write Your Own Higher-Order Functions
Periodic Invocation
Timer
and TimerTask
classes are imported inside the TimedTask
object, and that the
run
method specified by the Java TimerTask
interface is implemented in Scala code.
The higher-order function apply
was written to accept two parameter lists; this was not required, however the usage of the higher-order
function is cleaner when one function is provided per parameter list.
object TimedTask {
import java.util.Timer
import java.util.TimerTask
def apply(intervalSeconds: Int=1)(op: => Unit) {
val task = new TimerTask {
def run = op
}
val timer = new Timer
timer.schedule(task, 0L, intervalSeconds*1000L)
}
}run
method required by the Java TimerTask
interface to the implementation passed via
the apply
method’s second parameter list.
TimedTask
object; you can find this code in TimedTask.scala
, in the courseNotes
directory.
You can use curly braces to surround lambdas any time.
object TimedFun extends App {
var i = 0
TimedTask(1) {
println(s"Tick #$i")
i = i + 1
}
}$ scala> sbt "runMain TimedFun"
Tick #0
Tick #1
Tick #2
Tick #3
Tick #4
Tick #5
Tick #6
Tick #7
Timing a Task
block
parameter is lazily evaluated; it is a call by name parameter and is only evaluated if
the body of the time method specifically invokes block
.
The block
parameter could accept an expression or a block of code that does not accept parameters.
It could also accept a variable or a constant value of any type, because all types are subclasses of Any
.
Any
, so it can return any type of result.
We will rewrite this method in the
Parametric Types
lecture later in this course so that it returns a strongly typed result instead of Any
.
def time(block: => Any): Any = {
val t0 = System.nanoTime()
val result: Any = block
val elapsedMs = (System.nanoTime() - t0) / 1000000
println("Elapsed time: " + elapsedMs + "ms")
result
}
time
method to see how long it takes to calculate Pi
to 1,000,000 decimal places.
Instead of writing a lambda, I have defined a method called calculatePiFor
.
scala> def calculatePiFor(decimalPlaces: Int): Double = {
| var acc = 0.0
| for (i <- 0 until decimalPlaces)
| acc += 4.0 * (1 - (i % 2) * 2) / (2 * i + 1)
| acc
| }
calculatePiFor: (decimalPlaces: Int)Double
scala> scala> time(calculatePiFor(100000))
Elapsed time: 3ms
res24: Any = 3.1415826535897198calculatePiFor
is called with decimalPlaces
set to 1,000,000.
The entire invocation with the bound parameter is passed into the time
method, not just the unbound calculatePiFor
method
like we saw earlier.
A Function1[Int, Double]
version of calculatePiFor
is provided in multi/package.scala
and could just as well
be used here.
$ scala> sbt "runMain TimedPi"
Exercise – Using the Predefined WrappedString Implicit Class
/etc/passwd
is fine.
.
), then tab.
Starting with Scala 2.11.8, the REPL reports implicit methods.
WrappedString
class available for implicit conversions are predefined by
predef.scala
;
does it has any useful methods?
WrappedString
is a predefined implicit class, will your final code actually need to mention that class?
String.split(" ")
is the dual of
Array.mkString(" ")
.
String.reverse
reverses a String
.
Solution
object StringReverser {
import scala.sys.process._
"cat /etc/passwd".!!.split(" ").map(_.reverse).mkString(" ")
}
TN-U:445:91:*:ecivreSlacoL
::81-5-1-S,:445:81:*:METSYS TN-U:445:02:*:ecivreSkrowteN
::91-5-1-S,ecivreSlacoL\YTIROHTUA TN-U:4927694924:4927694924:*:rellatsnIdetsurT
::445-23-5-1-S,:445:445:*:srotartsinimdA
::02-5-1-S,ecivreSkrowteN\YTIROHTUA ekiM
hsab/nib/:tseuG/emoh/:105-6299776593-9239486513-5407444803-12-5-1-S,tseuG\raeB-U:315:105:desunu:tseuG
hsab/nib/:rotartsinimdA/emoh/:005-6299776593-9239486513-5407444803-12-5-1-S,rotartsinimdA\raeB-U:315:005:desunu:rotartsinimdA
::4648741722-1362923581-4408301381-9462258143-588800659-08-5-1-S,rellatsnIdetsurT\ECIVRES ekiM\raeB-U:315:0001:desunu:nnilS
ekiM/emoh/:0001-6299776593-9239486513-5407444803-12-5-1-S,nnilS
hsab/nib/:resUsutadpU/emoh/:2001-6299776593-9239486513-5407444803-12-5-1-S,resUsutadpU\raeB-U,resUsutadpU:315:...
solution.StringReverser.sc
Scala worksheet.
Reading Binary Files Using Higher-Order Functions
io.Source.fromFile
uses an implicit to provide the value of a codec that specifies the character set used.
You can discover the default codec like this.
scala> implicitly[io.Codec]
res0: scala.io.Codec = UTF-8
byte
array by explicitly specifying an 8-bit codec, such as ISO-8859-1
.
import sys.process._
io.Source.fromFile("which scalac".!!.trim, "ISO-8859-1").map(_.toByte).toArray
which scalac
", using the Scala Process
facility.
I trimmed off the trailing newline returned from the process before passing the resulting fully qualified name to io.Source.fromFile
.
fromFile
returns an Iterator
of Character
.
The map
method I just showed converts each Character
to a Byte
.
In other words, map converts the Iterator[Character]
into an Iterator[Byte]
.
The resulting Iterator[Byte]
is converted to an Array[Byte]
by the toArray
method.
val source = scala.io.Source.fromFile("which scalac".!!.trim, "ISO-8859-1")
val byteArray = source.map(_.toByte).toArray
source.close()
try
/catch
/finally
should be used to guarantee source
is closed after it is read.
val source = scala.io.Source.fromFile("which scalac".!!.trim, "ISO-8859-1")
try { source.map(_.toByte).toArray } finally { source.close() }
Exercise – What does this program do?
import java.io.File
import java.sql.Date
object HigherFun extends App {
def uploadForm(saec: SignedAndEncodedComplete)(selectBucketUrl: PublisherConfig => String): String = {
s"""<form action="${selectBucketUrl(saec.pubConf)}" method="post" enctype="multipart/form-data">
<input type="hidden" name="key" value="${saec.fileName}">
<input type="hidden" name="AWSAccessKeyId" value="${saec.accessKey}">
<input type="hidden" name="acl" value="${saec.acl}">
<input type="hidden" name="policy" value="${saec.encodedPolicy}">
<input type="hidden" name="signature" value="${saec.signedPolicy}">
<input type="hidden" name="Content-Type" value="${saec.contentType}">
Select <code>${saec.fileName}</code> for uploading to S3:
<input name="file" type="file">
<br>
<input type="submit" value="Upload">
</form>""".stripMargin
}
val file = new File("/etc/passwd")
val pubConf = PublisherConfig(
name="blah",
awsAccountName="blah",
awsSecretKey="blah",
awsAccessKey="blah",
bucketName="bucket1")
val saec = SignedAndEncodedComplete(
fileName=file.getAbsolutePath,
contentLength=file.length,
accessKey="blahBlah",
secretKey="blah Blah",
acl="private",
pubConf=pubConf,
encodedPolicy="blah blah",
signedPolicy="blah blah",
contentType="blah blah")
val uf1 = uploadForm(saec)(_.homeworkBucketUrl(file.getName))
val uf2 = uploadForm(saec)(_.uploadBucketUrl(file.getName))
println(uf1)
println(uf2)
case class PublisherConfig(
name: String,
awsAccountName: String,
awsSecretKey: String,
awsAccessKey: String,
bucketName: String,
created: Date = new Date(System.currentTimeMillis),
active: Boolean = false,
id: Option[Long] = None
) {
def uploadBucketUrl(file: String): String = s"https://upload.$bucketName/$file"
def homeworkBucketUrl(file: String): String = s"https://homework.$bucketName/$file"
}
case class SignedAndEncodedComplete(
fileName: String,
contentLength: Long,
accessKey: String,
secretKey: String,
acl: String,
encodedPolicy: String,
signedPolicy: String,
pubConf: PublisherConfig,
contentType: String
)
}Solution
<form action="https://homework.bucket1/passwd" method="post" enctype="multipart/form-data">
<input type="hidden" name="key" value="/etc/passwd">
<input type="hidden" name="AWSAccessKeyId" value="blahBlah">
<input type="hidden" name="acl" value="private">
<input type="hidden" name="policy" value="blah blah">
<input type="hidden" name="signature" value="blah blah">
<input type="hidden" name="Content-Type" value="blah blah">
Select <code>/etc/passwd</code> for uploading to S3:
<input name="file" type="file">
<br>
<input type="submit" value="Upload">
</form>
<form action="https://upload.bucket1/passwd" method="post" enctype="multipart/form-data">
<input type="hidden" name="key" value="/etc/passwd">
<input type="hidden" name="AWSAccessKeyId" value="blahBlah">
<input type="hidden" name="acl" value="private">
<input type="hidden" name="policy" value="blah blah">
<input type="hidden" name="signature" value="blah blah">
<input type="hidden" name="Content-Type" value="blah blah">
Select <code>/etc/passwd</code> for uploading to S3:
<input name="file" type="file">
<br>
<input type="submit" value="Upload">
</form>
courseNotes/solutions
; it is called HigherFun.scala
.
Run it like this.
$ scala> sbt "runMain solutions.HigherFun"
© Copyright 1994-2024 Michael Slinn. All rights reserved.
If you would like to request to use this copyright-protected work in any manner,
please send an email.
This website was made using Jekyll and Mike Slinn’s Jekyll Plugins.