Hacer malabares con la pila está bien para palabras cortas. Cuando una palabra tiene varios
argumentos y necesitas referirte a ellos por su nombre, Factor ofrece locals: enlaces con
nombre que se introducen con :: (para definiciones de palabras) o [| | ] (para lambdas en
línea).
:: ( inputs -- outputs )Reemplaza : con :: y los nombres de los argumentos se convierten en variables locales
dentro del cuerpo:
USING: locals ;
:: hypotenuse ( a b -- c )
a a *
b b *
+ sqrt ;
Compáralo con la versión que reordena la pila:
! DOCTEST: SKIP (shown for comparison — would re-define hypotenuse)
: hypotenuse ( a b -- c )
[ sq ] bi@ + sqrt ;
Ambas son válidas. Los locals brillan cuando se hace referencia a un argumento más de una vez o cuando el orden de las operaciones no coincide de forma natural con el orden de la pila.
:>
Dentro del cuerpo de un :: (o de una lambda en línea [| ... |]), :> saca un valor de la
pila de datos y lo enlaza a un local nuevo:
value :> name ! immutable binding
value :> name! ! mutable binding (name! is its setter)
La forma inmutable es la habitual. Úsala para nombrar un valor intermedio y que el cuerpo se lea de arriba hacia abajo:
USING: locals math ;
:: average-3 ( x y z -- avg )
x y + z + :> sum
sum 3 / ;
La variante con ! declara el local como mutable; más adelante en el cuerpo, value name!
(sin dos puntos y sin :>) lo reasigna:
USING: kernel locals math math.order ;
:: clamped ( x lo hi -- y )
x :> v!
v lo < [ lo v! ] when
v hi > [ hi v! ] when
v ;
Los locals mutables brillan dentro de las palabras de iteración que conocerás en ejercicios
posteriores (por ejemplo, while).
[let: un ámbito desde cualquier lugar:> solo funciona dentro de un ámbito léxico, que :: y [| ... |] crean automáticamente.
Para introducir locals desde una palabra : normal o desde el listener, envuelve el código en
[let ... ]:
[let code :> name code :> name ... body ]
Cada :> name enlaza el valor en el tope de la pila de la expresión anterior; los enlaces son
visibles para el resto de la forma.
USING: locals math ;
[let 3 :> x 4 :> y x x * y y * + ] . ! => 25
[let es el puente entre las definiciones : al estilo de pila y los enlaces con nombre;
resulta útil cuando solo una parte de una palabra se beneficia de los locals.
[| inputs | body ]
Dentro de una quotation, [| inputs | body ] introduce locals a los que el cuerpo puede
referirse por su nombre. Esto es especialmente útil con palabras de orden superior como
assoc-map, cuya quotation recibe key y value:
USING: assocs locals ;
H{ { "a" 1 } { "b" 2 } }
[| key value | key value neg ] assoc-map .
! => H{ { "a" -1 } { "b" -2 } }
La misma forma funciona para map-index (en [sequences][sequences]), cuya quotation recibe
el elemento y su índice. Los nombres dejan claro qué operando desempeña cada papel:
USING: locals sequences ;
! Divide each element by its 1-based position:
{ 10 80 270 } [| elt i | elt i 1 + / ] map-index .
! => { 10 40 90 }
El efecto de pila de la lambda lo determinan la lista de argumentos y lo que produce su cuerpo.
Recurre a los locals cuando:
dup/swap/rot que lo corrige sea difícil de leer.assoc-map).Cuando el cuerpo es un flujo lineal y limpio, las definiciones : simples siguen siendo más
cortas y al menos igual de legibles.
assoc-map: una entrada a la vezassoc-map (en assocs) aplica una quotation a cada entrada de una colección
asociativa:
assoc-map ( assoc quot: ( key value -- newkey newvalue ) -- newassoc )
Una lambda con locals es la forma más limpia de escribir la quotation.
cond
Cuando necesitas elegir según f o según un valor numérico, cond tiene exactamente la
forma correcta: no hace falta case, porque no hay un único valor con el que comparar.
Estás de vuelta en la cocina, mejorando la receta de lasaña de La deliciosa lasaña de Leah. Las tareas de este ejercicio te piden manejar varios argumentos a la vez, y es justo ahí cuando los locals, los enlaces con nombre de Factor, demuestran para qué sirven.
Define cooking-status para que tome de la pila una lectura del temporizador y
devuelva un string con el estado.
0 → "Lasagna is done."
f (el temporizador nunca se puso) → "You forgot to set the timer."
"Not done, please wait."
12 cooking-status . ! => "Not done, please wait."
0 cooking-status . ! => "Lasagna is done."
f cooking-status . ! => "You forgot to set the timer."
Define preparation-time para que tome un array con los nombres de las capas y
un promedio de minutos por capa, y devuelva el tiempo total de preparación.
{ "sauce" "noodles" "sauce" "meat" "mozzarella" "noodles" } 3 preparation-time .
! => 18
Define quantities para que tome un array con los nombres de las capas y
devuelva dos valores: los gramos de fideos necesarios (50 g por cada capa de
fideos) y los litros de salsa necesarios (1/5 de litro por cada capa de
salsa).
{ "sauce" "noodles" "sauce" "meat" "mozzarella" "noodles" } quantities .s
! => 100
! => 2/5
Tu amigo te manda una lista de ingredientes; el último elemento es su
secreto. Define add-secret-ingredient para que tome su lista y la tuya, y
devuelva tu lista con el secreto agregado al final.
{ "noodles" "sauce" "mozzarella" "kampot pepper" }
{ "noodles" "meat" "sauce" "mozzarella" }
add-secret-ingredient .
! => { "noodles" "meat" "sauce" "mozzarella" "kampot pepper" }
La receta del libro rinde dos porciones. Define scale-recipe para que tome
una receta (un hashtable que va del nombre de cada ingrediente a su cantidad) y
un número de porciones objetivo, y devuelva una receta nueva escalada de forma
adecuada.
H{ { "noodles" 200 } { "sauce" 1/2 } { "mozzarella" 1 } { "meat" 100 } }
4 scale-recipe .
! => H{ { "noodles" 400 } { "sauce" 1 } { "mozzarella" 2 } { "meat" 200 } }
Regístrate en Exercism para aprender y dominar Factor con 47 conceptos163 ejercicios y mentoría humana real, todo gratis.