Perché Clojure trascura il principio di accesso uniforme?

6

Il mio background è Ruby, C #, JavaScript e Java. E ora sto imparando Clojure. Ciò che mi fa sentire a disagio in seguito è che il Clojure idiomatico sembra trascurare il principio di accesso uniforme ( wiki , c2 ) e quindi in una certa misura anche l'incapsulamento suggerendo di usare le mappe invece di una sorta di" strutture "o" classi ". Sembra un passo indietro. Quindi un paio di domande, se qualcuno ha informato:

  • Quali altre decisioni di progettazione / preoccupazioni sono in conflitto con e perché è stato considerato meno importante?
  • Avevi la stessa preoccupazione e come andava a finire quando passavi da una lingua che supportava UAP di default (Ruby, Eiffel, Python, C #) a Clojure?
posta Alexey 15.10.2013 - 02:35
fonte

2 risposte

14

In primo luogo, un disclaimer: non ho una profonda familiarità con il principio di accesso uniforme, quindi potresti voler prendere questo con un pizzico di sale. Detto questo, direi che Clojure osserva un principio di accesso uniforme: chiamate di funzione.

La citazione chiave su Wikipedia sembra essere che "tutti i servizi offerti da un modulo dovrebbero essere disponibili attraverso una notazione uniforme, che non tradisce se sono implementati attraverso lo storage o attraverso il calcolo", e questo è esattamente il caso con le chiamate di funzione in Clojure. Infatti, tutto in Clojure (e altri Lisps) è una chiamata di funzione ad eccezione di moduli speciali e macro - e le macro in realtà sono funzioni, con la differenza che operano sul codice sorgente. Puoi anche specificare il comportamento delle chiamate di funzione per i tuoi tipi implementando clojure.lang.IFn :

(deftype Invokable []
  clojure.lang.IFn
  (invoke [this]
    :was-invoked))

(def invokable (Invokable.))
(invokable)                  ;=> :was-invoked

Potresti avere in mente l'uso di parole chiave come funzioni, che sono più specificamente associate con le hash-map, ma puoi effettivamente implementare quel comportamento anche per tipi diversi. Ecco un esempio stupido in cui l'accesso a parole chiave legge un file (con lo stesso nome della parola chiave) dalla tua home directory o restituisce un valore predefinito se tale file non esiste:

(ns weird.example
  (:require [clojure.java.io :as io]))

(deftype WeirdKlass []
  clojure.lang.ILookup
  (valAt [this k not-found]
    (let [home (System/getProperty "user.home")
          file (io/file home (name k))]
     (if (.isFile file)
       (slurp file)
       not-found)))
  (valAt [this k]
    (.valAt this k nil)))

(def klass (WeirdKlass.))

;; assuming you have a file at $HOME/testfile
(:testfile klass)        ;=> "test content!"
(:blah klass)            ;=> nil
(:blah klass :not-found) ;=> :not-found

Aggiornamento basato sul commento di Alexey

Penso di avere un'idea migliore di ciò che intendi ora ma in pratica non ho trovato che fosse un inconveniente. Quando vuoi o hai bisogno di un livello di riferimento / astrazione tra la funzionalità del tuo codice e la rappresentazione dei dati sottostanti, il modo per ottenerlo è: funzioni!

Il modo più semplice sarebbe definire full-name come una funzione che capita di utilizzare la ricerca di parole chiave, ma che potrebbe essere successivamente modificata per calcolare il nome (senza che i clienti ne sappiano o si preoccupino):

;; For now
(defn full-name [person]
  (:full-name person))

;; Maybe later
(defn full-name [person]
  (str (:first-name person)
       " "
       (:last-name person)))

Naturalmente, puoi anche utilizzare record e protocolli:

(defprotocol IPerson
  (full-name [p])
  (first-name [p])
  (last-name [p]))

(defrecord Person1 [name]
  IPerson
  (full-name [_]
    name)
  (first-name [_]
    (first (.split name " ")))
  (last-name [_]
    (last (.split name " "))))

(def person1 (->Person1 "Joe Schmoe"))
(full-name person1)  ;=> "Joe Schmoe"
(first-name person1) ;=> "Joe"
(last-name person1)  ;=> "Schmoe"

(defrecord Person2 [f-name l-name]
  IPerson
  (full-name [_]
    (str f-name " " l-name))
  (first-name [_]
    f-name)
  (last-name [_]
    l-name))

(def person2 (->Person2 "Joe" "Schmoe"))
(full-name person2)  ;=> "Joe Schmoe"
(first-name person2) ;=> "Joe"
(last-name person2)  ;=> "Schmoe"

Il lato grande ( molto grande, a mio avviso) dell'usare strutture di dati "semplici" quando possibile è che significa che voi e i vostri utenti avete a disposizione la gamma completa di operazioni di mappa e sequenza di Clojure . Alan Perlis ha espresso bene questa idea quando ha affermato che " è meglio avere 100 funzioni operative su una struttura dati di 10 funzioni su 10 strutture di dati . "

    
risposta data 15.10.2013 - 05:04
fonte
6

In breve: non è considerato abbastanza prezioso da disturbare l'introduzione. Sostituisci una rappresentazione e riduci il fattore / modifica il codice dipendente quando ti serve.

Da un'intervista:

Fogus: Following that idea—some people are surprised by the fact that Clojure does not engage in data-hiding encapsulation on its types. Why did you decide to forgo data-hiding?

Hickey [creator of Clojure]: Let’s be clear that Clojure strongly emphasizes programming to abstractions. At some point though, someone is going to need to have access to the data. And if you have a notion of “private”, you need corresponding notions of privilege and trust. And that adds a whole ton of complexity and little value, creates rigidity in a system, and often forces things to live in places they shouldn’t. This is in addition to the other losing that occurs when simple information is put into classes. To the extent the data is immutable, there is little harm that can come of providing access, other than that someone could come to depend upon something that might change. Well, okay, people do that all the time in real life, and when things change, they adapt. And if they are rational, they know when they make a decision based upon something that can change that they might in the future need to adapt. So, it’s a risk management decision, one I think programmers should be free to make.

If people don’t have the sensibilities to desire to program to abstractions and to be wary of marrying implementation details, then they are never going to be good programmers.

    
risposta data 03.11.2013 - 11:55
fonte

Leggi altre domande sui tag