Assegnazione variabile in un linguaggio postfisso

4

TL; DR è val name <- più leggibile di name val <- ?

Sto progettando un linguaggio semi-concatenativo, postfisso. Non ho pensato molto allo stile in cui sono assegnate le variabili, e ora vedo che ci sono due (principali) modi per farlo. In origine, l'assegnazione di una variabile assomiglia a:

variable value <-

Quindi, ecco un programma di esempio per un algoritmo fisher-yates implementato in questa lingua:

{ arr :
  n  arr size <-
  0 n 2 - i [
    j    i n .. randin <-
    arr  arr [i j nswap] apply <-
  ] for
  arr isolate
} shuf def

Non hai proprio bisogno di conoscere le specifiche, ma voglio indicare tre linee dove avviene l'assegnazione delle variabili:

  n  arr size <-
    j    i n .. randin <-
    arr  arr [i j nswap] apply <-

Ho aggiunto spazi bianchi per compensare quello che penso sia un po 'illeggibile. Tuttavia, non avendo utilizzato molti linguaggi postfini leggibili, non sono sicuro di ciò che è considerato leggibile. Quindi, ho inizialmente definito l'assegnazione delle variabili in modo che rappresentasse più strettamente l'assegnazione di una variabile convenzionale, in questo modo:

my language:   a 4 <-
conventional:  a <- 4

Tuttavia, dopo aver effettivamente scritto alcuni programmi brevi relativi, mi sento come se la leggibilità potesse essere migliorata avendo il nome adiacente all'assegnazione, allo stesso modo in cui 2 3 * 4 + è più leggibile di 4 2 3 * + , poiché avendo solo 2 gli elementi sulla "pila della tua mente" alla volta ti permettono di valutarlo come un normale problema di matematica. Quindi, il programma rivisto sarebbe:

{ arr :
  arr size n <-
  0 n 2 - i [
    i n .. randin j <-
    arr [i j nswap] apply arr <-
  ] for
  arr isolate
} shuf def

Che sembra più leggibile.

(Salta in fondo alla domanda vera e propria, sto solo spiegando il programma originale in dettaglio da qui a lì per chi lo desidera.)

Bene, quindi un commento sembra (* ... *) . Ora, ho aggiunto commenti che spiegano il programma.

(* { name : ...} begins a lambda that takes a single argument
   'name' from the stack when executed. *)
{ arr :
  (* let n be the length of the array *)
  n  arr size <-
  (* from 0 to n - 2, using 'i' as a variable... *)
  0   n 2 -   i
  (* ...execute a simple func, which has 'i' in scope *)
  [
    (* set j to a random index, i <= j < n *)
    j  i n .. randin <-
    (* applies the stack operation 'nswap' to the array,
       swapping elements 'i' and 'j'. *)
    arr arr [i j nswap] apply <-
  ] for
  (* make arr the only thing on the stack for a return value *)
  arr isolate
(* close the lambda and define as a function 'shuf' *)
} shuf def

(* example usage *)
(1 2 3 4 5) shuf out

La domanda

In un linguaggio postfisso come questo, sarebbe meglio avere uno stile a lungo termine per avere il nome della variabile adiacente alla variabile o come primo argomento? Ho scelto quest'ultimo nella speranza che fosse più leggibile, ma ora sembra che il primo sia più leggibile.

(Mi rendo conto che non ho fornito le specifiche complete per il linguaggio, e questo è semplicemente perché non ne ho nessuno. Sentiti libero di "definire" i tuoi stessi esempi per il gusto dell'argomento.)

    
posta Conor O'Brien 09.12.2016 - 15:18
fonte

2 risposte

6

L'attrattiva di una lingua concatenativa è che la notazione corrisponde strettamente a un modello di valutazione basato sullo stack: abbiamo letterali che vengono inseriti nello stack e funzioni che possono estrarre valori dallo stack e spingere i valori nello stack. Le istruzioni possono essere applicate in ordine, ovvero questo è effettivamente un codice assembly per una macchina virtuale basata su stack.

Le variabili complicano questo argomento:

  • La memoria rappresentata da variabili mutabili è ortogonale al modello di stack. Alcuni potrebbero considerare questo impuro. Mentre la lettura da una variabile si inserisce in modo pulito nel modello di stack (una variabile è una funzione che non visualizza valori e inserisce un valore), la scrittura non lo fa.

  • L'associazione / assegnazione variabile è necessariamente sintassi . Dato tre token a b c , il linguaggio normalmente valuterà questi token nell'ordine che risulta nelle manipolazioni dello stack. Questo non può valere per l'assegnazione di variabili: se a o b è una variabile, mi aspetterei che sia valutata . Invece, l'operatore di assegnazione ha solo bisogno di un nome assegnabile. La variabile a cui stai assegnando non deve essere valutata. Ciò significa che l'associazione variabile non può essere una normale funzione di stack.

Poiché il binding variabile non si comporta come una funzione ordinaria, non dovrebbe apparire come una funzione ordinaria. È un operatore di livello linguistico molto speciale

.

Le tue soluzioni variable expression <- e start end variable expression for sono in realtà operatori di circumfix : un'espressione di assegnazione inizia con una variabile, quindi contiene un'espressione di pila ordinaria e termina con l'operatore di assegnazione. Penso che questo sia un po 'confuso poiché diffonde informazioni sull'assegnazione distanti. È difficile leggere tale codice poiché non è affatto ovvio se una determinata variabile verrà valutata o assegnata a meno che non si legge anche il resto del programma.

Le tue soluzioni expression variable <- e lambda variable def sono leggermente migliori poiché tutte le informazioni sull'assegnazione sono ora più vicine: quando incontri l'istruzione variable <- nel codice sorgente, lo stack conterrà il valore da assegnare. Ma l'operatore di assegnazione inizia ancora con la variabile in cui sembra che la variabile debba essere valutata.

Pertanto, consiglierei che l'assegnazione sia un operatore unario che contiene direttamente il nome della variabile, ad es. expression =variable o expression :variable . Se tra uno spazio e l'altro ci sono degli spazi vuoti, anche expression -> variable sarebbe OK. Ciò non viola la struttura postfissa della lingua perché la variabile è esente dalla valutazione normale.

Mi aspetterei che l'introduzione di una sintassi di assegnazione di variabile comune possa trasformare il codice di esempio in qualcosa di simile (si noti che ai fini di un modello sintattico più semplice presumo che il ciclo for a accetta un lambda come parametro, che consente di essere una funzione ordinaria invece di un operatore:

{ :arr
  arr size :n
  { :i
    i n .. randin :j
    arr i j nswap :arr
  } 0 n 2 - for
  arr isolate
} :shuf
    
risposta data 09.12.2016 - 16:18
fonte
2

Come esempio di un famoso linguaggio postfix, Manuale di riferimento del linguaggio Postscript (pdf) definisce l'operatore di assegnazione come segue (p.568):

def           key value def -

associates key with value in the current dictionary—the one on the top of the dictionary stack (see Section 3.4, “Stacks”). If key is already present in the current dictionary, def simply replaces its value; otherwise, def creates a new entry for key and stores value with it.

Esempi:

/ncnt 1 def           % Define ncnt to be 1 in current dict
/ncnt ncnt 1 add def  % ncnt now has value 2
    
risposta data 09.12.2016 - 15:56
fonte

Leggi altre domande sui tag