Qual è la differenza tra curry e applicazione parziale?


438

Spesso vedo su Internet varie lamentele sul fatto che gli esempi di curry di altre persone non sono curry, ma in realtà sono solo un'applicazione parziale.

Non ho trovato una spiegazione decente su cosa sia l'applicazione parziale o su come differisce dal curry. Sembra esserci una confusione generale, con esempi equivalenti descritti come curry in alcuni punti e applicazione parziale in altri.

Qualcuno potrebbe fornirmi una definizione di entrambi i termini e dettagli su come differiscono?

Risposte:


256

Currying sta convertendo una singola funzione di n argomenti in n funzioni con un singolo argomento ciascuno. Data la seguente funzione:

function f(x,y,z) { z(x(y));}

Quando curry, diventa:

function f(x) { lambda(y) { lambda(z) { z(x(y)); } } }

Per ottenere l'applicazione completa di f (x, y, z), è necessario:

f(x)(y)(z);

Molti linguaggi funzionali ti permettono di scrivere f x y z. Se solo si chiama f x yo f (x) (y) allora si ottiene una funzione-valore di ritorno parzialmente applicata è una chiusura di lambda(z){z(x(y))}con-passata nei valori di x ed y f(x,y).

Un modo per utilizzare un'applicazione parziale è definire le funzioni come applicazioni parziali di funzioni generalizzate, come fold :

function fold(combineFunction, accumulator, list) {/* ... */}
function sum     = curry(fold)(lambda(accum,e){e+accum}))(0);
function length  = curry(fold)(lambda(accum,_){1+accum})(empty-list);
function reverse = curry(fold)(lambda(accum,e){concat(e,accum)})(empty-list);

/* ... */
@list = [1, 2, 3, 4]
sum(list) //returns 10
@f = fold(lambda(accum,e){e+accum}) //f = lambda(accumulator,list) {/*...*/}
f(0,list) //returns 10
@g = f(0) //same as sum
g(list)  //returns 10

40
Stai dicendo che l'applicazione parziale è quando curry una funzione e usi alcune, ma non tutte le funzioni risultanti?
SpoonMeiser,

9
più o meno sì. Se fornisci solo un sottoinsieme degli argomenti, otterrai una funzione che accetta il resto degli argomenti
Mark Cidade,

1
La modifica di una funzione f (a, b, c, d) in g (a, b) conta come un'applicazione parziale? O è solo quando applicato alle funzioni al curry? Mi dispiace essere un dolore, ma sto cercando una risposta esplicita qui.
SpoonMeiser,

2
@Mark: Immagino che questo sia solo uno di quei concetti che mettono in luce il pedante in me - ma un appello a fonti autorevoli ha poco da soddisfare, dal momento che tutti sembrano puntarsi l'un l'altro. Wikipedia non è affatto quella che considero una fonte autorevole, ma capisco che è difficile trovare molto altro. Basti dire che penso che entrambi conosciamo ciò di cui parliamo e il potere di ciò, indipendentemente dal fatto che possiamo o meno essere d'accordo (o in disaccordo) sui particolari del vernacolo! :) Grazie Marco!
Jason Bunting,

5
@JasonBunting, per quanto riguarda il tuo primo commento, quello di cui stavi parlando è in calo . Currying sta prendendo una funzione multi-arg come input e restituendo una catena di funzioni 1-arg come output. Il de-currying sta prendendo una catena di funzioni 1-arg come input e restituendo una funzione multi-arg come output. Come elaborato sulla stackoverflow.com/a/23438430/632951
Pacerier

165

Il modo più semplice per vedere come differiscono è prendere in considerazione un esempio reale . Supponiamo di avere una funzione Addche accetta 2 numeri come input e restituisce un numero come output, ad es . Add(7, 5)Restituisce 12. In questo caso:

  • L'applicazione parziale della funzione Addcon un valore 7ci darà una nuova funzione come output. La stessa funzione accetta 1 numero come input e genera un numero. Come tale:

    Partial(Add, 7); // returns a function f2 as output
    
                     // f2 takes 1 number as input and returns a number as output
    

    Quindi possiamo farlo:

    f2 = Partial(Add, 7);
    f2(5); // returns 12;
           // f2(7)(5) is just a syntactic shortcut
    
  • L'attivazione della funzione Addci darà una nuova funzione come output. Tale funzione si prende 1 numero come input e output ancora un'altra nuova funzione. La terza funzione accetta quindi 1 numero come input e restituisce un numero come output. Come tale:

    Curry(Add); // returns a function f2 as output
    
                // f2 takes 1 number as input and returns a function f3 as output
                // i.e. f2(number) = f3
    
                // f3 takes 1 number as input and returns a number as output
                // i.e. f3(number) = number
    

    Quindi possiamo farlo:

    f2 = Curry(Add);
    f3 = f2(7);
    f3(5); // returns 12
    

In altre parole, "curry" e "applicazione parziale" sono due funzioni totalmente diverse. Currying richiede esattamente 1 input, mentre l'applicazione parziale richiede 2 (o più) input.

Anche se entrambi restituiscono una funzione come output, le funzioni restituite hanno forme totalmente diverse, come dimostrato sopra.


25
L'applicazione parziale trasforma una funzione da n-arya (x - n)-ary, curry da n-arya n * 1-ary. Una funzione parzialmente applicata ha una portata ridotta (dell'applicazione), ovvero Add7meno espressiva di Add. Una funzione al curry invece è espressiva quanto la funzione originale.
bob

4
Credo che il tratto più caratteristico sia quando curry f (x, y, z) => R, otteniamo f (x) che restituisce g (y) => h (z) => R, ciascuno consumando un singolo argomento; ma quando applichiamo parzialmente f (x, y, z) come f (x) otteniamo g (y, z) => R, cioè con due argomenti. Se non fosse per quel tratto, potremmo dire che il curry è come un'applicazione parziale a 0 argomenti, lasciando quindi tutti gli argomenti non associati; tuttavia in realtà f () parzialmente applicato a 0 argomenti è una funzione che consuma 3 arg in una volta, a differenza del curry f ().
Maksim Gumerov,

2
Ancora una volta la risposta corretta non è la prima o la più votata: la semplice spiegazione della firma di curry contro parziale alla fine di questa risposta è davvero il modo più semplice per risolvere la domanda.
fnl

2
Cosa significa il commento f2(7)(5) is just a syntactic shortcut? (So ​​molto poco.) Non f2contiene già / "sapere" 7?
Zach Mierzejewski,

@Pacerier, c'è curryun'implementazione da qualche parte (non pensare che sia dentro functools)
alancalvitti

51

Nota: questo è stato tratto da F # Basics, un eccellente articolo introduttivo per gli sviluppatori .NET che si dedicano alla programmazione funzionale.

Currying significa suddividere una funzione con molti argomenti in una serie di funzioni che ciascuna accetta un argomento e alla fine produce lo stesso risultato della funzione originale. Currying è probabilmente l'argomento più impegnativo per gli sviluppatori che non conoscono la programmazione funzionale, soprattutto perché è spesso confuso con un'applicazione parziale. Puoi vedere entrambi al lavoro in questo esempio:

let multiply x y = x * y    
let double = multiply 2
let ten = double 5

Immediatamente, dovresti vedere un comportamento diverso dalla maggior parte delle lingue imperative. La seconda istruzione crea una nuova funzione chiamata double passando un argomento a una funzione che ne accetta due. Il risultato è una funzione che accetta un argomento int e produce lo stesso output come se aveste chiamato moltiplicare con x uguale a 2 e y uguale a quell'argomento. In termini di comportamento, è lo stesso di questo codice:

let double2 z = multiply 2 z

Spesso, la gente dice erroneamente che si moltiplica per formare il doppio. Ma questo è solo un po 'vero. La funzione di moltiplicazione è curry, ma ciò accade quando è definita perché le funzioni in F # sono curry per impostazione predefinita. Quando viene creata la doppia funzione, è più preciso dire che la funzione di moltiplicazione è parzialmente applicata.

La funzione moltiplica è in realtà una serie di due funzioni. La prima funzione accetta un argomento int e restituisce un'altra funzione, legando effettivamente x a un valore specifico. Questa funzione accetta anche un argomento int che puoi considerare come il valore da associare a y. Dopo aver chiamato questa seconda funzione, xey sono entrambi associati, quindi il risultato è il prodotto di xey come definito nel corpo del doppio.

Per creare double, la prima funzione nella catena di funzioni moltiplicare viene valutata per applicare parzialmente moltiplicare. Alla funzione risultante viene assegnato il nome doppio. Quando viene valutato double, usa il suo argomento insieme al valore parzialmente applicato per creare il risultato.


33

Domanda interessante. Dopo un po 'di ricerca, "L'applicazione parziale delle funzioni non sta eseguendo il curry" ha dato la migliore spiegazione che ho trovato. Non posso dire che la differenza pratica sia particolarmente ovvia per me, ma non sono un esperto di FP ...

Un'altra pagina dall'aspetto utile (che confesso di non aver ancora letto completamente) è "Currying e applicazione parziale con chiusure Java" .

Sembra che questa sia una coppia di termini ampiamente confusi, intendiamoci.


5
Il primo collegamento è puntuale sulle differenze. Eccone un altro che ho trovato utile: bit.ly/CurryingVersusPartialApplication
Jason Bunting,

5
Currying ha a che fare con le tuple (trasformare una funzione che accetta un argomento tupla in uno che accetta n argomenti separati e viceversa). L'applicazione parziale è la capacità di applicare una funzione ad alcuni argomenti, producendo una nuova funzione per gli argomenti rimanenti. È facile ricordare se pensi solo a curry == a che fare con le tuple.
Don Stewart,

9
I link @Jon che hai pubblicato sono informativi, ma sarà meglio espandere la tua risposta e aggiungere qualche informazione in più qui.
Zaheer Ahmed,


11
Non riesco a credere di aver ottenuto 20 voti positivi per un paio di link e un'ammissione che non conosci davvero la differenza tra curry e applicazione parziale. Ben fatto, signore.
AlienWebguy,

16

Ho risposto a questo in un'altra discussione https://stackoverflow.com/a/12846865/1685865 . In breve, l'applicazione di funzione parziale riguarda la correzione di alcuni argomenti di una determinata funzione multivariabile per produrre un'altra funzione con meno argomenti, mentre Currying consiste nel trasformare una funzione di N argomenti in una funzione unaria che restituisce una funzione unaria ... [Un esempio di Currying è mostrato alla fine di questo post.]

Currying è principalmente di interesse teorico: si possono esprimere calcoli usando solo funzioni unarie (cioè ogni funzione è unaria). In pratica e come sottoprodotto, è una tecnica che può rendere banali molte applicazioni funzionali parziali (ma non tutte) se il linguaggio ha funzioni curry. Ancora una volta, non è l'unico mezzo per implementare applicazioni parziali. Quindi potresti incontrare scenari in cui l'applicazione parziale viene eseguita in altro modo, ma le persone lo stanno scambiando come Currying.

(Esempio di Currying)

In pratica non si scriverebbe semplicemente

lambda x: lambda y: lambda z: x + y + z

o javascript equivalente

function (x) { return function (y){ return function (z){ return x + y + z }}}

invece di

lambda x, y, z: x + y + z

per amore di Currying.


1
Diresti allora che il curry è un caso specifico di applicazione parziale?
SpoonMeiser

1
@SpoonMeiser, No, il curry non è un caso specifico di applicazione parziale: un'applicazione parziale di una funzione a 2 input non è la stessa di curry della funzione. Vedi stackoverflow.com/a/23438430/632951 .
Pacerier,

10

Currying è una funzione di un argomento che accetta una funzione fe restituisce una nuova funzione h. Si noti che haccetta un argomento Xe restituisce una funzione mappata Ya Z:

curry(f) = h 
f: (X x Y) -> Z 
h: X -> (Y -> Z)

L'applicazione parziale è una funzione di due (o più) argomenti che accetta una funzione fe uno o più argomenti aggiuntivi fe restituisce una nuova funzione g:

part(f, 2) = g
f: (X x Y) -> Z 
g: Y -> Z

La confusione sorge perché con una funzione a due argomenti vale la seguente uguaglianza:

partial(f, a) = curry(f)(a)

Entrambe le parti produrranno la stessa funzione a argomento singolo.

L'uguaglianza non è vera per le funzioni di arity più elevate perché in questo caso il curry restituirà una funzione a argomento singolo, mentre l'applicazione parziale restituirà una funzione a argomento multiplo.

La differenza sta anche nel comportamento, mentre il curry trasforma in modo ricorsivo l'intera funzione originale (una volta per ogni argomento), l'applicazione parziale è solo una sostituzione di un passo.

Fonte: Wikipedia Currying .


8

La differenza tra curry e applicazione parziale può essere meglio illustrata attraverso questo esempio JavaScript seguente:

function f(x, y, z) {
    return x + y + z;
}

var partial = f.bind(null, 1);

6 === partial(2, 3);

L'applicazione parziale risulta in una funzione di minore arità; nell'esempio sopra, fha un'arità di 3 mentre partialha solo un'arità di 2. Ancora più importante, una funzione parzialmente applicata restituirebbe immediatamente il risultato dopo essere stata invocata , non un'altra funzione lungo la catena del curry. Quindi, se stai vedendo qualcosa di simile partial(2)(3), non è un'applicazione parziale in realtà.

Ulteriori letture:


"una funzione parzialmente applicata restituirebbe immediatamente il risultato dopo essere stata invocata" - non è corretto, vero? quando applico parzialmente una funzione, quell'espressione restituisce una funzione, non "un risultato". Ok, probabilmente intendevi che quest'ultima funzione, quando chiamata con gli argomenti rimanenti, restituisce il risultato, a differenza di scavare un passo verso il basso nel curry. Ma nessuno dice in realtà che devi specificare tutti gli argomenti rimanenti: puoi applicare parzialmente il risultato di un'applicazione parziale e che sarà di nuovo una funzione, non un "risultato"
Maksim Gumerov

6

Semplice risposta

Curry: consente di chiamare una funzione, suddividendola in più chiamate, fornendo un argomento per chiamata.

Parziale: consente di chiamare una funzione, suddividendola in più chiamate, fornendo più argomenti per chiamata.


Semplici suggerimenti

Entrambi consentono di chiamare una funzione fornendo meno argomenti (o, meglio, fornendoli cumulativamente). In realtà entrambi vincolano (ad ogni chiamata) un valore specifico a specifici argomenti della funzione.

La vera differenza può essere vista quando la funzione ha più di 2 argomenti.


Semplice e (c) (campione)

(in Javascript)

function process(context, success_callback, error_callback, subject) {...}

perché passare sempre gli argomenti, come il contesto e i callback, se saranno sempre gli stessi? Basta associare alcuni valori per la funzione

processSubject = _.partial(process, my_context, my_success, my_error)

e chiamalo su subject1 e foobar con

processSubject('subject1');
processSubject('foobar');

Comodo, no? 😉

Con il curry dovrai passare un argomento alla volta

curriedProcess = _.curry(process);
processWithBoundedContext = curriedProcess(my_context);
processWithCallbacks = processWithBoundedContext(my_success)(my_error); // note: these are two sequential calls

result1 = processWithCallbacks('subject1');
// same as: process(my_context, my_success, my_error, 'subject1');
result2 = processWithCallbacks('foobar'); 
// same as: process(my_context, my_success, my_error, 'foobar');

disconoscimento

Ho saltato tutte le spiegazioni accademiche / matematiche. Perché non lo so. Forse ha aiutato 🙃


4

Ho avuto questa domanda molto durante l'apprendimento e da allora mi è stata posta molte volte. Il modo più semplice di descrivere la differenza è che entrambi sono uguali :) Mi spiego ... ci sono ovviamente differenze.

Sia l'applicazione parziale che il curry implicano fornire argomenti a una funzione, forse non tutti in una volta. Un esempio abbastanza canonico è l'aggiunta di due numeri. Nello pseudocodice (in realtà JS senza parole chiave), la funzione di base può essere la seguente:

add = (x, y) => x + y

Se volessi una funzione "addOne", potrei applicarla parzialmente o curry:

addOneC = curry(add, 1)
addOneP = partial(add, 1)

Ora usarli è chiaro:

addOneC(2) #=> 3
addOneP(2) #=> 3

Quindi qual è la differenza? Bene, è sottile, ma l'applicazione parziale comporta la fornitura di alcuni argomenti e la funzione restituita eseguirà quindi la funzione principale alla successiva chiamata mentre il curry continuerà ad aspettare fino a quando non avrà tutti gli argomenti necessari:

curriedAdd = curry(add) # notice, no args are provided
addOne = curriedAdd(1) # returns a function that can be used to provide the last argument
addOne(2) #=> returns 3, as we want

partialAdd = partial(add) # no args provided, but this still returns a function
addOne = partialAdd(1) # oops! can only use a partially applied function once, so now we're trying to add one to an undefined value (no second argument), and we get an error

In breve, utilizzare l'applicazione parziale per precompilare alcuni valori, sapendo che la prossima volta che si chiama il metodo, verrà eseguito, lasciando indefiniti tutti gli argomenti non forniti; utilizzare il curry quando si desidera restituire continuamente una funzione parzialmente applicata tutte le volte necessarie per soddisfare la firma della funzione. Un ultimo esempio inventato:

curriedAdd = curry(add)
curriedAdd()()()()()(1)(2) # ugly and dumb, but it works

partialAdd = partial(add)
partialAdd()()()()()(1)(2) # second invocation of those 7 calls fires it off with undefined parameters

Spero che sia di aiuto!

AGGIORNAMENTO: Alcune lingue o implementazioni lib ti permetteranno di passare un arity (numero totale di argomenti nella valutazione finale) all'implementazione parziale dell'applicazione che potrebbe confondere le mie due descrizioni in un pasticcio confuso ... ma a quel punto, le due tecniche sono ampiamente intercambiabile.


3

Per me l'applicazione parziale deve creare una nuova funzione in cui gli argomenti utilizzati sono completamente integrati nella funzione risultante.

La maggior parte dei linguaggi funzionali implementa il curry restituendo una chiusura: non valutare sotto lambda quando applicato parzialmente. Quindi, affinché l'applicazione parziale sia interessante, dobbiamo fare la differenza tra curry e applicazione parziale e considerare l'applicazione parziale come curry più valutazione sotto lambda.


3

Potrei sbagliarmi qui, dal momento che non ho un forte background in matematica teorica o programmazione funzionale, ma dalla mia breve incursione in FP, sembra che il curry tende a trasformare una funzione di N argomenti in N funzioni di un argomento, mentre l'applicazione parziale [in pratica] funziona meglio con le funzioni variadiche con un numero indeterminato di argomenti. Conosco alcuni degli esempi nelle risposte precedenti che sfidano questa spiegazione, ma mi ha aiutato di più a separare i concetti. Considera questo esempio (scritto in CoffeeScript per succinto, mi scuso se confonde ulteriormente, ma per favore chiedi chiarimenti, se necessario):

# partial application
partial_apply = (func) ->
  args = [].slice.call arguments, 1
  -> func.apply null, args.concat [].slice.call arguments

sum_variadic = -> [].reduce.call arguments, (acc, num) -> acc + num

add_to_7_and_5 = partial_apply sum_variadic, 7, 5

add_to_7_and_5 10 # returns 22
add_to_7_and_5 10, 11, 12 # returns 45

# currying
curry = (func) ->
  num_args = func.length
  helper = (prev) ->
    ->
      args = prev.concat [].slice.call arguments
      return if args.length < num_args then helper args else func.apply null, args
  helper []

sum_of_three = (x, y, z) -> x + y + z
curried_sum_of_three = curry sum_of_three
curried_sum_of_three 4 # returns a function expecting more arguments
curried_sum_of_three(4)(5) # still returns a function expecting more arguments
curried_sum_of_three(4)(5)(6) # returns 15
curried_sum_of_three 4, 5, 6 # returns 15

Questo è ovviamente un esempio inventato, ma si noti che l'applicazione parziale di una funzione che accetta un numero qualsiasi di argomenti ci consente di eseguire una funzione ma con alcuni dati preliminari. La valutazione di una funzione è simile ma ci consente di eseguire una funzione di parametro N in pezzi fino a quando, ma solo fino a quando, vengono considerati tutti i parametri N.

Ancora una volta, questa è la mia opinione sulle cose che ho letto. Se qualcuno non è d'accordo, apprezzerei un commento sul perché piuttosto che un downvote immediato. Inoltre, se CoffeeScript è difficile da leggere, visitare coffeescript.org, fare clic su "prova coffeescript" e incollare il mio codice per vedere la versione compilata, che potrebbe (si spera) avere più senso. Grazie!


2

Presumo che la maggior parte delle persone che pongono questa domanda abbiano già familiarità con i concetti di base, quindi non è necessario parlarne. È la sovrapposizione che è la parte confusa.

Potresti essere in grado di utilizzare appieno i concetti, ma li comprendi insieme come questa sfocatura concettuale amorfa pseudo-atomica. Ciò che manca è sapere dove si trova il confine tra di loro.

Invece di definire ciò che ciascuno è, è più facile evidenziare solo le loro differenze: il confine.

Currying è quando si definisce la funzione.

L'applicazione parziale è quando si chiama la funzione.

L'applicazione è matematica per chiamare una funzione.

L' applicazione parziale richiede di chiamare una funzione curry e di ottenere una funzione come tipo restituito.


1

Ci sono altre grandi risposte qui, ma credo che questo esempio (secondo la mia comprensione) in Java potrebbe essere di beneficio per alcune persone:

public static <A,B,X> Function< B, X > partiallyApply( BiFunction< A, B, X > aBiFunction, A aValue ){
    return b -> aBiFunction.apply( aValue, b );
}

public static <A,X> Supplier< X > partiallyApply( Function< A, X > aFunction, A aValue ){
    return () -> aFunction.apply( aValue );
}

public static <A,B,X> Function<  A, Function< B, X >  > curry( BiFunction< A, B, X > bif ){
    return a -> partiallyApply( bif, a );
}

Quindi curry ti offre una funzione a argomento singolo per creare funzioni, in cui l'applicazione parziale crea una funzione wrapper che codifica in modo rigido uno o più argomenti.

Se si desidera copiare e incollare, quanto segue è più rumoroso ma più amichevole con cui lavorare poiché i tipi sono più indulgenti:

public static <A,B,X> Function< ? super B, ? extends X > partiallyApply( final BiFunction< ? super A, ? super B, X > aBiFunction, final A aValue ){
    return b -> aBiFunction.apply( aValue, b );
}

public static <A,X> Supplier< ? extends X > partiallyApply( final Function< ? super A, X > aFunction, final A aValue ){
    return () -> aFunction.apply( aValue );
}

public static <A,B,X> Function<  ? super A,  Function< ? super B, ? extends X >  > curry( final BiFunction< ? super A, ? super B, ? extends X > bif ){
    return a -> partiallyApply( bif, a );
}

Quanto segue mi ha fornito la chiave di comprensione: "Quindi curry ti offre una funzione a argomento singolo per creare funzioni, in cui l'applicazione parziale crea una funzione wrapper che codifica in modo rigido uno o più argomenti".
Roland,

0

Nello scrivere questo, ho confuso curry e non curry. Sono trasformazioni inverse sulle funzioni. In realtà non importa come si chiama, purché si ottenga ciò che rappresentano la trasformazione e il suo inverso.

Il noncurrying non è definito in modo molto chiaro (o meglio, ci sono definizioni "contrastanti" che catturano tutti lo spirito dell'idea). Fondamentalmente, significa trasformare una funzione che accetta più argomenti in una funzione che accetta un singolo argomento. Per esempio,

(+) :: Int -> Int -> Int

Ora, come si trasforma in una funzione che accetta un singolo argomento? Imbrogli, ovviamente!

plus :: (Int, Int) -> Int

Si noti che ora Plus accetta un singolo argomento (composto da due cose). Super!

Che senso ha questo? Bene, se hai una funzione che accetta due argomenti e hai un paio di argomenti, è bello sapere che puoi applicare la funzione agli argomenti e ottenere comunque quello che ti aspetti. E, in effetti, l'idraulico per farlo esiste già, quindi non devi fare cose come la corrispondenza esplicita del modello. Tutto quello che devi fare è:

(uncurry (+)) (1,2)

Allora, qual è l'applicazione con funzione parziale? È un modo diverso di trasformare una funzione in due argomenti in una funzione con un argomento. Funziona diversamente però. Ancora una volta, prendiamo (+) come esempio. Come potremmo trasformarlo in una funzione che accetta un singolo Int come argomento? Noi imbrogliamo!

((+) 0) :: Int -> Int

Questa è la funzione che aggiunge zero a qualsiasi Int.

((+) 1) :: Int -> Int

aggiunge 1 a qualsiasi Int. Ecc. In ciascuno di questi casi, (+) è "parzialmente applicato".

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.