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.
:>
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ítioO :> 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.
[| 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.
Recorre aos locais quando:
dup/swap/rot necessário para corrigir isso é difícil de ler.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 vezO 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 revisitadoQuando 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.
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.
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."
"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 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
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
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" }
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 } }
Inscreve-te no Exercism para aprenderes e dominares Factor com 47 conceitos163 exercícios, e mentoria humana real, tudo grátis.