Semplice spiegazione di clojure protocolli

Sto cercando di capire clojure protocolli e i problemi che dovrebbero risolvere. Qualcuno ha una spiegazione chiara di cosa e perché di clojure protocolli?

InformationsquelleAutor Zubair | 2010-12-22

 

2 Replies
  1. 267

    Lo scopo di Protocolli in Clojure è quello di risolvere il Problema Espressione in modo efficiente.

    Così, qual è l’Espressione di Problema? Si riferisce al problema di base di estensibilità: i nostri programmi di manipolare i tipi di dati mediante operazioni. Come i nostri programmi si evolvono, abbiamo bisogno di ampliare con nuovi tipi di dati e di nuove attività. E, in particolare, vogliamo essere in grado di aggiungere nuove operazioni che lavorare con gli attuali tipi di dati, e si desidera aggiungere nuovi tipi di dati che lavoro con le attività esistenti. E vogliamo che questo sia vero estensione, cioè non vogliamo modificare il esistenti programma, vogliamo rispettare gli attuali astrazioni, vogliamo che la nostra estensioni moduli distinti, separati gli spazi dei nomi, compilato separatamente, implementate separatamente separatamente tipo controllato. Vogliamo essere sicure. [Nota: non tutti questi senso in tutte le lingue. Ma, per esempio, l’obiettivo di farli type-safe ha un senso, anche in una lingua come Clojure. Solo perché non possiamo staticamente controllare tipo di sicurezza non significa che vogliamo che il nostro codice in modo casuale pausa, giusto?]

    L’Espressione Problema è, come si fa a fornire tali estensibilità in una lingua?

    Si scopre che per il tipico ingenuo implementazioni procedurali e/o di programmazione funzionale, è molto facile aggiungere nuove operazioni (procedure, funzioni), ma molto difficile aggiungere nuovi tipi di dati, dal momento che praticamente le operazioni di lavorare con i tipi di dati utilizzando una sorta di caso di discriminazione (switch, case, pattern matching) e avete bisogno di aggiungere nuovi casi, vale a dire di modificare il codice esistente:

    func print(node):
      case node of:
        AddOperator => print(node.left) + '+' + print(node.right)
        NotOperator => '!' + print(node)
    
    func eval(node):
      case node of:
        AddOperator => eval(node.left) + eval(node.right)
        NotOperator => !eval(node)

    Ora, se si desidera aggiungere una nuova operazione, diciamo, tipo di controllo, che è facile, ma se si desidera aggiungere un nuovo tipo di nodo, è necessario modificare tutti i pattern matching espressioni in tutte le operazioni.

    E per i tipici ingenuo OO, si ha l’esatto problema opposto: è facile aggiungere nuovi tipi di dati che lavoro con le attività esistenti (sia ereditando o l’override di loro), ma è difficile aggiungere nuove operazioni, dal momento che in pratica significa modificare le esistenti classi/oggetti.

    class AddOperator(left: Node, right: Node) < Node:
      meth print:
        left.print + '+' + right.print
    
      meth eval
        left.eval + right.eval
    
    class NotOperator(expr: Node) < Node:
      meth print:
        '!' + expr.print
    
      meth eval
        !expr.eval

    Qui, l’aggiunta di un nuovo tipo di nodo è facile, perché si ereditare, sostituire o implementare tutte le operazioni necessarie, ma l’aggiunta di una nuova operazione è difficile, perché è necessario aggiungere a tutte foglia classi o a una classe di base, quindi modificare il codice esistente.

    Lingue diverse hanno diversi costrutti per risolvere il Problema Espressione: Haskell ha typeclasses, la Scala è implicita argomenti, la Racchetta ha Unità, Andare dispone di Interfacce, CLOS e Clojure hanno Multimethods. Ci sono anche le “soluzioni” che tentativo per risolverlo, ma non riescono in un modo o nell’altro: Interfacce e Metodi di Estensione in C# e Java, Monkeypatching in Ruby, Python, ECMAScript.

    Nota che Clojure in realtà già ha un meccanismo per la risoluzione di un Problema Espressione: Multimethods. Il problema che OO è con il parlamento che si bundle delle operazioni e dei tipi insieme. Con Multimethods sono separati. Il problema che FP è che essi bundle il funzionamento e il caso di discriminazione insieme. Di nuovo, con Multimethods sono separati.

    Così, proviamo a confrontare i Protocolli con Multimethods, dal momento che entrambi fanno la stessa cosa. O, per dirla in altro modo: Perché i Protocolli se abbiamo già hanno Multimethods?

    La cosa principale Protocolli di offrire oltre Multimethods è di Raggruppamento: è possibile raggruppare più funzioni insieme e dire “questi 3 funzioni insieme forma di Protocollo Foo“. Non si può fare che con Multimethods, che stanno sempre per conto loro. Per esempio, si potrebbe dichiarare che un Stack Protocollo consiste sia un push e un pop funzione insieme.

    Quindi, perchè non aggiungere la capacità di raggruppare Multimethods insieme? C’è un fatto puramente pragmatica ragione, ed è per questo che ho usato la parola “efficienza” nella mia frase introduttiva: le prestazioni.

    Clojure è ospitato lingua. I. e. è specificamente progettato per essere eseguito su un altro lingua della piattaforma. E si scopre che praticamente qualsiasi piattaforma che vuoi Clojure per funzionare su (JVM, CLI, ECMAScript, Objective-C), si è specializzato ad alte prestazioni di supporto per l’invio dei esclusivamente al tipo di argomento. Clojure Multimethods OTOH spedizione su proprietà arbitrarie di tutti gli argomenti.

    Così, Protocolli limitare voi per la spedizione solo sul primo argomento e solo il suo tipo (o come un caso speciale su nil).

    Questa non è una limitazione sull’idea di Protocolli di per sé, è un pragmatico scelta per ottenere l’accesso per l’ottimizzazione delle prestazioni della piattaforma sottostante. In particolare, ciò significa che i Protocolli hanno un banale mappatura per JVM/CLI Interfacce, che li rende molto veloce. Abbastanza veloce, infatti, per essere in grado di riscrivere le parti di Clojure che attualmente sono scritti in Java o C# in Clojure stesso.

    Clojure, in realtà, ha già avuto Protocolli sin dalla versione 1.0: Seq è un Protocollo, per esempio. Ma fino a 1.2, non si poteva scrivere Protocolli in Clojure, si doveva scrivere nella lingua del paese ospitante.

    • Grazie per una risposta esauriente, ma puoi chiarire il tuo punto, che riguarda Ruby. Suppongo che la capacità di (ri)definire i metodi di una classe (ad esempio, Stringa, Fixnum) in Ruby è analogia di Clojure è defprotocol.
    • Un ottimo articolo sull’Espressione del Problema e clojure protocolli – ibm.com/developerworks/library/j-clojure-protocols
    • Scusate il post un commento su una vecchia risposta, ma potrebbe spiegare perché le estensioni e le interfacce (C#/Java) non sono una buona soluzione per l’Espressione del Problema?
    • Java non ha le estensioni, nel senso che il termine è usato qui.
    • Ruby ha perfezionamenti che rende monkey patching obsoleti.
    • (Per inciso: Quale linguaggio di programmazione hai usato nell’esempio di codice?)

  2. 64

    Trovo più utile pensare di protocolli come concettualmente simile a una “interfaccia” in linguaggi object oriented come Java. Un protocollo definisce un insieme astratto di funzioni che possono essere implementate in un modo concreto per un determinato oggetto.

    Un esempio:

    (defprotocol my-protocol 
      (foo [x]))

    Definisce un protocollo con una funzione che si chiama “pippo” che agisce su di un solo parametro “x”.

    È quindi possibile creare strutture di dati che implementa il protocollo, ad esempio,

    (defrecord constant-foo [value]  
      my-protocol
        (foo [x] value))
    
    (def a (constant-foo. 7))
    
    (foo a)
    => 7

    Notare che qui l’oggetto che implementa il protocollo è passato come primo parametro x – un po ‘ come un implicito “questo” parametro in linguaggi object oriented.

    Uno dei molto potente e utile funzionalità dei protocolli è che si può estendere a oggetti anche se l’oggetto non è stato originariamente progettato per il supporto del protocollo. ad esempio, è possibile estendere il protocollo di cui sopra per il java.lang.Classe String se ti piace:

    (extend-protocol my-protocol
      java.lang.String
        (foo [x] (.length x)))
    
    (foo "Hello")
    => 5
    • > come implicito in “questo” parametro in linguaggio object oriented ho notato che il var passati per le funzioni di protocollo è spesso chiamato this in Clojure codice di troppo.
    • vimeo.com/11236603

Lascia un commento