Rutas
/
Factor
Factor
/
Ejercicios
/
Luminaria de la lasaña
Luminaria de la lasaña

Luminaria de la lasaña

Ejercicio de aprendizaje

Introducción

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.

Vincular valores intermedios con :>

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.

Lambdas: [| 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.

Cuándo usar locals

Recurre a los locals cuando:

  • La misma entrada se utiliza en más de un lugar.
  • El orden natural de las operaciones no coincide con el orden de la pila, y el dup/swap/rot necesario para arreglarlo es difícil de leer.
  • Estás dentro de una lambda en línea que se pasa a una palabra de orden superior que te entrega varios valores a la vez (como en 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 vez

assoc-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.

Volvemos a 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.

Instrucciones

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.

1. Determina el estado de cocción

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."
  • cualquier otro número → "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."

2. Estima el tiempo de preparación

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

3. Calcula la pasta y la salsa necesarias

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

4. Añade el ingrediente secreto

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" }

5. Escala la receta

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 } }
Editar en GitHub El enlace se abre en una ventana o pestaña nueva
Factor Exercism

¿Listo para empezar Luminaria de la lasaña?

Regístrate en Exercism para aprender y dominar Factor con 47 conceptos163 ejercicios y mentoría humana real, todo gratis.