Percursos
/
Factor
Factor
/
Exercícios
/
O Luminar da Lasanha
O Luminar da Lasanha

O Luminar da Lasanha

Exercício de aprendizagem

Introdução

Fazer malabarismos com a pilha funciona bem para palavras curtas. Quando uma palavra tem vários parâmetros de entrada e precisas de lhes fazer referência pelo nome, o Factor oferece locais: ligações com nome introduzidas com :: (para definições de palavras) ou [| | ] (para lambdas inline).

:: ( inputs -- outputs )

Substitui : por :: e os nomes dos parâmetros de entrada passam a ser variáveis locais no corpo:

USING: locals ;

:: hypotenuse ( a b -- c )
    a a *
    b b *
    + sqrt ;

Compara com a versão que baralha a pilha:

! DOCTEST: SKIP   (shown for comparison — would re-define hypotenuse)
: hypotenuse ( a b -- c )
    [ sq ] bi@ + sqrt ;

Ambas são válidas: os locais brilham quando um parâmetro de entrada é referenciado mais do que uma vez ou quando a ordem das operações não coincide naturalmente com a ordem da pilha.

Ligar valores intermédios com :>

Dentro de um corpo :: (ou de uma lambda inline [| ... |]), :> retira um valor da pilha de dados e liga-o a um novo local:

value :> name      ! immutable binding
value :> name!     ! mutable binding (name! is its setter)

A forma imutável é a mais comum: usa-a para dar nome a um valor intermédio, de modo a que o corpo se leia de cima para baixo:

USING: locals math ;

:: average-3 ( x y z -- avg )
    x y + z + :> sum
    sum 3 / ;

A variante com ! declara o local como mutável; mais à frente no corpo, value name! (sem dois pontos, sem :>) reatribui-o:

USING: kernel locals math math.order ;

:: clamped ( x lo hi -- y )
    x :> v!
    v lo < [ lo v! ] when
    v hi > [ hi v! ] when
    v ;

Os locais mutáveis brilham dentro das palavras de iteração que vais encontrar em exercícios posteriores (por exemplo, while).

[let: um âmbito a partir de qualquer sítio

O :> só funciona dentro de um âmbito lexical, que :: e [| ... |] criam automaticamente. Para introduzir locais a partir de uma palavra : simples ou no listener, envolve o código em [let ... ]:

[let code :> name code :> name ... body ]

Cada :> name liga o valor no topo da pilha da expressão anterior; as ligações ficam visíveis para o resto da forma.

USING: locals math ;

[let 3 :> x 4 :> y x x * y y * + ] .   ! => 25

O [let é a ponte entre as definições : ao estilo da pilha e as ligações com nome: dá jeito quando só uma parte de uma palavra beneficia dos locais.

Lambdas: [| inputs | body ]

Dentro de uma quotation, [| inputs | body ] introduz locais a que o corpo pode fazer referência pelo nome. Isto é especialmente útil com palavras de ordem superior como assoc-map, cuja quotation recebe key e value:

USING: assocs locals ;

H{ { "a" 1 } { "b" 2 } }
[| key value | key value neg ] assoc-map .
! => H{ { "a" -1 } { "b" -2 } }

A mesma forma funciona para map-index (em [sequences][sequences]), cuja quotation recebe o elemento e o seu índice. Os nomes deixam claro qual operando desempenha 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 }

O efeito na pilha da lambda é determinado pela lista de parâmetros de entrada e pelo que o seu corpo produz.

Quando usar locais

Recorre aos locais quando:

  • O mesmo parâmetro de entrada é usado em mais do que um sítio.
  • A ordem natural das operações não corresponde à ordem da pilha e o dup/swap/rot necessário para corrigir isso é difícil de ler.
  • Estás dentro de uma lambda inline passada a uma palavra de ordem superior que te entrega vários valores de uma só vez (como em assoc-map).

Quando o corpo é um fluxo linear simples, as definições : normais continuam mais curtas e pelo menos igualmente legíveis.

assoc-map: uma entrada de cada vez

O assoc-map (em assocs) aplica uma quotation a cada entrada de uma coleção associativa:

assoc-map ( assoc quot: ( key value -- newkey newvalue ) -- newassoc )

Uma lambda com locais é a forma mais limpa de escrever a quotation.

cond revisitado

Quando precisas de escolher o ramo com base em f ou num valor numérico, o cond é exatamente a forma certa: não é preciso case, porque não há um único valor com que comparar.

Instruções

Estás de volta à cozinha, a aperfeiçoar a receita de lasanha d'A Deliciosa Lasanha da Leah. As tarefas aqui pedem-te que faças malabarismos com vários parâmetros de entrada ao mesmo tempo, e é exatamente aí que os locals, os vínculos com nome do Factor, se revelam úteis.

1. Determina o estado de cozedura

Define cooking-status para recolher da pilha a leitura de um temporizador e devolver uma string de estado.

  • 0 → "Lasagna is done."
  • f (o temporizador nunca foi definido) → "You forgot to set the timer."
  • qualquer outro 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 o tempo de preparação

Define preparation-time para receber um array com os nomes das camadas e uma média de minutos por camada, e devolver o tempo total de preparação.

{ "sauce" "noodles" "sauce" "meat" "mozzarella" "noodles" } 3 preparation-time .
! => 18

3. Calcula a massa e o molho necessários

Define quantities para receber um array com os nomes das camadas e devolver dois valores: os gramas de massa necessários (50 g por camada de massa) e os litros de molho necessários (1/5 de litro por camada de molho).

{ "sauce" "noodles" "sauce" "meat" "mozzarella" "noodles" } quantities .s
! => 100
! => 2/5

4. Adiciona o ingrediente secreto

Uma pessoa amiga envia-te uma lista de ingredientes; o último item é o segredo dela. Define add-secret-ingredient para receber a lista dela e a tua, e devolver a tua lista com o segredo acrescentado no fim.

{ "noodles" "sauce" "mozzarella" "kampot pepper" }
{ "noodles" "meat" "sauce" "mozzarella" }
add-secret-ingredient .
! => { "noodles" "meat" "sauce" "mozzarella" "kampot pepper" }

5. Ajusta a receita

A receita do livro de cozinha dá duas porções. Define scale-recipe para receber uma receita (um hashtable que associa o nome de cada ingrediente à respetiva quantidade) e um número de porções pretendido, e devolver uma nova receita ajustada em conformidade.

H{ { "noodles" 200 } { "sauce" 1/2 } { "mozzarella" 1 } { "meat" 100 } }
4 scale-recipe .
! => H{ { "noodles" 400 } { "sauce" 1 } { "mozzarella" 2 } { "meat" 200 } }
Editar via GitHub A ligação abre numa nova janela ou separador
Factor Exercism

Estás pronto para começar O Luminar da Lasanha?

Inscreve-te no Exercism para aprenderes e dominares Factor com 47 conceitos163 exercícios, e mentoria humana real, tudo grátis.