Los malabares con la pila van bien para palabras cortas. Cuando una palabra tiene varias
entradas y necesitas referirte a ellas por su nombre, Factor ofrece
locals: vinculaciones con nombre introducidas con :: (para definir palabras) o
[| | ] (para lambdas en línea).
:: ( inputs -- outputs )Sustituye : por :: y los nombres de las entradas 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 reorganiza la pila:
! DOCTEST: SKIP (shown for comparison — would re-define hypotenuse)
: hypotenuse ( a b -- c )
[ sq ] bi@ + sqrt ;
Ambas son válidas: locals brilla cuando se hace referencia a una entrada más de una vez o cuando el orden de las operaciones no coincide de forma natural con el orden de la pila.
:>
Dentro de un cuerpo :: (o de una lambda [| ... |] en línea), :> saca un
valor de la pila de datos y lo vincula a un local nuevo:
value :> name ! immutable binding
value :> name! ! mutable binding (name! is its setter)
La forma inmutable es la habitual: úsala para dar nombre a un valor intermedio de modo que el cuerpo se lea de arriba 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, 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 scope desde cualquier lugar:> solo funciona dentro de un scope léxico, que :: y
[| ... |] crean automáticamente. Para introducir locals desde una
palabra : simple o desde el listener, envuelve el código en [let ... ]:
[let code :> name code :> name ... body ]
Cada :> name vincula el valor de la cima de la pila de la expresión anterior;
las vinculaciones 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 la pila y las
vinculaciones con nombre: resulta práctico 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 resulta 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 entradas y lo que produce su cuerpo.
Recurre a los locals cuando:
dup/swap/rot necesario para arreglarlo es 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 cada 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 es
exactamente la forma adecuada: no hace falta case porque no hay un único
valor con el que comparar.
Estás de vuelta en la cocina, perfeccionando la receta de lasaña de la deliciosa lasaña de Leah. Las tareas de aquí te piden manejar varias entradas a la vez, que es exactamente cuando locals, los enlaces con nombre de Factor, se ganan su sitio.
Define cooking-status para que tome una lectura del temporizador de la pila y devuelva un string con el estado.
0 → "Lasagna is done."
f (el temporizador nunca se ajustó) → "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 número medio 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 pasta necesarios (50 g por cada capa de pasta) 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 envía un array de ingredientes; el último elemento es su secreto. Define add-secret-ingredient para que tome su array y el tuyo y devuelva tu array con el secreto añadido al final.
{ "noodles" "sauce" "mozzarella" "kampot pepper" }
{ "noodles" "meat" "sauce" "mozzarella" }
add-secret-ingredient .
! => { "noodles" "meat" "sauce" "mozzarella" "kampot pepper" }
La receta del libro de recetas da para dos porciones. Define scale-recipe para que tome una receta (una tabla hash que asocia cada nombre de ingrediente con 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.