Julia: Siaran memiliki satu pekerjaan (misalnya menyiarkan melalui iterator dan generator)

Dibuat pada 21 Sep 2016  ·  69Komentar  ·  Sumber: JuliaLang/julia

Sangat mengejutkan mengetahui bahwa broadcast tidak berfungsi dengan iterator

dict = Dict(:a => 1, :b =>2)
<strong i="7">@show</strong> string.(keys(dict)) # => Expected ["a", "b"]
"Symbol[:a,:b]"

Ini karena Broadcast.containertype mengembalikan Any https://github.com/JuliaLang/julia/blob/413ed79ec54f3a754ac8bc57c1d29835d17bd274/base/broadcast.jl#L31
mengarah ke fallback di: https://github.com/JuliaLang/julia/blob/413ed79ec54f3a754ac8bc57c1d29835d17bd274/base/broadcast.jl#L265

Mendefinisikan containertype menjadi Array untuk iterator itu menyebabkan masalah dengan memanggil size di atasnya, karena broadcast tidak memeriksa antarmuka iterator iteratorsize(IterType) .

map menyelesaikan ini dengan fallback map(f, A) = collect(Generator(f,A)) yang mungkin lebih masuk akal daripada definisi broadcast(f, Any, A) = f(A)

broadcast

Komentar yang paling membantu

Ini disengaja. broadcast adalah untuk wadah dengan bentuk, dan default untuk memperlakukan objek sebagai skalar. map adalah untuk wadah tanpa bentuk, dan default untuk memperlakukan objek sebagai iterator.

Misalnya, broadcast memperlakukan string sebagai "skalar", sedangkan map mengulangi karakter.

Semua 69 komentar

Ini disengaja. broadcast adalah untuk wadah dengan bentuk, dan default untuk memperlakukan objek sebagai skalar. map adalah untuk wadah tanpa bentuk, dan default untuk memperlakukan objek sebagai iterator.

Misalnya, broadcast memperlakukan string sebagai "skalar", sedangkan map mengulangi karakter.

Mungkin masalahnya adalah orang menganggap sintaks titik baru terlalu nyaman. Ada keinginan untuk memiliki cara yang ringkas untuk mengekspresikan map di masa lalu. Sayangnya, sintaks titik sudah diambil.

Juga, seperti yang telah ditunjukkan oleh @stevengj sebelumnya: pasti ada perbedaan antara map dan broadcast , jika tidak, apa gunanya memiliki keduanya.

@stevenngj Tapi Iterator memang memiliki bentuk (terutama generator) http://docs.julialang.org/en/release-0.5/manual/interfaces/#interfaces

Saya berpendapat bahwa iterator berada di ranah yang canggung ini adalah sebagian besar hal yang ingin Anda lakukan dengan wadah yang juga ingin Anda lakukan dengan iterator, dan ya mungkin itu murni fakta bahwa sintaks . terlalu nyaman (dan kesalahan yang Anda dapatkan sangat buram).

@pabloferz Perbedaan utama antara map dan broadcast adalah perlakuan terhadap skalar. Sekarang definisi skalar dapat diperdebatkan dan saya akan mengatakan bahwa segala sesuatu yang memiliki length(x) > 1 tidak boleh dianggap sebagai skalar.

Menandai argumen mana yang harus diperlakukan sebagai dapat diubah, alih-alih pemanggilan fungsi itu sendiri, akan menghilangkan ambiguitas. Menurut saya?

Untuk broadcast (saya percaya juga secara umum) memiliki bentuk, berarti memiliki size (bukan hanya length ) dan dapat diindeks. Kecuali untuk tupel apa pun tanpa size diperlakukan sebagai skalar. Mengingat implementasi saat ini, pertama-tama Anda membutuhkan getindex atau dapat menentukan satu untuk objek yang ingin Anda siarankan. Untuk iterator, itu tidak mungkin secara umum.

Saya juga mengalami ini. Berasal dari #16769 di mana saya mencari cara untuk fill! array dengan evaluasi berulang dari suatu fungsi (bukan nilai tetap), saya pikir dot-syntax mungkin sudah melakukan trik. Tapi, ketika a = zeros(2, 3); a .= [rand() for i=1:2, j=1:3] bekerja, (akan) lebih murah a .= (rand() for i=1:2, j=1:3) tidak; generator ini HasShape() , tetapi memang tidak memiliki kemampuan pengindeksan. Saya sangat ringan dalam memahami cara kerja sintaksis siaran/titik, tetapi apakah akan membantu di sini memiliki sifat untuk kemampuan pengindeksan? sudah ada PR (#22489) untuk itu...

@rfourquet , Anda dapat melakukan a = zeros(2, 3); a .= rand.()

Ya, tapi saya harus lebih tepat: Saya ingin menggunakan fungsi yang mendapatkan indeks sebagai parameter, seperti a .= (f(i, j) for i=1:2, j=1:3) .

Apa kelemahan dari dimensi penyiaran iterator HasShape ? Kedengarannya seperti hal yang alami untuk dilakukan.

@nalimilan , sekilas saya pikir itu masuk akal dan mungkin relatif mudah diterapkan. Akan melanggar jadi harus dilakukan oleh 1.0.

Satu masalah potensial dengan ini adalah bahwa HasShape iterator tidak selalu mendukung getindex , dan itu mungkin membuatnya sulit untuk diterapkan?

Satu kemungkinan adalah untuk sementara (untuk 1.0) membuat implementasi sederhana yang baru saja disalin ke array. Itu akan memungkinkan posting pengoptimalan 1.0

Satu masalah potensial dengan ini adalah bahwa iterator HasShape tidak selalu mendukung getindex, dan itu mungkin membuatnya sulit untuk diterapkan?

Seperti yang saya katakan di atas, saya memiliki PR di #22489 untuk memungkinkan pengindeksan ke iterator, jika ini dapat membantu.

Apa yang perlu dilakukan untuk 1.0 sehingga setidaknya kita dapat meningkatkan perilaku di 1.x?

Terima kasih @nalimilan telah mengemukakan hal itu, saya juga ingin melakukannya. Jika mengizinkan HasShape generator di sisi kanan ekspresi siaran tidak mungkin diterapkan untuk 1.0, haruskah kita membuat kesalahan ini sekarang, alih-alih memperlakukan generator sebagai skalar? sehingga ini dapat diaktifkan di 1.x.

:+1: Triage merekomendasikan untuk membuat kesalahan ini (pilihan yang aman) atau memanggil collect di atasnya (jika mudah dilakukan).

map memperlakukan semua argumennya sebagai wadah, dan mencoba mengulangi semuanya. Di dunia ideal saya, broadcast akan serupa, dan memperlakukan semua argumennya memiliki bentuk yang dapat disiarkan, dan memberikan kesalahan jika misalnya size tidak didefinisikan. Saya akan menunjukkan bahwa nilai apa pun dapat diperlakukan sebagai skalar dalam siaran dengan membungkusnya dengan fill , menghasilkan array 0-d:

julia> fill("a")
0-dimensional Array{String,0}:
"a"

julia> fill([2])
0-dimensional Array{Array{Int64,1},0}:
[2]

Apakah Anda benar-benar menyarankan memperlakukan semua skalar sebagai wadah secara default? Itu tidak terdengar sangat praktis.

Melihat bagaimana kami dapat mendukung iterable apa pun, atau hanya membuat kesalahan untuk mereka sampai kami mendukungnya, sepertinya kami memerlukan cara untuk mengidentifikasi iterator di BroadcastStyle . Saat ini itu tidak mungkin, karena Base.iteratorsize mengembalikan HasLength bahkan untuk skalar seperti Symbol . Kita dapat memperkenalkan sifat Base.isiterable (yang dapat berguna untuk hal lain), atau menjadikan Base.iteratorsize default ke NotIterable (yang juga masuk akal jika memiliki HasLength sebagai default selalu terdengar agak mengejutkan, jika tidak berbahaya).

(Kasus rumit untuk diskusi di masa mendatang: UniformScaling .)

@timholy Karena Anda telah melakukan desain ulang broadcast , ada saran?

@JeffBezanson , inti dari broadcast adalah untuk dapat "menyiarkan" skalar untuk mencocokkan wadah, misalnya untuk melakukan ["bug", "cow", "house"] .* "s" ----> ["bugs", "cows", "houses"] . Ini pada dasarnya berbeda dari perilaku map .

Inilah mengapa broadcast memperlakukan objek sebagai skalar secara default, sehingga mereka dapat digabungkan dengan container. Melempar kesalahan untuk jenis yang tidak dikenal akan membuatnya kurang berguna.

Seharusnya dimungkinkan untuk mendeklarasikan tipe tertentu sebagai wadah untuk broadcast dengan mendefinisikan beberapa metode yang sesuai di dalamnya, tetapi saya pikir defaultnya harus terus memperlakukan objek sebagai skalar.

Dalam PR yang tidak terkait (https://github.com/JuliaLang/julia/pull/25339), @Keno menyarankan menggunakan applicable(start, (x,)) untuk mengetahui apakah x dapat diubah atau tidak. Haruskah kita menggunakan pendekatan yang sama di sini? Saya akan merasa lebih jelas untuk memiliki definisi iterator yang lebih eksplisit (berdasarkan Base.iteratorsize atau pada suatu sifat), tetapi menggunakan start juga masuk akal.

Kita bisa memiliki sifat eksplisit yang defaultnya adalah applicable(start, (x,)) ; yang akan memungkinkan menimpanya jika perlu.

Saya telah mengajukan #25356 untuk mengilustrasikan solusi yang mungkin dan kekurangannya.

Dari contoh @stevengj ["bug", "cow", "house"] .* "s" ----> ["bugs", "cows", "houses"] , iterability tampaknya tidak cukup, karena string dapat diubah tetapi bertindak seperti skalar di sana. Jika Anda tetap perlu mendefinisikan suatu sifat, mungkin lebih baik untuk terus mewajibkan keikutsertaan untuk penyiaran, daripada menambahkan persyaratan ke semua iterator.

Untungnya keys(dict) sekarang mengembalikan AbstractSet , jadi jika kami menambahkan sifat siaran untuk AbstractSet itu akan memperbaiki contoh di OP. Kami juga dapat menambahkan kesalahan untuk menyiarkan Generator untuk menangkap beberapa kasus umum.

Menyiarkan lebih dari AbstractSet wadah tampaknya secara inheren agak bermasalah: Anda dapat menggabungkan AbstractSet dengan skalar, tetapi tidak dengan wadah lain karena urutan iterasi tidak ditentukan untuk satu set. Jenis ini mematahkan arti biasa dari operasi "siaran".

Ya, saya menyadari ketika menyiapkan PR yang ditetapkan sebenarnya bukan contoh terbaik dari iterator yang harus mendukung siaran. Hal-hal seperti Generator dan ProductIterator adalah kasus yang jauh lebih menarik.

Mungkin jawabannya adalah (mencoba) menyiarkan iterator yang memiliki HasShape , dan terus memperlakukan yang lainnya sebagai skalar? Tidak akan memperbaiki OP, tetapi sebaliknya cukup elegan.

Pikiran acak lainnya: mungkin menyiarkan lebih dari 1 argumen (seperti pada string.(x) ) harus menjadi kasus khusus yang berfungsi lebih seperti map , karena kompatibilitas bentuk tidak menjadi masalah?

Mungkin jawabannya adalah (mencoba) menyiarkan iterator yang memiliki HasShape, dan terus memperlakukan yang lainnya sebagai skalar? Tidak akan memperbaiki OP, tetapi sebaliknya cukup elegan.

Saya tidak yakin kami memiliki alasan kuat untuk mengecualikan HasLength iterator. Kami mendukung penyiaran melalui tupel (yang tidak mengimplementasikan size ), jadi mengapa tidak memperlakukan iterator tak berbentuk seperti tupel? Misalnya, sangat masuk akal untuk dapat menggunakan hasil keys(::OrderedDict) dengan broadcast . Jika kita tidak mendukungnya, orang akan tergoda untuk mendefinisikan iterator mereka sebagai HasShape hanya untuk dapat digunakan dengan broadcast (dan sintaks titik yang bagus).

Mengutip Steve,

broadcast adalah untuk wadah dengan bentuk

HasShape tampaknya merupakan cara yang masuk akal untuk mendefinisikannya dengan lebih tepat. Kalau tidak, menurut saya kita perlu mematahkan perilaku siaran pada string, misalnya.

Kami sudah memiliki inkonsistensi, dengan tupel dianggap sebagai wadah dan string sebagai skalar. String sangat istimewa, saya tidak berpikir perilaku mereka karena mereka tidak memiliki bentuk: itu lebih terkait dengan fakta bahwa mereka adalah satu-satunya koleksi yang lebih sering dianggap sebagai skalar daripada sebagai wadah.

Mungkin @stevenngj dapat mengembangkan mengapa menurutnya broadcast hanya mendukung wadah dengan bentuk? Apakah Anda mendukung mempertimbangkan tupel sebagai skalar juga?

Saya pikir alasan untuk memperlakukan tupel sebagai wadah di broadcast (#16986) adalah bahwa dalam praktiknya mereka sering digunakan sebagai vektor statis pada dasarnya, dan memperlakukannya sebagai "skalar" di broadcast tidak sangat berguna pula. Sebaliknya, string (a) sering diperlakukan sebagai "atom" untuk operasi pemrosesan string dan (b) tidak memiliki pengindeksan berurutan secara umum sehingga sangat tidak cocok dengan kerangka kerja broadcast .

Pada prinsipnya, saya akan mendukung HasShape iterator digunakan sebagai wadah di broadcast . Masalah utama, seperti yang saya sebutkan di atas, adalah memiliki HasShape tidak menjamin bahwa getindex berfungsi.

Masalah utama, seperti yang saya sebutkan di atas, adalah memiliki HasShape tidak menjamin getindex berfungsi

Apakah sesuatu seperti #22489 membantu, yaitu memiliki sifat iterator yang menunjukkan apakah iterator dapat diindeks?

Apakah sesuatu seperti #22489 membantu, yaitu memiliki sifat iterator yang menunjukkan apakah iterator dapat diindeks?

Tetapi hanya iterator yang dapat diindeks yang akan didukung dengan broadcast ? Kedengarannya terlalu membatasi, karena akan sangat berguna untuk dapat melakukan hal-hal seperti string.(itr, "1") untuk setiap iterable (misalnya hasil dari keys(::OrderedDict) ), dan pengindeksan tidak diperlukan untuk mengimplementasikannya. Saya pikir sebaiknya kita membuat kesalahan untuk semua iterator yang tidak mendukung pengindeksan di 0.7/1.0, dan mencoba mendukungnya di rilis berikutnya. Tidak terlalu berguna untuk memperlakukan iterator sebagai skalar. Kemudian kita dapat mengimplementasikan perilaku apa pun yang kita inginkan dalam rilis 1.x.

@stevenngj Saya setuju tentang argumen Anda tentang string dan tupel, tetapi mengapa kita tidak memperlakukan iterator HasLength sebagai tupel? Saya belum membaca pembenaran untuk ini sampai sekarang.

@nalimilan , saya cenderung berpikir hanya iterator yang dapat diindeks+hasshape yang harus didukung oleh broadcast . Mencoba menjejalkan iterator umum ke dalam fungsi ini terlalu membingungkan artinya — pada titik tertentu, Anda sebaiknya menggunakan map .

akan sangat berguna untuk dapat melakukan hal-hal string.(itr, "1") untuk setiap iterable ... Bagaimanapun juga tidak berguna untuk memperlakukan iterator sebagai skalar.

Kasus string bertentangan dengan ini — argumen "1" itu sendiri dapat diubah dalam contoh Anda. Ton hal yang iterable (misalnya PyObject s di PyCall mendefinisikan start dll), termasuk hal-hal seperti set unordered di mana broadcast konsep benar-benar rusak.

Perhatikan juga bahwa #24990 akan membuat map lebih mudah daripada sekarang, misalnya Anda akan dapat melakukan map(string(_,"1"), itr) .

@nalimilan , saya cenderung berpikir hanya iterator yang dapat diindeks+hasshape yang harus didukung oleh siaran. Mencoba menjejalkan iterator umum ke dalam fungsi ini terlalu membingungkan artinya — pada titik tertentu, Anda sebaiknya menggunakan map.

Kami tidak memiliki sifat saat ini untuk iterator yang dapat diindeks. Bagaimana Anda menyarankan menyerahkan itu? WIP PR #25356 saya akan menimbulkan kesalahan untuk iterator yang tidak mendukung pengindeksan, yang kedengarannya tidak terlalu buruk dengan asumsi itu tidak terlalu berguna untuk memperlakukan iterator sebagai skalar. Jika kita ingin memperlakukan mereka sebagai skalar, kita membutuhkan sifat lain, bukan?

Saya cenderung meningkatkan kesalahan untuk semua kasus yang tidak sepenuhnya jelas, sehingga kami dapat menerapkan perilaku apa pun di masa mendatang, daripada mengunci diri kami ke dalam perilaku default yang belum tentu sangat berguna (yaitu memperlakukan beberapa iterator sebagai skalar) . Seperti yang ditunjukkan oleh masalah ini, perilaku broadcast membutuhkan waktu untuk mendesain dengan benar.

(FWIW, PyObject tidak terdengar seperti contoh yang bagus bagi saya karena IIUC mengimplementasikan protokol iterasi hanya karena tidak tahu sebelumnya apakah itu akan membungkus iterator Python atau tidak. PyObject jelas merupakan pengecualian di sini, sama seperti itu perlu menggunakan kelebihan getfield agar tampak seperti objek Julia standar. Set adalah contoh Julian yang lebih banyak.)

Kita dapat menambahkan sifat untuk iterator HasShape dapat diindeks, seperti yang disarankan di tempat lain.

Triage menyukai gagasan untuk membuat siaran mengulangi semua argumen (seperti peta), dan menambahkan karakter operator (seperti melakukan const & = Ref seperti yang diusulkan sebelumnya dalam edisi lain, atau mungkin ~ ) untuk secara eksplisit menandai argumen 0-d.

@vtjnash , apa artinya itu untuk iterator non- HasShape ? Maksud Anda, Anda ingin siaran untuk mengulangi hal-hal seperti string dan set? Implementasi broadcast ini terkait erat dengan getindex … pernahkah Anda memikirkan bagaimana Anda akan mengimplementasikannya tanpa getindex , terutama untuk menggabungkan argumen dari dimensi yang berbeda?

Secara teori seharusnya dimungkinkan untuk mendukung iterator yang tidak dapat diindeks (setidaknya yang memiliki urutan yang berarti). Itu mudah ketika semua input memiliki bentuk yang sama; ketika mereka memiliki bentuk yang berbeda dan iterator memiliki bentuk yang berbeda (lebih kecil) dari hasilnya, beberapa penyimpanan perantara akan diperlukan.

Sepertinya sifat IteratorAccess dari PR https://github.com/JuliaLang/julia/pull/22489 dapat diadaptasi/digunakan kembali untuk mendeteksi iterator yang dapat diindeks. Mengetahui iterator mana yang dapat diindeks (dan karenanya harus mengimplementasikan keys ) juga diperlukan untuk https://github.com/JuliaLang/julia/pull/24774.

Cc: @rfourquet

👍 Triage merekomendasikan untuk membuat kesalahan ini (pilihan yang aman) atau memanggil kumpulkan (jika mudah dilakukan).

Bisakah triase memutuskan strategi khusus untuk diadopsi di sini? Misalnya apa "ini" di komentar @JeffBezanson di atas? Haruskah kita membuang kesalahan untuk semua iterator yang tidak mendukung pengindeksan (pilihan teraman untuk saat ini, sehingga kita dapat melakukan apa pun yang kita inginkan nanti), atau haruskah kita memperlakukan beberapa iterator sebagai skalar? Haruskah kita menambahkan sifat untuk iterator yang dapat diindeks, dan jika ya, dalam bentuk apa (sifat baru vs. pilihan baru untuk Base.IteratorSize )? Haruskah kita menambahkan sifat untuk iterator secara umum (sehingga kita dapat membedakannya dari skalar)?

Perilaku berikut tampaknya baik:

  • Secara default, cobalah untuk mengulangi dan menyiarkan setiap argumen.
  • Berikan kesalahan jika itu tidak berhasil karena alasan apa pun.
  • Lewati Ref(x) atau [x] untuk memaksa x diperlakukan sebagai skalar.
  • Tambahkan sifat yang dapat didefinisikan untuk memungkinkan tipe baru diperlakukan sebagai skalar alih-alih memberikan kesalahan. Catatan ini tidak boleh digunakan untuk memilih antara iterasi vs tidak iterasi. Itu hanya untuk mengubah kesalahan menjadi perilaku skalar.

Bisakah Anda mengklarifikasi catatan pada poin terakhir (mungkin dengan sebuah contoh)? Saya tidak yakin apa artinya sifat itu ada tetapi tidak digunakan untuk memilih antara iterasi vs. tidak iterasi.

Jadi pada dasarnya "coba untuk mengulangi dan menyiarkan setiap argumen" menyiratkan bahwa kita perlu mendefinisikan BroadcastStyle untuk mengembalikan Scalar() untuk semua jenis non-koleksi (terutama Number , Symbol dan AbstractString )? Kedengarannya seperti "sifat" yang disebutkan peluru terakhir.

Sejujurnya, saya akan merasa lebih murah untuk mendefinisikan sifat untuk iterable daripada mendefinisikan sifat untuk non-interable/skalar. Saya khawatir semua jenis non-koleksi pada titik tertentu akan menerapkan sifat Scalar , karena itu dapat berguna dalam beberapa kasus (mungkin jarang).

Kedengarannya seperti "sifat" yang disebutkan peluru terakhir

Tidak, poin terakhir berarti bahwa jika sesuatu mengimplementasikan iterasi, lalu menyiarkan iterasinya – sifat Skalar akan hilang. Untuk beberapa tipe umum yang tidak dapat diulang (seperti subtipe Type dan Function ), maka kita mungkin ingin memiliki NotIterable sifat yang mengubah MethodError menjadi iterasi yang menghasilkan satu nilai (objek itu). Saya tidak benar-benar ingat mengapa ini diperlukan.

Jadi pada dasarnya "coba untuk mengulangi dan menyiarkan setiap argumen" menyiratkan bahwa kita perlu mendefinisikan BroadcastStyle untuk mengembalikan Scalar() untuk semua jenis non-koleksi (terutama Number, Symbol, dan AbstractString)? Kedengarannya seperti "sifat" yang disebutkan peluru terakhir.

Tidak, semua subtipe skalar dari Number iterate sendiri dan baik-baik saja. Kita perlu mendefinisikannya untuk simbol. AbstractString akan beroperasi sebagai koleksi.

Saya tidak suka desain apa pun yang mengharuskan kita mendefinisikan metode agar tipe diperlakukan sebagai skalar. Itu harus menjadi default. Saya juga tidak berpikir string harus diperlakukan sebagai wadah untuk siaran.

Saya masih berpikir siaran harus memperlakukan hanya iterator HasShape sebagai wadah; ini konsisten dengan desain siaran dari awal. Apa yang salah dengan itu?

Masalahnya adalah yang ada di OP; jika Anda memiliki iterator tanpa bentuk, memperlakukannya sebagai skalar memberikan jawaban yang gila.

Juga, saya akan dengan senang hati membuang bagian "sifat" dari proposal tersebut. Tidak ada yang mengeluh tentang

julia> map(string, [1,2], :a)
ERROR: MethodError: no method matching start(::Symbol)

Bisa dibilang alasan hasil di OP tidak terduga adalah karena tidak ada yang benar-benar bermaksud panggilan siaran untuk memperlakukan _all_ argumen sebagai skalar; jika hanya ada satu argumen dan ada cara sama sekali untuk memperlakukannya sebagai koleksi/iterator, itu hampir pasti yang diinginkan pengguna. Meskipun tentu saja 1 .+ 1 harus terus bekerja?

Itu terjadi pada saya, tetapi tampaknya membingungkan untuk menjadikan satu argumen sebagai kasus khusus.

Saya melihat asimetri berikut: memperlakukan iterable sebagai skalar memberikan hasil yang sangat aneh, tetapi memperlakukan skalar sebagai iterable memberikan kesalahan. Saat Anda mendapatkan kesalahan, mudah untuk memperbaikinya dengan membungkus argumen. Sedangkan dalam kasus pertama tidak ada hal sederhana yang dapat Anda lakukan untuk mengulangi argumen tersebut.

Saya masih berpikir siaran harus memperlakukan hanya iterator HasShape sebagai wadah; ini konsisten dengan desain siaran dari awal. Apa yang salah dengan itu?

@stevenngj Apa yang salah IMHO adalah bahwa itu membuat beberapa operasi tidak berfungsi ketika perilaku yang sangat masuk akal dapat diterapkan: perlakukan HasLength iterator seperti Tuple , yang saat ini dalam kasus khusus. Bahkan jika kita tidak mendukung mereka sekarang, saya ingin membuka kemungkinan untuk mendukung mereka di beberapa titik di 1.x.

Tidak ada yang mengeluh tentang

julia> peta(string, [1,2], :a)
GALAT: MethodError: tidak ada metode yang cocok mulai(::Simbol)

@JeffBezanson OTC, saya mendukung perilaku saat ini broadcast , yang mengulangi :a sesuai kebutuhan. Hal semacam ini bisa sangat berguna misalnya untuk mengubah nama serangkaian kolom DataFrame . Apakah Anda menyarankan mengubah broadcast untuk membuat kesalahan seperti map ?

Saya melihat asimetri berikut: memperlakukan iterable sebagai skalar memberikan hasil yang sangat aneh, tetapi memperlakukan skalar sebagai iterable memberikan kesalahan. Saat Anda mendapatkan kesalahan, mudah untuk memperbaikinya dengan membungkus argumen. Sedangkan dalam kasus pertama tidak ada hal sederhana yang dapat Anda lakukan untuk mengulangi argumen tersebut.

Mudah, tapi cukup merepotkan. Saya setuju dengan @stevenngj bahwa skalar harus disiarkan secara default, tidak menimbulkan kesalahan. Tentu saja karena tipe Number dapat diubah, gangguan tidak akan selalu terlihat, tetapi seperti yang ditunjukkan oleh contoh Symbol itu tidak akan sangat membantu secara umum. Char akan menjadi yang lain, dan banyak tipe khusus yang ditentukan dalam paket juga akan mengalami hal ini (dan akhirnya mendefinisikan BroadcastStyle sebagai Scalar() ).

Saya pikir inti masalahnya adalah kita tidak memiliki sifat untuk membedakan koleksi dari skalar. Jadi solusi paling langsung memang memperlakukan iterator HasShape sebagai koleksi, dan tipe lain sebagai skalar (termasuk HasLength iterator karena itu adalah default untuk semua tipe). Secara pribadi saya pikir memperkenalkan sifat untuk koleksi/iterable akan sangat masuk akal, tetapi jika kita tidak siap untuk melakukan itu dan jika kita tidak dapat mengandalkan start yang didefinisikan untuk mendeteksi iterable, saya takut kita harus menjaga perilaku saat ini.

Proposal Jeff di https://github.com/JuliaLang/julia/issues/18618#issuecomment -360594955 memiliki ruang untuk memungkinkan siaran memperlakukan Symbol dan Char sebagai tipe "skalar" — mereka hanya perlu ikut serta dalam perilaku tersebut. Secara default mereka akan menjadi kesalahan, karena mereka tidak mengimplementasikan start .

Bagian yang paling menarik di sini adalah bahwa satu-satunya definisi yang masuk akal untuk tipe "koleksi" adalah bahwa itu dapat diubah. Ya, itu berarti string adalah koleksi. Terkadang mereka digunakan seperti itu! Jadi mari kita default ke perilaku yang dengan mudah memungkinkan orang untuk ikut serta ke yang lain di situs panggilan.

Ada kutil di sini, meskipun. Karena angka dapat diubah (bahkan HasShape ), mereka akan diperlakukan sebagai wadah dimensi nol. Itu berarti, dibawa ke kesimpulan logisnya, 1 .+ 2 != 3 . Itu akan menjadi fill(3, ()) sebagai gantinya.

EDIT: dalam upaya untuk menghindari penggelinciran utas lebih lanjut, pindah ke wacana:

https://discourse.julialang.org/t/lazycall-again-sorry/8629

Proposal Jeff di #18618 (komentar) memiliki ruang untuk memungkinkan siaran memperlakukan Simbol dan Char sebagai tipe "skalar" — mereka hanya perlu ikut serta dalam perilaku tersebut. Secara default mereka akan menjadi kesalahan, karena mereka tidak mengimplementasikan start.

Ya, posisi saya hanya didasarkan pada asumsi bahwa skalar adalah fallback yang lebih alami, terutama mengingat bahwa koleksi perlu menerapkan beberapa metode (iterasi, mungkin pengindeksan) sementara skalar hanyalah "sisanya" dan tidak memiliki kesamaan. Pada akhirnya tipe apa pun akan dapat mengimplementasikan perilaku apa pun yang diinginkannya, tetapi kita harus membuatnya senyaman dan selogis mungkin, yang khususnya akan membantu menghindari inkonsistensi (misalnya beberapa tipe dideklarasikan dan berperilaku sebagai skalar dan lainnya tidak).

Saya tidak terlalu peduli dengan memiliki beberapa pengecualian untuk tipe penting seperti string dan angka, selama aturannya jelas untuk tipe lain.

Saya telah memikirkan sedikit tentang antarmuka iterasi dan pengindeksan kami. Saya perhatikan bahwa kita dapat memiliki objek berguna yang berulang (tetapi tidak dapat diindeks), objek yang dapat diindeks (tetapi tidak dapat diubah), dan objek yang melakukan keduanya. Berdasarkan itu, saya bertanya-tanya apakah:

  • map bisa sangat terikat pada protokol iterasi - tampaknya valid bahwa kita dapat membuat malas out = map(f, iterable) untuk setiap sewenang-wenang iterable sehingga misalnya first(out) adalah sama seperti f(first(iterable)) , dan menurut saya operasi malas generik ini bisa berguna.
  • broadcast dapat sangat terikat dengan antarmuka pengindeksan - tampaknya valid bahwa kita dapat membuat out = broadcast(f, indexable) yang malas sedemikian rupa sehingga out[i] sama dengan f(indexable[i]) , dan bagi saya tampaknya operasi malas generik ini dapat berguna. Jelas broadcast dengan banyak input masih dapat melakukan semua hal mewah yang dilakukannya sekarang. Untuk tujuan penyiaran, skalar adalah hal-hal yang tidak dapat diindeks (atau diindeks secara sepele seperti Number dan Ref dan AbstractArray{0} ).

Saya juga merasa bahwa akan diinginkan jika satu argumen map dan satu-argumen broadcast melakukan hal yang sangat mirip untuk koleksi yang dapat diubah dan dapat diindeks. Namun, fakta bahwa iterasi AbstractDict mengembalikan hal yang berbeda dari getindex tampaknya menghalangi penyatuan yang bagus di sini. :frowning_face: (Jenis koleksi kami yang lain sepertinya baik-baik saja)

(Bagi saya, fakta bahwa hal-hal seperti string mungkin harus secara eksplisit dibungkus seperti ["bug", "cow", "house"] .* ("s",) tidak terdengar seperti pemecah kesepakatan di sini. Saya memiliki masalah yang sama ketika saya ingin memikirkan 3-vektor sebagai "titik 3D tunggal" dan tidak terlalu sulit untuk ditangani (xref #18379)).

Saya setuju bahwa broadcast harus untuk wadah yang dapat diindeks, tetapi saya pikir itu harus dapat diindeks secara berurutan , yang tidak termasuk string. misalnya collect(eachindex("aαb🐨γz")) memberikan [1, 2, 4, 5, 9, 11] , yang akan bermain buruk dengan implementasi broadcast berdasarkan pengindeksan.

Tetapi untuk wadah yang dapat diindeks pada dasarnya adalah bahwa wadah memerlukan sifat untuk ikut serta, yang pada dasarnya adalah apa yang saya anjurkan.

Saya tidak yakin indeks berurutan adalah batasan yang baik - kamus akan memiliki indeks arbitrer, misalnya.

Namun, broadcast(f, ::String) tidak dapat membuat String dan menjamin bahwa indeks keluaran tetap sama dengan indeks masukan karena lebar karakter UTF-8 mungkin berubah di bawah f ( itu harus berubah menjadi sesuatu seperti AbstractDict{Int, Char} untuk membuat jaminan itu, yang tampaknya tidak terlalu berguna!). Saya hampir akan mengatakan bahwa indeks dari String lebih seperti "token" untuk pencarian cepat daripada indeks yang penting secara semantik (misalnya Anda dapat mengonversi ke string UTF-32 yang setara dan indeks akan berubah).

Saya tidak keberatan jika kita membuat perilaku penyiaran memilih melalui sifat; Saya hanya mengatakan bahwa membayangkan bagaimana broadcast(f, ::Any) generik berperilaku adalah cara yang baik untuk memandu implementasi hal-hal seperti broadcast(f, ::AbstractDict) (dan secara alami akan menjawab pertanyaan yang saya ajukan di #25904, yaitu disiarkan melalui nilai kamus dan bukan pasangan nilai kunci).

Apakah orang benar-benar senang dengan perubahan ini? Saya tidak pernah perlu menyiarkan melalui wadah tanpa bentuk, sementara saya menyiarkan hal-hal yang harus diperlakukan sebagai skalar sepanjang waktu . Setiap peringatan penghentian yang saya 'perbaiki' membuat saya meneteskan air mata.

Saya menyiarkan hal-hal yang harus diperlakukan sebagai skalar _sepanjang waktu_.

Apa saja jenis-jenis barang tersebut?

Bisa apa saja. Misalnya, dalam paket yang mendefinisikan tipe model pengoptimalan Model dan variabel keputusan tipe Variable , Anda mungkin memiliki x::Vector{Variable} yang ingin Anda dapatkan nilainya setelah menyelesaikan model model menggunakan fungsi value(::Variable, ::Model)::Float64 . Sebelumnya, Anda bisa melakukannya seperti:

value.(x, model)

Ini juga sering terjadi bahwa tipe argumen berasal dari paket lain, jadi menambahkan metode ke broadcastable untuk tipe tersebut akan menjadi pembajakan tipe dalam kasus itu. Jadi, Anda harus menggunakan Ref atau tuple satu elemen. Itu tidak dapat diatasi, tetapi itu hanya membuat kasus umum menjadi kurang elegan untuk mendukung pola penggunaan yang relatif tidak jelas, menurut pendapat saya.

Ya, saya mengerti maksud Anda, dan saya setuju bahwa itu menjengkelkan dalam situasi seperti itu. Yang mengatakan, perilaku lama benar-benar bermasalah — itu adalah salah satu dari "penggantian default pasti salah dalam beberapa kasus".

Singkatnya, ada empat opsi yang menghindari fallback yang salah:

  1. memerlukan _semuanya_ untuk mengimplementasikan beberapa metode yang menjelaskan cara mereka menyiarkan
  2. Default untuk memperlakukan hal-hal sebagai wadah dan kesalahan/mengusir untuk non-wadah.

    • Kami hanya akan mencoba iterate objek yang tidak dikenal dan itu akan membuat kesalahan untuk skalar

    • Ada dua pintu keluar untuk skalar — pengguna dapat membungkusnya di situs panggilan dan penulis perpustakaan dapat memilih untuk menerima siaran seperti skalar yang tidak dibuka.

  3. Default untuk memperlakukan hal-hal sebagai skalar dan kesalahan untuk wadah yang tidak dikenal

    • Mengingat bahwa tidak ada metode relevan yang hanya ditentukan untuk skalar, kita harus menegaskan bahwa iterate melempar kesalahan metode. Itu lambat dan berputar-putar.

    • Hanya akan ada satu pintu keluar yang tersedia untuk wadah khusus agar tidak salah: penulis perpustakaan mereka secara eksplisit ikut serta untuk menyiarkan. Ini tampaknya cukup mundur untuk fungsi yang tujuan utamanya adalah bekerja dengan wadah.

  4. Periksa applicable(iterate, …) dan ubah perilaku yang sesuai

    • Ini saat ini tidak berfungsi karena mekanisme penghentian dari awal/berikutnya/selesai, dan secara umum bisa salah untuk jenis pembungkus yang menunda metode ke anggota.

Opsi 1 lebih buruk untuk semua orang, opsi 2 adalah status quo, dan opsi 3 mundur, dan opsi 4 adalah sesuatu yang belum pernah kita lakukan sebelumnya dan cenderung bermasalah.

Saya kira beberapa diskusi pasti terjadi di belakang layar, tetapi saya tidak yakin dengan argumen yang saya lihat di utas ini dan di https://github.com/JuliaLang/julia/pull/25356 melawan nalimilan Posisi stevengj .

Hanya akan ada satu pintu keluar yang tersedia untuk wadah khusus agar tidak salah: penulis perpustakaan mereka secara eksplisit ikut serta untuk menyiarkan. Ini tampaknya cukup mundur untuk fungsi yang tujuan utamanya adalah bekerja dengan wadah.

Ini adalah poin utama ketidaksepakatan saya. Bagi saya tampaknya di semua kode Julia # of iterator types << # of types that should be treated as scalars in a broadcast situation < # of broadcast calls . Jadi saya lebih suka jika berapa kali sesuatu 'ekstra' perlu dilakukan skala dengan jumlah jenis iterator, daripada dengan jumlah panggilan siaran. Dan jika seorang penulis perpustakaan mendefinisikan sebuah iterator, tidak sepenuhnya tidak masuk akal untuk meminta mereka mendefinisikan satu metode lagi, sedangkan _is_ sama sekali tidak masuk akal untuk meminta setiap pembuat paket untuk mendefinisikan Base.broadcastable(x) = Ref(x) untuk semua tipe non-iterable mereka untuk menghindari jelek (IMHO) Ref s dalam persentase yang tinggi dari broadcast panggilan.

Saya tahu bahwa memiliki satu metode untuk menerapkan untuk menentukan iterasi bagus, tetapi itu tidak banyak pekerjaan untuk menerapkan satu lagi untuk baik sifat baru, atau untuk membuatnya diperlukan untuk menentukan Base.iteratorsize untuk iterator baru (dan menyingkirkan HasLength default yang bermasalah). Metode fallback broadcastable kemudian dapat didasarkan pada sifat itu. Atau, jika Anda benar-benar menyukai mendefinisikan iterasi dengan satu metode, Anda dapat (post-deprecation-removal) memiliki sifat eksplisit default ke applicable(iterate, ...) seperti pada https://github.com/JuliaLang/ julia/issues/18618#issuecomment -354618742, dan cukup ganti default itu jika perlu. Kasus sudut seperti String juga masih dapat ditangani dengan spesialisasi lebih lanjut broadcastable jika diinginkan.

Itu secara efektif desain 0,6, yang menyebabkan masalah ini dan #26421 dan #19577 dan #23197 dan #23746 dan mungkin lebih — mencari ini sulit.

Ini berarti bahwa Base menyediakan fallback default yang salah untuk seluruh kelas objek. Itu sebabnya saya lebih suka mekanisme yang error kecuali Anda ikut serta, dengan satu atau lain cara. Ini berpendirian dan transisi itu menyakitkan, tetapi memaksa Anda untuk menjadi eksplisit.

Anda mungkin benar bahwa ada lebih banyak jenis kustom "seperti skalar" daripada yang seperti iterator, tetapi saya mendukung fakta bahwa penyiaran adalah yang pertama dan terutama merupakan operasi pada wadah. Saya berharap f.(x) melakukan semacam pemetaan dan tidak hanya menjadi f(x) .

Dan, akhirnya, wadah yang mendapatkan perlakuan skalar-default tidak dapat digunakan secara elemen dengan penyiaran. Sebagai contoh, String adalah tipe koleksi yang memiliki kasus khusus untuk berperilaku seperti skalar; tidak mungkin untuk "menjangkau" dan bekerja secara elemen, meskipun itu tampaknya masuk akal dalam beberapa situasi (mis., isletter.("a1b2c3") ). Itulah argumen asimetri: Anda dapat lebih efisien membungkus wadah dalam Ref untuk memperlakukannya sebagai skalar daripada collect menjadi koleksi yang benar-benar dapat disiarkan.

Itulah argumen-argumen utama. Sejauh keburukan Ref, saya setuju sepenuhnya. Solusinya ada #27608.

Cukup adil. Saya tidak punya argumen knockdown atau solusi ajaib untuk masalah itu, dan https://github.com/JuliaLang/julia/pull/27608 akan memperbaiki keadaan.

@tkoolen Saya memiliki masalah dan kasus penggunaan yang sama.

@mbauman Argumen yang diberikan di atas mungkin tidak sepenuhnya meyakinkan. Berikut adalah dua pertanyaan agar lebih lengkap:

1) Dimungkinkan untuk membuat broadcastable antarmuka yang diperlukan untuk setiap iterable.
Ini akan benar-benar sistematis, dan akan memaksa pengembang untuk memikirkannya
bagaimana iterator mereka harus berperilaku di bawah siaran.
Rekomendasi untuk menetapkannya sebagai collect(x) akan membuat transisi relatif mudah dalam banyak kasus.
Tidak akan ada penurunan kinerja, kan?

2) Jadi bermuara pada keinginan untuk memiliki kesalahan untuk f.(x) jika x disiarkan sebagai skalar.
Mengapa tidak peringatan/kesalahan linter untuk f.(x, y, z) , seperti "semua argumen 'f' disiarkan sebagai skalar"?

Bagaimanapun, mungkin bijaksana untuk memperbaiki #27563 (misalnya dengan #27608) dan membiarkan pengguna memainkannya beberapa saat sebelum 1.0.
[0.7 dan 1.0.0-rc1.0 dirilis tanpa perbaikan].

Bagaimanapun, mungkin bijaksana untuk memperbaiki #27563 (misalnya dengan #27608) dan membiarkan pengguna memainkannya beberapa saat sebelum 1.0.
[0.7 dan 1.0.0-rc1.0 dirilis tanpa perbaikan].

Saya kira Anda melewatkan berita bahwa 1.0 telah dirilis .

@StefanKarpinski Merindukan itu, memang. Selamat untuk semua pengembang, julia luar biasa, teruskan!

Apakah halaman ini membantu?
0 / 5 - 0 peringkat

Masalah terkait

sbromberger picture sbromberger  ·  3Komentar

arshpreetsingh picture arshpreetsingh  ·  3Komentar

manor picture manor  ·  3Komentar

StefanKarpinski picture StefanKarpinski  ·  3Komentar

yurivish picture yurivish  ·  3Komentar