Bagaimana Ruby mengembalikan dua nilai?

95

Setiap kali saya menukar nilai dalam array, saya pastikan saya menyimpan salah satu nilai dalam variabel referensi. Tetapi saya menemukan bahwa Ruby dapat mengembalikan dua nilai serta secara otomatis menukar dua nilai. Sebagai contoh,

array = [1, 3, 5 , 6 ,7]
array[0], array[1] = array[1] , array[0] #=> [3, 1] 

Saya bertanya-tanya bagaimana Ruby melakukan ini.

Pete
sumber
9
Secara teknis Ruby tidak mengembalikan dua nilai. Itu dapat mengembalikan satu larik yang pada gilirannya akan ditugaskan ke dua variabel.
Charles Caldwell

Jawaban:

166

Tidak seperti bahasa lain, nilai kembalian dari setiap pemanggilan metode di Ruby selalu berupa objek. Ini dimungkinkan karena, seperti semua yang ada di Ruby, nilitu sendiri adalah sebuah objek.

Ada tiga pola dasar yang akan Anda lihat. Tidak mengembalikan nilai tertentu:

def nothing
end

nothing
# => nil

Mengembalikan nilai singular:

def single
  1
end

x = single
# => 1

Ini sejalan dengan apa yang Anda harapkan dari bahasa pemrograman lain.

Hal-hal menjadi sedikit berbeda ketika berhadapan dengan beberapa nilai pengembalian. Ini perlu ditentukan secara eksplisit:

def multiple
  return 1, 2
end

x = multiple
# => [ 1, 2 ]
x
# => [ 1, 2 ]

Saat melakukan panggilan yang mengembalikan beberapa nilai, Anda dapat memecahnya menjadi variabel independen:

x, y = multiple
# => [ 1, 2 ]
x
# => 1
y
# => 2

Strategi ini juga berfungsi untuk jenis substitusi yang Anda bicarakan:

a, b = 1, 2
# => [1, 2]
a, b = b, a
# => [2, 1]
a
# => 2
b
# => 1
anak laki-laki
sumber
8
Anda bisa secara eksplisit mengembalikan array [1, 2]dan ini akan bekerja sama seperti contoh Anda di atas.
Hauleth
6
@hauleth Pengamatan yang bagus. Saya harus menjelaskan bahwa 1,2dengan sendirinya tidak valid, tetapi salah satu return 1,2atau [1,2]berfungsi.
tadman
51

Tidak, Ruby sebenarnya tidak mendukung pengembalian dua objek. (BTW: Anda mengembalikan objek, bukan variabel. Lebih tepatnya, Anda mengembalikan pointer ke objek.)

Itu, bagaimanapun, mendukung penugasan paralel. Jika Anda memiliki lebih dari satu objek di sisi kanan tugas, objek dikumpulkan menjadi Array:

foo = 1, 2, 3
# is the same as
foo = [1, 2, 3]

Jika Anda memiliki lebih dari satu "target" (variabel atau metode penyetel) di sisi kiri tugas, variabel terikat ke elemen Arraydi sisi kanan:

a, b, c = ary
# is the same as
a = ary[0]
b = ary[1]
c = ary[2]

Jika ruas kanan bukan sebuah Array, itu akan diubah menjadi satu dengan menggunakan to_arymetode ini

a, b, c = not_an_ary
# is the same as
ary = not_an_ary.to_ary
a = ary[0]
b = ary[1]
c = ary[2]

Dan jika kita menggabungkan keduanya, kita mendapatkannya

a, b, c = d, e, f
# is the same as
ary = [d, e, f]
a = ary[0]
b = ary[1]
c = ary[2]

Terkait dengan ini adalah operator percikan di sisi kiri tugas. Artinya "ambil semua elemen kiri-atas Arraydi sisi kanan":

a, b, *c = ary
# is the same as
a = ary[0]
b = ary[1]
c = ary.drop(2) # i.e. the rest of the Array

Dan yang tak kalah pentingnya, tugas paralel dapat disarangkan menggunakan tanda kurung:

a, (b, c), d = ary
# is the same as
a = ary[0]
b, c = ary[1]
d = ary[2]
# which is the same as
a = ary[0]
b = ary[1][0]
c = ary[1][1]
d = ary[2]

Ketika Anda returndari sebuah metode atau nextatau breakdari sebuah blok, Ruby akan memperlakukan hal semacam ini seperti sisi kanan sebuah tugas, jadi

return 1, 2
next 1, 2
break 1, 2
# is the same as
return [1, 2]
next [1, 2]
break [1, 2]

Ngomong-ngomong, ini juga berfungsi dalam daftar parameter metode dan blok (dengan metode yang lebih ketat dan blok tidak terlalu ketat):

def foo(a, (b, c), d) p a, b, c, d end

bar {|a, (b, c), d| p a, b, c, d }

Pemblokiran menjadi "kurang ketat" misalnya yang membuat Hash#eachberhasil. Sebenarnya ini yieldadalah satu dua elemen Arraykunci dan nilai ke blok, tetapi biasanya kami menulis

some_hash.each {|k, v| }

dari pada

some_hash.each {|(k, v)| }
Jörg W Mittag
sumber
17

tadman dan Jörg W Mittag mengenal Ruby lebih baik dari saya, dan jawaban mereka tidak salah, tapi saya rasa mereka tidak menjawab apa yang OP ingin tahu. Saya pikir pertanyaannya tidak jelas. Dalam pemahaman saya, apa yang OP ingin tanyakan tidak ada hubungannya dengan mengembalikan banyak nilai.


Pertanyaan sebenarnya adalah, ketika Anda ingin mengganti nilai dua variabel adan b(atau dua posisi dalam array seperti pada pertanyaan awal), mengapa tidak perlu menggunakan variabel temporal tempseperti:

a, b = :foo, :bar
temp = a
a = b
b = temp

tapi bisa dilakukan langsung seperti:

a, b = :foo, :bar
a, b = b, a

Jawabannya, dalam penugasan rangkap seluruh ruas kanan dievaluasi sebelum penugasan ruas kiri secara keseluruhan, dan tidak dilakukan satu persatu. Jadi a, b = b, atidak sama dengan a = b; b = a.

Pertama mengevaluasi seluruh sisi kanan sebelum penugasan adalah kebutuhan yang mengikuti dari penyesuaian ketika kedua sisi =memiliki jumlah istilah yang berbeda, dan deskripsi Jörg W Mittag mungkin secara tidak langsung terkait dengan itu, tetapi itu bukan masalah utama.

sawa
sumber
8

Array adalah pilihan yang bagus jika Anda hanya memiliki sedikit nilai. Jika Anda ingin beberapa nilai kembali tanpa harus mengetahui (dan bingung dengan) urutan hasil, alternatifnya adalah mengembalikan Hash yang berisi nilai bernama apa pun yang Anda inginkan.

misalnya

def make_hash
  x = 1
  y = 2
  {x: x, y: y}
end

hash = make_hash
# => {:x=>1, :y=>2}
hash[:x]
# => 1
hash[:y]
# => 2
kata ganti
sumber