2016-02-11 23 views
10

Spesso mi trovo in una situazione in cui io davvero non importa se ho un vettore o di una mappa:Perché le chiavi e le val non funzionano sui vettori?

[:foo :bar :baz :qux] 
{0 :foo, 1 :bar, 2 :baz, 3 :qux} 

Le funzioni importanti (get, assoc, ecc) lavorare su entrambi. Alcuni, come dissoc, non funzionano sui vettori, ma hanno buoni motivi per non farlo.

Tuttavia, ho semplicemente non capisco perché keys e vals lavoro sulle mappe e non su vettori. C'è qualche buona ragione per cui non sono implementati qualcosa di simile (o forse con una soluzione polimorfa più elegante, invece)?

(defn keys [m] 
    (if (vector? m) 
    (seq (range (count m))) 
    (clojure.lang.RT/keys m))) 

(defn vals [m] 
    (if (vector? m) 
    (seq m) 
    (clojure.lang.RT/vals m))) 

Se non v'è alcuna buona ragione, come posso fare per cercare di ottenere questo implementato nella norma Clojure?

risposta

5

Penso che sia un'idea valida che ha il merito.

La domanda su quali funzioni dovrebbero funzionare e su quale dovrebbe essere un errore è difficile da tracciare una linea. Ad una estremità una lingua può essere eccessivamente permissiva (JavaScript?) Al punto che ti permette di farti del male. Dall'altro lato può essere così prescrittivo che ti rimane senza potenti astrazioni che compongono (C?).

In questo caso particolare stiamo eliminando la possibilità di rilevare un errore in cui si sta scrivendo il codice che si aspetta una mappa, ma viene passato un vettore; contro il vantaggio di poter scrivere più codice generico. Personalmente mi piace il fatto che venga trattato come un errore perché a volte faccio passare il tipo sbagliato di raccolta a una funzione e preferisco farlo saltare in aria piuttosto che fare qualcosa.

Trovo confuso chiamare keys su un vettore perché un vettore non ha chiavi, è indicizzabile. Per me sono cose diverse. Quindi se scrivo codice che chiama keys su un vettore, probabilmente è un errore. Se davvero voglio un intervallo che abbia le stesse dimensioni del conteggio del vettore, lo scriverei esplicitamente. E se volessi avere una funzione che potesse gestire sia le mappe che i vettori, userei un condizionale di tipo per scegliere le chiavi o il conteggio degli intervalli. Ovviamente queste sono solo le mie preferenze, ma per me sono un motivo significativo per non voler che keys funzioni sui vettori. In particolare, la ragione è che voglio rilevare gli errori.

Tuttavia è del tutto comprensibile che si preferisca che lavori con i vettori, visto che sono associativi per indice.

Per quanto riguarda lo dissoc, qualcun altro potrebbe chiedere perché no? (dissoc [1 2 3] 0) -> [2 3]) C'è un problema di prestazioni perché non è possibile rimuovere elementi in O (1), beh, non proprio se Clojure ha adottato rbb-vector. A volte è molto conveniente quando devi fare quell'operazione. È something people need to do, ed è piuttosto brutto e opaco in Clojure! Nessuno di noi vuole che questa sia una caratteristica, ma scommetto che sarebbe davvero elegante in alcune situazioni. Ma non si tratta di un limite tecnico, lo preferiamo in questo modo.

Clojure ha un processo aperto contribution che si riduce a: Discutere di ciò che si sta tentando di fare con gli altri sul gruppo Clojure Dev di Google. È probabile che siano in grado di offrire commenti e suggerimenti che si tradurranno in una modifica di qualità superiore e in un processo di invio più fluido. Una volta inviata la CA, puoi inviare patch tramite JIRA.

Chiunque può inviare un bug o una richiesta di miglioramento a Clojure. Chiunque abbia firmato l'accordo con il contributore può fornire patch o lavorare per migliorare i biglietti. Agli screener è stata concessa la possibilità di spostare i ticket attraverso (alcune delle) fasi del processo. BDFL - Rich Hickey è il creatore e Dictator benevolo per la vita di ciò che va in Clojure. Stuart Halloway ha anche un livello speciale di accesso e tipicamente commette patch per Clojure.

Se ritieni che questo sia un buon cambiamento per Clojure, ti consiglio di discuterne prima nel Gruppo Clojure per raccogliere un po 'di supporto per l'idea e poi portarla al Clojure Dev Group. Generalmente la tua idea sarà meglio accolta quando ci sono artefatti di supporto come "Ecco un caso di grande utilità dove dimostra valore" e "Ecco alcune discussioni in cui altre persone desiderano la stessa proposta di valore".

+3

Sono d'accordo con il sentimento, ma penso che il problema più grande che questo comporterebbe è che renderebbe le rappresentazioni vettoriali delle mappe in questione, ad esempio '(seq {: foo: bar, 1 2}) => [[: foo: bar] [1 2] ' Dato questo cambiamento, mi aspetterei ' (in {} [: foo: bar 1 2]) => {0: foo, 1: bar, 2 1, 3 2 } che è in realtà piuttosto confuso IMO –

+1

Giusto per chiarire, il problema con il vettore 'dissoc' come lo hai proposto è che' (dissoc [1 2 3] 0) 'restituire' [2 3] ', che sarebbe equivalente alla mappa '{0 2, 1 3}'. D'altra parte, '(dissoc {0 1, 1 2, 2 3} 0)' restituire '{1 2, 2 3}', che è una mappa completamente diversa. –

+1

Diversi sicuri, ma perché questo è un problema? –

1

Come sfondo re il modello di raccolta: http://insideclojure.org/2016/03/16/collections/

Tasti e vals sono entrambe definite come funzioni che accettano un seqable delle voci della mappa. Data questa definizione, non è facilmente possibile ampliarla per prendere anche una collezione con il tratto associativo, perché una raccolta come il vettore non produrrà un seq di voci, ma piuttosto un seq di valori vettoriali. La possibilità di seq di mappare le voci è qualcosa fornito solo dalle mappe.

Sì, sarebbe possibile eseguire una funzione che ha funzionato su entrambi, ma penso che così facendo si interromperà il contratto esistente di queste funzioni, non lo si estenderà.

Alla domanda di progettazione se avrebbe dovuto essere fatto in questo modo o meno in primo luogo, è più difficile per me dire.

Problemi correlati