forked from CodeAcademyTeam/Eloquent-JavaScript
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy path03_functions.txt
1025 lines (840 loc) · 36.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
989
990
991
992
993
994
995
996
997
998
999
1000
:chap_num: 3
:prev_link: 02_program_structure
:next_link: 04_data
= Functions =
[chapterquote="true"]
[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.
____
(((Knuth+++,+++ Donald)))(((function)))(((code structure)))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 subprograms, and to
isolate these subprograms from each other.
(((human language)))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.
(((abstraction)))Typical adult English speakers have some 20,000 words
in their vocabulary. Few programming languages come with 20,000
commands built in. And the vocabulary that _is_ available tends to be
more precisely defined, and thus less flexible, than in human
language. Therefore, we usually _have_ to add some of our own
vocabulary to avoid repeating ourselves too much.
== Defining a function ==
(((square)))(((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
----
indexsee:[braces,curly braces]
(((curly braces)))(((block)))(((syntax)))(((function
keyword)))(((function,body)))(((function,as value)))A function is
created by an expression that starts with the keyword `function`.
Functions have a set of _((parameter))s_ (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 previous example).
(((power example)))A function can have multiple parameters or no
parameters at all. In the following example, `makeNoise` 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 value)))(((return keyword)))(((undefined)))Some functions
produce a value, such as `power` and `square`, and some don't, such as
`makeNoise`, which produces only 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 ==
(((function,application)))(((variable,from parameter)))The
((parameter))s 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.
(((function,scope)))(((scope)))(((local variable)))An
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 scope,global scope]
(((var keyword)))(((global scope)))(((variable,global)))This
“localness” of variables applies only to the parameters and to variables
declared with the `var` keyword inside the function body. Variables
declared outside of any function are called _global_, because they are
visible throughout the program. It is possible to access such
variables from inside a function, as long as you haven't declared a
local variable with the same name.
(((variable,assignment)))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
----
(((variable,naming)))(((scope)))(((global scope)))(((code,structure
of)))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.
[[scoping]]
== Nested scope ==
(((nesting,of functions)))(((nesting,of
scope)))(((scope)))(((inner function)))(((lexical
scoping)))JavaScript distinguishes not just between _global_ and
_local_ variables. Functions can be created inside other functions,
producing several degrees of locality.
(((landscape example)))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());
// → ___/''''\______/'\_
----
(((function,scope)))(((scope)))The `flat` and `mountain` functions
can “see” the variable called `result`, since they are inside the
function that defines it. But they cannot 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 `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 useful only to group the body of an `if` statement
or a loop.
(((let keyword)))(((ECMAScript 6)))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_.
== Funksiyalar, dəyər kimi ==
(((function,as value)))Funksiya ((dəyişən))ləri əsasən müəyyən proqram
hissəsi üçün ad kimi davranır. Belə dəyişən bir dəfə elan olunur ve heç vaxt
dəyişilmir. Buna görə funksiyanı və onun adını qarışdırmaq çox asandır.
(((variable,assignment)))Lakin bu iki anlayış fərqlidir. Funksiya
dəyəri digər bütün dəyərlərin bacardığını edə bilir. Onu çağırmaqdan
başqa, həm də ((ixtiyari)) ifadədə istifadə etmək olar. Funksiyanın
dəyərini yeni yerdə saxlamaq, başqa funskiyaya arqument kimi ötürmək
və s. olar. Funksiyanı saxlayan dəyişən eyni zamanda həm də adi bir
dəyişən olduğu üçün ona aşağıdakı kimi yeni dəyər mənimsətmək olar:
// test: no
[source,javascript]
----
var raketleriIsheSal = function(value) {
raketSistemi.isheSal("indi");
};
if (tehlukesiz rejim)
raketleriIsheSal = function(value) {/* heç nə etmə */};
----
(((function,higher-order)))
link:05_higher_order.html#higher_order[Fəsil 5] -də funksiya dəyərini
başqa funksiyalara ötürməklə necə möhtəşəm nəticələr ala biləyəcimizi
müzakirə edəcəyik.
== Elan qaydası ==
(((syntax)))(((square example)))(((function
keyword)))(((function,definition)))(((function,declaration)))
“++var kvadrat = function…++” kimi yazmaqdan daha qısa üsul var.
`function` açar sözü aşağıdakı nümunədəki kimi bəyanatın (statement)
əvvəlində də gələ bilər.
[source,javascript]
----
function kvadrat(x) {
return x * x;
}
----
(((future)))(((execution order)))Bu funksiya _bəyanıdır_(declaration).
Bu bəyanat `kvadrat` dəyişənini elan edir və verilmiş funksiyaya işarə
edir. Bəyanatın bu cür formasında bir incə(sublety) məqam var.
[source,javascript]
----
console.log("Gələcək deyir:", gelecek());
function gelecek() {
return "Hələ də uçan avtomobillərimiz yoxdur.";
}
----
Baxmayaraq ki, funksiyanın elanı istifadəsindən _aşağıda_ yer alıb,
bu kod işləyəcək. Bu ona görədir ki, funksiya bəyanları adi yuxarıdan-
aşağıya nəzarət axınının (flow of control) bir hissəsi deyil. Onlar
konsept olaraq öz təsir çərçivəsininin(scope) yuxarısına köçürülür və həmin
çərçivə daxilində bütün kodda istifadə oluna bilər. Bu bəzən yararlıdır,
çünki bu bizə kodu, funksiya bəyanlarını kodun yuxarısında və ya aşağısında
yerləşdirməyimizdən asılı olmayaraq, bizə məntiqli görünən - istədiyimiz
strukturla yazmaq imkanı verir.
(((function,declaration)))Bəs funksiya elanlarını şərt (`if`) və ya dövr
blokuna yerləşdirdikdə nə baş verir? Bunu etməyin. Belə vəziyyətdə fərqli
JavaScript platformaları fərqli brauzerlərdə müxtəlif nəticələr vermişlər
və ən son ((standart)) bunu ümumiyyətlə qadağan edir. Əgər proqramınızın
davamlı olmağını istəyirsinizsə, bu cür funksiya elanlarını ancaq
funksiyanın və ya proqramın səvviyəcə ən üst blokunda edin.
[source,javascript]
----
function numune() {
function a() {} // OK
if (nə isə) {
function b() {} // Təhlükə!
}
}
----
[[stack]]
== The call stack ==
indexsee:[stack,call stack]
(((call stack)))(((function,application)))Kodda ardıcıllığın(axın) necə təmin
olunduğuna yaxından baxaq. Aşağıda bir neçə funksiya çağırışı edən kiçik
proqrama baxaq:
[source,javascript]
----
function salamla(kim) {
console.log("Salam " + kim);
}
salamla("Aysel");
console.log("Hələlik");
----
(((control flow)))(((execution order)))(((console.log)))Burada nəzarət axını
kobud olaraq belə baş verir: `salamla` funksiyasının çağırışı nəzarət axının
funksiyanın başlanğıcına getməsinə səbəb olur (sətir 2). O da öz növbəsində
nəzarət axınını ələ keçirən `console.log` -u (brauzer funksiyası) çağırır,
öz işini görür və yenidən sətir 2-ə qayıdır. Bundan sonra `salamla` funksiyasının
sonuna çatır, onun çağırıldığı sətirə, yəni 4-cü sətirə geri qayıdır. Bundan
sonrakı sətir `console.log`-u yenidən çağırır.
Sxematik olaraq bunu aşağıdakı kimi göstərmək olar:
----
üst
salamla
console.log
salamla
üst
console.log
üst
----
(((return keyword)))(((memory)))Funksiya çağırılıb yekunlaşdıqdan sonra
nəzarət axını çağırıldığı sətirə geri qayıtdığına görə kompüter həmin
konteksti yadda saxlamalıdır. Bir halda `console.log` `salamla` funksiyasına
tullanır, digər halda isə proqramın sonuna aparır.
Kompüterin bu konteksti saxladığı yer isə _((sorğu steki))_ (call stack) adlanır.
Hər dəfə funksiya çağırılanda, cari konktest bu “stekin” yuxarısına yerləşdirilir.
Funksiyanın icrası başa çatdıqda ən üstdəki konktest stekdən kənarlaşdırılır
və proqramın sonrakı icrası üçün istifadə olunur.
(((infinite loop)))(((stack overflow)))(((recursion)))Bu steki saxlamaq
kompüterin yaddaşında yer tələb edir. Stek çox böyüdükcə kompüter “stek
yaddaşı dolub” və ya “həddindən çox rekursiya” ismarıcı ilə səhv verə
bilər (fail). Aşağıdakı kod bunu kompüterə çox çətin sual verməklə,
iki funksiya arasında sonsuz önə-arxaya getməklə nümayiş etdirir.
Kompüterin steki sonsuz olsaydı, sonsuz olaraq bu proses davam edəcəkdi.
Olmadığına görə yaddaş “dolub daşacaq”.
// test: no
[source,javascript]
----
function toyuq() {
return yumurta();
}
function yumurta() {
return toyuq();
}
console.log(toyuq() + " birinci yaranıb.");
// → ??
----
== Könüllü(optional) Arqumentlər ==
(((argument)))(((function,application)))Aşağıdakı kod səhvsiz icra olunur:
[source,javascript]
----
alert("Salam", "Axşamın xeyir", "Necəsən?");
----
(((alert function)))`alert` funksiyası rəsmi olaraq yalnız bir arqument
qəbul edir. Buna baxmayaraq belə çağırdıqda heç bir səhv baş vermir.
Sadəcə digər arqumentlərə məhəl qoyulmur (ignores) və “Salam” çıxarır.
(((undefined)))(((parameter)))JavaScript funksiyaya ötürülən arqumentlərin
sayına görə çox tolerantdır (broad-minded). Əgər lazımdan çox ötürmüsünüzsə,
əlavələ sadəcə iqnor olunacaq, əksinə, lazımından az ötürülürsə, buraxılmış
arqumentlərə sadəcə olaraq `undefined` mənimsədilir.
Bunun mənfi tərəfi odur ki, funksiyaya təsadüfən arqumentlərin sayını səhv
ötürsəniz, heç kim sizə bu haqda deməyəcək.
[[power]]
(((power example)))(((optional argument)))Üstün tərəfi isə odur ki,
“könüllü” (“optional”) arqumentlər yarada bilərik. Məsələn, `quvvet` -in
aşağıdakı versiyası iki arqumentlə də, bir arqumentlə də icra oluna bilər.
Bir arqumentli halda eksponent iki götürülür və `kvadrat` funksiyası kimi
davranır.
// test: wrap
[source,javascript]
----
function quvvet(esas, eksponent) {
if (eksponent == undefined)
eksponent = 2;
var netice = 1;
for (var say = 0; say < eksponent; say++)
netice *= esas;
return netice;
}
console.log(quvvet(4));
// → 16
console.log(quvvet(4, 3));
// → 64
----
(((console.log)))In the link:04_data.html#arguments_object[Növbəti
fəsildə] funksiyanın gövdəsinin ötürülən arqumentlərin sayını necə
dəqiq müəyyənləşdirdiyini görəcəyik. İstədiyimiz sayda arqument
ötürə bilmək cəhətdən bu çox yararlıdır. `console.log` bundan
istifadə edərək ötürdüyümüz bütün arqumentləri çap edir.
[source,javascript]
----
console.log("R", 2, "D", 2);
// → R 2 D 2
----
== Closure ==
(((call stack)))(((local variable)))(((function,as
value)))(((closure)))(((scope)))The ability to treat functions as
values, combined with the fact that local variables are “re-created”
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`, which creates a local variable. It then returns a function
that accesses and returns this local variable.
[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 re-created 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.
(((multiplier function)))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
----
(((variable,from parameter)))The explicit `localVariable` from the
`wrapValue` example isn't needed since a parameter is itself a local
variable.
(((function,model of)))Thinking about programs like this takes some
practice. 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, frozen for
later use.
In the example, `multiplier` returns a frozen chunk of code 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 ==
(((power example)))(((stack
overflow)))(((recursion)))(((function,application)))It is perfectly
okay for a function to call itself, as long as it takes care not to
overflow the stack. A function that calls itself is called
_recursive_. Recursion allows some functions to be written in a
different style. 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
----
(((loop)))(((readability)))(((mathematics)))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.
(((function,application)))(((efficiency)))But this implementation has
one important problem: in typical JavaScript implementations, it's
about 10 times slower than the looping version. Running through a
simple loop is a lot cheaper than calling a function multiple times.
(((optimization)))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
must decide on an appropriate balance.
In the case of the link:03_functions.html#power[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.
(((profiling)))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, that take longer to
write than their more straightforward equivalents and that usually run
only marginally faster.
(((branching recursion)))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.
[[recursive_puzzle]]
(((recursion)))(((number puzzle example)))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
((argument))s—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`.
(((null)))(((|| operator)))(((short-circuit evaluation)))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`.
(((call stack)))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 time
`find` is called it 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 less than or equal to 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 because 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 ==
(((function,definition)))There are two more or less natural ways for
functions to be introduced into programs.
(((repetition)))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 that sounds like it deserves its own function.
You'll start by naming the function, and you'll then write its body.
You might even start writing code that uses the function before you
actually define the function itself.
(((function,naming)))(((variable,naming)))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's go through an example.
(((farm example)))We want to write a program that prints two numbers,
the numbers of cows and chickens on a farm, with the words `Cows` and
`Chickens` after them, and zeros 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);
----
(((length property,for string)))(((while loop)))Adding `.length`
after a string value will give us the length of that string. Thus, the
`while` loops keep adding zeros 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?
(((copy-paste programming)))We sure can. But just as we're in the
process of copying and 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);
----
(((function,naming)))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.
(((zeroPad function)))Instead of lifting out the repeated part of our
program wholesale, let's 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);
----
(((readability)))(((pure function)))A function with a nice, obvious
name like `zeroPad` 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.
(((interface,design)))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
“((framework))s” 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.
[[pure]]
== 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 is
definitely also possible to have both side effects and return a
value.)
(((reuse)))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.
(((substitution)))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. Nonpure
functions might return different values based on all kinds of factors
and have side effects that might be hard to test and think about.
(((optimization)))(((console.log)))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 easier 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, re-created 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 tasks your program performs into different
functions is helpful. You won't have to repeat yourself as much, and
functions can make a program more readable by grouping code into
conceptual chunks, in the same way that chapters and sections help
organize regular text.
== Exercises ==
=== Minimum ===
(((Math object)))(((minimum (exercise))))(((Math.min
function)))(((minimum)))The
link:02_program_structure.html#return_values[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::interactive_target[]
// test: no
[source,javascript]
----
// Your code here.
console.log(min(0, 10));
// → 0
console.log(min(0, -10));
// → -10
----
endif::interactive_target[]
!!hint!!
(((minimum (exercise))))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.
(((return keyword)))A function may contain multiple `return`
statements.
!!hint!!
=== Recursion ===
(((recursion)))(((isEven (exercise))))(((even number)))We've seen
that `%` (the remainder operator) can be used to test whether a number
is even or odd by using `% 2` to check whether 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.
(((stack overflow)))Test it on 50 and 75. See how it behaves on -1.
Why? Can you think of a way to fix this?
ifdef::interactive_target[]
// test: no
[source,javascript]
----
// Your code here.
console.log(isEven(50));
// → true
console.log(isEven(75));
// → false
console.log(isEven(-1));
// → ??
----
endif::interactive_target[]
!!hint!!
(((isEven (exercise))))(((if keyword,chaining)))(((recursion)))Your
function will likely look somewhat similar to the inner `find`
function in the recursive `findSolution`
link:03_functions.html#recursive_puzzle[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.
(((stack overflow)))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.
!!hint!!
=== Bean counting ===
(((bean counting (exercise))))(((charAt
method)))(((string,indexing)))(((zero-based 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`.
In other words, 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 counting only uppercase “B” characters).
Rewrite `countBs` to make use of this new function.
ifdef::interactive_target[]
// test: no