forked from marijnh/Eloquent-JavaScript
-
Notifications
You must be signed in to change notification settings - Fork 41
Expand file tree
/
Copy path03_functions.txt
More file actions
1024 lines (841 loc) · 39 KB
/
03_functions.txt
File metadata and controls
1024 lines (841 loc) · 39 KB
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
= Funciones =
[chapterquote="true"]
[quote, Donald Knuth]
____
La gente piensa que las ciencias de la computación son las
artes de los genios pero la realidad es lo contrario, es sólo un
montón de gente haciendo cosas que se construyen sobre otras,
como una pared de piedras muy pequeñas.
____
(((Knuth+++,+++ Donald)))(((función)))(((estructura del código)))Has visto
valores función, como `alert`, y cómo llamarlos. Las funciones son el pan de
cada día en la programación en JavaScript. El concepto de de envolver
una porción del programa en un valor(variable) tiene muchos usos.
Es una herramienta para estructurar programas más grandes, para reducir
la repetición, para asociar nombres con subprogramas, y para separar
estos programas de los demás.
(((lenguaje humano)))La aplicación más obvia de las funciones es la de
definir un nuevo ((vocabulario)). Crear nuevas palabras en la prosa regular en
lenguaje humano es usualmente un mal estilo. Pero en programación, es
indispensable.
(((abstracción)))Un adulto promedio tiene unas 20,000 palabras en su
vocabulario. Pocos lenguajes de programación tienen 20,000 comandos
incorporados. Y el vocabulario que _está_ disponible tiende a ser
definido de forma más precisa, así que es menos flexible
que en un lenguaje humano. En consecuencia, usualmente _tenemos_
que añadir algo de nuestro propio vocabulario para evitar repetirnos
demasiado.
== Definiendo una función ==
(((cuadrado)))(((función,definición)))Una definición de una función
es sólo una definición regular de una ((variable)) cuando ocurre que el
valor dado a la variable es una función. Por ejemplo, el siguiente código
define la variable `cuadrado` para referirse a la función que produce
el cuadrado de un número dado:
[source,javascript]
----
var cuadrado = function(x) {
return x * x;
};
console.log(cuadrado(12));
// → 144
----
indexsee:[llaves]
(((llaves)))(((bloque)))(((sintaxis)))(((palabra reservada
function)))(((función, cuerpo)))(((función,como valor)))Una función es
creada por una expresión que empieza con la palbra reservada `function`.
Las funciones tienen un conjunto de _((parametros))_ (en este caso
sólo `x`) y un _cuerpo_, que contiene las sentencias que serán ejecutadas
cuando la función sea llamada. El cuerpo de la función tiene que estar
siempre encerrado en llaves, incluso cuando consista de una sola
((instrucción)) (como en el ejemplo previo).
(((ejemplo de la potencia)))Una función puede tener varios parámetros
o puede no tener ninguno. En el siguiente ejemplo `hazRuido` no tiene
parámetros, mientras que `potencia` tiene dos:
[source,javascript]
----
var hazRuido = function() {
console.log("Pling!");
};
hazRuido();
// → Pling!
var potencia = function(base, exponente) {
var resultado = 1;
for (var cuenta = 0; cuenta < exponente; cuenta++)
resultado *= base;
return resultado;
};
console.log(potencia(2, 10));
// → 1024
----
(((valor de retorno)))(((palabra reservada return)))(((undefined)))Algunas
funciones producen un valor, como `potencia` y `cuadrado`, y algunas no,
como `hazRuido`, la cuál produce sólo un ((efecto secundario)). Una sentencia
`return` determina el valor que una función regresa. Cuando el control
pasa a esta sentencia, inmediatamente salta fuera de la función actual
y pasa el valor retornado a la código que llamó la función. La palabra
reservada `return` sin una expresión después de ella hará que la función
devuelva `undefined`.
== Parámetros y ámbitos ==
(((función, aplicación)))(((variable,de parametro)))Los
((parametro))s para una función se comportan como variables normales,
pero su valor inicial esta dado por _quien llama_ a la función, no por
el código mismo de la función.
(((función,ámbito)))(((ámbito)))(((variable local)))Un propiedad
importante de las funciones es que las varibales creades dentro de ellas,
incluyendo sus parámetros, son _locales_ para la función. Este significa,
por ejemplo, que la varibale `resultado` en el ejemplo `potencia` será
creada de nuvo cada vez que la función es llamada, y estas instancias
separadas no interfieren entre ellas.
indexsee:[ámbito superior,ámbito global]
(((palabra reservada var)))(((ámbito global)))(((variable,global)))Esta
"localidad" de las variables aplica sólo a los parámetros y variables
declaradas con la palabra reservada `var` dentro del cuerpo de la función.
Las variables declaradas duera de cualquier función son llamadas _globales_,
porque son visibles a través de todo el programa. Es posible acceder a
estas variables desde dentro de una función, mientras no hayas declarado una
variable local con el mismo nombre.
(((variable,asignación)))El siguiente código demuestra eso. Define y llama
dos funciones que asignan un valor a la variable `x`. La primera declara
la variable como local y de esta manera cambia únicamente la variable que
creó. La segunda no declara `x` localmente, así que la `x` dentro de ella
hace referencia a la `x` definida al principio del ejemplo.
[source,javascript]
----
var x = "fuera";
var f1 = function() {
var x = "dentro de f1";
};
f1();
console.log(x);
// → fuera
var f2 = function() {
x = "dentro de f2";
};
f2();
console.log(x);
// → dentro de f2
----
(((variable,nombrado)))(((ámbito)))(((ámbito global)))(((code,estructura
de)))Este comportamiento ayuda a prevenir interferencia accidental entre
las funciones. Si todas las variables fueran compartidas por el programa
entero, sería muy difícil asegurarse de que algún nombre no fue usado para
dos propósitos diferentes. Y si _reusaste_ un nombre de variable, podrías
ver efectos extraños de código no relacionado causando problemas en el valor
de tu variable. Al tratar tus variables locales a la función, el lenguaje
hace posible leer y entender las funciones como un pequeños universos,
sin tener que preocuparse de todo el código a la vez.
[[scoping]]
== Ámbitos Anidados ==
(((anidado,de funciones)))(((anidado,de
ámbitos)))(((ámbito)))(((función interna)))(((definición léxica
de ámbito)))JavaScript distingue nos solo entre variables _globales_ y _locales_
Las funciones pueden ser creadas dentro de otras funciones,
produciendo distindos grados de localidad.
(((ejemplo paisaje)))Por ejemplo, esta función sin sentido, tiene dos
funciones dentro de ella:
[source,javascript]
----
var landscape = function() {
var resultado = "";
var meseta = function(tamano) {
for (var cuenta = 0; cuenta < size; cuenta++)
resultado += "_";
};
var montana = function(tamano) {
result += "/";
for (var cuenta = 0; cuenta < size; cuenta++)
resultado += "'";
resultado += "\\";
};
meseta(3);
montana(4);
meseta(6);
montana(1);
meseta(1);
return resultado;
};
console.log(landscape());
// → ___/''''\______/'\_
----
(((función,ámbito)))(((ámbito)))Las funciones `meseta` y `montana`
pueden "ver" la variable llamada `resultado`, debido a que están
dentro de la función que la define. Pero no pueden ver la variable
`cuenta` entre ellas, porque están definidas fuera del ámbito de la otra.
El entorno fuera de la función `paisaje` no puede acceder a ninguna de
las variables definidas dentro de `paisaje`.
En pocas palabras, cada ámbito local también puede ver los ámbitos
locales que lo contienen. El conjunto de variables visible dentro de
la función es determinado por el lugar de la función en el texto del
programa. Todas las variables de los bloques que _envuelven_ una
la definición de una función son visibles para ella, esto es, en todos
los cuerpos de las funciones que la envuelven y las correspondientes
al nivel superior(ámbito global). Este manera de resolver la visibilidad
de las variables es llamada _((definición léxica de ámbito))_(lexical scoping).
((({} (bloque))))Las personas que tienen experiencia con otros lenguajes
de programción podrían esperar que cualquier bloque entre llaves produzca
un nuevo entorno local. Pero en JavaScript, las funciones son lo único
que crea un nuevo ámbito. Pero puedes usar bloques independientes.
[source,javascript]
----
var algo = 1;
{
var algo = 2;
// Haz algo con la variable...
}
// Fuera del bloque...
----
Pero la variable `algo` dentro del bloque se refiere a la misma variable
que la que está fuera del bloque. De hecho, aunque bloques como este
son permitidos, sólo son útiles para agrupar el cuerpo de las sentencias
`if` o de los bucles.
(((palabra clave let)))(((ECMAScript 6)))Si esto te parece raro, no eres
el único. La próxima versión de JavaScript introducirá una palabra
reservada `let`, que trabaja como `var` pero crea una variable que es
local para el _bloque_ en el que está, no para la función.
== Funciones como valores ==
(((funciones,como valor)))Las ((variables)) de función normalmente actúan
como nombres para una parte específica del programa. Esa variables es
definida una vez y nunca cambiada. Esto hace fácil empezar a confundir
la función con su nombre.
(((vriable,asignación)))Pero son diferentes entre sí. Un valor función
puede hacer todo los que los otros valores pueden hacer; lo puedes usar
en ((expresiones)) arbitrarias, no sólo llamarlos. Es posible guardar la
función en un nuevo lugar, pasar como argumento a otra función, etc. De
manera parecida, la variable que contiene una función sigue siendo una variable
normal a la que se le puede asignar otro valor, como sigue:
// test: no
[source,javascript]
----
var lanzarMisiles = function(valor) {
sistemaDeMisiles.lanzar("ahora");
};
if (modoSeguro)
lanzarMisiles = function(valor) {/* No hacer nada. */};
----
(((función,orden superior)))En
link:05_higher_order.html#higher_order[Capítulo 5], hablaremos de las
maravillosas cosas que puedes hacer al pasar valores función a otras funciones.
== Notación de Declaración ==
(((sintaxis)))(((ejemplo del cuadrado)))(((palabra clave
function)))(((función,definición)))(((función,declaración)))Existe una
forma más corta de decir “++var square = function…++”. La palabra reservada
`function` puede ser usada al principio de una sentencia, como sigue:
[source,javascript]
----
function cuadrado(x) {
return x * x;
}
----
(((futuro)))(((order de ejecución)))Esta es una _declaración_ de función.
La expresión define la variable `cuadrado` y la apunta a la función
dada. Hasta aquí todo bien. sin embargo, hay una pequeña sutileza con
esta forma de definición.
[source,javascript]
----
console.log("El futuro dice: ", futuro());
function future() {
return "Seguimos sin tener carros voladores.";
}
----
Este código funciona aunque la función está definida _debajo_ del
código que la usa. Esto es debido a que las declaraciones de función
no toman parte en el flujo de control regular de arriba hacia abajo.
Son movidas conceptualmente a la parte superior de su ámbito y pueden
ser usadas por todo el código en ese ámbito. Esto es útil algunas veces
porque nos da la libertad de organizar el código de una manera parezca
significativa sin preocuparnos por definir todas las funciones antes de
su primer uso.
(((función,declaración)))¿Qué pasa cuando pones una declaración
de función dentro de un bloque condicional (`if`) o dentro de un bucle?
Bueno, mejor no lo hagas. Diferentes plataformas de JavaScript en
diferentes navegadores hacen diferentes cosas tradicionalmente en esa
situación, y el último ((estándar)) de hecho lo prohibe. Si quieres que
tus programas sean consistentes, usa las sentencia de declaración de función
en el bloque más externo de tu función o programa.
[source,javascript]
----
function ejemplo() {
function a() {} // Bien
if (alguna_condicion) {
function b() {} // ¡Peligro!
}
}
----
[[stack]]
== La pila de llamadas ==
indexsee:[pila,pila de llamadas]
(((pila de llamadas)))(((función,aplicación))) Será útil mirar más de
cerca la forma en que el control se mueve a través de las funciones.
Aquí hay un simple programa que hace unas cuantas llamadas a funciones.
[source,javascript]
----
function saluda(a_quien) {
console.log("Hola " + a_quien);
}
saluda("Harry");
console.log("Adiós");
----
(((flujo de control)))(((order de ejecución)))(((console.log))Una ejecución
de este programa va más o menos así: la llamada a `saluda` causa que el control
pase al inicio de esa función (línea 2). Esta llama a `control.log` (una función
incluída en los navegadores), que toma el control, hace su trabajo, y devuelve
el control a la línea 2. Después, se alcanza el dinal de la función `saluda`, así
que se regresa al lugar en dónde se llamó, en la línea 4. La línea siguiente llama
a `console.log` otra vez.
Podemos mostrar el flujo de control esquemáticamente así:
----
raíz
saluda
console.log
saluda
raíz
console.log
raíz
----
(((palabra reservada return)))(((memoria)))Debido a que una función tiene que
saltar de regreso al lugar en que fue llamada cuando termine, la computadora
debe recordar el contexto en el que fue llamada. En un caso, `console.log` tiene
que regresar a la función `saluda`. En el otro caso salta al final del programa.
El lugar en el que la computadora guarda este context es la _((pila de llamadas))_.
Cada vez que una función es llamada, el contexto actual es puesto en la parte superior
de esta pila. Cuando la función retorne, remueve el contexto superior de la "pila" y
lo usa para continuar la ejecución.
(((loo infinito)))(((desbordamiento de la pila)))(((recursión)))Guardar esta pila requiere
espacio en la memoria de la coputadora. Cuando la pila se hace demasiado grande
la computadora mostrará un error parecido a "out of stack space" (sin espacio en la
en la pila) o "too much recursion" (demasiada recursión). El siguiente código lo ilustra
al preguntarle algo realmente difícil a la computadora, lo que causa un ir y venir
infinito entre funciones. Más bien, _sería_ infinito, si la computadora
tuviera una pila infinita. Como son las cosas, nos quedaremos sin espacio,
o "volaremos la pila".
// test: no
[source,javascript]
----
function gallina() {
return huevo();
}
function huevo() {
return gallina();
}
console.log(gallina() + " fue primero");
// → ??
----
== Argumentos Opcionales ==
(((argumento)))(((función,aplicación)))El siguiente código es permitido y se ejecuta sin
ningún problema:
[source,javascript]
----
alert("Hola", "Buenas Noches", "¿Cómo estás?");
----
(((función alert)))La función `alert` oficialmente acepta sólo un argumento.
Aún así, cuando la llamas como aquí, no se queja. Simplemente ignora los otros
argumentos y te muestra "Hola".
(((undefined)))(((parámetros)))JavaScript es extremadamente abierta de mente
acerca del número de argumentos que le pasas a una función. Sí le pasas demasiados,
loa argumentos extra son ignorados. Si le pasas muy pocos, los parámetros quie faltan
simplemente son asignados a `undefined`.
El lado malo de esto es que es posible, probable a menudo, que le pasarás
accidentalmente un número incorrecto de argumentos a las funciones y nadie te
avisará.
[[potencia]]
(((ejemplo de la potencia)))(((argumentos opcionales))) El lado bueno de este
comportamiento es que puede ser usado para tener una función que
tome parámetros "opcionales". Por ejemplo, la siguiente versión de
`potencia` puede ser llamada con dos argumentos o uno solo, caso en el que
el exponente se asume como dos, y la función se comporta como `cuadrado`.
// test: wrap
[source,javascript]
----
function potencia(base, exponente) {
if (exponente == undefined)
exponente = 2;
var resultado = 1;
for (var cuenta = 0; cuenta < exponente; cuenta++)
resultado *= base;
return resultado;
}
console.log(potencia(4));
// → 16
console.log(potencia(4, 3));
// → 64
----
(((console.log)))En el link:04_data.html#arguments_object[próximo
capítulo], veremos una forma en la que el cuerpo de una función puede
obetener la lista exacta de argumentos que se le pasaron. Esto es útil
porque permite a una función aceptar un número indeterminado de argumentos.
Por ejemplo, `console.log` hace uso de esto: imprime todos los valores que
se le pasaron.
[source,javascript]
----
console.log("R", 2, "D", 2);
// → R 2 D 2
----
== Closure ==
(((pila de llamadas)))(((variable local)))(((función,como
valor)))(((closure)))(((ámbito))) La habilidad de tratar Funciones
como valores, combinada con el hecho de que las varibles locales son
"re-creadas" cada vez que una función es llamada, saca a la luz
una pregunta interesante. ¿Qué pasa con las variables locales
cuando la función que las creó ya no está activa?
El siguiente código muestra un ejemplo de esto. Define una funnción,
`envuelveValor`, que crea una variable local. Después devuelve una
función que accede y devuelve esta variable local.
[source,javascript]
----
function envuelveValor(n) {
var variableLocal = n;
return function() { return variableLocal; };
}
var envoltura1 = envuelveValor(1);
var envoltura2 = envuelveValor(2);
console.log(envoltura11());
// → 1
console.log(envoltura2());
// → 2
----
Esto está permitido y funciona como esperarías; la variable todavía puede
leerse. De hecho, múltiples instancias de las variables pueden existir
al mismo tiempo, lo que es otra buena ilustración del concepto de que las
variables locales son re-creadas realmente para cada llamada; diferentes
llamadas no pueden afectar otras variables locales.
Esta característica -ser capaces de hacer referencia a una instancia
local de varables en un función que las encierra- se llama '_closure_'.
Una función que "encapsula" algunas variables locales es llamada _una_
closure. Este comportamiento no sólo te libera de preocuparte de los
tiempos de vida de las variables, además permite algunos usos creativos
de las funciones.
(((función multiplicador))) Con un pequeño cambio, podemos podemos hacer
del ejemplo anterior funciones que multiplicquen por un número arbitrario.
[source,javascript]
----
function multiplicador(factor) {
return function(numero) {
return numero * factor;
};
}
var doble = multiplicador(2);
console.log(doble(5));
// → 10
----
(((variable, desde parámetros))) La variable explícita `variableLocal` de la
función `envuelveValor` en el ejemplo previo no es necesaria porque un
parámetro en sí mismo es una variable local.
(((función, modelo de)))Concebir los parámetros de esta forma
requiere algo de práctica. Un buen modelo mental es pensar en la
palabra clave `function` como si "congelara" el código que está dentro de
ella en un paquete(el valor función). Así, cuando leas `return function(...){...}`,
piensa en que esto regresa un acceso a un conjunto de cálculos, congelados
para uso posterior.
En el ejemplo, `multiplicador` regresa un pedazo congelado de código que
se guarda en la variable `doble`. La última línea entonces llama el valor
guardado en esta variable, haciendo que el código congelado (`return numero * factor;`)
se active. Este todavía tiene acceso a la variable `factor` de la llamada a
`multiplicador` que lo creó, y además obtiene acceso al argumento que se pasa
cuando se activa el código, 5, a través de su parámetro `numero`.
== Recursión ==
(((ejemplo de la potencia)))(((desbordamiento de
pila)))(((recursión)))(((función,aplicación)))Es perfectamente correcto que
una función se llame a sí misma, mientras tenga cuidado de no desbordar la pila.
Una función que se llama a sí misma se llama _recursiva_. La recursión permite
que algunas funciones se escriban con un estilo diferente. Tomemos por ejemplo,
esta implementación alternativa de `potencia`:
// test: wrap
[source,javascript]
----
function potencia(base, exponente) {
if (exponente == 0)
return 1;
else
return base * potencia(base, exponente - 1);
}
console.log(potencia(2, 3));
// → 8
----
(((bucle)))(((legibilidad)))(((matemáticas))) Esto es más cercano
al modo en que los matemáticos definen la potenciación y describe
el concepto de un modo más elegante que la variante que lo hace con bucles.
La función se llama a sí misma varias veces con diferentes argumentos
para conseguir la multiplicación repetida.
(((función, applicación)))(((eficiencia)))Pero esta implementación tiene
un problema importante: en implementaciones típicas de JavaScript, es
cerca de 10 veces más lenta que la versión con bucles. Correr a través
de un siple bucle es más barato que llamar a una función muchas veces.
(((optimización)))El dilema de velocidad contra ((elegancia)) es interesante.
Puedes verlo como un continuum entre amigabilidad-humano y amigabilidad-máquina.
Casi cualquier programa puede hacerse más rápido haciéndolo más grande
y convolucionado. El programador debe de decidir el balance apropiado.
En el caso de la función link:03_functions.html#potencia[anterior] `potencia`
la poco elegante versión(iterativa) es aún bastante simple y fácil de leer.
No tiene mucho sentido reemplazarla con la versión recursiva. A menudo,
sin embargo, un programa tiene conceptos tan complejos que sacrificar
un poco de eficiencia para hacer el programa más claro se vuelve una
opción atractiva.
(((profiling))) La regla básica, que ha sido repetida por muchos programadores
y con la cuál concuerdo de todo corazón, es no preocuparse por
la eficiencia hasta que estés seguro que el programa es demasiado lento.
Si lo es, busca las partes que están abarcando la mayoría del tiempo
y empieza a cambiar elegancia por eficiencia en esas partes.
Por supuesto, esta regla no significa que debas ignorar el rendimiento
completamente. En muchos casos, como en la función `potencia`,
no se gana demasiada simplicidad de la solución "elegante". Y algunas
veces un programador experimentado puede inmediatamente que un
soulución simple nunca va a ser lo suficientemente rápida.
(((optimización prematura)))La razón por la que estoy hablando tanto
de esto es que muchos programadores novatos se enfocan fanáticamente
en la eficiencia, incluso en los detalles más pequeños. El resultado
son programas más grandes, más complicados y a menudo menos correctos,
que toman más tiempo en escribirse que sus equivalentes más sencillos
y que generalmente corren solo un poco más rápido.
(((recursión ramificada)))Pero la recursión no es siempre sólo
una alternativa menos eficiente a los bucles. Alguos problemas son
mucho más fáciles de resolver con recursión que con bucles. La mayoría
de estos son problemas que requieren explorar o procesar varias "ramas",
cada una de las cuáles se puede ramificar otra vez.
[[rompecabezas_recursivo]]
(((recursión)))(((ejemplo del rompecabezas numéricos)))Considera el siguente
rompecabezas: empezando por el número 1 y añadiendo repetidamente 5 o
multiplicándolo por 3, un número infinito de nuevos números puede ser producido.
¿Cómo escribirías una función que, dado un número, trate de encontrar una
secuencia de sumas y multipliclaciones que producen ese número?
Por ejemplo, el número 13 puede ser producido al multiplicar por 3 primero
y después sumar 5 dos veces, mientras que el número 15 no puede ser producido.
Aquí hay una solución recursiva:
[source,javascript]
----
function encontrarSolucion(objetivo) {
function encontrar(inicio, historia) {
if (inicio == objetivo)
return historia;
else if (inicio > objetivo)
return null;
else
return encontrar(inicio + 5, "(" + historia + " + 5)") ||
encontrar(inicio * 3, "(" + historia + " * 3)");
}
return encontrar(1, "1");
}
console.log(encontrarSolucion(24));
// → (((1 * 3) + 5) * 3)
----
Nota que este programa no encuentra necesariamente la ruta
_más corta_ de operaciones. Se satisface cuando cuentre cualquier sequencia.
No necesariamente espero que veas como este trabaja esto inmediatamente. Pero
trabajemos en eso, porque es un gran ejercicio en el pensamiento recursivo.
La función interna `encontrar` hace la recursividad. Toma dos
((argumento))s – el número actual y una cadena que registra como
hemos alcanzado el número – y regresa o una cadena que muestra como
llegar al objetivo o `null`.
(((null)))(((operador ||)))(((evaluación de corto circuito))) Para hacer esto
la función realiza una de tres acciones. Si el número actual es el
número objetivo, entonces la historia actual es una forma de alcanzar
este objetivo, así que siemplemente es devuelta. Si el número actual
es mayot que el número objetivo, no tiene sentido seguir haciendo más
exploraciones porque tanto sumar como multiplicar sólo hará mayor el número.
Y finalmente, si estamos todavía debajo del objetivo la función prueba
los dos caminos posibles que empiezan con el número actual llamándose dos
veces a sí misma, una vez por cada uno de los pasos permitos. Si la
primera llamada regresa cualquier cosa que no sea `null`, se devuelve como
resultado. De otra forma, la segunda opción es la que se devuelve, independientemente
de si produce una cadena o `null`.
(((pila de llamadas)))Para entender mejor como esta función produce el
efecto que estamos buscando, miremos a todas las llamadas a `encontrar` que
se hacen cuando estamos buscando la solución para el número 13.
----
encuentra(1, "1")
encuentra(6, "(1 + 5)")
encuentra(11, "((1 + 5) + 5)")
encuentra(16, "(((1 + 5) + 5) + 5)")
demasiado grande
encuentra(33, "(((1 + 5) + 5) * 3)")
demasiado grande
encuentra(18, "((1 + 5) * 3)")
demasiado grande
encuentra(3, "(1 * 3)")
encuentra(8, "((1 * 3) + 5)")
encuentra(13, "(((1 * 3) + 5) + 5)")
¡Encontrado!
----
El sangrado (indentación) indica la profundidad en la pila de llamadas. La primera vez que
`encuentra` es llamada, se llama dos veces a sí misma para explorar las
soluciones que empiezan con `(1 + 5)` y `(1 * 3)`. La primera llamada intenta
encontrar una solución que empiece con `(1 + 5)` y, usando la recursión,
explora _todas_ las soluciónes que produzcan un número menor o igual que
el número buscado. Dado que no encuentra una solución que corresponda con
el objetivo, regresa `null` a la primera llamada. Entonces el operador `||`
ahce que la llamada que explora `(1 * 3)` suceda. Esta búsqueda tiene más suerte
debido a que su primera llamada recursiva, a través de _otra_ llamada recursiva,
llega al número que buscamos, 13. Esta llamada recursuva, la más interna,
regresa una cadena y cada uno de los operadores `||` en la llamada superior inmediata
pasan esa cadena, finalmente regresando nuestra solución.
== Funciones crecientes ==
(((función, definición)))Existen dos formas más o menos naturales de
intorducir las funciones en los programas.
(((repetición)))La primera es que te encuentras a ti mismo escribiendo el mismo
código varias veces. Necesitamos evitar hacer debido a que más código significa
más espacio para que los errores se oculten y más material para leer para que
leer para las personas que quiere entender el programa. Así que tomamos
funcionalidad repetida, encontramos un buen nombre para esta, y la ponemos dentro
de una función.
La segunda forma es que encuentres que necesitas cierta funcionalidad
que no has escrito aún y que suena como a que merece su propia función.
Empezarás por nombrar la función y después escribirás su cuerpo. Podrías
incluso empezar a escribir el código que usa la función antes de realmente
definir la función misma.
(((función,nombramiento)))(((variable,nombramiento)))Qué tan difícil es encontrar
un nombre para una función es un buen indicador de que tan claro tienes el
concepto de aquello que estás tratando de envolver. Hagamos un ejemplo.
(((ejemplo de la granja)))Necesitamos escribir un problema que imprima dos números,
el número de las vacas y de los pollos de una granja, con las palabras `Cows` y
`Pollos` después de estos, y completados con ceros para que siempre sean de 3 dígitos
de longitud.
----
007 Vacas
011 Pollos
----
Esto claramente requiere una función de dos argumentos. Empecemos a programar.
[source,javascript]
----
function imprimeInventarioGranja(vacas, pollos) {
var vacasString = String(vacas);
while (vacasString.length < 3)
vacasString = "0" + vacasString;
console.log(vacasString + " Vacas");
var pollosString = String(pollos);
while (pollosString.length < 3)
pollosString = "0" + pollosString;
console.log(pollosString + " Pollos");
}
imprimeInventarioGranja(7, 11);
----
(((length,propiedad de cadenas)))(((bucle while)))Añadir `.length` despues
de un valor de cadena nos dará la longitud de esa cadena. Así, los `while`
continuán agregando ceros al principio de la cadena del número hasta que
son por lo menos de 3 caracteres.
¡Misión cumplida! Pero casi cuando le vamos a mandar el código al granjero
(con una buena factura) nos llama y nos dice que ha empezado a críar puercos,
y que si podríamos extender el software para también mostrar los puerquitos,
¿por favor?.
(((copy-paste, programación)))De seguro podemos. Pero mientras estamos en el
proceso de copiar y pegar esas cuatro líneas una vez más, paramos y reconsideramos.
Existe una mejor forma. Aquí hay un intento:
[source,javascript]
----
function imprimeConCerosYEtiqueta(numero, etiqueta) {
var numeroString = String(numero);
while (numeroString.length < 3)
numeroString = "0" + numeroString;
console.log(numeroString + " " + etiqueta);
}
function imprimeInventarioGranja(vacas, pollos, puercos) {
imprimeConCerosYEtiqueta(vacas, "Vacas");
imprimeConCerosYEtiqueta(pollos, "Pollos");
imprimeConCerosYEtiqueta(puercos, "Puercos");
}
imprimeInventarioGranja(7, 11, 3);
----
(((función,nombrado)))¡Funciona! Pero ese nombre,
`imprimeConCerosYEtiqueta`, es un poco feo. Amonotona tres cosas–imprimir,
completar con ceros, y agragar la etiqueta–en una sola función.
(((función rellenoConCero)))En vez de quitar la parte repetida de nuestra
programa por mayoreo, tratemos de escoger un solo _concepto_.
[source,javascript]
----
function rellenoConCero(numero, ancho) {
var cadena = String(numero);
while (cadena.length < ancho)
cadena = "0" + cadena;
return cadena;
}
function imprimeInvantarioGranja(vacas, pollos, puercos) {
console.log(rellenoConCero(vacas, 3) + " Vacas");
console.log(rellenoConCero(pollos, 3) + " Pollos");
console.log(rellenoConCero(puercos, 3) + " Puercos");
}
imprimeInvantarioGranja(7, 16, 3);
----
(((legibilidad)))(((función pura)))Una función con un bonito y obvio
nombre como `rellenoConCero` hace más fácil para alguien que lea el
código descubrir lo que hace. Y eso es útil en más situaciones que sólo
en este programa específico. Por ejemplo, puedes usarla para imprimir
una tabla bien alineada de números.
(((interface,diseño)))¿Qué tan inteligente y versátil debería ser nuestra
función? Podríamos escribir cualquier cosa desde una función terriblemente
simple que sólamente rellena un número de tal manera que tenga 3 caracteres
hasta una complicada, muy generalizada función, un sistema de formateo de
números que maneje fracciones, números negativos, alineación de puntos, relleno
con diferentes carecteres y así por el estilo.
Un principio útil no añadir características a menos que estés completamente
seguro de que las vas a ocupar. Puede ser tentador escribir marcos de trabajo
generals "((framework))s" para cada pequeño pedazo de funcionalidad que te
encuentras. Resiste el impulso. No acabarás ningún trabajo real y terminarás
escribiendo mucho código que nadie usará alguna vez.
[[pura]]
== Funciones y efectos colaterales ==
(((efecto colateral)))(((función pura)))(((función,pureza)))Las funciones
pueden ser más o menos divididas en aquellas que son llamadas por sus
efectos colaterales y aquellas que son llamadas por su valor de resultado.
(Aunque es completamente posible tener tanto efectos colaterales
como retornar un valor).
(((reuso)))La primer función de ayuda en el ((ejemplo de la granja)),
`imprimeConCerosYEtiqueta`, es llamada por su efecto colateral:
imprime su valor de resultado. La segunda versión, `rellenoConCero`, es
llamada por su valor de resultado. No es coincidencia que la segunda sea
útil en más situaciones que la primera. Las funciones que crean valores
son más fáciles de combinar en nuevas formas que funciones que
realizan directamente un efecto colateral.
(((sustitución)))Una función _pura_ es un tipo de función que produce un valor
que no sólo carece de efectos colaterales, tampoco usa los efectos colaterales
de otro código–por ejemplo, no lee variables globales que son ocasionalmente
cambiadas por otro código. Una función pura tiene la agradable propiedad de que,
cuando se llama los mismos argumentos, siempre produce el mismo valor (y
no hace nada más). Esto hace fácil razonar con ella. Una llamada a
una función como esta puede ser sustituida mentalmente como su resultado,
sin cambiar el significado del código. Cuando no estas seguro de que una
función pura funciona correctamente, puedes probarlo simplemente llamándola,
y sabiendo que trabaja bien este contexto, trabajará bien en cualquier context.
Funciones no puras pueden regresar diferentes valores basadas en todo tipo
de factores y tienen efectos secundarios y que pueden ser difícil de probar
y de razonar con ellas.
(((optimización)))(((console.log)))Aún así, no hay necesidad de sentirse mal
cuando escribimos funciones que no son puras o de armar una guerra santa
para eliminarlas de tu código. Los efectos secundarios a menudo son útiles.
No habría forma de imprimir una versión pura de `console.log`, por ejemplo, y
`console.log` es ciertamente útil. Algunas operaciones son además más fáciles
de expresar en una forma eficiente cuando se usan los efectos scundarios,
así que la velocidad de cómputo puede ser una razón para evitar la pureza.
== Sumrio ==
Este capítulo enseñó como escribir tus propias funciones. La palabra
resevada `function`, cuando se usa como una expresión, puede crear un
valor función. Cuando es usada como sentencia, puede ser usada para declarar
una variable y darle una función como valor.
[source,javascript]
----
// Crear un valor función f
var f = function(a) {
console.log(a + 2);
};
// Declarar g como función
function g(a, b) {
return a * b * 3.5;
}
----
Un aspecto clave para entender las funciones es entender los ámbitos
locales. Los parámetros y variables declaradas dentro de una función son
locales para la función, re-creados cada vez que la función es llamada y no
visibles de desde fuera. Las funciones declaradas dentro de otra función
tienen acceso al ámbito local externo de la función.
Seprar las tareas que tu tarea realiza en diferentes funciones es útil.
No tendrás que repetir lo mismo tanto, y las funciones pueden hacer un
programa más legible al agrupar el código en partes conceptuales, de la misma
forma que capítulos y secciones ayudan a organizar el texto regular.
== Ejercicios ==
=== Mínimo ===
(((Objeto Math)))(((mínimo (ejercicio))))(((función
Math.min)))(((mínimo)))El
link:02_program_structure.html#return_values[capítulo anterior]
intordujo la función estándar `Math.min` que devuelve su argumento más pequeño.
Ahora nosotros mismos podemos hacer eso. Escribe una función `min` que
tome dos argumentos y devuelva el mínimo.
ifdef::interactive_target[]
// test: no
[source,javascript]
----
// Tu código va aquí.
console.log(min(0, 10));
// → 0
console.log(min(0, -10));
// → -10
----
endif::interactive_target[]
!!pista!!
(((mínimo (ejercicio))))Si tienes problemas poniendo las llaves y los
paréntesis en el lugar correcto para obtener una definición de
función válida, empieza por copiar uno de los ejemplos del capítulo
y modificarlo.
(((palabra reservada return)))Una función puede contener múltiples `return`.
!!pista!!
=== Recursión ===
(((recursión)))(((esPar (ejercicio))))(((número par)))Hemos visto que
`%` (el operador de sobrante) puede ser usado para probar si un número
es par o impar usando `% 2` para checar si es divisible por dos. Aquí
hay otra forma de definir si un número entero es par o impar:
- Cero es par.
- Uno es impar.
- Para cualquier otro número _N_, su paridad es la misma que
_N_ - 2.
Escribe una función recursiva `esPar` que corresponda a esta descripción.
La función debería aceptar un `numero` como parámetro y regresar un Booleano.
(((pila desbordada)))Pruebala con 50 y 75. Observa cómo se
comporta con -1. ¿Por qué? ¿Puedes pensar en alguna forma de
componer esto?
ifdef::interactive_target[]
// test: no
[source,javascript]
----
// Tu código va aquí.
console.log(esPar(50));
// → true
console.log(esPar(75));
// → false
console.log(esPar(-1));
// → ??
----
endif::interactive_target[]
!!pista!!
(((esPar (ejercicio))))(((palabra reservada
if,encadenamiento)))(((recursión))) La función será algo similar al
`encuentra` interno en la solución recursiva
link:03_functions.html#rompecabezas_recursivo[ejemplo] `encuentraSolucion`
en este capítulo, con una cadena de `if`/`else if`/`else` que probará
cuál de los tres casos aplica. El `else` final, correspondiente al tercer caso,
hace la llamada recursiva. Cada una de las ramas debe de contener una
sentencia `return` o de alguna otra forma arrglárselas para devolver un
valor específico.
(((pila desbordada))) Cuando se le da un número negativo, la función va
llamarse a sí misma una y otra vez, pasándose un número cada vez más negativo,
así alejándose más y más de regresar una solución. En algún momento
se quedará sin espacio en la pila y abortará.
!!pista!!
=== Contando Frijoles ===
(((contando frijoles (ejercicio))))(((método
charAt)))(((string,indexado)))(((conteo con base cero)))Puedes obtener el
n-avo caracter, o letra, de una cadena escribiendo `"cadena".charAt(N)`,
similar a como obtienes su longitud con `"cadena".length`. El valor
obtenido será una cadena que contiene sólo un caracter(por ejemplo, `"b"`).
El primer caracter tiene la posición cero, lo cual hace que el último
pueda ser encontrado en la posición `string.lenght -1`. En otras palabras,
una cadenas de dos caracteres tiene un “lenght” o longitud de 2, y sus
caracteres tienen las posiciones 0 y 1.
Escribe una función `cuentaFs` que tome una cadena como su único
argumento y regrese un número que indique cunántos caracteres “F” mayúscula
hay en la cadena.
A continuación, escribe una funcióon llamada `cuentaCaracter` que se comoporte
como `cuentaFs`, con la diferencia de que tome un segundo caracter que
indique el caracter que será contado(en vez de sólo caracteres “F”).
Reescribe `cuentaFs` para hacer uso de esta nueva función.
ifdef::interactive_target[]
// test: no
[source,javascript]
----