Va bene definire un metodo [] in NilClass di Ruby?

7

Ruby per impostazione predefinita non include il metodo [] per NilClass

Ad esempio, per verificare se foo["bar"] esiste quando foo può essere nullo, devo fare:

foo = something_that_may_or_may_not_return_nil
if foo && foo["bar"]
  # do something with foo["bar"] here
end

Se definisco questo metodo:

class NilClass
  def [](arg)
    nil
  end
end

Qualcosa del genere lo renderebbe possibile, anche se foo è nullo:

if foo["bar"]
  # do something with foo["bar"]
end

O anche:

if foo["bar"]["baz"]
  # do something with foo["bar"]["baz"] here
end

Domanda: È una buona idea o c'è qualche ragione per cui ruby non include questa funzionalità di default?

EDIT di luglio 2017: il mio reclamo principale è stato l'ingombrante% di idioma% di carattere necessario per accedere ai valori annidati quando in qualsiasi momento il valore potrebbe essere foo && foo["bar"] && foo ["bar"]["baz"] . A partire da Ruby 2.3, Matrice # array e Hash#dig esiste , che risolve la mia preoccupazione. Ora posso usare il seguente idioma!

if foo && foo.dig("bar", "baz")
  # do something
end

# Note that errors will still occur if a value isn't what you expect. e.g.
foo = { "a" => { "b" => "c" } }
foo && foo.dig("a", "b", "c") # raises TypeError: String does not have #dig method
    
posta silasjmatson 19.10.2012 - 21:33
fonte

6 risposte

3

I valori Nullable devono essere risolti attentamente, non è la stessa variabile che ha sempre un certo tipo di altri che a volte ha questo tipo, a volte è nullo. I linguaggi funzionali di solito sono i più attenti a questo riguardo e hanno tipi personalizzati per gestirlo ( Maybe in Haskell, Option in Ocaml / F # / Scala, ...).

In Ruby non estenderei NilClass come proposto, potrebbe portare a codice buggy (nota che stai modificando un oggetto base usato da ogni libreria, è un disastro che aspetta di accadere). Tuttavia, è comprensibile che tu voglia che alcuni pattern interagiscano sinteticamente con valori che possono essere nil per evitare di sporcare il codice con i condizionali.

Un'opzione è il modello proxy maybe proposto in Ick :

hash.maybe[key1].maybe[key2].maybe[key3]

O la sintassi del blocco:

hash.maybe { |h| h[key1][key2][key3] }
    
risposta data 19.10.2012 - 23:57
fonte
1

In qualche codice rubino che ho dovuto modificare, ho aggiunto isFalse o qualcosa di quella natura all'oggetto nil per rendere alcune parti non insoddisfatte l'una dell'altra.

Le classi aperte non sono la mia cosa preferita e apportare un tale cambiamento alla classe nil non è un piccolo cambiamento. È un cambiamento significativo perché può cambiare l'intero funzionamento dell'intero sistema.

Puoi farlo. Assicurati di eseguire il test di regressione tutto e documenta tutte le modifiche agli oggetti di base in modo che le altre persone non si sorprendano quando improvvisamente si comporta in modo inaspettato.

Riguardo al perché questa non è la funzionalità di default - questo è il regno della speculazione nella mente di Matz.

    
risposta data 19.10.2012 - 23:01
fonte
1

Se questo è per una condizione specifica, e sospetto che lo sia, delegare il comportamento. Verifica che l'oggetto sia definito, quindi controlla se è nullo e lavora di conseguenza.

Conserverà il comportamento previsto nel core e ti darà il comportamento specifico modificato quando lo desideri.

Se in un secondo momento è necessario modificare il comportamento per includere False, hai un posto dove andare per rendere conto della modifica necessaria.

Cambiare il comportamento di base per un'esigenza specifica dovrebbe sempre essere considerato con attenzione.

    
risposta data 02.11.2012 - 14:41
fonte
1

Perché something_that_may_or_may_not_return_nil deve restituire nil ? E se non vuoi cambiare quel metodo, basta avvolgerlo in un altro metodo che restituisce un hash vuoto se il metodo spostato restituisce nil .

Come per la nidificazione, puoi utilizzare Hash 's default_proc per restituire gli hash vuoti quando non si trova una chiave:

# self-referential, to provide arbitrary depth
default_proc = ->(h,k){ Hash.new(&default_proc) }
some_hash.default_proc = default_proc

Quanto sopra ti consente di accedere a voci profondamente indefinite senza apportare alcuna modifica. Ma devi anche usare empty? invece di considerare il risultato come un semplice booleano.

some_hash[:a][:b][:c].empty?

Se vuoi una soluzione che memorizzi anche i nuovi hash vuoti nel genitore, usa invece default_proc :

default_proc = ->(h,k){ h[k] = {}.tap{|newh| newh.default_proc = default_proc } }
# This will auto-vivify:
some_hash = {}
some_hash.default_proc = default_proc
some_hash[:a][:b][:c]
some_hash  # {:a=>{:b=>{:c=>{}}}}

Inoltre, Rails fornisce il metodo try , che chiama il metodo named solo se il ricevitore non è nil . Sì, è molto più brutto.

foo.try(:[], 'bar').try(:[], 'baz')

Potrebbe essere meglio avvolgere l'hash arbitrariamente profondo in una classe che nasconde la bruttezza. Ad esempio, la voce ['bar']['baz'] ha un significato nel mondo reale? In tal caso, puoi mettere un metodo su quella classe e far sì che il metodo raggiunga tale profondità senza che il chiamante debba preoccuparsi di ciò.

    
risposta data 31.01.2014 - 00:25
fonte
0

Ti consigliamo di utilizzare Hash # fetch per accedere alle voci di hash quando non sei sicuro che saranno presenti. In questo modo, se la chiave richiesta non viene trovata, otterrai un KeyError che puoi gestire in modo appropriato. Quando accedete ciecamente a chiavi inesistenti ottenete errori confusamente confusi che finiscono con il vostro tentativo di agire su nils imprevisti in posti strani in altre parti del vostro codice.

> { a: { b: 1 } }[:a][:z].to_s
=> ""
> { a: { b: 1 } }.fetch(:z).fetch(:b).to_s
KeyError: key not found: :z

A parte: ho sviluppato il mio gusto per Hash#fetch dopo aver guardato Avdi Grimm's RubyTapas Episodio # 8. Io pago soldi per questo e finora ne vale la pena.

    
risposta data 04.12.2012 - 03:32
fonte
0

Ho scritto una gemma progettata proprio per questo caso. Mi sono imbattuto in questa domanda quando cercavo una soluzione, quindi ho pensato di postare qui su ciò che ho trovato.

La gemma si chiama Dottie ed è disponibile all'indirizzo link .

Ecco un breve esempio di come è stato progettato per essere utilizzato:

car = {
  'type' => {
    'make' => 'Tesla',
    'model' => 'Model S'
  }
}

# normal Hash access assumes a certain structure
car['type']['make']     # => "Tesla"
car['specs']['mileage'] # => # undefined method '[]' for nil:NilClass

# Dottie lets you reach deep into a hash with no assumptions
d = Dottie(car)
d['type.make']     # => "Tesla"
d['specs.mileage'] # => nil

C'è molta più documentazione sulla pagina del progetto GitHub. Spero che questo aiuti qualcuno.

    
risposta data 08.05.2014 - 19:48
fonte

Leggi altre domande sui tag