Mengapa objek Regexp dianggap "palsu" di Ruby?

16

Ruby memiliki gagasan universal tentang " kebenaran " dan " kepalsuan ".

Ruby memang memiliki dua kelas khusus untuk objek Boolean, TrueClassdan FalseClass, dengan instance tunggal dilambangkan oleh variabel khusus truedan false, masing-masing.

Namun, kebenaran dan kepalsuan tidak terbatas pada contoh dua kelas itu, konsepnya bersifat universal dan berlaku untuk setiap objek tunggal di Ruby. Setiap objek adalah baik truthy atau falsy . Aturannya sangat sederhana. Secara khusus, hanya dua objek yang palsu :

Setiap objek tunggal lainnya adalah truthy . Ini termasuk objek genap yang dianggap falsy dalam bahasa pemrograman lain, seperti

Aturan-aturan ini dibangun ke dalam bahasa dan tidak dapat didefinisikan pengguna. Tidak ada to_boolkonversi tersirat atau yang serupa.

Berikut ini kutipan dari Spesifikasi Bahasa Ruby ISO :

6.6 Nilai Boolean

Suatu objek diklasifikasikan menjadi objek trueish atau objek falseish .

Hanya false dan nil yang merupakan objek falseish. false adalah satu-satunya instance dari kelas FalseClass(lihat 15.2.6), di mana ekspresi-palsu mengevaluasi (lihat 11.5.4.8.3). nil adalah satu-satunya instance dari kelas NilClass(lihat 15.2.4), yang dievaluasi nil-ekspresi (lihat 11.5.4.8.2).

Objek selain false dan nil diklasifikasikan menjadi objek trueish. true adalah satu-satunya instance dari kelas TrueClass(lihat 15.2.5), yang dievaluasi oleh ekspresi-sejati (lihat 11.5.4.8.3).

Ruby / Spec yang dapat dieksekusi tampaknya setuju :

it "considers a non-nil and non-boolean object in expression result as true" do
  if mock('x')
    123
  else
    456
  end.should == 123
end

Menurut dua sumber, saya akan berasumsi bahwa Regexps juga truthy , tetapi menurut tes saya, mereka tidak:

if // then 'Regexps are truthy' else 'Regexps are falsy' end
#=> 'Regexps are falsy'

Saya menguji ini pada YARV 2.7.0-preview1 , TruffleRuby 19.2.0.1 , dan JRuby 9.2.8.0 . Ketiga implementasi setuju satu sama lain dan tidak setuju dengan Spesifikasi Bahasa Ruby ISO dan interpretasi saya terhadap Ruby / Spec.

Lebih tepatnya, Regexpbenda-benda yang merupakan hasil dari evaluasi Regexp literal adalah falsy , sedangkan Regexpbenda-benda yang merupakan hasil dari beberapa ekspresi lainnya adalah truthy :

r = //
if r then 'Regexps are truthy' else 'Regexps are falsy' end
#=> 'Regexps are truthy'

Apakah ini bug, atau perilaku yang diinginkan?

Jörg W Mittag
sumber
Hal yang menarik adalah itu Regex.new("a")adalah kebenaran.
mrzasa
!!//itu salah tetapi !!/r/itu benar. Memang aneh.
maks
@max !!/r/menghasilkan falseuntuk saya menggunakan (RVM) Ruby 2.4.1.
3limin4t0r
Maaf @ 3limin4t0r saya yang buruk. Kamu benar. Saya pasti telah melakukan sesuatu yang sangat bodoh seperti meninggalkan tanda seru.
maks
2
Sebuah hipotesis, saya pikir //dalam if // thendiartikan sebagai tes (jalan pintas untuk if //=~nil then) (yang selalu salah pola apa pun) dan bukan sebagai contoh Regexp.
Casimir et Hippolyte

Jawaban:

6

Ini bukan bug. Apa yang terjadi adalah Ruby menulis ulang kode itu

if /foo/
  whatever
end

menjadi efektif

if /foo/ =~ $_
  whatever
end

Jika Anda menjalankan kode ini dalam skrip normal (dan tidak menggunakan -eopsi) maka Anda akan melihat peringatan:

warning: regex literal in condition

Ini mungkin agak membingungkan sebagian besar waktu, itulah sebabnya peringatan diberikan, tetapi dapat berguna untuk satu baris menggunakan -eopsi. Misalnya Anda dapat mencetak semua baris yang cocok dengan regexp yang diberikan dari file dengan

$ ruby -ne 'print if /foo/' filename

(Argumen default untuk printini $_juga.)

matt
sumber
Lihat juga -n, -p, -adan -lpilihan, serta beberapa metode Kernel yang hanya tersedia bila -natau -pdigunakan ( chomp, chop, gsubdan sub).
matt
Ada juga bagian kedua dari pengurai di mana peringatan itu dikeluarkan. Saya tidak tahu apa yang terjadi di sana.
matt
Saya percaya bahwa "bagian kedua" adalah yang benar-benar berlaku untuk pertanyaan ini. NODE_LITdengan tipe T_REGEXP. Yang Anda posting dalam jawaban Anda adalah untuk literal dinamisRegexp , yaitu Regexpliteral yang menggunakan interpolasi, misalnya /#{''}/.
Jörg W Mittag
@ JörgWMittag, saya pikir Anda benar. Mengaduk-aduk di dalam kompiler dan bytecode yang dihasilkan, sepertinya dalam kasus regexp dinamis pohon parse ditulis ulang untuk secara eksplisit menambahkan $_sebagai simpul yang ditangani oleh kompiler seperti biasa, sementara dalam kasus statis semuanya ditangani oleh penyusun. Yang memalukan bagi saya karena "hei, Anda dapat melihat di mana pohon parse ditulis ulang di sini" membuat jawaban yang bagus.
matt
4

Ini adalah hasil dari (sejauh yang saya tahu) fitur tidak terdokumentasi dari bahasa ruby, yang paling baik dijelaskan oleh spesifikasi ini :

it "matches against $_ (last input) in a conditional if no explicit matchee provided" do
  -> {
    eval <<-EOR
    $_ = nil
    (true if /foo/).should_not == true
    $_ = "foo"
    (true if /foo/).should == true
    EOR
  }.should complain(/regex literal in condition/)
end

Anda umumnya dapat menganggap $_sebagai "string terakhir dibaca oleh gets"

Untuk membuat masalah lebih membingungkan, $_(bersama dengan $-) bukan variabel global; ini memiliki ruang lingkup lokal .


Saat skrip ruby ​​dimulai $_ == nil,.

Jadi kodenya:

// ? 'Regexps are truthy' : 'Regexps are falsey'

Ditafsirkan seperti:

(// =~ nil) ? 'Regexps are truthy' : 'Regexps are falsey'

... Yang mengembalikan falsey.

Di sisi lain, untuk regexp non-literal (misalnya r = //atau Regexp.new('')), interpretasi khusus ini tidak berlaku.

//benar; sama seperti semua objek lain di ruby ​​selain nildan false.


Kecuali menjalankan skrip ruby ​​langsung pada baris perintah (yaitu dengan -ebendera), parser ruby ​​akan menampilkan peringatan terhadap penggunaan tersebut:

peringatan: regex literal dalam kondisi

Anda bisa memanfaatkan perilaku ini dalam skrip, dengan sesuatu seperti:

puts "Do you want to play again?"
gets
# (user enters e.g. 'Yes' or 'No')
/y/i ? play_again : back_to_menu

... Tetapi akan lebih normal untuk menetapkan variabel lokal ke hasil getsdan melakukan pemeriksaan regex terhadap nilai ini secara eksplisit.

Saya tidak mengetahui adanya kasus penggunaan untuk melakukan pemeriksaan ini dengan regex kosong , terutama ketika didefinisikan sebagai nilai literal. Hasil yang Anda sorot memang akan membuat sebagian besar pengembang rubi lengah.

Tom Lord
sumber
Saya hanya menggunakan kondisional sebagai contoh. !// #=> truememiliki perilaku yang sama dan tidak dalam kondisi. Saya tidak dapat menemukan konteks boolean (bersyarat atau tidak), di mana ia berperilaku seperti yang diharapkan.
Jörg W Mittag
@ JörgWMittag Maksud Anda, eg, !// ? true : falsekembali true? Saya pikir ini adalah poin yang sama lagi - ini sedang ditafsirkan seperti:!(// =~ nil) ? true : false
Tom Lord
Jika Anda mengatur secara manual $_ = 'hello world'sebelum menjalankan kode di atas, maka Anda harus mendapatkan hasil yang berbeda - karena // =~ 'hello world', tetapi tidak cocok nil.
Tom Lord
Tidak, maksud saya !// tanpa syarat untuk mengevaluasi true. Spesifikasi yang Anda kutip adalah tentang Regexpliteral dalam kondisi, tetapi dalam contoh ini, tidak ada kondisi, jadi spesifikasi ini tidak berlaku.
Jörg W Mittag
2
Ah .. Ya, sangat mengejutkan. Perilaku tampaknya terkait, meskipun: puts !//; $_ = ''; puts !//- Saya kira karena parser memperluasnya seperti makro; itu tidak perlu harus di dalam kondisi?
Tom Lord