-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathscala_examples.txt
7287 lines (6181 loc) · 242 KB
/
scala_examples.txt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
Exercise .result
result
[
]
[1](#1)
chap:example-oneA First Example
As a first example, here is an implementation of Quicksort in Scala.
lstlisting
def sort(xs: Array[Int])
def swap(i: Int, j: Int)
val t = xs(i); xs(i) = xs(j); xs(j) = t
def sort1(l: Int, r: Int)
val pivot = xs((l + r) / 2)
var i = l; var j = r
while (i <= j)
while (xs(i) < pivot) i += 1
while (xs(j) > pivot) j -= 1
if (i <= j)
swap(i, j)
i += 1
j -= 1
if (l < j) sort1(l, j)
if (j < r) sort1(i, r)
sort1(0, xs.length - 1)
lstlisting
The implementation looks quite similar to what one would write in Java
or C. We use the same operators and similar control structures.
There are also some minor syntactical differences. In particular:
itemize
Definitions start with a reserved word. Function definitions start
with def, variable definitions start with var and
definitions of values (i.e. read only variables) start with val.
The declared type of a symbol is given after the symbol and a colon.
The declared type can often be omitted, because the compiler can infer
it from the context.
Array types are written Array[T] rather than T[],
and array selections are written a(i) rather than a[i].
Functions can be nested inside other functions. Nested functions can
access parameters and local variables of enclosing functions. For
instance, the name of the array xs is visible in functions
swap and sort1, and therefore need not be passed as a
parameter to them.
itemize
So far, Scala looks like a fairly conventional language with some
syntactic peculiarities. In fact it is possible to write programs in a
conventional imperative or object-oriented style. This is important
because it is one of the things that makes it easy to combine Scala
components with components written in mainstream languages such as
Java, C or Visual Basic.
However, it is also possible to write programs in a style which looks
completely different. Here is Quicksort again, this time written in
functional style.
lstlisting
def sort(xs: Array[Int]): Array[Int] =
if (xs.length <= 1) xs
else
val pivot = xs(xs.length / 2)
Array.concat(
sort(xs filter (pivot >)),
xs filter (pivot ==),
sort(xs filter (pivot <)))
lstlisting
The functional program captures the essence of the quicksort algorithm
in a concise way:
itemize
If the array is empty or consists of a single element,
it is already sorted, so return it immediately.
If the array is not empty, pick an an element in the middle of
it as a pivot.
Partition the array into two sub-arrays containing elements that
are less than, respectively greater than the pivot element, and a
third array which contains elements equal to pivot.
Sort the first two sub-arrays by a recursive invocation of
the sort function. This is not quite what the imperative algorithm does;
the latter partitions the array into two sub-arrays containing elements
less than or greater or equal to pivot.
The result is obtained by appending the three sub-arrays together.
itemize
Both the imperative and the functional implementation have the same
asymptotic complexity -- in the average case and
in the worst case. But where the imperative implementation
operates in place by modifying the argument array, the functional
implementation returns a new sorted array and leaves the argument
array unchanged. The functional implementation thus requires more
transient memory than the imperative one.
The functional implementation makes it look like Scala is a language
that's specialized for functional operations on arrays. In fact, it is
not; all of the operations used in the example are simple library
methods of a sequence class Seq[T] which is part of the
standard Scala library, and which itself is implemented in
Scala. Because arrays are instances of all sequence
methods are available for them.
In particular, there is the method filter which takes as
argument a predicate function. This predicate function must map
array elements to boolean values. The result of filter is an
array consisting of all the elements of the original array for which the
given predicate function is true. The filter method of an object
of type Array[T] thus has the signature
lstlisting
def filter(p: T => Boolean): Array[T]
lstlisting
Here, T => Boolean is the type of functions that take an element
of type t and return a Boolean. Functions like
filter that take another function as argument or return one as
result are called higher-order functions.
Scala does not distinguish between identifiers and operator names. An
identifier can be either a sequence of letters and digits which begins
with a letter, or it can be a sequence of special characters, such as
``+'', ``*'', or ``:''. Any identifier can
be used as an infix operator in Scala. The binary operation
is always interpreted as the method call . This holds also
for binary infix operators which start with a letter. Hence, the
expression filter (pivot >)@ is equivalent to the
method call .filter(pivot >)@.
In the quicksort program, filter is applied three times to an
anonymous function argument. The first argument, pivot >,
represents a function that takes an argument and returns the value
> @. This is an example of a partially applied
function. Another, equivalent way to write this function which makes
the missing argument explicit is => pivot > x@.
The function is anonymous, i.e. it is not defined with a name. The
type of the x parameter is omitted because a Scala compiler can
infer it automatically from the context where the function is used. To
summarize, xs.filter(pivot >) returns a list consisting
of all elements of the list xs that are smaller than
pivot.
Looking again in detail at the first, imperative implementation of
Quicksort, we find that many of the language constructs used in the
second solution are also present, albeit in a disguised form.
For instance, ``standard'' binary operators such as +,
-, or < are not treated in any special way. Like
append, they are methods of their left operand. Consequently,
the expression i + 1 is regarded as the invocation
i.+(1) of the + method of the integer value x.
Of course, a compiler is free (if it is moderately smart, even expected)
to recognize the special case of calling the + method over
integer arguments and to generate efficient inline code for it.
For efficiency and better error diagnostics the while loop is a
primitive construct in Scala. But in principle, it could have just as
well been a predefined function. Here is a possible implementation of it:
lstlisting
def While (p: => Boolean) (s: => Unit)
if (p) s ; While(p)(s)
lstlisting
The While function takes as first parameter a test function,
which takes no parameters and yields a boolean value. As second
parameter it takes a command function which also takes no parameters
and yields a result of type . While invokes the
command function as long as the test function yields true.
Scala's type roughly corresponds to
in Java; it is used whenever a function does not return an interesting
result. In fact, because Scala is an expression-oriented language,
every function returns some result. If no explicit return expression
is given, the value ()@, which is pronounced ``unit'', is
assumed. This value is of type .
Unit-returning functions are also called procedures.
Here's a more
``expression-oriented'' formulation of the function
in the first implementation of quicksort, which makes this explicit:
lstlisting
def swap(i: Int, j: Int)
val t = xs(i); xs(i) = xs(j); xs(j) = t
()
lstlisting
The result value of this function is simply its last expression -- a
keyword is not necessary. Note that functions
returning an explicit value always need an ``='' before their
body or defining expression.
Programming with Actors and Messages
chap:example-auction
Here's an example that shows an application area for which Scala is
particularly well suited. Consider the task of implementing an
electronic auction service. We use an Erlang-style actor process
model to implement the participants of the auction. Actors are
objects to which messages are sent. Every actor has a ``mailbox'' of
its incoming messages which is represented as a queue. It can work
sequentially through the messages in its mailbox, or search for
messages matching some pattern.
lstlisting[style=floating,label=fig:simple-auction-msgs,caption=Message
Classes for an Auction Service]
import scala.actors.Actor
abstract class AuctionMessage
case class Offer(bid: Int, client: Actor) extends AuctionMessage
case class Inquire(client: Actor) extends AuctionMessage
abstract class AuctionReply
case class Status(asked: Int, expire: Date) extends AuctionReply
case object BestOffer extends AuctionReply
case class BeatenOffer(maxBid: Int) extends AuctionReply
case class AuctionConcluded(seller: Actor, client: Actor)
extends AuctionReply
case object AuctionFailed extends AuctionReply
case object AuctionOver extends AuctionReply
lstlisting
For every traded item there is an auctioneer actor that publishes
information about the traded item, that accepts offers from clients
and that communicates with the seller and winning bidder to close the
transaction. We present an overview of a simple implementation
here.
As a first step, we define the messages that are exchanged during an
auction. There are two abstract base classes
AuctionMessage for messages from clients to the auction
service, and AuctionReply for replies from the service to the
clients. For both base classes there exists a number of cases, which
are defined in Figure fig:simple-auction-msgs.
lstlisting[style=floating,label=fig:simple-auction,caption=Implementation of an Auction Service]
class Auction(seller: Actor, minBid: Int, closing: Date) extends Actor
val timeToShutdown = 36000000 // msec
val bidIncrement = 10
def act()
var maxBid = minBid - bidIncrement
var maxBidder: Actor = null
var running = true
while (running)
receiveWithin ((closing.getTime() - new Date().getTime()))
case Offer(bid, client) =>
if (bid >= maxBid + bidIncrement)
if (maxBid >= minBid) maxBidder ! BeatenOffer(bid)
maxBid = bid; maxBidder = client; client ! BestOffer
else
client ! BeatenOffer(maxBid)
case Inquire(client) =>
client ! Status(maxBid, closing)
case TIMEOUT =>
if (maxBid >= minBid)
val reply = AuctionConcluded(seller, maxBidder)
maxBidder ! reply; seller ! reply
else
seller ! AuctionFailed
receiveWithin(timeToShutdown)
case Offer(_, client) => client ! AuctionOver
case TIMEOUT => running = false
lstlisting
For each base class, there are a number of case classes which
define the format of particular messages in the class. These messages
might well be ultimately mapped to small XML documents. We expect
automatic tools to exist that convert between XML documents and
internal data structures like the ones defined above.
Figure fig:simple-auction presents a Scala implementation of a
class Auction for auction actors that coordinate the bidding
on one item. Objects of this class are created by indicating
itemize
a seller actor which needs to be notified when the auction is over,
a minimal bid,
the date when the auction is to be closed.
itemize
The behavior of the actor is defined by its act method. That method
repeatedly selects (using receiveWithin) a message and reacts to it,
until the auction is closed, which is signaled by a TIMEOUT
message. Before finally stopping, it stays active for another period
determined by the timeToShutdown constant and replies to
further offers that the auction is closed.
Here are some further explanations of the constructs used in this
program:
itemize
The receiveWithin method of class Actor takes as
parameters a time span given in milliseconds and a function that
processes messages in the mailbox. The function is given by a sequence
of cases that each specify a pattern and an action to perform for
messages matching the pattern. The receiveWithin method selects
the first message in the mailbox which matches one of these patterns
and applies the corresponding action to it.
The last case of receiveWithin is guarded by a
TIMEOUT pattern. If no other messages are received in the meantime, this
pattern is triggered after the time span which is passed as argument
to the enclosing receiveWithin method. TIMEOUT is a
special message, which is triggered by the Actor implementation itself.
Reply messages are sent using syntax of the form
destination ! SomeMessage. ! is used here as a
binary operator with an actor and a message as arguments. This is
equivalent in Scala to the method call
destination.!(SomeMessage), i.e. the invocation of
the ! method of the destination actor with the given message as
parameter.
itemize
The preceding discussion gave a flavor of distributed programming in
Scala. It might seem that Scala has a rich set of language constructs
that support actor processes, message sending and receiving,
programming with timeouts, etc. In fact, the opposite is true. All the
constructs discussed above are offered as methods in the library class
Actor. That class is itself implemented in Scala, based on the underlying
thread model of the host language (e.g. Java, or .NET).
The implementation of all features of class Actor used here is
given in Section sec:actors.
The advantages of the library-based approach are relative simplicity
of the core language and flexibility for library designers. Because
the core language need not specify details of high-level process
communication, it can be kept simpler and more general. Because the
particular model of messages in a mailbox is a library module, it can
be freely modified if a different model is needed in some
applications. The approach requires however that the core language is
expressive enough to provide the necessary language abstractions in a
convenient way. Scala has been designed with this in mind; one of its
major design goals was that it should be flexible enough to act as a
convenient host language for domain specific languages implemented by
library modules. For instance, the actor communication constructs
presented above can be regarded as one such domain specific language,
which conceptually extends the Scala core.
chap:simple-funsExpressions and Simple Functions
The previous examples gave an impression of what can be done with
Scala. We now introduce its constructs one by one in a more
systematic fashion. We start with the smallest level, expressions and
functions.
Expressions And Simple Functions
A Scala system comes with an interpreter which can be seen as a fancy
calculator. A user interacts with the calculator by typing in
expressions. The calculator returns the evaluation results and their
types. For example:
lstlisting
scala> 87 + 145
unnamed0: Int = 232
scala> 5 + 2 * 3
unnamed1: Int = 11
scala> "hello" + " world!"
unnamed2: java.lang.String = hello world!
lstlisting
It is also possible to name a sub-expression and use the name instead
of the expression afterwards:
lstlisting
scala> def scale = 5
scale: Int
scala> 7 * scale
unnamed3: Int = 35
lstlisting
lstlisting
scala> def pi = 3.141592653589793
pi: Double
scala> def radius = 10
radius: Int
scala> 2 * pi * radius
unnamed4: Double = 62.83185307179586
lstlisting
Definitions start with the reserved word def; they introduce a
name which stands for the expression following the = sign. The
interpreter will answer with the introduced name and its type.
Executing a definition such as def x = e will not evaluate the
expression e. Instead e is evaluated whenever x
is used. Alternatively, Scala offers a value definition
val x = e, which does evaluate the right-hand-side e as part of the
evaluation of the definition. If x is then used subsequently,
it is immediately replaced by the pre-computed value of
e, so that the expression need not be evaluated again.
How are expressions evaluated? An expression consisting of operators
and operands is evaluated by repeatedly applying the following
simplification steps.
itemize
pick the left-most operation
evaluate its operands
apply the operator to the operand values.
itemize
A name defined by def is evaluated by replacing the name by the
(unevaluated) definition's right hand side. A name defined by val is
evaluated by replacing the name by the value of the definitions's
right-hand side. The evaluation process stops once we have reached a
value. A value is some data item such as a string, a number, an array,
or a list.
Here is an evaluation of an arithmetic expression.
lstlisting
(2 * pi) * radius
(2 * 3.141592653589793) * radius
6.283185307179586 * radius
6.283185307179586 * 10
62.83185307179586
lstlisting
The process of stepwise simplification of expressions to values is
called reduction.
Parameters
Using def, one can also define functions with parameters. For example:
lstlisting
scala> def square(x: Double) = x * x
square: (Double)Double
scala> square(2)
unnamed0: Double = 4.0
scala> square(5 + 3)
unnamed1: Double = 64.0
scala> square(square(4))
unnamed2: Double = 256.0
scala> def sumOfSquares(x: Double, y: Double) = square(x) + square(y)
sumOfSquares: (Double,Double)Double
scala> sumOfSquares(3, 2 + 2)
unnamed3: Double = 25.0
lstlisting
Function parameters follow the function name and are always enclosed
in parentheses. Every parameter comes with a type, which is indicated
following the parameter name and a colon. At the present time, we
only need basic numeric types such as the type scala.Double of
double precision numbers. Scala defines type aliases for some
standard types, so we can write numeric types as in Java. For instance
double is a type alias of scala.Double and int is
a type alias for scala.Int.
Functions with parameters are evaluated analogously to operators in
expressions. First, the arguments of the function are evaluated (in
left-to-right order). Then, the function application is replaced by
the function's right hand side, and at the same time all formal
parameters of the function are replaced by their corresponding actual
arguments.
lstlisting
sumOfSquares(3, 2+2)
sumOfSquares(3, 4)
square(3) + square(4)
3 * 3 + square(4)
9 + square(4)
9 + 4 * 4
9 + 16
25
lstlisting
The example shows that the interpreter reduces function arguments to
values before rewriting the function application. One could instead
have chosen to apply the function to unreduced arguments. This would
have yielded the following reduction sequence:
lstlisting
sumOfSquares(3, 2+2)
square(3) + square(2+2)
3 * 3 + square(2+2)
9 + square(2+2)
9 + (2+2) * (2+2)
9 + 4 * (2+2)
9 + 4 * 4
9 + 16
25
lstlisting
The second evaluation order is known as call-by-name,
whereas the first one is known as call-by-value. For
expressions that use only pure functions and that therefore can be
reduced with the substitution model, both schemes yield the same final
values.
Call-by-value has the advantage that it avoids repeated evaluation of
arguments. Call-by-name has the advantage that it avoids evaluation of
arguments when the parameter is not used at all by the function.
Call-by-value is usually more efficient than call-by-name, but a
call-by-value evaluation might loop where a call-by-name evaluation
would terminate. Consider:
lstlisting
scala> def loop: Int = loop
loop: Int
scala> def first(x: Int, y: Int) = x
first: (Int,Int)Int
lstlisting
Then first(1, loop) reduces with call-by-name to 1,
whereas the same term reduces with call-by-value repeatedly to itself,
hence evaluation does not terminate.
lstlisting
first(1, loop)
first(1, loop)
first(1, loop)
...
lstlisting
Scala uses call-by-value by default, but it switches to call-by-name evaluation
if the parameter type is preceded by =>.
lstlisting
scala> def constOne(x: Int, y: => Int) = 1
constOne: (Int,=> Int)Int
scala> constOne(1, loop)
unnamed0: Int = 1
scala> constOne(loop, 2) // gives an infinite loop.
^C // stops execution with Ctrl-C
lstlisting
Conditional Expressions
Scala's if-else lets one choose between two alternatives. Its
syntax is like Java's if-else. But where Java's if-else
can be used only as an alternative of statements, Scala allows the
same syntax to choose between two expressions. That's why Scala's
if-else serves also as a substitute for Java's conditional
expression ... ? ... : ....
lstlisting
scala> def abs(x: Double) = if (x >= 0) x else -x
abs: (Double)Double
lstlisting
Scala's boolean expressions are similar to Java's; they are formed
from the constants
true and
false, comparison operators, boolean negation ! and the
boolean operators && and .
sec:sqrtExample: Square Roots by Newton's Method
We now illustrate the language elements introduced so far in the
construction of a more interesting program. The task is to write a
function
lstlisting
def sqrt(x: Double): Double = ...
lstlisting
which computes the square root of x.
A common way to compute square roots is by Newton's method of
successive approximations. One starts with an initial guess y
(say: y = 1). One then repeatedly improves the current guess
y by taking the average of y and x/y. As an
example, the next three columns indicate the guess y, the
quotient x/y, and their average for the first approximations of
.
lstlisting
1 2/1 = 2 1.5
1.5 2/1.5 = 1.3333 1.4167
1.4167 2/1.4167 = 1.4118 1.4142
1.4142 ... ...
lstlisting
One can implement this algorithm in Scala by a set of small functions,
which each represent one of the elements of the algorithm.
We first define a function for iterating from a guess to the result:
lstlisting
def sqrtIter(guess: Double, x: Double): Double =
if (isGoodEnough(guess, x)) guess
else sqrtIter(improve(guess, x), x)
lstlisting
Note that sqrtIter calls itself recursively. Loops in
imperative programs can always be modeled by recursion in functional
programs.
Note also that the definition of sqrtIter contains a return
type, which follows the parameter section. Such return types are
mandatory for recursive functions. For a non-recursive function, the
return type is optional; if it is missing the type checker will
compute it from the type of the function's right-hand side. However,
even for non-recursive functions it is often a good idea to include a
return type for better documentation.
As a second step, we define the two functions called by
sqrtIter: a function to improve the guess and a
termination test isGoodEnough. Here is their definition.
lstlisting
def improve(guess: Double, x: Double) =
(guess + x / guess) / 2
def isGoodEnough(guess: Double, x: Double) =
abs(square(guess) - x) < 0.001
lstlisting
Finally, the sqrt function itself is defined by an application
of sqrtIter.
lstlisting
def sqrt(x: Double) = sqrtIter(1.0, x)
lstlisting
exercise The isGoodEnough test is not very precise for small
numbers and might lead to non-termination for very large ones (why?).
Design a different version of isGoodEnough which does not have
these problems.
exercise
exercise Trace the execution of the sqrt(4) expression.
exercise
Nested Functions
The functional programming style encourages the construction of many
small helper functions. In the last example, the implementation
of sqrt made use of the helper functions sqrtIter,
improve and isGoodEnough. The names of these functions
are relevant only for the implementation of sqrt. We normally
do not want users of sqrt to access these functions directly.
We can enforce this (and avoid name-space pollution) by including
the helper functions within the calling function itself:
lstlisting
def sqrt(x: Double) =
def sqrtIter(guess: Double, x: Double): Double =
if (isGoodEnough(guess, x)) guess
else sqrtIter(improve(guess, x), x)
def improve(guess: Double, x: Double) =
(guess + x / guess) / 2
def isGoodEnough(guess: Double, x: Double) =
abs(square(guess) - x) < 0.001
sqrtIter(1.0, x)
lstlisting
In this program, the braces ... enclose a block.
Blocks in Scala are themselves expressions. Every block ends in a
result expression which defines its value. The result expression may
be preceded by auxiliary definitions, which are visible only in the
block itself.
Every definition in a block must be followed by a semicolon,
which separates this definition from subsequent definitions or the result
expression. However, a semicolon is inserted implicitly at the end of
each line, unless one of the following conditions is
true.
enumerate
Either the line in question ends in a word such as a period
or an infix-operator which would not be legal as the end of an expression.
Or the next line begins with a word that cannot start a expression.
Or we are inside parentheses ... or brackets ,
because these cannot contain multiple statements anyway.
enumerate
Therefore, the following are all legal:
lstlisting
def f(x: Int) = x + 1;
f(1) + f(2)
def g1(x: Int) = x + 1
g(1) + g(2)
def g2(x: Int) = x + 1; /* `;' mandatory */ g2(1) + g2(2)
def h1(x) =
x +
y
h1(1) * h1(2)
def h2(x: Int) = (
x // parentheses mandatory, otherwise a semicolon
+ y // would be inserted after the `x'.
)
h2(1) / h2(2)
lstlisting
Scala uses the usual block-structured scoping rules. A name defined in
some outer block is visible also in some inner block, provided it is
not redefined there. This rule permits us to simplify our
sqrt example. We need not pass x around as an additional parameter of
the nested functions, since it is always visible in them as a
parameter of the outer function sqrt. Here is the simplified code:
lstlisting
def sqrt(x: Double) =
def sqrtIter(guess: Double): Double =
if (isGoodEnough(guess)) guess
else sqrtIter(improve(guess))
def improve(guess: Double) =
(guess + x / guess) / 2
def isGoodEnough(guess: Double) =
abs(square(guess) - x) < 0.001
sqrtIter(1.0)
lstlisting
Tail Recursion
Consider the following function to compute the greatest common divisor
of two given numbers.
lstlisting
def gcd(a: Int, b: Int): Int = if (b == 0) a else gcd(b, a
lstlisting
Using our substitution model of function evaluation,
gcd(14, 21) evaluates as follows:
lstlisting
gcd(14, 21)
if (21 == 0) 14 else gcd(21, 14
if (false) 14 else gcd(21, 14
gcd(21, 14
gcd(21, 14)
if (14 == 0) 21 else gcd(14, 21
gcd(14, 21
gcd(14, 7)
if (7 == 0) 14 else gcd(7, 14
gcd(7, 14
gcd(7, 0)
if (0 == 0) 7 else gcd(0, 7
7
lstlisting
Contrast this with the evaluation of another recursive function,
factorial:
lstlisting
def factorial(n: Int): Int = if (n == 0) 1 else n * factorial(n - 1)
lstlisting
The application factorial(5) rewrites as follows:
lstlisting
factorial(5)
if (5 == 0) 1 else 5 * factorial(5 - 1)
5 * factorial(5 - 1)
5 * factorial(4)
5 * (4 * factorial(3))
5 * (4 * (3 * factorial(2)))
5 * (4 * (3 * (2 * factorial(1))))
5 * (4 * (3 * (2 * (1 * factorial(0))))
5 * (4 * (3 * (2 * (1 * 1))))
120
lstlisting
There is an important difference between the two rewrite sequences:
The terms in the rewrite sequence of gcd have again and again
the same form. As evaluation proceeds, their size is bounded by a
constant. By contrast, in the evaluation of factorial we get longer
and longer chains of operands which are then multiplied in the last
part of the evaluation sequence.
Even though actual implementations of Scala do not work by rewriting
terms, they nevertheless should have the same space behavior as in the
rewrite sequences. In the implementation of gcd, one notes that
the recursive call to gcd is the last action performed in the
evaluation of its body. One also says that gcd is
``tail-recursive''. The final call in a tail-recursive function can be
implemented by a jump back to the beginning of that function. The
arguments of that call can overwrite the parameters of the current
instantiation of gcd, so that no new stack space is needed.
Hence, tail recursive functions are iterative processes, which can be
executed in constant space.
By contrast, the recursive call in factorial is followed by a
multiplication. Hence, a new stack frame is allocated for the
recursive instance of factorial, and is deallocated after that
instance has finished. The given formulation of the factorial function
is not tail-recursive; it needs space proportional to its input
parameter for its execution.
More generally, if the last action of a function is a call to another
(possibly the same) function, only a single stack frame is needed for
both functions. Such calls are called ``tail calls''. In principle,
tail calls can always re-use the stack frame of the calling function.
However, some run-time environments (such as the Java VM) lack the
primitives to make stack frame re-use for tail calls efficient. A
production quality Scala implementation is therefore only required to
re-use the stack frame of a directly tail-recursive function whose
last action is a call to itself. Other tail calls might be optimized
also, but one should not rely on this across implementations.
exercise Design a tail-recursive version of
factorial.
exercise
chap:first-class-funsFirst-Class Functions
A function in Scala is a ``first-class value''. Like any other value,
it may be passed as a parameter or returned as a result. Functions
which take other functions as parameters or return them as results are
called higher-order functions. This chapter introduces
higher-order functions and shows how they provide a flexible mechanism
for program composition.
As a motivating example, consider the following three related tasks:
enumerate
Write a function to sum all integers between two given numbers a and b:
lstlisting
def sumInts(a: Int, b: Int): Int =
if (a > b) 0 else a + sumInts(a + 1, b)
lstlisting
Write a function to sum the squares of all integers between two given numbers
a and b:
lstlisting
def square(x: Int): Int = x * x
def sumSquares(a: Int, b: Int): Int =
if (a > b) 0 else square(a) + sumSquares(a + 1, b)
lstlisting
Write a function to sum the powers of all integers between
two given numbers a and b:
lstlisting
def powerOfTwo(x: Int): Int = if (x == 0) 1 else 2 * powerOfTwo(x - 1)
def sumPowersOfTwo(a: Int, b: Int): Int =
if (a > b) 0 else powerOfTwo(a) + sumPowersOfTwo(a + 1, b)
lstlisting
enumerate
These functions are all instances of
^b_a f(n) for different values of .
We can factor out the common pattern by defining a function sum:
lstlisting
def sum(f: Int => Int, a: Int, b: Int): Int =
if (a > b) 0 else f(a) + sum(f, a + 1, b)
lstlisting
The type Int => Int is the type of functions that
take arguments of type Int and return results of type
Int. So sum is a function which takes another function as
a parameter. In other words, sum is a higher-order
function.
Using sum, we can formulate the three summing functions as
follows.
lstlisting
def sumInts(a: Int, b: Int): Int = sum(id, a, b)
def sumSquares(a: Int, b: Int): Int = sum(square, a, b)
def sumPowersOfTwo(a: Int, b: Int): Int = sum(powerOfTwo, a, b)
lstlisting
where
lstlisting
def id(x: Int): Int = x
def square(x: Int): Int = x * x
def powerOfTwo(x: Int): Int = if (x == 0) 1 else 2 * powerOfTwo(x - 1)
lstlisting
Anonymous Functions
Parameterization by functions tends to create many small functions. In
the previous example, we defined id, square and
power as separate functions, so that they could be
passed as arguments to sum.
Instead of using named function definitions for these small argument
functions, we can formulate them in a shorter way as anonymous
functions. An anonymous function is an expression that evaluates to a
function; the function is defined without giving it a name. As an
example consider the anonymous square function:
lstlisting
(x: Int) => x * x
lstlisting
The part before the arrow `=>' are the parameters of the function,
whereas the part following the `=>' is its body.
For instance, here is an anonymous function which multiples its two arguments.
lstlisting
(x: Int, y: Int) => x * y
lstlisting
Using anonymous functions, we can reformulate the first two summation
functions without named auxiliary functions:
lstlisting
def sumInts(a: Int, b: Int): Int = sum((x: Int) => x, a, b)
def sumSquares(a: Int, b: Int): Int = sum((x: Int) => x * x, a, b)
lstlisting
Often, the Scala compiler can deduce the parameter type(s) from the
context of the anonymous function in which case they can be omitted.
For instance, in the case of sumInts or sumSquares, one
knows from the type of sum that the first parameter must be a
function of type Int => Int. Hence, the parameter type
Int is redundant and may be omitted. If there is a single
parameter without a type, we may also omit the parentheses around it:
lstlisting
def sumInts(a: Int, b: Int): Int = sum(x => x, a, b)
def sumSquares(a: Int, b: Int): Int = sum(x => x * x, a, b)
lstlisting
Generally, the Scala term
(x: T, ..., x: T) => E
defines a function which maps its parameters
x, ..., x to the result of the expression E
(where E may refer to x, ..., x). Anonymous
functions are not essential language elements of Scala, as they can
always be expressed in terms of named functions. Indeed, the
anonymous function
lstlisting
(x: T, ..., x: T) => E
lstlisting
is equivalent to the block
lstlisting
def f (x: T, ..., x: T) = E ; f _
lstlisting
where f is fresh name which is used nowhere else in the program.
We also say, anonymous functions are ``syntactic sugar''.
Currying
The latest formulation of the summing functions is already quite
compact. But we can do even better. Note that
a and b appear as parameters and arguments of every function
but they do not seem to take part in interesting combinations. Is
there a way to get rid of them?
Let's try to rewrite sum so that it does not take the bounds
a and b as parameters:
lstlisting
def sum(f: Int => Int): (Int, Int) => Int =
def sumF(a: Int, b: Int): Int =
if (a > b) 0 else f(a) + sumF(a + 1, b)
sumF
lstlisting
In this formulation, sum is a function which returns another
function, namely the specialized summing function sumF. This
latter function does all the work; it takes the bounds a and
b as parameters, applies sum's function parameter f to all
integers between them, and sums up the results.
Using this new formulation of sum, we can now define:
lstlisting
def sumInts = sum(x => x)
def sumSquares = sum(x => x * x)
def sumPowersOfTwo = sum(powerOfTwo)
lstlisting
Or, equivalently, with value definitions:
lstlisting
val sumInts = sum(x => x)
val sumSquares = sum(x => x * x)
val sumPowersOfTwo = sum(powerOfTwo)
lstlisting
sumInts, sumSquares, and sumPowersOfTwo can be
applied like any other function. For instance,
lstlisting
scala> sumSquares(1, 10) + sumPowersOfTwo(10, 20)
unnamed0: Int = 2096513
lstlisting
How are function-returning functions applied? As an example, in the expression
lstlisting
sum(x => x * x)(1, 10) ,
lstlisting
the function sum is applied to the squaring function
(x => x * x). The resulting function is then
applied to the second argument list, (1, 10).
This notation is possible because function application associates to the left.
That is, if and are argument lists, then
lcl
f(args_1)(args_2) & is equivalent to & (f(args_1))(args_2)
In our example, sum(x => x * x)(1, 10) is equivalent to the
following expression:
(sum(x => x * x))(1, 10).
The style of function-returning functions is so useful that Scala has
special syntax for it. For instance, the next definition of sum
is equivalent to the previous one, but is shorter:
lstlisting
def sum(f: Int => Int)(a: Int, b: Int): Int =
if (a > b) 0 else f(a) + sum(f)(a + 1, b)
lstlisting
Generally, a curried function definition
lstlisting
def f (args) ... (args) = E