sintassi lambda preferita?

6

Sto giocando un po 'con la mia C come la grammatica DSL e vorrei qualche oppio.

Ho riservato l'uso di "(...)" per le invocazioni. ad esempio:

foo(1,2);

La mia grammatica supporta "chiusure finali", proprio come i blocchi di Ruby che possono essere passati come ultimo argomento di una chiamata.

Attualmente il mio supporto grammaticale chiude le chiusure in questo modo:

foo(1,2)
{
   //parameterless closure passed as the last argument to foo
}

o

foo(1,2) [x]
{
    //closure with one argument (x) passed as the last argument to foo
    print (x);
}

Il motivo per cui utilizzo [args] invece di (args) è che (args) è ambiguo:

foo(1,2) (x)
{
}

In questo caso non è possibile stabilire se foo si aspetta 3 argomenti (int, int, closure (x)) o se foo si aspetta 2 argomenti e restituisce una chiusura con un argomento (int, int) - > chiusura (x) Quindi questo è praticamente il motivo per cui uso [] per ora.

Potrei cambiarlo in qualcosa tipo:

foo(1,2) : (x)
{
}

o

foo(1,2) (x) ->
{
}

Quindi la domanda attuale è, quale pensi sia la migliore?

[...] è un po 'ostile al polso.

let x = [a,b] 
{
}

idee?

    
posta Roger Johansson 11.01.2011 - 11:12
fonte

6 risposte

4

Mi piace la sintassi di Groovy, che è

foo(1,2) { x ->
   ...
}
    
risposta data 11.01.2011 - 11:25
fonte
3

Mi piace la sintassi C #, bella e breve     (x, y) = > {/ * codice * /}

E per dichiarare
    Azione < int, int >

    
risposta data 16.02.2011 - 06:53
fonte
2

Ho sempre pensato che fossero minacciati come qualsiasi altro parametro normale.

Considera javascript

 sort( sortFunctionGoesHere );

Invocato come:

sort( function( a, b ) {
    return b - a; 
});

Quindi preferirei avere

foo( int, int , closure(int) )

E usalo in questo modo:

foo( 1, 2, (x) {
...    
})
    
risposta data 11.01.2011 - 12:10
fonte
2

Prima di tutto, il parametro di chiusura dovrebbe essere una parte normale dell'elenco dei parametri.

Quindi, non c'è alcuna differenza fondamentale nella dichiarazione o nell'uso. Non apprezzo strongmente Ruby che introduce casi speciali con yield , funzioni non di prima classe e un parametro & -parameter.

Proprio

function foreach(collection, f) 
  // code
end

così

foreach([1, 2, 3], print)

dovrebbe funzionare in ogni caso.

Detto questo, puoi aggiungere zucchero sintattico per fornire un argomento lambda finale in un modo più conveniente. Vedo due opzioni principali, a seconda del design della tua lingua.

  1. Zucchero sintattico semplice. Mi piace l'approccio di @ ammoQ con

    foreach([1, 2, 3]) { x ->
       print(x)
    }
    

    Quindi f(args ...) { vars -> body } viene letteralmente tradotto in f(args ..., λvars -> body)

  2. È così che Scala lo fa. Come hai detto

    There is no way in this case to tell if foo expects 3 arguments (int,int,closure(x)) or if foo expects 2 arguments and returns a closure with one argument(int,int) -> closure(x)

    Usa questa idea - una funzione che restituisce un'altra funzione da chiamare è chiamata currying , che è un modello molto comune in programmazione funzionale.

    Se hai un curry nella tua lingua come fa Scala, hai solo bisogno di una sintassi per le chiusure con { } uguale alle parentesi regolari. Il resto è una normale chiamata semantica. Cioè.

    function foo(a, b)(c)(f) 
    end
    ...
    foo(1, 2)(3) { x -> x + 1 }
    
risposta data 11.01.2011 - 13:25
fonte
2

Non penso che legare a C come la sintassi sia una buona idea. Spero che tu abbia controllato la sintassi di Haskell. Se stai creando una DSL, penso che lo scopo principale sia astrazione del dominio. Fondamentalmente, la sintassi di C like è troppo prolissa ...

Esempio di Haskell:

-- Defining a function
add x y = x + y

-- Defining a lambda function ('\' means lambda character 'λ')
add = \ x y -> x + y
    
risposta data 16.02.2011 - 03:56
fonte
0

Fondamentalmente, la definizione dell'argomento all'interno del blocco è più semplice, poiché risolve molte ambiguità, ad esempio il modo in cui Groovy lo fa, come sottolineato da @ammoQ.

Ruby fa anche questo, ma è un po 'più facile da analizzare di Groovy: foo(1,2) { |x,y| ... }

Questo può essere semplicemente analizzato da sinistra a destra ed è più semplice da leggere. Inoltre, le probabilità sono buone, non concederai mai | come operatore unario (ad esempio all'inizio di un'istruzione), quindi il rischio di ambiguità è estremamente basso.

Se è troppo "polso intenso", suppongo che foo(1,2) { <x,y> ... } possa essere d'aiuto sulle tastiere inglesi.

    
risposta data 11.01.2011 - 12:20
fonte

Leggi altre domande sui tag