Cosa sono i combinatori e come vengono applicati ai progetti di programmazione? (spiegazione pratica)


51

Cosa sono i combinatori?

Sto cercando:

  • una spiegazione pratica
  • esempi di come vengono utilizzati
  • esempi di come i combinatori migliorano la qualità / generalità del codice

Non sto cercando:

  • spiegazioni di combinatori che non mi aiutano a svolgere il lavoro (come il combinatore a Y)

I combinatori sono simili agli "avverbi", le funzioni che accettano funzioni e restituiscono altre funzioni. Possono aiutare a rimuovere la duplicazione del codice perché non è necessario tra le variabili. Alcuni utili sono due (f) = \ x -> f (f (x)), capovolgi (op) -> \ xy -> y op x, (.) Come in (fg) x = f (g (x ( )), ($) può aiutare con la mappa (chiamata <$> in infix) come in ($ 5) <$> [(+1), (* 2)] = [6, 10], il curry può essere usato in Lisp / Python / JavaScript per un'applicazione parziale e non confuso può essere usato per funzioni che richiedono record (tuple) in Haskell. Quando x |> f = fa, x |> (lunghezza &&& sum) |> non confuso (/) è la media.
aoeu256,

Risposte:


51

Da un punto di vista pratico i combinatori sono tipi di costrutti di programmazione che ti permettono di mettere insieme pezzi di logica in modi interessanti e spesso avanzati. In genere il loro utilizzo dipende dalla possibilità di essere in grado di impacchettare il codice eseguibile in oggetti, spesso chiamati (per ragioni storiche) funzioni lambda o espressioni lambda, ma il chilometraggio può variare.

Un semplice esempio di un (utile) combinatore è quello che accetta due funzioni lambda senza parametri e ne crea una nuova che le esegue in sequenza. L'attuale combinatore si presenta in uno pseudocodice generico come questo:

func in_sequence(first, second):
  lambda ():
    first()
    second()

La cosa cruciale che rende questo un combinatore è la funzione anonima (funzione lambda) sulla seconda riga; quando chiami

a = in_sequence(f, g)

l'oggetto risultante a non è il risultato dell'esecuzione prima di f () e poi g (), ma è un oggetto che è possibile chiamare in seguito per eseguire f () e g () in sequenza:

a() // a is a callable object, i.e. a function without parameters

Allo stesso modo puoi avere un combinatore che esegue due blocchi di codice in parallelo:

func in_parallel(first, second):
  lambda ():
    t1 = start_thread(first)
    t2 = start_thread(second)
    wait(t1)
    wait(t2)

E poi di nuovo,

a = in_parallel(f, g)
a()

La cosa interessante è che 'in_parallel' e 'in_sequence' sono entrambi combinatori con lo stesso tipo / firma, cioè entrambi prendono due oggetti funzione senza parametri e ne restituiscono uno nuovo. Puoi effettivamente scrivere cose come

a = in_sequence(in_parallel(f, g), in_parallel(h, i))

e funziona come previsto.

Fondamentalmente così i combinatori ti consentono di costruire il flusso di controllo del tuo programma (tra le altre cose) in modo procedurale e flessibile. Ad esempio, se si utilizza il combinatore in_parallel (..) per eseguire il parallelismo nel programma, è possibile aggiungere il debug relativo a quello all'implementazione del combinatore in_parallel stesso. Successivamente, se sospetti che il tuo programma abbia un bug relativo al parallelismo, puoi semplicemente reimplementare in_parallel:

in_parallel(first, second):
  in_sequence(first, second)

e con un colpo solo, tutte le sezioni parallele sono state convertite in sequenziali!

I combinatori sono molto utili se usati correttamente.

Il combinatore Y, tuttavia, non è necessario nella vita reale. È un combinatore che ti consente di creare funzioni auto-ricorsive e puoi crearle facilmente in qualsiasi linguaggio moderno senza il combinatore Y.


9

È sbagliato combinare il marchio Y come qualcosa che non "aiuterà a portare a termine il lavoro". L'ho trovato molto utile in diverse occasioni. Il caso più ovvio è quando devi avviare rapidamente un linguaggio interpretato incorporato. Se si fornisce un insieme minimo di primitive, vale a dire sequence, select, call, conste closure allocation, è già sufficiente per la costruzione di un linguaggio complesso arbitrario completa. Non è necessario alcun supporto speciale per la ricorsione: può essere aggiunto tramite un combinatore a punto fisso. Altrimenti avrai bisogno di primitivi molto più complicati.

Un altro caso ovvio per i combinatori è l'offuscamento. Un codice tradotto nel calcolo SKI è praticamente illeggibile. Se devi davvero offuscare un'implementazione di un algoritmo, considera l'utilizzo di combinatori, ecco un esempio .

E, naturalmente, i combinatori sono uno strumento importante per l'implementazione di linguaggi funzionali. L'approccio più semplice (come nell'esempio sopra) è tramite SKI o calcolo equivalente. I supercombinatori sono utilizzati in alcune altre implementazioni. Questo libro ne parla in modo approfondito.

Questo è uno scherzo , ma uno scherzo che merita una lettura molto attenta, dal momento che molte tecniche di programmazione arcana e teorie sono trattate lì.


1
@MattFenwick, spesso è necessario inserire un semplice interprete incorporato dove non ti aspetteresti mai. Ad esempio, nel mio caso era un linguaggio che dovevo progettare per estendere un protocollo di comunicazione. L'IPC semplice non era abbastanza, quindi il protocollo doveva essere eseguibile.
SK-logic,

@MattFenwick, come per la tua domanda: puoi provare a scrivere del codice in APL o J. I combinatori sono essenziali lì, quindi avrai un'idea di come applicarli correttamente. Inoltre, leggere su uno stile senza punti può aiutare: en.wikipedia.org/wiki/Tacit_programming
SK-logic

7

Scavando un po ', ho trovato una domanda StackOverflow, una buona spiegazione di "Combinatori" (per i non matematici) che è un cugino stretto di questa domanda. Una delle risposte ha indicato il blog di Reginald Braithwaite, Homoiconic , che collega diversi esempi utili di combinatori in codice (ad esempio il combinatore K , implementato dal Object#tapmetodo di Ruby - leggi la pagina per esempi del perché è utile).

La pagina di Wikipedia sulla logica combinatoria descrive i combinatori in modo più globale.


Questo affronta il secondo punto della mia domanda. Grazie per l'esempio!

1
Questo post ha alcuni buoni collegamenti ma in realtà non risponde direttamente alla domanda. Le risposte devono essere complete da sole e utilizzare i collegamenti come riferimenti se necessario.
Aaronaught,
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.