forked from marijnh/Eloquent-JavaScript
-
Notifications
You must be signed in to change notification settings - Fork 0
/
03_functions.txt
988 lines (800 loc) · 33.5 KB
/
03_functions.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
:chap_num: 3
:prev_link: 02_program_structure
:next_link: 04_data
= Functions =
[quote, Donald Knuth]
____
People think that computer science is the art of geniuses but the
actual reality is the opposite, just many people doing things that
build on each other, like a wall of mini stones.
____
(((function)))You've seen function values, such as `alert`, and how
to call them. Functions are the bread and butter of JavaScript
programming. The concept of wrapping a piece of program in a value
has many uses. It is a tool to structure larger programs, to reduce
repetition, to associate names with sub-programs, and to isolate
these sub-programs from each other.
The most obvious application of functions is defining new vocabulary.
Creating new words in regular, human-language prose is usually bad
style. But in programming, it is indispensable.
Typical adult English speakers have some twenty thousand words in
their vocabulary. Very few programming languages come with twenty
thousand commands built in. And the vocabulary that _is_ available
tends to be more precisely defined, and thus less flexible, than
in human language. Thus, we usually _have_ to add some of our own
vocabulary to avoid repeating ourselves too much.
== Defining a function ==
(((function,definition)))A function definition is just a regular
variable definition where the value given to the variable happens to
be a function. For example, the following code defines the variable
`square` to refer to a function that produces the square of a given
number:
[source,javascript]
----
var square = function(x) {
return x * x;
};
console.log(square(12));
// → 144
----
(((function,keyword)))(((function,body)))A function is created by an
expression that starts with the keyword `function`. Functions have a
set of _parameters_ (in this case, only `x`), and a _body_, which
contains the statements that are to be executed when the function is
called. The function body must always be wrapped in braces, even when
it consists of only a single statement (as in the example above).
A function can have multiple parameters, or no parameters at all. For
example, `makeNoise` below does not list any parameter names, whereas
`power` lists two.
[source,javascript]
----
var makeNoise = function() {
console.log("Pling!");
};
makeNoise();
// → Pling!
var power = function(base, exponent) {
var result = 1;
for (var count = 0; count < exponent; count++)
result *= base;
return result;
};
console.log(power(2, 10));
// → 1024
----
(((return keyword)))Some functions produce a value, such as `power`
and `square`, and some don't, such as `makeNoise`, which only
produces a side effect. A `return` statement determines the value the
function returns. When control comes across such a statement, it
immediately jumps out of the current function and gives the returned
value to the code that called the function. The `return` keyword
without an expression after it will cause the function to return
`undefined`.
== Parameters and scopes ==
(((variable)))The parameters to a function behave like regular
variables, but their initial values are given by the _caller_ of the
function, not the code in the function itself.
(((variable,scope)))(((local variable)))A very important property of
functions is that the variables created inside of them, including
their parameters, are _local_ to the function. This means, for
example, that the `result` variable in the `power` example will be
newly created every time the function is called, and these separate
incarnations do not interfere with each other.
indexsee:[top-level variable,global variable]
(((var keyword)))(((global variable)))This “localness” of variables
applies only to the parameters and variables declared with the `var`
keyword inside the function body. It is possible to access _global_
(non-local) variables from inside a function, as long as you haven't
declared a local variable with the same name.
The following code demonstrates this. It defines and calls two
functions that both assign a value to the variable `x`. The first one
declares the variable as local and thus changes only the local
variable. The second does not declare `x` locally, so references to
`x` inside of it refer to the global variable `x` defined at the top
of the example.
[source,javascript]
----
var x = "outside";
var f1 = function() {
var x = "inside f1";
};
f1();
console.log(x);
// → outside
var f2 = function() {
x = "inside f2";
};
f2();
console.log(x);
// → inside f2
----
This behavior helps prevent accidental interference between
functions. If all variables were shared by the whole program, it'd
take a lot of effort to make sure no name is ever used for two different purposes. And if
you _did_ reuse a variable name, you might see strange effects from
unrelated code messing with the value of your variable. By treating
function-local variables as existing only within the function, the
language makes it possible to read and understand functions as small
universes, without having to worry about all the code at once.
== Nested scope ==
(((variable,scope)))(((inner function)))(((nested functions)))
(((lexical scoping)))JavaScript does not just distinguish between
_global_ and _local_ variables. Functions can be created inside other
functions, producing several degrees of locality.
For example, this rather nonsensical function has two functions inside
of it:
[source,javascript]
----
var landscape = function() {
var result = "";
var flat = function(size) {
for (var count = 0; count < size; count++)
result += "_";
};
var mountain = function(size) {
result += "/";
for (var count = 0; count < size; count++)
result += "'";
result += "\\";
};
flat(3);
mountain(4);
flat(6);
mountain(1);
flat(1);
return result;
};
console.log(landscape());
// → ___/''''\______/'\_
----
The `flat` and `mountain` functions can “see” the variable called
`result`, since they are inside of the function that defines it. But
they can not see each other's `count` variables, since
they are outside each other's scope. The environment outside of the
`landscape` function doesn't see any of the variables defined inside of
`landscape`.
In short, each local scope can also see all the local scopes that
contain it. The set of variables visible inside a function is
determined by the place of that function in the program text. All
variables from blocks “around” a function's definition are
visible—meaning both those in function bodies that enclose it and
those at the top level of the program. This approach to variable
visibility is called _lexical scoping_.
((({} (block))))People who have experience with other programming
languages might expect that any block of code between braces produces
a new local environment. But in JavaScript, functions are the only
things that create a new scope. You are allowed to use free-standing
blocks:
[source,javascript]
----
var something = 1;
{
var something = 2;
// Do stuff with variable something...
}
// Outside of the block again...
----
But the `something` inside the block refers to the same variable
as the one outside the block. In fact, although blocks like this are
allowed, they are only useful to group the body of an `if`
statement or a loop.
If you find this odd, you're not alone. The next version of JavaScript
will introduce a `let` keyword, which works like `var`, but creates a
variable that is local to the enclosing _block_, not the enclosing
_function_.
== Functions as values ==
(((function,as value)))Usually, function variables simply act as
names for a specific piece of the program. The variables are defined
once, and never change. This makes it easy to start confusing the
function and its name.
But the two are different. A function value can do all the things that
other values can do—you can use it in all kinds of expressions, not
just call it. It is possible to store a function value in a new place, or
pass it as an argument to a function, and so on. Similarly, a variable
that holds a function is still just a regular variable, and can be
assigned a new value, like so:
// test: no
[source,javascript]
----
var launchMissiles = function(value) {
missileSystem.launch("now");
};
if (safeMode)
launchMissiles = function(value) {/* do nothing */};
----
In Chapter 5, we will discuss the wonderful things that can be done by
passing around function values to other functions.
== Declaration notation ==
There is a slightly shorter way to say “`var x = function…`”. The
`function` keyword can also be used at the start of a statement, as in the following:
[source,javascript]
----
function square(x) {
return x * x;
}
----
This is a function _declaration_. The statement defines the variable
`square` and points it at the given function. So far so good. There
is one subtlety with this form of function definition, however:
[source,javascript]
----
console.log("The future says:", future());
function future() {
return "We STILL have no flying cars.";
}
----
This code works, even though the function is defined _below_ the
code that first uses it. This is because function
declarations are not part of the regular top-to-bottom flow of
control. They are conceptually moved to the top of their scope, and can be used by
all the code in that scope. This is sometimes useful, as it gives us the freedom
to order code in a way that seems meaningful, without worrying about having to
define all functions above their first use.
What happens when you put such a function definition inside of a
conditional (`if`) block, or a loop? Well, don't do that. Different
JavaScript platforms in different browsers have traditionally done
different things in that situation, and the latest standard actually
forbids it. If you want your programs to behave consistently, only
use this form of function-defining statements in the outermost block
of a function or program.
[source,javascript]
----
function example() {
function a() {} // Okay
if (something) {
function b() {} // Danger!
}
}
----
== The call stack ==
indexsee:[stack,call stack]
(((call stack)))(((function,application)))I believe it will be helpful
to take a closer look at the way control flows through functions. Here
is a simple program that makes a few function calls:
[source,javascript]
----
function greet(who) {
console.log("Hello " + who);
}
greet("Harry");
console.log("Bye");
----
A run through this program goes roughly like this: The call to
`greet` causes control to jump to the start of that function (line
2). It calls `console.log` (a built-in browser function), which takes
control, does its job, then returns control to line 2. Then it
reaches the end of the `greet` function, so it returns to the place that
called it, at line 4. The line after that calls `console.log` again.
We could show the flow of control schematically like this:
....
top
greet
console.log
greet
top
console.log
top
....
Because a function has to jump back to the place of the call when it
returns, the computer must remember the context from which the
function was called. In one case, `console.log` had to jump back to
the `greet` function. In the other case, it jumps back to the end of
the program.
The place where the computer stores this context is the _call stack_.
Every time a function is called, the current context is put on top of
this “stack”. When the function returns, it takes the top context
from the stack, and uses it to continue execution.
(((stack overflow)))(((recursion)))Storing this stack requires space
in the computer's memory. When the stack grows too big, the computer
will fail with a message like “out of stack space” or “too much
recursion”. The following code illustrates this by asking the
computer a really hard question, which causes an infinite
back-and-forth between two functions. Rather, it _would_ be infinite,
if the computer had an infinite stack. As it is, we will run out of
space, or “blow the stack”.
// test: no
[source,javascript]
----
function chicken() {
return egg();
}
function egg() {
return chicken();
}
console.log(chicken() + " came first.");
// → ??
----
== Optional Arguments ==
(((argument)))(((function,application)))The following code is allowed
and executes without any problem:
[source,javascript]
----
alert("Hello", "Good Evening", "How do you do?");
----
(((alert function)))The function `alert` officially accepts only one
argument. Yet when you call it like this, it doesn't complain. It
simply ignores the other arguments and shows you “Hello”.
(((undefined value)))JavaScript is extremely broad-minded about the
number of arguments you pass to a function. If you pass too many, the
extra ones are ignored. If you pass too few, the missing parameters
simply get assigned the value `undefined`.
The downside of this is that it is possible—likely, even—that you'll
accidentally pass the wrong number of arguments to functions, and no
one will tell you about it.
indexsee:[optional argument,argument optional]
(((argument,optional)))The upside is that this behavior can be used
to have a function take “optional” arguments. For example, the
following version of `power` can be called either with two arguments
or with a single argument, in which case the exponent is assumed to
be two, and the function behaves like `square`.
// test: wrap
[source,javascript]
----
function power(base, exponent) {
if (exponent == undefined)
exponent = 2;
var result = 1;
for (var count = 0; count < exponent; count++)
result *= base;
return result;
}
console.log(power(4));
// → 16
console.log(power(4, 3));
// → 64
----
In the next chapter, we will see a way in which a function body can
get at the exact list of arguments that were passed. This is helpful
because it makes it possible for a function to accept any number of
arguments. `console.log` makes use of this—it outputs all of the
values it is given.
[source,javascript]
----
console.log("R", 2, "D", 2);
// → R 2 D 2
----
== Closure ==
(((closure)))(((variable,scope)))The ability to treat functions as
values, combined with the fact that local variables are “recreated”
every time a function is called, brings up an interesting question.
What happens to local variables when the function call that created
them is no longer active?
The following code shows an example of this. It defines a function,
`wrapValue`, that creates a local variable. It then returns a function
that accesses this local variable, returning its value when it is called.
[source,javascript]
----
function wrapValue(n) {
var localVariable = n;
return function(){ return localVariable; };
}
var wrap1 = wrapValue(1);
var wrap2 = wrapValue(2);
console.log(wrap1());
// → 1
console.log(wrap2());
// → 2
----
This is allowed, and works as you'd hope—the variable
can still be accessed. In fact, multiple instances of the variable can be alive at
the same time, which is another good illustration of the concept that
local variables really are recreated for every call—different calls
can't trample on one another's local variables.
This feature—being able to reference a specific instance of local
variables in an enclosing function—is called _closure_. A function
that “closes over” some local variables is called _a_ closure. This
behavior not only frees you from having to worry about lifetimes of
variables, but also allows for some creative use of function values.
With a slight change, we can turn the previous example into a way to
create functions that multiply by an arbitrary amount:
[source,javascript]
----
function multiplier(factor) {
return function(number) {
return number * factor;
};
}
var twice = multiplier(2);
console.log(twice(5));
// → 10
----
The explicit `localVariable` from the `wrapValue` example isn't
needed, since a parameter is itself a local variable.
Thinking about programs like this takes some exercise. A good mental
model is to think of the `function` keyword as “freezing” the code in
its body, and wrapping it into a package (the function value). So when you read
`return function(...) {...}`, think of it as returning a handle to a piece of
computation being frozen for later use.
In the example, `multiplier` returns a frozen computation that gets
stored in the `twice` variable. The last line then calls the value in
this variable, causing the frozen code (`return number * factor;`) to
be activated. It still has access to the `factor` variable from the
`multiplier` call that created it, and in addition it gets access to
the argument passed when unfreezing it, 5, through its `number` parameter.
== Recursion ==
(((recursion)))(((function,application)))It is perfectly okay for a
function to call itself. A function that calls itself is called
_recursive_. Recursion allows some functions to be written in a funny
way. Take, for example, this alternative implementation of `power`:
// test: wrap
[source,javascript]
----
function power(base, exponent) {
if (exponent == 0)
return 1;
else
return base * power(base, exponent - 1);
}
console.log(power(2, 3));
// → 8
----
This is rather close to the way mathematicians define exponentiation,
and arguably describes the concept in a more elegant way than the
looping variant does. The function calls itself multiple times with
different arguments to achieve the repeated multiplication.
(((stack overflow)))But this implementation has one important
problem: In typical JavaScript implementations, it's about ten times
slower than the looping version. Running through a simple loop is a
lot cheaper than calling a function multiple times. On top of that,
using a sufficiently large exponent to this function might cause the
stack to overflow.
(((efficiency)))The dilemma of speed versus ((elegance)) is
an interesting one. You can see it as a kind of continuum
between human-friendliness and machine-friendliness. Almost
any program can be made faster by making it
bigger and more convoluted. The programmer may decide on an appropriate balance.
In the case of the earlier `power` function, the inelegant (looping)
version is still fairly simple and easy to read. It doesn't make much
sense to replace it with the recursive version. Often, though, a
program deals with such complex concepts that giving up some
efficiency in order to make the program more straightforward becomes
an attractive choice.
The basic rule, which has been repeated by many programmers and with
which I wholeheartedly agree, is to not worry about efficiency until
you know for sure that the program is too slow. If it is, find out
which parts are taking up the most time, and start exchanging
elegance for efficiency in those parts.
Of course, this rule doesn't mean one should start ignoring
performance altogether. In many cases, like the `power` function, not
much simplicity is gained from the “elegant” approach. And sometimes
an experienced programmer can see right away that a simple approach
is never going to be fast enough.
(((premature optimization)))The reason I'm stressing this is that
surprisingly many beginning programmers focus fanatically on
efficiency, even in the smallest details. The result is bigger, more
complicated, and often less correct programs, which take longer to
write than their more straightforward equivalents, and which run only
marginally faster.
But recursion is not always just a less-efficient alternative to
looping. Some problems are much easier to solve with recursion than
with loops. Most often these are problems that require exploring or
processing several “branches”, each of which might branch out again
into more branches.
Consider this puzzle: By starting from the number 1 and repeatedly
either adding 5 or multiplying by 3, an infinite amount of new
numbers can be produced. How would you write a function that, given a
number, tries to find a sequence of such additions and
multiplications that produce that number? For example, the number 13
could be reached by first multiplying by 3 and then adding 5
twice, whereas the number 15 cannot be reached at all.
Here is a recursive solution:
[source,javascript]
----
function findSolution(target) {
function find(start, history) {
if (start == target)
return history;
else if (start > target)
return null;
else
return find(start + 5, "(" + history + " + 5)") ||
find(start * 3, "(" + history + " * 3)");
}
return find(1, "1");
}
console.log(findSolution(24));
// → (((1 * 3) + 5) * 3)
----
Note that this program doesn't necessarily find the _shortest_
sequence of operations. It is satisfied when it finds any sequence at
all.
I don't necessarily expect you to see how it works right away. But
let's work through it, since it makes for a great exercise in
recursive thinking.
The inner function `find` does the actual recursing. It takes two
arguments—the current number and a string that records how we reached
this number—and returns either a string that shows how to get to the
target, or `null`.
(((|| operator)))(((shortcut evaluation)))In order to do this, the
function performs one of three actions. If the current number is the
target number, the current history is a way to reach that target, so
it is simply returned. If the current number is greater than the
target, there's no sense in further exploring this history, since
both adding and multiplying will only make the number bigger. And
finally, if we're still below the target, the function tries both
possible paths that start from the current number, by calling itself
twice, once for each of the allowed next steps. If the first call
returns something that is not `null`, it is returned. Otherwise, the
second call is returned—regardless of whether it produces a string
or `null`.
To better understand how this function produces the effect we're
looking for, let's look at all the calls to `find` that are made when
searching for a solution for the number 13:
....
find(1, "1")
find(6, "(1 + 5)")
find(11, "((1 + 5) + 5)")
find(16, "(((1 + 5) + 5) + 5)")
too big
find(33, "(((1 + 5) + 5) * 3)")
too big
find(18, "((1 + 5) * 3)")
too big
find(3, "(1 * 3)")
find(8, "((1 * 3) + 5)")
find(13, "(((1 * 3) + 5) + 5)")
found!
....
The indentation suggests the depth of the call stack. The first call
to `find` calls itself twice, to explore the solutions that start
with `(1 + 5)` and `(1 * 3)`. The first call tries to find a solution
that starts with `(1 + 5)`, and, using recursion, explores _every_
solution that yields a number smaller or equal to than the target
number. Since it doesn't find a solution that hits the target, it
returns `null` back to the first call. There the “||” operator causes
the call that explores `(1 * 3)` to happen. This search has more
luck, as its first recursive call, through yet _another_ recursive
call, hits upon the target number, 13. This innermost recursive call
returns a string, and each of the “||” operators in the intermediate
calls pass that string along, ultimately returning our solution.
== Growing functions ==
There are two more or less natural ways for functions to be introduced
into programs.
The first is that you find yourself writing very similar code multiple
times. We want to avoid doing that, since having more code means more space
for mistakes to hide, and more material to read for people trying to
understand the program. So we take the repeated functionality, find a
good name for it, and put it into a function.
The second way is that you find you need some functionality that you
haven't written yet, and which sounds like it deserves its own
function. You'll start by naming the function, and then write its
body. You might even start writing code that uses the function before
you actually define the function itself.
How difficult it is to find a good name for a function is a good
indication of how clear a concept it is that you're trying to wrap.
Let us go through an example.
We want to write a program that prints two numbers, the amounts of
cows and chickens on a farm, with the words `Cows` and `Chickens`
after them, and zeroes padded before both numbers so that they are
always three digits long.
....
007 Cows
011 Chickens
....
That clearly asks for a function of two arguments. Let's get coding.
[source,javascript]
----
function printFarmInventory(cows, chickens) {
var cowString = String(cows);
while (cowString.length < 3)
cowString = "0" + cowString;
console.log(cowString + " Cows");
var chickenString = String(chickens);
while (chickenString.length < 3)
chickenString = "0" + chickenString;
console.log(chickenString + " Chickens");
}
printFarmInventory(7, 11);
----
Adding `.length` after a string value will give us the length of that
string. Thus, the `while` loops keep adding zeroes in front of the
number strings until they are at least three characters long.
Mission accomplished! But just as we are about to send the farmer the code (along
with a hefty invoice, of course), he calls and tells us he's
also started keeping pigs, and couldn't we please extend the software
to also print pigs?
We sure can. But just as we're in the process of copy-pasting those
four lines one more time, we stop and reconsider. There has to be a
better way. Here's a first attempt:
[source,javascript]
----
function printZeroPaddedWithLabel(number, label) {
var numberString = String(number);
while (numberString.length < 3)
numberString = "0" + numberString;
console.log(numberString + " " + label);
}
function printFarmInventory(cows, chickens, pigs) {
printZeroPaddedWithLabel(cows, "Cows");
printZeroPaddedWithLabel(chickens, "Chickens");
printZeroPaddedWithLabel(pigs, "Pigs");
}
printFarmInventory(7, 11, 3);
----
It works! But that name, `printZeroPaddedWithLabel`, is a little
awkward. It conflates three things—printing, zero-padding, and adding
a label—into a single function.
Instead of lifting out the repeated part of our program wholesale,
let us try to pick out a single _concept_:
[source,javascript]
----
function zeroPad(number, width) {
var string = String(number);
while (string.length < width)
string = "0" + string;
return string;
}
function printFarmInventory(cows, chickens, pigs) {
console.log(zeroPad(cows, 3) + " Cows");
console.log(zeroPad(chickens, 3) + " Chickens");
console.log(zeroPad(pigs, 3) + " Pigs");
}
printFarmInventory(7, 16, 3);
----
`zeroPad` has a nice, obvious name, which makes it easier for someone
who reads the code to figure out what it does. And it is useful in
more situations than just this specific program. For example, you
could use it to help print nicely aligned tables of numbers.
How smart and versatile should our function be? We could write
anything from a terribly simple function that simply pads a number so
that it's three characters wide, to a complicated generalized
number-formatting system that handles fractional numbers, negative
numbers, alignment of dots, padding with different characters, and so
on.
A useful principle is not to add cleverness unless you are absolutely
sure you're going to need it. It can be tempting to write general
“frameworks” for every little bit of functionality you come across.
Resist that urge. You won't get any real work done, and you'll end up
writing a lot of code that no one will ever use.
== Functions and side effects ==
(((side effect)))(((pure function)))(((function,purity)))Functions
can be roughly divided into those that are called for their side
effects, and those that are called for their return value. (Though
it's definitely also possible to have both side effects and return a
value.)
The first helper function in the farm example,
`printZeroPaddedWithLabel`, is called for its side effect: It prints
a line. The second version, `zeroPad`, is called for its return
value. It is no coincidence that the second is useful in more
situations than the first. Functions that create values are easier to
combine in new ways than functions that directly perform side effects.
A _pure_ function is a specific kind of value-producing function that
not only has no side effects, but also doesn't rely on side effects
from other code—for example, it doesn't read global variables that are
occasionally changed by other code. A pure function has the pleasant
property that, when called with the same arguments, it always produces
the same value (and doesn't do anything else). This makes it easy to
reason about. A call to such a function can be mentally substituted by its result,
without changing the meaning of the code. When you are not sure that a
pure function is working correctly, you can test it by simply
calling it and know that if it works in that context, it will work in
any context. Non-pure functions might return different values based on
all kinds of factors and have side effects that might be hard to test
and think about.
Still, there's no need to feel bad when writing functions that are
not pure, or to wage a holy war to purge them from your code. Side
effects are often useful. There'd be no way to write a pure version
of `console.log`, for example, and `console.log` is certainly useful.
Some operations are also easer to express in an efficient way when
we use side effects, so computing speed can be a reason to avoid purity.
== Summary ==
This chapter taught you how to write your own functions. The `function`
keyword, when used as an expression, can create a function value. When
used as a statement, it can be used to declare a variable and give it
a function as its value.
[source,javascript]
----
// Create a function value f
var f = function(a) {
console.log(a + 2);
};
// Declare g to be a function
function g(a, b) {
return a * b * 3.5;
}
----
A key aspect in understanding functions is understanding local scopes.
Parameters and variables declared inside a function are local to the
function, recreated every time the function is called, and not visible
from the outside. Functions declared inside another function have
access to the outer function's local scope.
Separating the different tasks your program performs into different
functions is helpful. You won't have to repeat yourself so much, and
they can help someone trying to read your program by grouping the
code into conceptual chunks, in the same way that chapters and
sections help organize regular text.
== Exercises ==
=== Minimum ===
The previous chapter introduced the standard function `Math.min` that
returns its smallest argument. We can do that ourselves now. Write a
function `min` that takes two arguments and returns their minimum.
ifdef::html_target[]
// test: no
[source,javascript]
----
// Your code here.
console.log(min(0, 10));
// → 0
console.log(min(0, -10));
// → -10
----
endif::html_target[]
!!solution!!
If you have trouble putting braces and parentheses in the right place
to get a valid function definition, start by copying one of the
examples in this chapter and modifying it.
A function may contain multiple `return` statements.
!!solution!!
=== Recursion ===
We've seen that `%` (the remainder operator) can be used to test
whether a number is even or odd by using `% 2` to check if it's
divisible by two. Here's another way to define whether a (positive,
whole) number is even or odd:
- Zero is even.
- One is odd.
- For any other number N, its evenness is the same as N - 2.
Define a recursive function `isEven` corresponding to this
description. The function should accept a `number` parameter and
return a boolean.
Test it out on 50 and 75. See how it behaves on -1. Why? Can you think
of a way to fix this?
ifdef::html_target[]
// test: no
[source,javascript]
----
// Your code here.
console.log(isEven(50));
// → true
console.log(isEven(75));
// → false
console.log(isEven(-1));
// → ??
----
endif::html_target[]
!!solution!!
Your function will likely look somewhat similar to the inner `find` function in
the recursive `findSolution` example in this chapter, with an
`if`/`else if`/`else` chain that tests which of the three cases
applies. The final `else`, corresponding to the third case, makes the
recursive call. Each of the
branches should contain a `return` statement, or in some other way
arrange for a specific value to be returned.
When given a negative number, the function will recurse again and
again, passing itself an ever more negative number, thus getting
further and further away from returning a result. It will eventually
run out of stack space and abort.
!!solution!!
=== Bean counting ===
You can get the Nth character, or letter, from a string by writing
`"string".charAt(N)`, similar to how you get its length with
`"s".length`. The returned value will be a string containing only
one character (for example, `"b"`). The first character has position zero,
which causes the last one to be found at position `string.length - 1`. I.e.
a two-character string has length 2, and its characters have positions 0 and 1.
Write a function `countBs` that takes a string as its only argument
and returns a number that indicates how many uppercase “B” characters
are in the string.
Next, write a function called `countChar` that behaves like
`countBs`, except it takes a second argument that indicates the
character that is to be counted (rather than only counting uppercase
"B" characters). Rewrite `countBs` to make use of this new function.
ifdef::html_target[]
// test: no
[source,javascript]
----
// Your code here.
console.log(countBs("BBC"));
// → 2
console.log(countChar("kakkerlak", "k"));
// → 4
----
endif::html_target[]
!!solution!!
A loop in your function will have to look at every characters in
the string by running an index from zero to one below its length (`<
string.length`). If the character at the current position is the
same as the one the function is looking for, it adds 1 to a counter variable.
Once the loop has finished, the counter can be returned.
Take care to make all the variables used in the function _local_ to
the function by using the `var` keyword.
!!solution!!