Julia: Sintaks alternatif untuk `map(func, x)`

Dibuat pada 23 Sep 2014  ·  283Komentar  ·  Sumber: JuliaLang/julia

Ini telah dibahas secara rinci di sini . Saya mengalami kesulitan menemukannya, dan berpikir itu layak mendapatkan masalah sendiri.

breaking speculative

Komentar yang paling membantu

Setelah anggota dari tiga serangkai (Stefan, Jeff, Viral) bergabung #15032 (yang menurut saya sudah siap untuk digabungkan), saya akan menutup ini dan mengajukan masalah peta jalan untuk menguraikan sisa perubahan yang diusulkan: perbaiki perhitungan tipe siaran, tidak digunakan lagi @vectorize , ubah .op menjadi gula siaran, tambahkan "broadcast-fusion" tingkat sintaksis, dan terakhir gabungkan dengan penugasan di tempat. Dua yang terakhir mungkin tidak akan berhasil menjadi 0,5.

Semua 283 komentar

+1

Atau func.(args...) sebagai gula sintaksis untuk

broadcast(func, args...)

Tapi mungkin aku satu-satunya yang lebih suka itu?
Bagaimanapun, +1.

:-1: Jika ada, saya pikir saran Stefan yang lain tentang f[...] memiliki kesamaan yang bagus dengan pemahaman.

Seperti @ihnorton , saya juga tidak terlalu menyukai ide ini. Secara khusus, saya tidak suka asimetri memiliki keduanya a .+ b dan sin.(a) .

Mungkin kita tidak membutuhkan sintaks khusus. Dengan #1470, kita bisa melakukan sesuatu seperti

call(f::Callable,x::AbstractArray) = applicable(f,x) ? apply(f,x) : map(f,x)

Baik? Mungkin ini akan terlalu ajaib, untuk mendapatkan peta otomatis pada fungsi apa pun.

@quinnj Satu baris itu merangkum ketakutan terbesar saya tentang mengizinkan panggilan yang berlebihan. Aku tidak akan bisa tidur selama berhari-hari.

Belum yakin secara sintaksis mungkin, tetapi bagaimana dengan .sin(x) ? Apakah itu lebih mirip dengan a .+ b ?

Saya pikir [] terlalu kelebihan beban dan tidak akan berfungsi untuk tujuan ini. Misalnya, kita mungkin dapat menulis Int(x) , tetapi Int[x] membuat array sehingga tidak bisa berarti map .

Saya akan bergabung dengan .sin(x) .

Kita harus mencakar kembali beberapa sintaks untuk itu, tetapi jika Int(x) adalah versi skalar maka Int[x] masuk akal dengan analogi untuk membangun vektor tipe elemen Int . Menurut saya, kesempatan untuk membuat sintaks itu lebih koheren sebenarnya adalah salah satu aspek yang paling menarik dari proposal f[v] .

Bagaimana cara membuat sintaks f[v] untuk map membuat sintaks lebih koheren? Saya tidak mengerti. map memiliki "bentuk" yang berbeda dari sintaks konstruktor array T[...] saat ini. Bagaimana dengan Vector{Int}[...] ? Bukankah itu tidak akan berhasil?

lol, maaf untuk menakut-nakuti @JeffBezanson! Haha, kelebihan panggilan jelas sedikit menakutkan, sesekali, saya berpikir tentang jenis pengaburan kode yang dapat Anda lakukan di julia dan dengan call , Anda dapat melakukan beberapa hal buruk.

Saya pikir .sin(x) terdengar seperti ide yang bagus juga. Apakah ada konsensus tentang apa yang harus dilakukan dengan multi-args?

:-1:. Menyimpan beberapa karakter dibandingkan dengan menggunakan fungsi tingkat tinggi yang menurut saya tidak sepadan dengan biaya dalam keterbacaan. Dapatkah Anda membayangkan sebuah file dengan .func() / func.() dan func() diselingi di mana-mana?

Sepertinya kita akan menghapus sintaks a.(b) , setidaknya.

Wow, bicara tentang mengaduk sarang lebah! Saya mengubah nama untuk lebih mencerminkan diskusi.

Kami juga dapat mengganti nama 2-argumen map menjadi zipWith :)

Jika beberapa sintaks benar-benar diperlukan, bagaimana dengan [f <- b] atau permainan kata lain tentang pemahaman _di dalam_ tanda kurung?

( @JeffBezanson Anda hanya takut seseorang akan menulis CJOS atau Moose.jl :) ... jika kita mendapatkan fitur itu, taruh saja di bagian Don't do stupid stuff: I won't optimize that dari manual)

Saat ini menulis Int[...] menunjukkan bahwa Anda sedang membangun array bertipe elemen Int . Tetapi jika Int(x) berarti mengonversi x menjadi Int dengan menerapkan Int sebagai fungsi, maka Anda juga dapat mempertimbangkan Int[...] berarti "menerapkan Int untuk setiap hal di ...", oh yang kebetulan menghasilkan nilai tipe Int . Jadi menulis Int[v] akan setara dengan [ Int(x) for x in v ] dan Int[ f(x) for x in v ] akan setara dengan [ Int(f(x)) for x in v ] . Tentu saja, Anda telah kehilangan beberapa kegunaan penulisan Int[ f(x) for x in v ] di tempat pertama – yaitu bahwa kita dapat mengetahui secara statis bahwa tipe elemen adalah Int – tetapi jika menerapkannya Int(x) harus menghasilkan nilai tipe Int (bukan batasan yang tidak masuk akal), maka kita dapat memulihkan properti itu.

Bagi saya lebih banyak vektorisasi/kucing implisit. Apa yang akan dilakukan Int[x, y] ? Atau lebih buruk, Vector{Int}[x] ?

Saya tidak mengatakan itu adalah ide terbaik yang pernah ada atau bahkan menganjurkannya – saya hanya menunjukkan bahwa itu tidak _sepenuhnya_ berbenturan dengan penggunaan yang ada, yang merupakan sedikit peretasan. Jika kita bisa membuat penggunaan yang ada menjadi bagian dari pola yang lebih koheren, itu akan menjadi kemenangan. Saya tidak yakin apa arti f[v,w] – pilihan yang jelas adalah [ f(x,y) for x in v, y in w ] atau map(f,v,w) tetapi masih ada lebih banyak pilihan.

Saya merasa a.(b) jarang digunakan. Menjalankan tes cepat dan hanya digunakan di 54 dari ~4.000 file sumber julia di alam liar: https://Gist.github.com/jakebolewski/104458397f2e97a3d57d.

Saya pikir itu benar-benar bentrok. T[x] memiliki "bentuk" T --> Array{T} , sedangkan map memiliki bentuk Array{T} --> Array{S} . Itu sangat tidak cocok.

Untuk melakukan ini, saya pikir kita harus menyerahkan T[x,y,z] sebagai konstruktor untuk Vector{T} . Pengindeksan array lama biasa, A[I] di mana I adalah vektor, dapat dilihat sebagai map(i->A[i], I) . "Menerapkan" array seperti menerapkan fungsi (tentu saja matlab bahkan menggunakan sintaks yang sama untuk mereka). Dalam hal ini sintaks benar-benar berfungsi, tetapi kita akan kehilangan sintaks vektor yang diketik dalam prosesnya.

Saya merasa seperti memperdebatkan sintaks di sini mengalihkan perhatian dari perubahan yang lebih penting: membuat map dengan cepat.

Jelas membuat map cepat (yang, omong-omong, perlu menjadi bagian dari desain ulang yang cukup menyeluruh dari gagasan fungsi di julia) lebih penting. Namun beralih dari sin(x) ke map(sin, x) sangat signifikan dari perspektif kegunaan, jadi untuk benar-benar mematikan vektorisasi sintaksnya cukup penting.

Namun beralih dari sin(x) ke map(sin, x) sangat signifikan dari perspektif kegunaan, jadi untuk benar-benar mematikan vektorisasi sintaksnya cukup penting.

Sepenuhnya setuju.

Saya setuju dengan @JeffBezanson bahwa f[x] sangat tidak dapat didamaikan dengan konstruksi array yang diketik saat ini Int[x, y] dll.

Alasan lain untuk memilih .sin daripada sin. adalah untuk akhirnya mengizinkan penggunaan misalnya Base.(+) untuk mengakses fungsi + di Base (sekali a.(b) dihapus).

Ketika sebuah modul mendefinisikan fungsinya sendiri sin (atau apa pun) dan kita ingin menggunakan fungsi itu pada sebuah vektor, apakah kita melakukannya Module..sin(v) ? Module.(.sin(v)) ? Module.(.sin)(v) ? .Module.sin(v) ?

Tak satu pun dari opsi ini benar-benar tampak bagus lagi.

Saya merasa diskusi ini melewatkan inti masalahnya. Yaitu: ketika memetakan fungsi argumen tunggal ke wadah, saya merasa sintaks map(func, container) _sudah_ jelas dan ringkas. Sebaliknya, hanya ketika berhadapan dengan banyak argumen yang saya rasa kita mungkin bisa mendapatkan keuntungan dari sintaks yang lebih baik untuk currying.

Ambil contoh verbositas map(x->func(x,other,args), container) , atau rantai operasi filter untuk memperburuknya filter(x->func2(x[1]) == val, map(x->func1(x,other,args), container)) .

Dalam kasus ini, saya merasa sintaks peta yang dipersingkat tidak akan banyak membantu. Bukannya saya pikir ini sangat buruk, tetapi a) Saya tidak berpikir map short-hand akan banyak membantu dan b) Saya suka mencari beberapa sintaks Haskell. ;)

IIRC, di Haskell di atas dapat ditulis filter ((==val) . func2 . fst) $ map (func1 other args) container dengan sedikit perubahan urutan argumen menjadi func1 .

Di elm .func didefinisikan oleh x->x.func dan ini sangat berguna, lihat elm records . Ini harus dipertimbangkan sebelum menggunakan sintaks ini untuk map .

Aku suka itu.

Meskipun akses lapangan bukan masalah besar di Julia seperti dalam banyak bahasa.

Ya rasanya kurang relevan di sini karena bidang di Julia lebih untuk penggunaan "pribadi". Tetapi dengan diskusi yang sedang berlangsung tentang kelebihan akses lapangan, itu mungkin menjadi lebih masuk akal.

f.(x) sepertinya solusi yang tidak terlalu bermasalah, jika bukan karena asimetri dengan .+ . Tetapi menjaga asosiasi simbolis . ke "operasi elemen-bijaksana" adalah ide yang bagus IMHO.

Jika konstruksi array yang diketik saat ini dapat ditinggalkan, maka func[v...] dapat diterjemahkan ke map(func, v...) , dan array literal kemudian dapat ditulis T[[a1, ..., an]] (sebagai ganti T[a1, ..., an] saat ini

Saya menemukan sin∘v tenang alami juga (ketika array v terlihat aplikasi dari indeks ke nilai yang terkandung), atau lebih sederhana sin*v atau v*[sin]' ( yang membutuhkan mendefinisikan *(x, f::Callable) ) dll.

Kembali ke masalah ini dengan pikiran yang segar, saya menyadari f.(x) dapat dilihat sebagai sintaks yang cukup alami. Alih-alih membacanya sebagai f. dan ( , Anda dapat membacanya sebagai f dan .( . .( kemudian secara metaforis merupakan versi elemen dari operator panggilan fungsi ( , yang sepenuhnya konsisten dengan .+ dan teman-teman.

Gagasan .( menjadi operator panggilan fungsi membuat saya sangat sedih.

@johnmyleswhite Ingin menguraikan? Saya berbicara tentang intuisi sintaksis, atau konsistensi visualnya dengan bahasa lainnya, bukan tentang implementasi teknis sama sekali.

Bagi saya, ( sama sekali bukan bagian dari semantik bahasa: itu hanya bagian dari sintaks. Jadi saya tidak ingin harus menemukan cara untuk .( dan ( untuk mulai berbeda. Apakah yang pertama menghasilkan multicall Expr bukannya call Expr ?

Tidak. Seperti yang saya katakan, saya tidak menyiratkan sama sekali harus ada dua operator panggilan yang berbeda. Hanya mencoba menemukan sintaks _visually_ yang konsisten untuk operasi elemen-bijaksana.

Bagi saya apa yang membunuh opsi ini adalah pertanyaan tentang bagaimana membuat vektor fungsi multi-argumen. Tidak ada cara tunggal untuk melakukannya dan apa pun yang cukup umum untuk mendukung setiap cara yang mungkin mulai terlihat sangat mirip dengan pemahaman array multidimensi yang sudah kita miliki.

Ini cukup standar untuk multi-argumen map untuk mengulangi semua argumen.
Jika kita melakukan ini, saya akan membuat .( sintaks untuk panggilan ke peta. Sintaks itu mungkin
tidak terlalu bagus karena berbagai alasan, tetapi saya akan baik-baik saja dengan aspek-aspek ini.

Fakta bahwa beberapa generalisasi dimungkinkan untuk fungsi multi-argumen tidak dapat menjadi argumen untuk mendukung setidaknya beberapa kasus khusus -- sama seperti transpos matriks berguna bahkan jika dapat digeneralisasi dalam beberapa cara untuk tensor.

Kita hanya perlu memilih solusi yang paling berguna. Pilihan yang memungkinkan telah dibahas di sini: https://github.com/JuliaLang/julia/issues/8389#issuecomment -55953120 (dan komentar berikut). Seperti yang dikatakan @JeffBezanson , perilaku map saat ini masuk akal. Kriteria yang menarik adalah dapat mengganti @vectorize_2arg .

Maksud saya adalah bahwa memiliki sin.(x) dan x .+ y hidup berdampingan itu canggung. Saya lebih suka memiliki .sin(x) -> map(sin, x) dan x .+ y -> map(+, x, y) .

.+ sebenarnya menggunakan broadcast .

Beberapa ide lain, karena putus asa:

  1. Kelebihan titik dua, sin:x . Tidak menggeneralisasi dengan baik ke banyak argumen.
  2. sin.[x] --- sintaks ini tersedia, saat ini tidak berarti.
  3. sin@x --- tidak tersedia, tapi mungkin saja

Saya benar-benar tidak yakin bahwa kita membutuhkan ini.

Aku juga tidak. Saya pikir f.(x) adalah pilihan terbaik di sini, tapi saya tidak menyukainya.

Tetapi tanpa ini, bagaimana kita dapat menghindari pembuatan semua jenis fungsi vektor, khususnya hal-hal seperti int() ? Inilah yang mendorong saya untuk memulai diskusi ini di https://github.com/JuliaLang/julia/issues/8389.

Kita harus mendorong orang untuk menggunakan map(func, x) . Tidak banyak mengetik dan langsung jelas bagi siapa saja yang berasal dari bahasa lain.

Dan tentu saja, pastikan itu cepat.

Ya, tetapi untuk penggunaan interaktif saya merasa sangat menyakitkan. Itu akan menjadi gangguan besar bagi orang-orang yang berasal dari R (setidaknya, saya tidak tahu tentang bahasa lain), dan saya tidak ingin ini memberi perasaan bahwa Julia tidak cocok untuk analisis data.

Masalah lain adalah konsistensi: kecuali jika Anda ingin menghapus semua fungsi yang saat ini di-vektor, termasuk log , exp , dll., dan meminta orang untuk menggunakan map sebagai gantinya (yang mungkin tes yang menarik dari kepraktisan keputusan ini), bahasanya akan menjadi tidak konsisten, sehingga sulit untuk mengetahui terlebih dahulu apakah suatu fungsi divektorkan atau tidak (dan pada argumen mana).

Banyak bahasa lain telah menggunakan map selama bertahun-tahun.

Saat saya memahami rencana untuk menghentikan semua vektorisasi, menghapus sebagian besar/semua fungsi vektor selalu menjadi bagian dari strategi.

Ya, dari _course_ kami akan berhenti membuat vektor semuanya. Ketidakkonsistenan sudah ada: sudah sulit untuk mengetahui fungsi mana yang atau harus divektorkan, karena tidak ada alasan yang benar-benar meyakinkan mengapa sin , exp dll. harus dipetakan secara implisit melalui array.

Dan memberitahu penulis perpustakaan untuk menempatkan @vectorize pada semua fungsi yang sesuai adalah konyol; anda harus dapat menulis sebuah fungsi, dan jika seseorang ingin menghitungnya untuk setiap elemen, mereka menggunakan map .

Mari kita bayangkan apa yang terjadi jika kita menghapus fungsi matematika vektor yang umum digunakan:

  1. Secara pribadi, saya tidak keberatan menulis map(exp, x) alih-alih exp(x) , meskipun yang terakhir sedikit lebih pendek dan lebih bersih. Namun, ada perbedaan _besar_ dalam kinerja. Fungsi vektor sekitar 5x lebih cepat daripada peta di mesin saya.
  2. Saat Anda bekerja dengan ekspresi majemuk, masalahnya lebih menarik. Pertimbangkan ekspresi majemuk: exp(0.5 * abs2(x - y)) , maka kita memiliki
# baseline: the shortest way
exp(0.5 * abs2(x - y))    # ... takes 0.03762 sec (for 10^6 elements)

# using map (very cumbersome for compound expressions)
map(exp, 0.5 * map(abs2, x - y))   # ... takes 0.1304 sec (about 3.5x slower)

# using anonymous function (shorter for compound expressions)
map((u, v) -> 0.5 * exp(abs2(u - v)), x, y)   # ... takes 0.2228 sec (even slower, about 6x baseline)

# using array comprehension (we have to deal with two array arguments)

# method 1:  using zip to combine the arguments (readability not bad)
[0.5 * exp(abs2(u - v)) for (u, v) in zip(x, y)]  # ... takes 0.140 sec, comparable to using map

# method 2:  using index, resulting in a slightly longer statement
[0.5 * exp(abs2(x[i] - y[i])) for i = 1:length(x)]  # ... takes 0.016 sec, 2x faster than baseline 

Jika kita akan menghapus fungsi matematika vektor, satu-satunya cara yang dapat diterima baik dalam keterbacaan dan kinerja tampaknya adalah pemahaman array. Namun, mereka tidak senyaman matematika vektor.

-1 untuk menghapus versi vektor. Faktanya perpustakaan seperti VML dan Yeppp menawarkan kinerja yang jauh lebih tinggi untuk versi vektor dan kita perlu mencari cara untuk memanfaatkannya.

Apakah ini pada dasarnya atau tidak adalah diskusi yang berbeda dan diskusi yang lebih besar, tetapi kebutuhannya nyata dan kinerjanya bisa lebih tinggi dari yang kita miliki.

@lindahua dan @ViralBShah : Beberapa kekhawatiran Anda tampaknya didasarkan pada asumsi bahwa kami akan menyingkirkan fungsi vektor sebelum kami melakukan peningkatan pada map , tetapi saya tidak yakin ada orang yang mengusulkan melakukan itu.

Saya pikir contoh @lindahua cukup jitu: sintaksis vektor jauh lebih bagus dan lebih dekat dengan rumus matematika daripada solusi lainnya. Saya akan sangat buruk untuk kehilangan itu, dan orang-orang yang datang dari bahasa ilmiah lain mungkin akan menganggap ini sebagai poin negatif di Julia.

Saya baik-baik saja dengan menghapus semua fungsi vektor (ketika map cukup cepat), dan lihat bagaimana kelanjutannya. Saya percaya minat memberikan sintaks kenyamanan akan lebih terlihat pada saat itu, dan masih akan ada waktu untuk menambahkannya jika ternyata memang demikian.

Saya pikir Julia berbeda dari banyak bahasa lain karena menekankan penggunaan interaktif (ekspresi yang lebih panjang mengganggu untuk mengetik dalam kasus itu), dan perhitungan matematis (rumus harus sedekat mungkin dengan ekspresi matematika untuk membuat kode dapat dibaca). Itu sebagian mengapa Matlab, R dan Numpy menawarkan fungsi vektor (alasan lainnya tentu saja adalah kinerja, masalah yang bisa hilang di Julia).

Perasaan saya dari diskusi adalah bahwa pentingnya matematika vektor diremehkan. Sebenarnya, salah satu keuntungan utama dari matematika vektor terletak pada keringkasan ekspresi -- ini lebih dari sekedar pengganti sementara untuk "bahasa dengan for-loop yang lambat".

Membandingkan y = exp(x) dan

for i = 1:length(x)
    y[i] = exp(x[i])
end

Yang pertama jelas jauh lebih ringkas dan mudah dibaca daripada yang terakhir. Fakta bahwa Julia membuat loop menjadi efisien tidak berarti bahwa kita harus selalu mende-vektor kode, yang, bagi saya, cukup kontraproduktif.

Kita harus mendorong orang menulis kode dengan cara yang alami. Di satu sisi, ini berarti bahwa kita tidak boleh mencoba menulis kode yang berbelit-belit dan memutar fungsi vektor dalam konteks yang tidak sesuai; di sisi lain, kita harus mendukung penggunaan matematika vektor setiap kali mereka paling masuk akal.

Dalam praktiknya, memetakan rumus ke array angka adalah operasi yang sangat umum, dan kita harus berusaha membuatnya nyaman daripada membuatnya rumit. Untuk tujuan ini, kode vektor tetap menjadi cara yang paling alami dan ringkas. Selain kinerja, mereka masih lebih baik daripada memanggil fungsi map , terutama untuk ekspresi majemuk dengan banyak argumen.

Kami tentu tidak ingin semuanya versi vektor, tetapi menggunakan peta setiap kali untuk membuat vektor akan mengganggu karena alasan yang baru saja disebutkan Dahua di atas.

Jika peta cepat, itu pasti akan membuat kita fokus untuk memiliki seperangkat fungsi vektor yang lebih kecil dan bermakna.

Saya harus mengatakan bahwa saya sangat mendukung notasi peta yang singkat. Saya merasa itu adalah kompromi terbaik antara kebutuhan yang berbeda.

Saya tidak suka fungsi vektor. Ia menyembunyikan apa yang sedang terjadi. Kebiasaan membuat versi fungsi yang di-vektor ini mengarah ke kode misteri. Katakanlah Anda memiliki beberapa kode di mana fungsi f dari sebuah paket dipanggil pada vektor. Bahkan jika Anda memiliki beberapa gagasan tentang apa fungsinya, Anda tidak dapat memastikan dari membaca kode apakah itu berfungsi dari segi elemen atau bekerja pada vektor secara keseluruhan. Jika bahasa komputasi ilmiah tidak memiliki sejarah memiliki fungsi vektor ini, saya tidak berpikir kita akan menerimanya sekarang.

Ini juga mengarah pada situasi di mana Anda secara implisit didorong untuk menulis versi vektor dari fungsi untuk mengaktifkan kode singkat di mana fungsi digunakan.

Kode yang paling eksplisit tentang apa yang sedang terjadi adalah loop, tetapi seperti yang dikatakan @lindahua , akhirnya menjadi sangat bertele-tele, yang memiliki kelemahannya sendiri, terutama dalam bahasa yang juga dimaksudkan untuk penggunaan interaktif.

Ini mengarah pada kompromi map , yang menurut saya lebih mendekati ideal, tetapi saya masih setuju dengan @lindahua bahwa itu tidak cukup singkat.

Di mana saya akan tidak setuju dengan @lindahua adalah bahwa fungsi vektor adalah pilihan terbaik, untuk alasan yang saya sebutkan sebelumnya. Alasan saya mengarah adalah bahwa Julia harus memiliki notasi yang sangat singkat untuk map .

Saya menemukan bagaimana Mathematica melakukannya dengan notasi pendeknya sangat menarik. Notasi singkat untuk menerapkan fungsi ke argumen di Mathematica adalah @ , jadi Anda akan Apply fungsi f ke vektor sebagai: f @ vector . Notasi singkat terkait untuk memetakan suatu fungsi adalah /@ , jadi Anda memetakan f ke vektor sebagai: f /@ vector . Ini memiliki beberapa karakteristik yang menarik. Kedua tangan pendek itu singkat. Fakta bahwa keduanya menggunakan simbol @ menekankan bahwa ada hubungan antara apa yang mereka lakukan, tetapi / di peta masih membuatnya berbeda secara visual untuk memperjelas kapan Anda memetakan dan kapan Anda tidak. Ini bukan berarti Julia harus menyalin notasi Mathematica secara membabi buta, hanya notasi yang bagus untuk pemetaan yang sangat berharga.

Saya tidak menyarankan untuk menyingkirkan semua fungsi vektor. Kereta itu sudah lama meninggalkan stasiun. Sebaliknya, saya menyarankan untuk menjaga daftar fungsi vektor sesingkat mungkin dan memberikan notasi peta singkat yang baik untuk mencegah penambahan ke daftar fungsi vektor.

Semua ini, tentu saja, bergantung pada map dan fungsi anonim menjadi cepat. Saat ini Julia berada dalam posisi yang aneh. Dulu kasus dalam bahasa komputasi ilmiah bahwa fungsi divektorkan karena loop lambat. Ini bukan masalah. Sebaliknya, di Julia, Anda membuat fungsi vektor karena peta dan fungsi anonim lambat. Jadi kami kembali ke tempat kami memulai, tetapi untuk alasan yang berbeda.

Fungsi perpustakaan yang divektorkan memiliki satu kelemahan -- hanya fungsi yang secara eksplisit disediakan oleh perpustakaan yang tersedia. Yaitu, misalnya sin(x) cepat ketika diterapkan ke vektor, sementara sin(2*x) tiba-tiba jauh lebih lambat karena memerlukan larik perantara yang perlu dilalui dua kali (pertama menulis, lalu membaca).

Salah satu solusinya adalah perpustakaan fungsi matematika vectorizABLE. Ini akan menjadi implementasi dari sin , cos , dll. yang tersedia untuk LLVM untuk inlining. LLVM kemudian dapat membuat vektor loop ini, dan mudah-mudahan menghasilkan kode yang sangat efisien. Yeppp tampaknya memiliki kernel loop yang tepat, tetapi tampaknya tidak mengeksposnya untuk inlining.

Masalah lain dengan vektorisasi sebagai paradigma adalah bahwa itu tidak berfungsi sama sekali jika Anda menggunakan tipe wadah lain yang diberkati oleh perpustakaan standar. Anda dapat melihat ini di DataArrays: ada banyak kode metaprogramming yang digunakan untuk memvektor ulang fungsi untuk tipe container baru yang kami definisikan.

Gabungkan itu dengan poin @eschnett dan Anda mendapatkan:

  • Fungsi yang divektorkan hanya berfungsi jika Anda membatasi diri pada fungsi di perpustakaan standar
  • Fungsi yang divektorkan hanya berfungsi jika Anda membatasi diri Anda pada jenis wadah di perpustakaan standar

Saya ingin mengklarifikasi bahwa maksud saya bukanlah bahwa kita harus selalu menjaga fungsi vektor, tetapi kita membutuhkan cara yang sesingkat menulis fungsi vektor. Menggunakan map mungkin tidak memuaskan ini.

Saya suka ide @eschnett untuk menandai beberapa fungsi sebagai _vectorizable_, dan kompiler dapat secara otomatis memetakan fungsi yang dapat di-vektorkan ke array tanpa mengharuskan pengguna untuk secara eksplisit mendefinisikan versi vektor. Kompiler juga dapat menggabungkan rantai fungsi yang dapat divektorkan ke dalam loop yang menyatu.

Inilah yang ada dalam pikiran saya, terinspirasi oleh komentar @eschnett :

# The <strong i="11">@vec</strong> macro tags the function that follows as vectorizable
<strong i="12">@vec</strong> abs2(x::Real) = x * x
<strong i="13">@vec</strong> function exp(x::Real) 
   # ... internal implementation ...
end

exp(2.0)  # simply calls the function

x = rand(100);
exp(x)    # maps exp to x, as exp is tagged as vectorizable

exp(abs2(x))  # maps v -> exp(abs2(v)), as this is applying a chain of vectorizable functions

Kompiler juga dapat memvektorisasi ulang perhitungan (pada tingkat yang lebih rendah) dengan mengidentifikasi peluang menggunakan SIMD.

Tentu saja, @vec harus tersedia bagi pengguna akhir, sehingga orang dapat mendeklarasikan fungsi mereka sendiri sebagai dapat divektorkan.

Terima kasih, @lindahua : klarifikasi Anda sangat membantu.

Apakah @vec berbeda dengan mendeklarasikan fungsi @pure ?

@vec menunjukkan bahwa fungsi dapat dipetakan dengan cara elemen-bijaksana.

Tidak semua fungsi murni termasuk dalam kategori ini, misalnya sum adalah fungsi murni, dan menurut saya tidak disarankan untuk mendeklarasikannya sebagai _vectorizable_.

Tidak dapatkah seseorang menurunkan kembali properti vec dari sum dari tag pure dan associative pada + beserta pengetahuan tentang cara reduce / foldl / foldr berfungsi ketika diberikan fungsi pure dan associative ? Jelas ini semua hipotetis, tetapi jika Julia memasukkan semua sifat untuk tipe, saya bisa membayangkan secara substansial meningkatkan keadaan seni untuk vektorisasi dengan juga memasukkan semua sifat untuk fungsi.

Saya merasa seperti menambahkan sintaks baru adalah kebalikan dari apa yang kita inginkan (setelah hanya membersihkan sintaks khusus untuk Any[] dan Dict). Seluruh _point_ menghapus fungsi vektor ini adalah untuk mengurangi kasus khusus (dan saya tidak berpikir sintaks harus berbeda dari fungsi semantik). Tapi saya setuju bahwa peta singkat akan berguna.

Jadi mengapa tidak menambahkan operator map infix singkat? Di sini saya akan memilih $ secara sewenang-wenang. Ini akan membuat contoh @lindahua berubah dari

exp(0.5 * abs2(x - y))

ke

exp $ (0.5 * abs2 $ (x-y))

Sekarang, jika kami hanya memiliki dukungan seperti Haskell untuk operator infix yang ditentukan pengguna, ini hanya akan menjadi satu baris perubahan ($) = map . :)

IMO, proposal sintaks lainnya terlalu dekat secara visual dengan sintaks yang ada, dan akan membutuhkan upaya mental lebih lanjut untuk menguraikan ketika melihat melalui kode:

  • foo.(x) -- secara visual mirip dengan akses anggota tipe standar
  • foo[x] -- apakah saya mengakses anggota ke-x dari array foo atau memanggil peta di sini?
  • .foo(x) -- memiliki masalah seperti yang ditunjukkan oleh @kmsquire

Dan saya merasa solusi @vec terlalu dekat dengan @vectorize yang kami coba hindari sejak awal. Tentu saja, beberapa anotasi ala #8297 akan menyenangkan untuk dimiliki dan dapat membantu masa depan, kompiler yang lebih cerdas kemudian dapat mengenali peluang fusi aliran ini dan mengoptimalkannya. Tapi saya tidak suka gagasan memaksanya.

Peta infix plus fungsi anonim cepat juga dapat membantu pembuatan sementara jika memungkinkan Anda melakukan sesuatu seperti:

(x, y) -> exp(0.5 * abs2(x - y)) $ x, y

Saya ingin tahu apakah ide dari Trait.jl baru yang keren dapat dipinjam dalam konteks menunjuk suatu fungsi yang dapat divektorkan. Tentu saja, dalam kasus ini kita melihat _instances_ individual dari tipe Function dapat divektorkan atau tidak, alih-alih Tipe julia yang memiliki sifat tertentu.

Sekarang, jika kami hanya memiliki dukungan seperti Haskell untuk operator infix yang ditentukan pengguna

6582 #6929 tidak cukup?

Ada gunanya dalam diskusi ini tentang membuat vektor seluruh ekspresi dengan sesedikit mungkin sementara array. Pengguna yang menginginkan sintaks yang di-vektor tidak hanya menginginkan exp(x) yang di-vektorkan; mereka ingin menulis ekspresi seperti

y =  √π exp(-x^2) * sin(k*x) + im * log(x-1)

dan membuatnya menjadi vektor secara ajaib

Tidak perlu menandai fungsi sebagai "dapat divektorkan". Ini lebih merupakan properti tentang bagaimana fungsi diimplementasikan dan tersedia untuk Julia. Jika diimplementasikan misalnya dalam C, maka mereka perlu dikompilasi ke bytecode LLVM (bukan file objek) sehingga pengoptimal LLVM masih dapat mengaksesnya. Menerapkannya di Julia juga akan berhasil.

Vectorizability berarti bahwa seseorang mengimplementasikan fungsi dengan cara yang dijelaskan dengan cukup baik oleh proyek Yeppp: Tidak ada cabang, tidak ada tabel, pembagian, atau akar kuadrat jika tersedia sebagai instruksi vektor di perangkat keras, dan jika tidak, banyak operasi perkalian-tambah dan vektor yang menyatu menggabungkan operasi.

Sayangnya, implementasi tersebut akan bergantung pada perangkat keras, yaitu seseorang mungkin harus memilih algoritma yang berbeda atau implementasi yang berbeda tergantung pada instruksi perangkat keras yang efisien. Saya telah melakukan ini di masa lalu ( https://bitbucket.org/eschnett/vecmathlib/wiki/Home ) di C++, dan dengan audiens target yang sedikit berbeda (operasi berbasis stensil yang divektorkan secara manual alih-alih vektorisasi otomatis penyusun).

Di sini, di Julia, segalanya akan lebih mudah karena (a) kita tahu bahwa kompilernya adalah LLVM, dan (b) kita bisa mengimplementasikan ini di Julia daripada C++ (makro vs. templat).

Ada satu hal lain yang perlu dipertimbangkan: Jika seseorang melepaskan sebagian dari standar IEEE, maka kecepatannya dapat ditingkatkan secara signifikan. Banyak pengguna tahu bahwa misalnya nomor yang didenormalisasi tidak penting, atau bahwa input akan selalu kurang dari sqrt(max(Double)) , dll. Pertanyaannya adalah apakah akan menawarkan jalur cepat untuk kasus ini. Saya tahu bahwa saya akan sangat tertarik dengan ini, tetapi orang lain mungkin lebih memilih hasil yang benar-benar dapat direproduksi.

Biarkan saya memasak prototipe exp yang dapat di-vektorkan di Julia. Kami kemudian dapat melihat bagaimana LLVM melakukan vektorisasi loop, dan kecepatan apa yang kami peroleh.

Apakah terlalu menakutkan untuk menggunakan tanda kurung lebar penuh di sekitar argumen fungsi~

Maaf, saya tidak menyadari bahwa saya mengulangi hal yang sama persis yang dibicarakan @johnmyleswhite di atas tentang fungsi dengan sifat. Lanjutkan.

@eschnett Saya rasa tidak masuk akal untuk menautkan API (apakah fungsi dapat divektorkan atau tidak) ke detail implementasi (bagaimana fungsi dikompilasi). Kedengarannya cukup rumit untuk dipahami dan untuk tetap stabil dalam waktu dan lintas arsitektur, dan itu tidak akan berfungsi saat memanggil fungsi di perpustakaan eksternal, misalnya log tidak akan terdeteksi sebagai dapat divektorkan karena memanggil fungsi dari openlibm.

Ide OTOH @johnmyleswhite menggunakan sifat-sifat untuk mengomunikasikan apa yang merupakan sifat matematika suatu fungsi bisa menjadi solusi yang bagus. ( Usulan @lindahua adalah fitur yang saya sarankan di suatu tempat beberapa waktu lalu, tetapi solusi menggunakan sifat mungkin lebih baik.)

Sekarang, jika kami hanya memiliki dukungan seperti Haskell untuk operator infix yang ditentukan pengguna

6582 #6929 tidak cukup?

Saya seharusnya mengatakan: ... operator infix _non-unicode_ yang ditentukan pengguna, karena saya tidak berpikir kami ingin meminta pengguna mengetikkan karakter unicode untuk mengakses fungsionalitas inti seperti itu. Meskipun saya melihat bahwa $ sebenarnya adalah salah satu dari yang ditambahkan, jadi terima kasih untuk itu! Wow, jadi ini benar-benar berfungsi di Julia _today_ (walaupun belum "cepat"...):

julia> ($) = map
julia> sin $ (0.5 * (abs2 $ (x-y)))

Saya tidak tahu apakah itu pilihan terbaik untuk map tetapi menggunakan $ untuk xor sepertinya benar-benar sia-sia. Bitwise xor tidak sering digunakan. map jauh lebih penting.

Poin @jiahao di atas sangat bagus: fungsi vektor individual seperti exp sebenarnya semacam peretasan untuk mendapatkan _ekspresi_ vektor seperti exp(-x^2) . Sintaks yang melakukan sesuatu seperti @devec akan sangat berharga: Anda akan mendapatkan kinerja yang di-devectorized ditambah secara umum tidak perlu mengidentifikasi fungsi secara individual sebagai vektor.

Kemampuan untuk menggunakan ciri-ciri fungsi untuk ini akan keren, tapi menurut saya masih kurang memuaskan. Apa yang sebenarnya terjadi secara umum adalah bahwa satu orang menulis suatu fungsi dan orang lain mengulanginya.

Saya setuju bahwa ini bukan properti fungsi, ini properti penggunaan fungsi. Diskusi tentang menerapkan sifat tampak seperti kasus menggonggong pohon yang salah.

Brainstorming: bagaimana kalau Anda menandai argumen yang ingin Anda petakan sehingga akan mendukung pemetaan multi-args:

a = split("the quick brown")
b = split("fox deer bear")
c = split("jumped over the lazy")
d = split("dog cat")
e = string(a, " ", b., " ", c, " ", d.) # -> 3x2 Vector{String} of the combinations   
# e[1,1]: """["the","quick", "brown"] fox ["jumped","over","the","lazy"] dog"""

Saya tidak yakin apakah .b atau b. lebih baik untuk menunjukkan bahwa Anda ingin itu dipetakan. Saya suka mengembalikan hasil 3x2 multi-dimensi dalam kasus ini karena mewakili bentuk ping map .

Lembah kecil

Di sini https://github.com/eschnett/Vecmathlib.jl adalah repo dengan sampel
implementasi exp , ditulis dengan cara yang dapat dioptimalkan oleh LLVM.
Implementasi ini sekitar dua kali lebih cepat dari standar exp
implementasi pada sistem saya. Ini (mungkin) belum mencapai kecepatan Yeppp,
mungkin karena LLVM tidak membuka gulungan SIMD masing-masing sebagai
agresif sebagai Yeppp. (Saya membandingkan instruksi yang dibongkar.)

Menulis fungsi exp yang dapat divektorkan tidaklah mudah. Menggunakannya terlihat seperti ini:

function kernel_vexp2{T}(ni::Int, nj::Int, x::Array{T,1}, y::Array{T,1})
    for j in 1:nj
        <strong i="16">@simd</strong> for i in 1:ni
            <strong i="17">@inbounds</strong> y[i] += vexp2(x[i])
        end
    end
end

di mana loop j dan argumen fungsi hanya ada untuk
tujuan benchmarking.

Apakah ada makro @unroll untuk Julia?

-erik

Pada Minggu, 2 November 2014 pukul 20:26, Tim Holy [email protected] menulis:

Saya setuju bahwa ini bukan properti dari fungsi, ini adalah properti dari
penggunaan fungsi. Diskusi tentang menerapkan ciri-ciri sepertinya
kasus menggonggong pohon yang salah.

Balas email ini secara langsung atau lihat di GitHub
https://github.com/JuliaLang/julia/issues/8450#issuecomment -61433026.

Erik Schnetter [email protected]
http://www.perimeterinstitute.ca/personal/eschnetter/

fungsi vektor individu seperti exp sebenarnya semacam peretasan untuk mendapatkan _ekspresi_ vektor seperti exp(-x^2)

Sintaks inti untuk mengangkat seluruh ekspresi dari domain skalar akan sangat menarik. Vektorisasi hanyalah salah satu contoh (di mana domain target adalah vektor); kasus penggunaan menarik lainnya akan diangkat ke domain matriks (#5840) di mana semantiknya sangat berbeda. Dalam domain matriks, akan berguna juga untuk mengeksplorasi bagaimana pengiriman pada ekspresi yang berbeda dapat bekerja, karena dalam kasus umum Anda akan menginginkan Schur-Parlett dan algoritme lain yang lebih khusus jika Anda menginginkan sesuatu yang lebih sederhana seperti sqrtm . (Dan dengan sintaks yang cerdas Anda dapat menyingkirkan fungsi *m sepenuhnya - expm , logm , sqrtm , ...)

Apakah ada makro @unroll untuk Julia?

menggunakan Base.Cartesian
@nexpr 4 d->(y[i+d] = exp(x[i+d])

(Lihat http://docs.julialang.org/en/latest/devdocs/cartesian/ jika Anda memiliki pertanyaan.)

@jiahao Menggeneralisasi ini ke fungsi matriks terdengar seperti tantangan yang menarik, tetapi pengetahuan saya tentang itu hampir nol. Apakah Anda punya ide tentang cara kerjanya? Bagaimana itu diartikulasikan dengan vektorisasi? Bagaimana sintaks memungkinkan membuat perbedaan antara menerapkan exp elemen-bijaksana pada vektor/matriks, dan menghitung matriks eksponensialnya?

@timholy : Terima kasih! Saya tidak berpikir untuk menggunakan Cartesian untuk membuka gulungan.

Sayangnya, kode yang dihasilkan oleh @nexprs (atau dengan membuka gulungan manual) tidak lagi di-vektorkan. (Ini LLVM 3.3, mungkin LLVM 3.5 akan lebih baik.)

Re: unrolling, lihat juga postingan @toivoh di julia-users . Mungkin juga layak untuk mencoba #6271.

@nalimilan Saya belum memikirkan ini, tetapi scalar->matriks lifting akan cukup sederhana untuk diterapkan dengan satu fungsi matrixfunc (katakanlah). Satu sintaks hipotetis (benar-benar mengarang sesuatu) bisa jadi

X = randn(10,10)
c = 0.7
lift(x->exp(c*x^2)*sin(x), X)

yang kemudian

  1. mengidentifikasi domain sumber dan target peningkatan dari X bertipe Matrix{Float64} dan memiliki elemen (parameter tipe) Float64 (dengan demikian secara implisit mendefinisikan peningkatan Float64 => Matrix{Float64} ) , kemudian
  2. panggil matrixfunc(x->exp(c*x^2)*sin(x), X) untuk menghitung yang setara dengan expm(c*X^2)*sinm(X) , tetapi menghindari perkalian matriks.

Dalam beberapa kode lain X bisa menjadi Vector{Int} dan pengangkatan tersirat akan dari Int menjadi Vector{Int} , dan kemudian lift(x->exp(c*x^2)*sin(x), X) bisa hubungi map(x->exp(c*x^2)*sin(x), X) .

Orang juga dapat membayangkan metode lain yang menetapkan domain sumber dan target secara eksplisit, misalnya lift(Number=>Matrix, x->exp(c*x^2)*sin(x), X) .

@nalimilan Vektorisasi sebenarnya bukan properti API. Dengan teknologi kompiler saat ini, suatu fungsi hanya dapat divektorkan jika inline. Hal-hal sebagian besar bergantung pada implementasi fungsi - jika ditulis dengan "cara yang benar", maka kompiler dapat membuat vektor (setelah memasukkannya ke dalam loop di sekitarnya).

@eschnett : Apakah Anda menggunakan arti vektorisasi yang sama dengan yang lain? Sepertinya Anda tentang SIMD, dll., Yang bukan maksud saya @nalimilan .

Benar. Ada dua pengertian yang berbeda tentang vektorisasi di sini. Satu penawaran
dengan mendapatkan SIMD untuk loop dalam yang ketat (vektorisasi prosesor). utama
masalah yang sedang dibahas di sini adalah sintaks/semantik entah bagaimana "secara otomatis"
mampu memanggil fungsi argumen tunggal (atau multi) pada koleksi.

Pada Selasa, 4 Nov 2014 jam 19:04, John Myles White [email protected]
menulis:

@eschnett https://github.com/eschnett : Apakah Anda menggunakan arti yang sama?
vektorisasi seperti yang lain? Kedengarannya seperti Anda tentang SIMD, dll., yang
bukan itu yang saya mengerti @nalimilan https://github.com/nalimilan to
berarti.


Balas email ini secara langsung atau lihat di GitHub
https://github.com/JuliaLang/julia/issues/8450#issuecomment -61738237.

Dalam simetri ke operator . lainnya, haruskah f.(x) tidak menerapkan kumpulan fungsi ke kumpulan nilai? (Misalnya, untuk mengubah dari beberapa sistem koordinat satuan ke koordinat fisik.)

Saat mendiskusikan sintaks, muncul gagasan bahwa menggunakan loop eksplisit untuk mengekspresikan setara dengan map(log, x) terlalu lambat. Oleh karena itu, jika seseorang dapat membuatnya dengan cukup cepat, maka pemanggilan map (atau menggunakan sintaks khusus) atau loop penulisan setara pada tingkat semantik, dan seseorang tidak perlu memperkenalkan disambiguasi sintaksis. Saat ini, memanggil fungsi vektor-log jauh lebih cepat daripada menulis loop di atas array, mendorong orang untuk meminta cara untuk mengekspresikan perbedaan ini dalam kode mereka.

Ada dua tingkat masalah di sini: (1) sintaks & semantik, (2) implementasi.

Masalah sintaks & semantik adalah tentang bagaimana pengguna dapat mengekspresikan maksud pemetaan perhitungan tertentu dengan cara elemen/penyiaran ke array yang diberikan. Saat ini, Julia mendukung dua cara: menggunakan fungsi vektor dan memungkinkan pengguna untuk menulis loop secara eksplisit (terkadang dengan bantuan makro). Tidak ada cara yang ideal. Sementara fungsi vektor memungkinkan seseorang untuk menulis ekspresi yang sangat ringkas seperti exp(0.5 * (x - y).^2) , mereka memiliki dua masalah: (1) sulit untuk menarik garis fungsi mana yang harus menyediakan versi vektor dan mana yang tidak, sehingga sering menghasilkan dalam perdebatan tak berujung di sisi pengembang dan kebingungan di sisi pengguna (Anda sering harus mencari dokumen untuk mengetahui apakah fungsi tertentu di-vektor). (2) Itu membuat sulit untuk menggabungkan loop melintasi batas-batas fungsi. Pada titik ini dan mungkin beberapa bulan/tahun yang akan datang, kompiler mungkin tidak akan dapat melakukan tugas-tugas kompleks seperti melihat beberapa fungsi bersama-sama, mengidentifikasi aliran data bersama, dan menghasilkan jalur kode yang dioptimalkan melintasi batas-batas fungsi.

Menggunakan fungsi map mengatasi masalah (1) di sini. Namun, ini masih tidak memberikan bantuan apa pun dalam memecahkan masalah (2) -- menggunakan fungsi, baik fungsi vektor tertentu atau generik map , selalu menciptakan batas fungsi yang menghambat peleburan loop, yang sangat penting dalam komputasi kinerja tinggi. Menggunakan fungsi peta juga menyebabkan verbositas, misalnya ekspresi di atas sekarang menjadi pernyataan yang lebih panjang sebagai map(exp, 0.5 * map(abs2, x - y)) . Anda mungkin membayangkan bahwa masalah ini akan diperparah dengan ekspresi yang lebih kompleks.

Di antara semua proposal yang diuraikan di utas ini, saya pribadi merasa bahwa menggunakan notasi khusus untuk menunjukkan pemetaan adalah cara yang paling menjanjikan ke depan. Pertama-tama, itu mempertahankan keringkasan ekspresi. Ambil notasi $-sebagai contoh, ekspresi di atas sekarang dapat ditulis sebagai exp $(0.5 * abs2$(x - y)) . Ini sedikit lebih lama dari ekspresi vektor asli, tetapi tidak terlalu buruk -- yang diperlukan hanyalah menyisipkan $ ke setiap panggilan pemetaan. Di sisi lain, notasi ini juga berfungsi sebagai indikator yang jelas dari pemetaan yang dilakukan, yang dapat digunakan oleh kompiler untuk memecahkan batas fungsi dan menghasilkan loop yang menyatu. Dalam kursus ini, kompiler tidak harus melihat implementasi internal dari fungsi -- yang perlu diketahui adalah bahwa fungsi tersebut akan dipetakan ke setiap elemen dari array yang diberikan.

Mengingat semua fasilitas CPU modern, terutama kemampuan SIMD, menggabungkan beberapa loop menjadi satu hanyalah satu langkah menuju komputasi kinerja tinggi. Langkah ini sendiri tidak memicu penggunaan instruksi SIMD. Kabar baiknya adalah kita sekarang memiliki makro @simd . Kompilator dapat menyisipkan makro ini ke awal loop yang dihasilkan jika dianggap aman dan bermanfaat.

Singkatnya, saya pikir $-notation (atau proposal serupa) sebagian besar dapat mengatasi masalah sintaks & semantik, sambil memberikan informasi yang diperlukan bagi kompiler untuk menggabungkan loop dan mengeksploitasi SIMD, dan dengan demikian memancarkan kode berkinerja tinggi.

Ringkasan @lindahua bagus IMHO.

Tapi saya pikir akan menarik untuk memperluas ini lebih jauh. Julia layak mendapatkan sistem ambisius yang akan membuat banyak pola umum seefisien loop yang tidak digulung.

  • Pola menggabungkan panggilan fungsi bersarang ke dalam satu loop juga harus diterapkan ke operator, sehingga A .* B .+ C tidak mengarah pada pembuatan dua sementara, tetapi hanya satu untuk hasilnya.
  • Kombinasi fungsi dan reduksi elemen-bijaksana harus ditangani juga, sehingga reduksi diterapkan dengan cepat setelah menghitung nilai setiap elemen. Biasanya, ini akan memungkinkan penghapusan sumabs2(A) , menggantinya dengan notasi standar seperti sum(abs$(A)$^2) (atau sum(abs.(A).^2) ).
  • Terakhir, pola iterasi non-standar harus didukung untuk larik non-standar, sehingga untuk matriks sparse A .* B hanya perlu menangani entri bukan nol, dan mengembalikan matriks sparse. Ini juga akan berguna jika Anda ingin menerapkan fungsi elemen-bijaksana ke Set , Dict , atau bahkan Range .

Dua poin terakhir dapat bekerja dengan membuat fungsi elemen-bijaksana mengembalikan tipe AbstractArray khusus, katakanlah LazyArray , yang akan menghitung elemennya dengan cepat (mirip dengan Transpose ketik dari https://github.com/JuliaLang/julia/issues/4774#issuecomment-59422003). Tetapi alih-alih mengakses elemen-elemennya secara naif dengan melewatinya menggunakan indeks linier dari 1 hingga length(A) , protokol iterator dapat digunakan. Iterator untuk tipe tertentu akan secara otomatis memilih apakah iterasi baris-bijaksana atau kolom-bijaksana adalah yang paling efisien, tergantung pada tata letak penyimpanan tipe. Dan untuk matriks jarang, itu akan memungkinkan melewatkan entri nol (asli dan hasilnya akan diminta untuk memiliki struktur umum, lih. https://github.com/JuliaLang/julia/issues/7010, https://github. com/JuliaLang/julia/issues/7157).

Ketika tidak ada pengurangan yang diterapkan, objek dengan tipe dan bentuk yang sama dengan objek aslinya hanya akan diisi dengan mengulangi LazyArray (setara dengan collect , tetapi menghormati jenis array asli ). Satu-satunya hal yang diperlukan untuk itu adalah iterator mengembalikan objek yang dapat digunakan untuk memanggil getindex pada LazyArray dan setindex! pada hasilnya (mis. koordinat).

Ketika pengurangan diterapkan, itu akan menggunakan metode iterasi yang relevan pada argumennya untuk beralih pada dimensi yang diperlukan dari LazyArray , dan mengisi array dengan hasilnya (setara dengan reduce tetapi menggunakan iterator khusus untuk beradaptasi dengan tipe array). Satu fungsi (yang digunakan dalam paragraf terakhir) akan mengembalikan iterator yang memeriksa semua elemen dengan cara yang paling efisien; orang lain akan mengizinkan melakukannya di atas dimensi tertentu.

Seluruh sistem ini juga akan mendukung operasi di tempat dengan cukup mudah.

Saya berpikir sedikit untuk menangani sintaks dan memikirkan .= untuk menerapkan operasi elemen ke array.
Jadi contoh @nalimilan sum(abs.(A).^2)) sayangnya harus ditulis dalam dua langkah:

A = [1,2,3,4]
a .= abs(A)^2
result = sum(a)

Ini akan memiliki keuntungan karena mudah dibaca dan berarti bahwa fungsi elemen hanya perlu ditulis untuk satu (atau beberapa) input dan dioptimalkan untuk kasus itu alih-alih menulis metode khusus array.

Tentu saja, tidak ada yang lain selain kinerja dan keakraban yang menghentikan siapa pun dari sekadar menulis map((x) -> abs(x)^2, A) sekarang seperti yang telah dinyatakan.

Atau, mengelilingi ekspresi yang akan dipetakan dengan .() bisa berhasil.
Saya tidak tahu betapa sulitnya melakukan ini tetapi .sin(x) dan .(x + sin(x)) kemudian akan memetakan ekspresi di dalam tanda kurung atau fungsi yang mengikuti . .
Ini kemudian akan memungkinkan pengurangan seperti contoh @nalimilan di mana sum(.(abs(A)^2)) kemudian dapat ditulis dalam satu baris.

Kedua proposal ini menggunakan awalan . yang saat menggunakan siaran secara internal, membuat saya memikirkan operasi elemen pada array. Ini dapat dengan mudah ditukar dengan $ atau simbol lain.
Ini hanyalah alternatif untuk menempatkan operator peta di sekitar setiap fungsi yang akan dipetakan dan sebagai gantinya mengelompokkan seluruh ekspresi dan menetapkannya untuk dipetakan.

Saya telah bereksperimen dengan ide LazyArray yang saya ungkapkan pada komentar terakhir saya: https://Gist.github.com/nalimilan/e737bc8b3b10288abdad

Bukti konsep ini tidak memiliki gula sintaksis, tetapi (a ./ 2).^2 akan diterjemahkan ke apa yang tertulis di intinya sebagai LazyArray(LazyArray(a, /, (2,)), ^, (2,)) . Sistem bekerja dengan cukup baik, tetapi perlu lebih banyak pengoptimalan agar dapat bersaing dari jarak jauh dengan loop yang berkaitan dengan kinerja. Masalah (yang diharapkan) tampaknya adalah bahwa panggilan fungsi pada baris 12 tidak dioptimalkan (hampir semua alokasi terjadi di sana), bahkan dalam versi di mana argumen tambahan tidak diperbolehkan. Saya pikir saya perlu memparametrikan LazyArray pada fungsi yang dipanggilnya, tetapi saya belum tahu bagaimana saya bisa melakukan itu, apalagi menangani argumen juga. Ada ide?

Adakah saran tentang cara meningkatkan kinerja LazyArray ?

@nalimilan Saya bereksperimen dengan pendekatan serupa setahun yang lalu, menggunakan tipe functor di NumericFuns untuk membuat parameter tipe ekspresi malas. Saya mencoba berbagai trik, tetapi tidak berhasil menjembatani kesenjangan kinerja.

Pengoptimalan kompiler telah ditingkatkan secara bertahap selama setahun terakhir. Tetapi saya masih merasa bahwa itu masih belum dapat mengoptimalkan overhead yang tidak perlu. Hal-hal semacam ini membutuhkan kompiler untuk fungsi sebaris yang agresif. Anda dapat mencoba menggunakan @inline dan melihat apakah itu membuat segalanya lebih baik.

@lindahua @inline tidak membuat perbedaan pada pengaturan waktu, yang logis bagi saya karena getindex(::LazyArray, ...) khusus untuk tanda tangan LazyArray yang diberikan, yang tidak menentukan fungsi mana yang harus disebut. Saya membutuhkan sesuatu seperti LazyArray{T1, N, T2, F} , dengan F fungsi yang harus dipanggil, sehingga ketika mengkompilasi getindex panggilannya diketahui. Apakah ada cara melakukan itu?

Inlining akan menjadi peningkatan besar lainnya, tetapi saat ini waktunya jauh lebih buruk daripada panggilan non-inline.

Anda dapat mempertimbangkan untuk menggunakan NumericFuns dan F dapat berupa tipe functor.

dahua

Saya membutuhkan fungsi di mana saya tahu tipe pengembalian untuk didistribusikan
komputasi, di mana saya membuat referensi ke hasil sebelum hasil (dan
demikian jenisnya) diketahui. Saya sendiri telah menerapkan hal yang sangat mirip, dan
mungkin harus beralih menggunakan apa yang Anda sebut "Functors". (Saya tidak suka
nama "functor", karena mereka biasanya sesuatu yang lain <
http://en.wikipedia.org/wiki/Functor>, tapi saya rasa C++ mengacaukan segalanya
di sini.)

Saya pikir masuk akal untuk memisahkan bagian Functor Anda dari
fungsi matematika.

-erik

Pada Kam, 20 Nov 2014 jam 10:35, Dahua [email protected]
menulis:

Anda dapat mempertimbangkan untuk menggunakan NumericFuns dan F dapat menjadi tipe functor.

Balas email ini secara langsung atau lihat di GitHub
https://github.com/JuliaLang/julia/issues/8450#issuecomment -63826019.

Erik Schnetter [email protected]
http://www.perimeterinstitute.ca/personal/eschnetter/

@lindahua Saya sudah mencoba menggunakan functors, dan memang kinerjanya jauh lebih masuk akal:
https://Gist.github.com/nalimilan/d345e1c080984ed4c89a

With functions:
# elapsed time: 3.235718017 seconds (1192272000 bytes allocated, 32.20% gc time)

With functors:
# elapsed time: 0.220926698 seconds (80406656 bytes allocated, 26.89% gc time)

Loop:
# elapsed time: 0.07613788 seconds (80187556 bytes allocated, 45.31% gc time) 

Saya tidak yakin apa lagi yang bisa dilakukan untuk memperbaiki keadaan, karena kode yang dihasilkan tampaknya belum optimal. Saya membutuhkan mata yang lebih ahli untuk mengetahui apa yang salah.

Sebenarnya, pengujian di atas menggunakan Pow , yang tampaknya memberikan perbedaan kecepatan yang besar tergantung pada apakah Anda menulis loop eksplisit atau menggunakan LazyArray . Saya kira ini ada hubungannya dengan penggabungan instruksi yang hanya akan dilakukan dalam kasus terakhir. Fenomena yang sama terlihat dengan misalnya penambahan. Tetapi dengan fungsi lain perbedaannya jauh lebih kecil, baik dengan matriks 100x100 atau 1000x1000, kemungkinan karena mereka eksternal dan dengan demikian inlining tidak mendapatkan banyak:

# With sqrt()
julia> test_lazy!(newa, a);
julia> <strong i="8">@time</strong> for i in 1:1000 test_lazy!(newa, a) end
elapsed time: 0.151761874 seconds (232000 bytes allocated)

julia> test_loop_dense!(newa, a);
julia> <strong i="9">@time</strong> for i in 1:1000 test_loop_dense!(newa, a) end
elapsed time: 0.121304952 seconds (0 bytes allocated)

# With exp()
julia> test_lazy!(newa, a);
julia> <strong i="10">@time</strong> for i in 1:1000 test_lazy!(newa, a) end
elapsed time: 0.289050295 seconds (232000 bytes allocated)

julia> test_loop_dense!(newa, a);
julia> <strong i="11">@time</strong> for i in 1:1000 test_loop_dense!(newa, a) end
elapsed time: 0.191016958 seconds (0 bytes allocated)

Jadi saya ingin mencari tahu mengapa pengoptimalan tidak terjadi dengan LazyArray . Perakitan yang dihasilkan cukup panjang untuk operasi sederhana. Misalnya, untuk x/2 + 3 :

julia> a1 = LazyArray(a, Divide(), (2.0,));

julia> a2 = LazyArray(a1,  Add(), (3.0,));

julia> <strong i="17">@code_native</strong> a2[1]
    .text
Filename: none
Source line: 1
    push    RBP
    mov RBP, RSP
Source line: 1
    mov RAX, QWORD PTR [RDI + 8]
    mov RCX, QWORD PTR [RAX + 8]
    lea RDX, QWORD PTR [RSI - 1]
    cmp RDX, QWORD PTR [RCX + 16]
    jae L64
    mov RCX, QWORD PTR [RCX + 8]
    movsd   XMM0, QWORD PTR [RCX + 8*RSI - 8]
    mov RAX, QWORD PTR [RAX + 24]
    mov RAX, QWORD PTR [RAX + 16]
    divsd   XMM0, QWORD PTR [RAX + 8]
    mov RAX, QWORD PTR [RDI + 24]
    mov RAX, QWORD PTR [RAX + 16]
    addsd   XMM0, QWORD PTR [RAX + 8]
    pop RBP
    ret
L64:    movabs  RAX, jl_bounds_exception
    mov RDI, QWORD PTR [RAX]
    movabs  RAX, jl_throw_with_superfluous_argument
    mov ESI, 1
    call    RAX

Berbeda dengan yang setara:

julia> fun(x) = x/2.0 + 3.0
fun (generic function with 1 method)

julia> <strong i="21">@code_native</strong> fun(a1[1])
    .text
Filename: none
Source line: 1
    push    RBP
    mov RBP, RSP
    movabs  RAX, 139856006157040
Source line: 1
    mulsd   XMM0, QWORD PTR [RAX]
    movabs  RAX, 139856006157048
    addsd   XMM0, QWORD PTR [RAX]
    pop RBP
    ret

Bagian hingga jae L64 adalah pemeriksaan batas array. Menggunakan @inbounds dapat membantu (jika sesuai).

Bagian di bawah ini, di mana dua baris berurutan dimulai dengan mov RAX, ... , adalah tipuan ganda, yaitu mengakses pointer ke pointer (atau array array, atau pointer ke array, dll.). Ini mungkin ada hubungannya dengan representasi internal LazyArray -- mungkin menggunakan yang tidak dapat diubah (atau mewakili yang tidak dapat diubah secara berbeda oleh Julia) dapat membantu di sini.

Bagaimanapun, kodenya masih cukup cepat. Untuk membuatnya lebih cepat, itu perlu dimasukkan ke dalam pemanggil, memperlihatkan peluang pengoptimalan lebih lanjut. Apa yang terjadi jika Anda memanggil ekspresi ini misalnya dari sebuah loop?

Juga: Apa yang terjadi jika Anda membongkar ini bukan dari REPL tetapi dari dalam suatu fungsi?

Mau tidak mau juga memperhatikan bahwa versi pertama melakukan yang sebenarnya
pembagian sedangkan yang kedua telah mengubah x/2 menjadi perkalian.

Terima kasih atas komentarnya.

@eschnett LazyArray sudah tidak dapat diubah, dan saya menggunakan @inbounds dalam loop. Setelah menjalankan Intisari di https://Gist.github.com/nalimilan/d345e1c080984ed4c89a , Anda dapat memeriksa apa yang diberikan dalam satu lingkaran dengan ini:

function test_lazy!(newa, a)
    a1 = LazyArray(a, Divide(), (2.0,))
    a2 = LazyArray(a1, Add(), (3.0,))
    collect!(newa, a2)
    newa
end
<strong i="11">@code_native</strong> test_lazy!(newa, a); 

Jadi mungkin yang saya butuhkan hanyalah dapat memaksa inlining? Dalam upaya saya, menambahkan @inline ke getindex tidak mengubah pengaturan waktu.

@toivoh Apa yang bisa menjelaskan bahwa dalam kasus terakhir pembagian tidak disederhanakan?

Saya terus bereksperimen dengan versi dua argumen (disebut LazyArray2 ). Ternyata untuk operasi sederhana seperti x .+ y , sebenarnya lebih cepat menggunakan LazyArray2 daripada .+ saat ini, dan juga cukup dekat dengan loop eksplisit (ini untuk 1000 panggilan , lihat https://Gist.github.com/nalimilan/d345e1c080984ed4c89a):

# With LazyArray2, filling existing array
elapsed time: 0.028212517 seconds (56000 bytes allocated)

# With explicit loop, filling existing array
elapsed time: 0.013500379 seconds (0 bytes allocated)

# With LazyArray2, allocating a new array before filling it
elapsed time: 0.098324278 seconds (80104000 bytes allocated, 74.16% gc time)

# Using .+ (thus allocating a new array)
elapsed time: 0.078337337 seconds (80712000 bytes allocated, 52.46% gc time)

Jadi sepertinya strategi ini layak untuk menggantikan semua operasi elemen, termasuk operator .+ , .* , dll.

Itu juga terlihat sangat kompetitif untuk mencapai operasi umum seperti menghitung jumlah perbedaan kuadrat di sepanjang dimensi matriks, yaitu sum((x .- y).^2, 1) (lihat lagi intinya):

# With LazyArray2 and LazyArray (no array allocated except the result)
elapsed time: 0.022895754 seconds (1272000 bytes allocated)

# With explicit loop (no array allocated except the result)
elapsed time: 0.020376307 seconds (896000 bytes allocated)

# With element-wise operators (temporary copies allocated)
elapsed time: 0.331359085 seconds (160872000 bytes allocated, 50.20% gc time)

@nalimilan
Pendekatan Anda dengan LazyArrays tampaknya mirip dengan cara kerja fusi uap Haskell [1, 2]. Mungkin kita bisa menerapkan ide-ide dari Area itu?

[1] http://citeseer.ist.psu.edu/viewdoc/summary?doi=10.1.1.104.7401
[2] http://citeseer.ist.psu.edu/viewdoc/summary?doi=10.1.1.421.8551

@vchuravy Terima kasih. Ini memang mirip, tetapi lebih kompleks karena Julia menggunakan model imperatif. Sebaliknya di Haskell, kompiler perlu menangani berbagai macam kasus, dan bahkan menangani masalah SIMD (yang ditangani oleh LLVM nanti di Julia). Tapi sejujurnya saya tidak bisa mengurai semuanya di koran-koran ini.

@nalimilan saya tahu perasaan itu. Saya menemukan makalah kedua sangat menarik karena membahas Generalized Stream Fusion, yang tampaknya memungkinkan model komputasi yang bagus di atas vektor.

Saya pikir poin utama yang harus kita ambil dari ini bahwa konstruksi seperti map dan reduce dalam kombinasi dengan kemalasan bisa cukup cepat (atau bahkan lebih cepat daripada loop eksplisit).

Sejauh yang saya tahu, tanda kurung kurawal masih tersedia dalam sintaks panggilan. Bagaimana jika ini menjadi func{x} ? Mungkin agak terlalu boros?

Pada topik fast vectoring (dalam artian SIMD), apakah ada cara kita bisa meniru cara Eigen melakukannya?

Berikut adalah proposal untuk mengganti semua operasi elemen saat ini dengan generalisasi dari apa yang saya sebut LazyArray dan LazyArray2 di atas. Ini tentu saja bergantung pada asumsi bahwa kita dapat membuat ini cepat untuk semua fungsi tanpa bergantung pada fungsi dari NumericFuns.jl.

1) Tambahkan sintaks baru f.(x) atau f$(x) atau apa pun yang akan membuat panggilan LazyArray f() pada setiap elemen x .

2) Generalisasi sintaks ini mengikuti cara kerja broadcast saat ini, sehingga misalnya f.(x, y, ...) atau f$(x, y, ...) membuat LazyArray , tetapi memperluas dimensi tunggal x , y , ... untuk memberi mereka ukuran yang sama. Ini tentu saja akan dilakukan dengan cepat dengan perhitungan pada indeks sehingga array yang diperluas tidak benar-benar dialokasikan.

3) Hasilkan .+ , .- , .* , ./ , .^ , dll. gunakan LazyArray alih-alih broadcast .

4) Perkenalkan operator penugasan baru .= atau $= yang akan mengubah (memanggil collect ) a LazyArray menjadi array nyata (bertipe tergantung pada input melalui aturan promosi, dan tipe elemen tergantung pada tipe elemen input dan fungsi yang dipanggil).

5) Bahkan mungkin mengganti broadcast dengan panggilan ke LazyArray dan collect ion langsung dari hasil menjadi array nyata.

Poin 4 adalah kuncinya: operasi elemen-bijaksana tidak akan pernah mengembalikan array nyata, selalu LazyArray s, sehingga ketika menggabungkan beberapa operasi, tidak ada salinan yang dibuat, dan loop dapat digabungkan untuk efisiensi. Ini memungkinkan pengurangan panggilan seperti sum pada hasil tanpa mengalokasikan sementara. Jadi ekspresi semacam ini akan menjadi idiomatis dan efisien, untuk array padat dan matriks jarang:

y .= sqrt.(x .+ 2)
y .=  √π exp.(-x .^ 2) .* sin.(k .* x) .+ im * log.(x .- 1)
sum((x .- y).^2, 1)

Saya pikir mengembalikan objek ringan semacam ini sangat cocok dengan gambar baru tampilan array dan Transpose / CTranspose . Ini berarti bahwa di Julia Anda dapat melakukan operasi kompleks dengan sangat efisien dengan sintaks yang padat dan mudah dibaca, meskipun dalam beberapa kasus Anda harus secara eksplisit memanggil copy ketika Anda memerlukan "array semu" untuk dibuat independen dari array nyata yang menjadi dasarnya.

Ini benar-benar terdengar seperti fitur penting. Perilaku operator elemen-bijaksana saat ini adalah jebakan bagi pengguna baru, karena sintaksnya bagus dan pendek tetapi kinerjanya biasanya sangat buruk, tampaknya lebih buruk daripada di Matlab. Selama minggu lalu, beberapa utas pada pengguna julia memiliki masalah kinerja yang akan hilang dengan desain seperti itu:
https://groups.google.com/d/msg/julia-users/t0KvvESb9fA/6_ZAp2ujLpMJ
https://groups.google.com/d/msg/julia-users/DL8ZsK6vLjw/w19Zf1lVmHMJ
https://groups.google.com/d/msg/julia-users/YGmDUZGOGgo/LmsorgEfXHgJ

Untuk tujuan masalah ini, saya akan memisahkan sintaks dari kemalasan. Namun, proposal Anda menarik.

Tampaknya ada titik di mana hanya ada _begitu banyak titik_. Contoh tengah khususnya akan lebih baik ditulis sebagai

x .|> x->exp(-x ^ 2) * sin(k * x) + im * log(x - 1)

yang hanya membutuhkan fungsi dasar dan map yang efisien ( .|> ).

Ini perbandingan yang menarik:

y .=  √π exp.(-x .^ 2) .* sin.(k .* x) .+ im * log.(x .- 1)
y =  [√π exp(-x[i]^ 2) .* sin(k * x[i]) .+ im * log(x[i] - 1) for i = 1:length(x)]

Jika Anda mengabaikan bagian for ... , pemahamannya hanya satu karakter lagi. Saya hampir lebih suka memiliki sintaks pemahaman yang disingkat daripada semua titik itu.

Pemahaman 1d tidak mempertahankan bentuk, tetapi sekarang kita memiliki for i in eachindex(x) yang dapat berubah juga.

Satu masalah dengan pemahaman adalah bahwa mereka tidak mendukung DataArrays.

Saya pikir mungkin bermanfaat untuk melihat banyak hal yang terjadi di .Net yang terlihat sangat mirip dengan ide LazyArray. Pada dasarnya itu terlihat cukup dekat dengan pendekatan gaya LINQ bagi saya, di mana Anda memiliki sintaks yang terlihat seperti hal-hal elemen yang kita miliki sekarang, tetapi sebenarnya sintaks itu membangun pohon ekspresi, dan pohon ekspresi itu kemudian dievaluasi dalam beberapa cara yang efisien . Apakah itu entah bagaimana dekat?

Di .Net mereka melangkah jauh dengan ide ini: Anda bisa menjalankan pohon ekspresi ini secara paralel pada beberapa CPU (dengan menambahkan .AsParallel()), atau Anda bisa menjalankannya di cluster besar dengan DryadLINQ, atau bahkan di http:/ /research.microsoft.com/en-us/projects/accelerator/ (yang terakhir mungkin tidak sepenuhnya terintegrasi dengan LINQ, tetapi dekat dalam semangat jika saya mengingatnya dengan benar), atau tentu saja dapat diterjemahkan ke dalam SQL jika itu data dalam bentuk itu dan Anda hanya menggunakan operator yang dapat diterjemahkan ke dalam pernyataan SQL.

Perasaan saya adalah bahwa Blaze juga menuju ke arah itu, yaitu cara untuk dengan mudah membangun objek yang menggambarkan perhitungan, dan kemudian Anda dapat memiliki mesin eksekusi yang berbeda untuk itu.

Tidak yakin ini sangat jelas, tetapi bagi saya tampaknya seluruh masalah ini harus dilihat dalam konteks bagaimana seseorang dapat menghasilkan kode seperti SIMD yang efisien tingkat rendah, dan bagaimana ini dapat digunakan untuk komputasi GPU, pengelompokan, paralel perhitungan dll.

Ya, Anda benar bahwa contoh yang lebih panjang memiliki terlalu banyak titik. Tetapi dua yang lebih pendek lebih khas, dan dalam hal ini memiliki sintaksis yang pendek adalah penting. Saya ingin memisahkan sintaks dari kemalasan, tetapi seperti yang ditunjukkan oleh komentar Anda tampaknya sangat sulit, kami selalu menggabungkan keduanya!

Orang bisa membayangkan mengadaptasi sintaks pemahaman, sesuatu seperti y = [sqrt(x + 2) over x] . Tetapi seperti yang dicatat oleh @johnmyleswhite, mereka kemudian harus mendukung DataArrays , tetapi juga matriks jarang dan jenis array baru apa pun. Jadi ini lagi-lagi kasus pencampuran sintaks dan fitur.

Lebih mendasar, saya pikir dua fitur yang ditawarkan proposal saya di atas alternatif adalah:
1) Dukungan untuk penugasan di tempat yang bebas alokasi menggunakan y[:] = sqrt.(x .+ 2) .
2) Dukungan untuk pengurangan alokasi bebas seperti sum((x .- y).^2, 1) .

Bisakah itu diberikan dengan solusi lain (mengabaikan masalah sintaksis)?

@davidanthoff Terima kasih, lihat sekarang (saya pikir LazyArray dapat dibuat untuk mendukung komputasi paralel juga).

Mungkin ini bisa digabungkan dengan Generator — mereka juga semacam array malas. Saya agak menyukai sintaks pemahaman [f(x) over x] , meskipun secara konseptual bisa sulit bagi pendatang baru (karena nama yang sama digunakan secara efektif untuk elemen dan array itu sendiri). Jika pemahaman tanpa tanda kurung akan membuat generator (seperti yang saya mainkan sejak lama ), maka wajar untuk menggunakan pemahaman gaya-over-x baru ini tanpa tanda kurung untuk mengembalikan LazyArray alih-alih segera mengumpulkannya.

@mbauman Ya, generator dan array malas berbagi banyak properti. Gagasan menggunakan tanda kurung untuk mengumpulkan generator/array malas, dan tidak menambahkannya untuk menjaga objek malas terdengar keren. Jadi mengenai contoh saya di atas, seseorang akan dapat menulis keduanya 1) y[:] = sqrt(x + 2) over x dan sum((x - y)^2 over (x, y), 1) (walaupun saya merasa wajar bahkan untuk pendatang baru, mari kita tinggalkan masalah over untuk sesi bikeshedding dan berkonsentrasilah pada dasar-dasarnya terlebih dahulu).

Saya suka ide f(x) over x . Kami bahkan dapat menggunakan f(x) for x untuk menghindari kata kunci baru. Sebenarnya [f(x) for x=x] sudah berfungsi. Kami kemudian perlu membuat pemahaman yang setara dengan map , sehingga mereka dapat bekerja untuk non-Array. Array hanya akan menjadi default.

Saya harus mengakui bahwa saya juga menyukai ide over . Satu perbedaan antara over sebagai peta dan for dalam pemahaman daftar adalah apa yang terjadi jika beberapa iterator: [f(x, y) for x=x, y=y] menghasilkan matriks. Untuk kasus peta Anda biasanya masih menginginkan sebuah vektor, yaitu [f(x, y) over x, y] akan setara dengan [f(x, y) for (x,y) = zip(x, y)]] . Karena itu, saya masih berpikir bahwa memperkenalkan kata kunci tambahan over layak dilakukan, karena, seiring dengan meningkatnya masalah ini, map menggunakan beberapa vektor sangat umum dan harus singkat.

Hei, saya meyakinkan Jeff tentang sintaksnya! ;-)

Ini termasuk di samping #4470, jadi tambahkan ke proyek 0,4 untuk saat ini.

Jika saya memahami inti diskusi, masalah utamanya adalah kami ingin mendapatkan sintaks seperti pemetaan yang:

  • bekerja dengan berbagai tipe data, seperti DataArrays, tidak hanya array asli;
  • secepat loop yang ditulis secara manual.

Dimungkinkan untuk melakukannya menggunakan inlining, tetapi berhati-hatilah untuk memastikan inlining berfungsi.

Bagaimana dengan pendekatan yang berbeda: menggunakan makro tergantung pada tipe data yang disimpulkan. Jika kita dapat menyimpulkan bahwa struktur data adalah DataArray, kita menggunakan map-makro yang disediakan oleh perpustakaan DataArrays. Jika SomeKindOfStream, kami menggunakan perpustakaan aliran yang disediakan. Jika kami tidak dapat menyimpulkan tipe, kami hanya menggunakan implementasi umum yang dikirim secara dinamis.

Ini mungkin memaksa pembuat struktur data untuk menulis makro seperti itu, tetapi itu akan diperlukan hanya jika pembuatnya ingin memiliki eksekusi yang sangat efisien.

Jika apa yang saya tulis tidak jelas, maksud saya sesuatu seperti [EXPR for i in collection if COND] dapat diterjemahkan ke eval(collection_mapfilter_macro(:(i), :(EXPR), :(COND))) , di mana collection_mapfilter_macro dipilih berdasarkan jenis koleksi yang disimpulkan.

Tidak, kami tidak ingin melakukan hal-hal seperti itu. Jika DataArray mendefinisikan map (atau yang setara), definisinya harus selalu dipanggil untuk DataArrays terlepas dari apa yang dapat disimpulkan.

Masalah ini sebenarnya bukan tentang implementasi, tetapi sintaks. Saat ini banyak orang yang terbiasa memetakan sin(x) secara implisit jika x adalah array, tetapi ada banyak masalah dengan pendekatan itu. Pertanyaannya adalah sintaks alternatif apa yang dapat diterima.

1) Dukungan untuk penugasan di tempat yang bebas alokasi menggunakan y[:] = sqrt.(x .+ 2)
2) Dukungan untuk pengurangan alokasi bebas seperti sum((x .- y).^2, 1)

y = exp(-x^2) * sin(k*x) + im * log(x-1)

Melihat ketiga contoh ini dari orang lain, saya pikir dengan sintaks for ini akan berakhir seperti ini:
1) y[:] = [ sqrt(x + 2) for x ])
2) sum([ (x-y)^2 for x,y ], 1)
dan
y = [ √π exp(-x^2) * sin(k*x) + im * log(x-1) for x,k ]

Saya sangat menyukai ini! Fakta itu membuat array sementara cukup eksplisit dan masih dapat dibaca dan singkat.

Pertanyaan kecil, bisakah x[:] = [ ... for x ] memiliki keajaiban untuk mengubah array tanpa mengalokasikan yang sementara?
Saya tidak yakin apakah ini akan memberikan banyak manfaat tetapi saya dapat membayangkan itu akan membantu untuk array besar.
Saya dapat percaya bahwa itu mungkin ketel ikan yang sama sekali berbeda yang harus didiskusikan di tempat lain.

@Mike43110 x[:] = [ ... for x ] Anda dapat ditulis x[:] = (... for x) , RHS membuat generator, yang akan dikumpulkan elemen demi elemen untuk diisi x , tanpa mengalokasikan salinan. Itulah ide di balik eksperimen LazyArray saya di atas.

Sintaks [f <- y] akan lebih baik jika dikombinasikan dengan sintaks Int[f <- y] untuk peta yang mengetahui tipe keluarannya dan tidak perlu menginterpolasi dari f(y[1]) apa elemen lainnya nantinya.

Terutama, karena ini memberikan antarmuka intuitif untuk mapslices juga, [f <- rows(A)] di mana rows(A) (atau columns(A) atau slices(A, dims) ) mengembalikan Slice objek sehingga pengiriman dapat digunakan:

map(f, slice::Slice) = mapslices(f, slice.A, slice.dims)

Saat Anda menambahkan pengindeksan, ini menjadi sedikit lebih sulit. Sebagai contoh

f(x[:,j]) .* g(x[i,:])

Sulit untuk mencocokkan keringkasan itu. Ledakan dari gaya pemahaman sangat buruk:

[f(x[m,j])*g(x[i,n]) for m=1:size(x,1), n=1:size(x,2)]

di mana, untuk memperburuk keadaan, kepintaran diperlukan untuk mengetahui bahwa ini adalah kasus iterasi bersarang, dan tidak dapat dilakukan dengan satu over . Meskipun jika f dan g agak mahal, ini mungkin lebih cepat:

[f(x[m,j]) for m=1:size(x,1)] .* [g(x[i,n]) for _=1, n=1:size(x,2)]

tapi lebih lama lagi.

Contoh semacam ini tampaknya mendukung "titik", karena itu dapat memberikan f.(x[:,j]) .* g.(x[i,:]) .

@JeffBezanson Saya tidak yakin apa maksud dari komentar Anda. Adakah yang menyarankan untuk menyingkirkan sintaks .* ?

Tidak; Saya fokus pada f dan g di sini. Ini adalah contoh di mana Anda tidak bisa hanya menambahkan over x di akhir baris.

Oke, saya mengerti, saya melewatkan akhir komentar. Memang versi titik lebih bagus dalam kasus itu.

Meskipun dengan tampilan array, akan ada alternatif yang cukup efisien (AFAICT) dan tidak terlalu jelek:
[ f(y) * g(z) for y in x[:,j], z in x[i,:] ]

Bisakah contoh di atas diselesaikan dengan bersarang di atas kata kunci?

f(x)*g(y) over x,y

diartikan sebagai

[f(x)*g(y) for (x,y) = zip(x,y)]

sedangkan

f(x)*g(y) over x over y

menjadi

[f(x)*g(y) for x=x, y=y]

Kemudian, contoh spesifik di atas akan menjadi seperti

f(x[:,n])*g(x[m,:]) over x[:,n] over x[m,:]

EDIT: Dalam retrospeksi, itu tidak sesingkat yang saya kira.

@JeffBezanson Bagaimana?

f(x[:i,n]) * g(x[m,:i]) over i

memberikan setara dengan f.(x[:,n] .* g.(x[m,:]) . Sintaks baru x[:i,n] berarti i diperkenalkan secara lokal sebagai iterator di atas indeks wadah x[:,n] . Saya tidak tahu apakah itu mungkin untuk diterapkan. Tapi sepertinya (prima facie) tidak jelek atau rumit, dan sintaksnya sendiri memberikan batasan untuk iterator, yaitu 1:length(x[:,n]). Sejauh menyangkut kata kunci, "over" dapat menandakan bahwa seluruh rentang akan digunakan, sedangkan "untuk" dapat digunakan jika pengguna ingin menentukan subrentang 1:length(x[:,n]):

f(x[:i,n]) * g(x[m,:i]) for i in 1:length(x[:,n])-1 .

@davidagold , :i sudah berarti simbol i .

Ah ya, poin yang bagus. Nah, selama titik-titik adalah permainan yang adil, bagaimana dengan

f(x[.i,n]) * g(x[m,.i]) over i

di mana titik menunjukkan bahwa i diperkenalkan secara lokal sebagai iterator di atas 1:length(x[:,n). Saya kira pada dasarnya ini mengubah notasi titik dari memodifikasi fungsi menjadi memodifikasi array, atau lebih tepatnya indeksnya. Ini akan menyelamatkan satu dari "dot creep" Jeff mencatat:

[ f(g(e^(x[m,.i]))) * p(e^(f(y[.i,n]))) over i ]

sebagai lawan

f.(g.(e.^(x[m,:]))) .* p.(e.^(f.(y[:,n])))

meskipun saya kira yang terakhir ini sedikit lebih pendek. [EDIT: juga, jika mungkin untuk menghilangkan over i ketika tidak ada ambiguitas, maka sebenarnya ada sintaks yang sedikit lebih pendek:

[ f(g(e^(x[m,.i]))) * p(e^(f(y[.i,n]))) ] ]

Salah satu keuntungan potensial dari sintaks pemahaman adalah bahwa hal itu dapat memungkinkan untuk pola operasi elemen-bijaksana yang lebih luas. Misalnya, jika parser memahami bahwa pengindeksan dengan i di x[m, .i] secara implisit modulo length(x[m,:]), maka seseorang dapat menulis

[ f(x[.i]) * g(y[.j]) over i, j=-i ]

Untuk mengalikan elemen x terhadap elemen y dalam urutan yang berlawanan, yaitu elemen pertama dari x terhadap elemen terakhir dari y , dll . Seseorang bisa menulis

[ f(x[.i]) * g(y[.j]) over i, j=i+1 ]

untuk mengalikan elemen ke i dari x dengan elemen ke i+1 dari y (di mana elemen terakhir dari x akan dikalikan oleh elemen pertama y karena pengindeksan dipahami dalam konteks ini sebagai modulo length(x)). Dan jika p::Permutation mengubah (1, ..., length(x)) seseorang dapat menulis

[ f(x[.i]) * g(y[.j]) over i, j=p(i) ]

untuk mengalikan elemen ke i dari x dengan elemen ke p(i) dari y .

Bagaimanapun, itu hanya pendapat sederhana dari orang luar tentang masalah yang sepenuhnya spekulatif. =p Saya sangat menghargai waktu yang dibutuhkan orang untuk mempertimbangkannya.

Versi vektorisasi yang disempurnakan yang akan menggunakan daur ulang gaya r bisa sangat berguna. Artinya, argumen yang tidak cocok dengan ukuran argumen terbesar diperpanjang melalui daur ulang. Kemudian pengguna dapat dengan mudah membuat vektor apa pun yang mereka inginkan, terlepas dari jumlah argumen, dll.

unvectorized_sum(a, b, c, d) = a + b + c + d
vectorized_sum = @super_vectorize(unvectorized_sum)

a = [1, 2, 3, 4]
b = [1, 2, 3]
c = [1, 2]
d = 1

A = [1, 2, 3, 4]
B = [1, 2, 3, 1]
C = [1, 2, 1, 2]
D = [1, 1, 1, 1]

vectorized_sum(a, b, c, d) = vectorized_sum(A, B, C, D) = [4, 7, 8, 8]

Saya cenderung berpikir bahwa daur ulang mengorbankan terlalu banyak keamanan untuk kenyamanan. Dengan daur ulang, sangat mudah untuk menulis kode kereta yang dijalankan tanpa menimbulkan kesalahan apa pun.

Pertama kali saya membaca tentang perilaku R itu, saya langsung bertanya-tanya mengapa seseorang berpikir itu adalah ide yang bagus. Itu adalah hal yang sangat aneh dan mengejutkan untuk dilakukan secara implisit pada array ukuran yang tidak cocok. Mungkin ada beberapa kasus di mana Anda ingin memperluas array yang lebih kecil, tetapi juga Anda mungkin ingin zero-padding atau mengulangi elemen akhir, atau ekstrapolasi, atau kesalahan, atau sejumlah aplikasi lain yang bergantung pada pilihan.

Apakah akan menggunakan @super_vectorize atau tidak akan berada di tangan pengguna. Dimungkinkan juga untuk memberikan peringatan untuk berbagai kasus. Misalnya, di R,

c(1, 2, 3) + c(1, 2)
[1] 2 4 4
Warning message:
In c(1, 2, 3) + c(1, 2) :
  longer object length is not a multiple of shorter object length

Saya tidak keberatan untuk menjadikan itu sebagai hal opsional yang dapat dipilih pengguna untuk digunakan atau tidak, tetapi itu tidak perlu diimplementasikan dalam bahasa dasar ketika itu juga dapat dilakukan dalam sebuah paket.

@vectorize_1arg dan @vectorize_2arg keduanya sudah termasuk dalam Base, dan opsi yang mereka berikan kepada pengguna tampaknya agak terbatas.

Tetapi masalah ini difokuskan pada desain sistem untuk menghapus @vectorize_1arg dan @vectorize_2arg dari Base. Tujuan kami adalah untuk menghapus fungsi vektor dari bahasa dan menggantinya dengan abstraksi yang lebih baik.

Misalnya, daur ulang dapat ditulis sebagai

[ A[i] + B[mod1(i,length(B))] for i in eachindex(A) ]

yang bagi saya cukup dekat dengan cara ideal untuk menulisnya. Tidak ada yang perlu membangunnya untuk Anda. Pertanyaan utamanya adalah (1) bisakah ini dibuat lebih ringkas, (2) bagaimana memperluasnya ke jenis wadah lain.

Melihat proposal @davidagold , saya bertanya-tanya apakah var: tidak dapat digunakan untuk hal semacam itu di mana variabel akan menjadi nama sebelum titik dua. Saya melihat sintaks ini dulu berarti A[var:end] jadi sepertinya tersedia.

f(x[:,j]) .* g(x[i,:]) kemudian akan menjadi f(x[a:,j]) * g(x[i,b:]) for a, b yang tidak terlalu buruk.

Beberapa titik dua akan sedikit aneh.

f(x[:,:,j]) .* g(x[i,:,:]) -> f(x[a:,a:,j]) * g(x[i,b:,b:]) for a, b adalah pemikiran awal saya tentang ini.

Oke, jadi inilah tikaman singkat tentang program daur ulang. Itu harus dapat menangani array n-dimensi. Mungkin akan mungkin untuk menggabungkan tupel secara analog ke vektor.

using DataFrames

a = [1, 2, 3]
b = 1
c = [1 2]
d = <strong i="6">@data</strong> [NA, 2, 3]

# coerce an array to a certain size using recycling
coerce_to_size = function(argument, dimension_extents...)

  # number of repmats needed, initialized to 1
  dimension_ratios = [dimension_extents...]

  for dimension in 1:ndims(argument)

    dimension_ratios[dimension] = 
      ceil(dimension_extents[dimension] / size(argument, dimension))
  end

  # repmat array to at least desired size
  if typeof(argument) <: AbstractArray
    rep_to_size = repmat(argument, dimension_ratios...)
  else
    rep_to_size = 
      fill(argument, dimension_ratios...)
  end

  # cut down array to exactly desired size
  dimension_ranges = [1:i for i in dimension_extents]
  dimension_ranges = tuple(dimension_ranges...)

  rep_to_size = getindex(rep_to_size, dimension_ranges...)  

end

recycle = function(argument_list...)

  # largest dimension in arguments
  max_dimension = maximum([ndims(i) for i in argument_list])
  # initialize dimension extents to 1
  dimension_extents = [1 for i in 1:max_dimension]

  # loop through argument and dimension
  for argument_index in 1:length(argument_list)
    for dimension in 1:ndims(argument_list[argument_index])
      # find the largest size for each dimension
      dimension_extents[dimension] = maximum([
        size(argument_list[argument_index], dimension),
        dimension_extents[dimension]
      ])
    end
  end

  expand_arguments = 
    [coerce_to_size(argument, dimension_extents...) 
     for argument in argument_list]
end

recycle(a, b, c, d)

mapply = function(FUN, argument_list...)
  argument_list = recycle(argument_list...)
  FUN(argument_list...)
end

mapply(+, a, b, c, d)

Jelas, ini bukan kode yang paling elegan atau cepat (saya seorang imigran R baru-baru ini). Saya tidak yakin bagaimana cara pergi dari sini ke makro @vectorize .

EDIT: gabungan loop redundan
EDIT 2: pisahkan paksaan dengan ukuran. saat ini hanya bekerja untuk 0-2 dimensi.
EDIT 3: Satu cara yang sedikit lebih elegan untuk melakukan ini adalah dengan mendefinisikan tipe array khusus dengan pengindeksan mod. Itu adalah,

special_array = [1 2; 3 5]
special_array.dims = (10, 10, 10, 10)
special_array[4, 1, 9, 7] = 3

EDIT 4: Hal-hal yang saya ingin tahu ada karena ini sulit untuk ditulis: generalisasi n-dimensi dari hcat dan vcat? Cara untuk mengisi array n-dimensi (mencocokkan ukuran array yang diberikan) dengan daftar atau tupel indeks dari setiap posisi tertentu? Generalisasi n-dimensi dari repmat?

[pao: penyorotan sintaks]

Anda benar-benar tidak ingin mendefinisikan fungsi dengan sintaks foo = function(x,y,z) ... end di Julia, meskipun itu berhasil. Itu menciptakan pengikatan nama yang tidak konstan ke fungsi anonim. Di Julia, normanya adalah menggunakan fungsi generik dan pengikatan ke fungsi secara otomatis konstan. Jika tidak, Anda akan mendapatkan kinerja yang buruk.

Saya tidak mengerti mengapa repmat diperlukan di sini. Array yang diisi dengan indeks setiap posisi juga merupakan tanda peringatan: seharusnya tidak perlu menggunakan sebagian besar memori untuk mewakili begitu sedikit informasi. Saya percaya teknik seperti itu benar-benar hanya berguna dalam bahasa di mana semuanya perlu "divektorkan". Menurut saya pendekatan yang tepat adalah dengan menjalankan loop di mana beberapa indeks diubah, seperti pada https://github.com/JuliaLang/julia/issues/8450#issuecomment -111898906 .

Ya, itu masuk akal. Ini adalah permulaan, tetapi saya mengalami kesulitan mencari cara untuk melakukan perulangan di akhir dan kemudian membuat makro @vectorize .

function non_zero_mod(big::Number, little::Number)
  result = big % little
  result == 0 ? little : result
end

function mod_select(array, index...)
  # just return singletons
  if !(typeof(array) <: AbstractArray) return array end
  # find a new index with moded values
  transformed_index = 
      [non_zero_mod( index[i], size(array, i) )
       for i in 1:ndims(array)]
  # return value at moded index
  array[transformed_index...]
end

function mod_value_list(argument_list, index...)
  [mod_select(argument, index...) for argument in argument_list]
end

mapply = function(FUN, argument_list...)

  # largest dimension in arguments
  max_dimension = maximum([ndims(i) for i in argument_list])
  # initialize dimension extents to 1
  dimension_extents = [1 for i in 1:max_dimension]

  # loop through argument and dimension
  for argument_index in 1:length(argument_list)
    for dimension in 1:ndims(argument_list[argument_index])
      # find the largest size for each dimension
      dimension_extents[dimension] = maximum([
        size(argument_list[argument_index], dimension),
        dimension_extents[dimension]
      ])
    end
  end

  # more needed here
  # apply function over arguments using mod_value_list on arguments at each position
end

Dalam pembicaraan, @JeffBezanson menyebutkan sintaks sin(x) over x , mengapa tidak sesuatu yang lebih seperti:
sin(over x) ? (atau gunakan beberapa karakter alih-alih memiliki over sebagai kata kunci)

Setelah ini diselesaikan, kami juga dapat menyelesaikan #11872

Saya harap saya tidak terlambat ke pesta, tetapi saya hanya ingin menawarkan +1 pada proposal sintaks @davidagold . Secara konseptual jelas, singkat, dan terasa sangat alami untuk ditulis. Saya tidak yakin apakah . akan menjadi karakter pengidentifikasi terbaik, atau seberapa layak implementasi yang sebenarnya, tetapi orang dapat membuat bukti konsep menggunakan makro untuk mencobanya (pada dasarnya seperti @devec , tetapi mungkin lebih mudah untuk diterapkan).

Ini juga memiliki manfaat "menyesuaikan" dengan sintaks pemahaman array yang ada:

result = [g(f(.i), h(.j)) over i, j]

vs.

result = [g(f(_i), h(_j)) for _i in eachindex(i), _j in eachindex(j)]

Perbedaan utama antara keduanya adalah bahwa yang pertama akan memiliki lebih banyak batasan pada pelestarian bentuk, karena ini menyiratkan peta.

over , range , dan window memiliki beberapa seni sebelumnya di ruang OLAP sebagai pengubah iterasi, ini tampaknya konsisten.

Saya tidak tertarik pada sintaks . karena itu tampak seperti merayap menuju kebisingan garis.

$ mungkin konsisten, masukkan nilai iterasi i,j ke dalam ekspresi?

result = [g(f($i), h($j)) over i, j]

Untuk vektorisasi otomatis ekspresi, bisakah kita tidak taint salah satu vektor dalam ekspresi dan meminta sistem tipe mengangkat ekspresi ke dalam ruang vektor?

Saya melakukan mirip dengan operasi deret waktu di mana ekspresi julia sudah memungkinkan saya untuk menulis

ts_a = GetTS( ... )
ts_b = GetTS( ... ) 
factors = [ 1,  2, 3 ]

ts_x = ts_a * 2 + sin( ts_a * factors ) + ts_b 

yang ketika diamati menghasilkan deret waktu vektor.

Bagian utama yang hilang adalah kemampuan untuk secara otomatis mengangkat fungsi yang ada ke dalam ruang. Ini harus dilakukan dengan tangan

Pada dasarnya saya ingin dapat mendefinisikan sesuatu seperti berikut ...

abstract TS{K}
function {F}{K}( x::TS{K}, y::TS{K} ) = tsjoin( F, x, y ) 
# tsjoin is a time series iteration operator

dan kemudian dapat berspesialisasi untuk operasi tertentu

function mean{K}(x::TS{K}) = ... # my hand rolled form

Hai @JeffBezanson ,

Jika saya mengerti dengan benar, saya ingin mengusulkan solusi untuk komentar JuliaCon 2015 Anda tentang komentar yang dibuat di atas:
"[...] Dan mengatakan kepada penulis perpustakaan untuk menempatkan @vectorize pada semua fungsi yang sesuai adalah konyol; Anda seharusnya bisa menulis sebuah fungsi, dan jika seseorang ingin menghitungnya untuk setiap elemen, mereka menggunakan peta."
(Tapi saya tidak akan membahas masalah mendasar lainnya "[..] tidak ada alasan yang benar-benar meyakinkan mengapa sin, exp dll. harus secara implisit dipetakan melalui array.")

Di Julia v0.40, saya bisa mendapatkan solusi yang agak lebih bagus (menurut saya) daripada @vectrorize :

abstract Vectorizable{Fn}
#Could easily have added extra argument to Vectorizable, but want to show inheritance case:
abstract Vectorizable2Arg{Fn} <: Vectorizable{Fn}

call{F}(::Type{Vectorizable2Arg{F}}, x1, x2) = eval(:($F($x1,$x2)))
function call{F,T1,T2}(fn::Type{Vectorizable2Arg{F}}, v1::Vector{T1}, v2::Vector{T2})
    RT = promote_type(T1,T2) #For type stability!
    return RT[fn(v1[i],v2[i]) for i in 1:length(v1)]
end

#Function in need of vectorizing:
function _myadd(x::Number, y::Number)
    return x+y+1
end

#"Register" the function as a Vectorizable 2-argument (alternative to @vectorize):
typealias myadd Vectorizable2Arg{:_myadd}

<strong i="13">@show</strong> myadd(5,6)
<strong i="14">@show</strong> myadd(collect(1:10),collect(21:30.0)) #Type stable!

Ini kurang lebih masuk akal, tetapi agak mirip dengan solusi @vectorize . Agar vektorisasi menjadi elegan, saya sarankan Julia mendukung yang berikut:

abstract Vectorizable <: Function
abstract Vectorizable2Arg <: Vectorizable

function call{T1,T2}(fn::Vectorizable2Arg, v1::Vector{T1}, v2::Vector{T2})
    RT = promote_type(T1,T2) #For type stability!
    return RT[fn(v1[i],v2[i]) for i in 1:length(v1)]
end

#Note: by default, functions would normally be <: Function:
function myadd(x::Number, y::Number) <: Vectorizable2Arg
    return x+y+1
end

Itu dia! Memiliki fungsi yang diwarisi dari fungsi yang dapat divektorkan akan membuatnya dapat divektorkan.

Saya harap ini sejalan dengan apa yang Anda cari.

Salam,

MA

Dengan tidak adanya pewarisan berganda, bagaimana suatu fungsi mewarisi dari Vectorizable dan dari sesuatu yang lain? Dan bagaimana Anda menghubungkan informasi pewarisan untuk metode tertentu dengan informasi pewarisan untuk fungsi generik?

@ma-laforge Anda sudah bisa melakukannya --- tentukan tipe myadd <: Vectorizable2Arg , lalu implementasikan call untuk myadd pada Number .

Terima kasih untuk itu @JeffBezanson!

Memang, saya hampir dapat membuat solusi saya terlihat hampir sebagus yang saya inginkan:

abstract Vectorizable
#Could easily have parameterized Vectorizable, but want to show inheritance case:
abstract Vectorizable2Arg <: Vectorizable

function call{T1,T2}(fn::Vectorizable2Arg, v1::Vector{T1}, v2::Vector{T2})
    RT = promote_type(T1,T2) #For type stability!
    return RT[fn(v1[i],v2[i]) for i in 1:length(v1)]
end

#SECTION F: Function in need of vectorizing:
immutable MyAddType <: Vectorizable2Arg; end
const myadd = MyAddType()
function call(::MyAddType, x::Number, y::Number)
    return x+y+1
end

<strong i="7">@show</strong> myadd(5,6)
<strong i="8">@show</strong> myadd(collect(1:10),collect(21:30.0)) #Type stable

Sekarang, satu-satunya hal yang hilang adalah cara untuk "mensubtipekan" fungsi apa pun, sehingga seluruh bagian F dapat diganti dengan sintaks yang lebih elegan:

function myadd(x::Number, y::Number) <: Vectorizable2Arg
    return x+y+1
end

CATATAN: Saya membuat tipe "MyAddType", dan nama fungsi menjadi objek tunggal "myadd" karena menurut saya sintaks yang dihasilkan lebih bagus daripada jika seseorang menggunakan Type{Vectorizable2Arg} dalam tanda tangan panggilan:

function call{T1,T2}(fn::Type{Vectorizable2Arg}, v1::Vector{T1}, v2::Vector{T2})

Sayangnya , dengan tanggapan Anda, sepertinya ini _not_ menjadi solusi yang memadai untuk "kekonyolan" makro @vectorize .

Salam,

MA

@johnmyleswhite :

Saya ingin menanggapi komentar Anda, tetapi saya rasa saya tidak mengerti. Bisakah Anda mengklarifikasi?

Satu hal yang saya _dapat_ katakan:
Tidak ada yang istimewa dari "Vectorizable". Idenya adalah bahwa siapa pun dapat mendefinisikan "kelas" fungsi mereka sendiri (Mis: MyFunctionGroupA<:Function ). Mereka kemudian dapat menangkap panggilan ke fungsi jenis itu dengan mendefinisikan tanda tangan "panggilan" mereka sendiri (seperti yang ditunjukkan di atas).

Karena itu: Saran saya adalah bahwa fungsi yang didefinisikan dalam Base harus menggunakan Base.Vectorizable <: Function (atau yang serupa) untuk menghasilkan algoritma vektor secara otomatis secara otomatis.

Saya kemudian menyarankan pengembang modul mengimplementasikan fungsi mereka sendiri menggunakan pola seperti:

myfunction(x::MyType, y::MyType) <: Base.Vectorizable

Tentu saja, mereka harus menyediakan promote_type(::Type{MyType},::Type{MyType}) versi mereka sendiri - jika defaultnya belum mengembalikan MyType .

Jika algoritme vektorisasi default tidak mencukupi, tidak ada yang menghentikan pengguna untuk mengimplementasikan hierarki mereka sendiri:

MyVectorizable{nargs} <: Function
call(fn::MyVectorizable{2}, x, y) = ...

myfunction(x::MyType, y:MyType) <: MyVectorizable{2}

MA

@ma-laforge, Maaf karena tidak jelas. Kekhawatiran saya adalah bahwa hierarki apa pun akan selalu kekurangan informasi penting karena Julia memiliki pewarisan tunggal, yang mengharuskan Anda untuk berkomitmen pada tipe induk tunggal untuk setiap fungsi. Jika Anda menggunakan sesuatu seperti myfunction(x::MyType, y::MyType) <: Base.Vectorizable maka fungsi Anda tidak akan mendapat manfaat dari orang lain yang mendefinisikan konsep seperti Base.NullableLiftable yang secara otomatis menghasilkan fungsi nullables.

Sepertinya ini tidak akan menjadi masalah dengan sifat (lih. https://github.com/JuliaLang/julia/pull/13222). Yang juga terkait adalah kemungkinan baru untuk mendeklarasikan metode sebagai murni (https://github.com/JuliaLang/julia/pull/13555), yang secara otomatis dapat menyiratkan bahwa metode tersebut dapat divektorkan (setidaknya untuk metode argumen tunggal).

@johnmyleswhite ,

Jika saya mengerti dengan benar: Saya rasa itu bukan masalah untuk kasus _this_ khususnya. Itu karena saya mengusulkan pola desain. Fungsi Anda tidak _memiliki_ untuk mewarisi dari Base.Vectorizable ... Anda dapat menggunakan fungsi Anda sendiri.

Saya tidak begitu tahu banyak tentang NullableLiftables (Sepertinya saya tidak memilikinya di versi Julia saya). Namun, dengan asumsi itu mewarisi dari Base.Function (yang juga tidak mungkin dalam versi Julia saya):

NullableLiftable <: Function

Modul Anda kemudian dapat mengimplementasikan (hanya sekali) sub-tipe _new_ yang dapat divektorkan:

abstract VectorizableNullableLiftable <: NullableLiftable

function call{T1,T2}(fn::VectorizableNullableLiftable, v1::Vector{T1}, v2::Vector{T2})
    RT = promote_type(T1,T2) #For type stability!
    return RT[fn(v1[i],v2[i]) for i in 1:length(v1)]
end

Jadi, mulai sekarang, siapa pun yang mendefinisikan fungsi <: VectorizableNullableLiftable akan menerapkan kode vektorisasi Anda secara otomatis!

function mycooladdon(scalar1, scalar2) <: VectorizableNullableLiftable
...

Saya mengerti, bahwa memiliki lebih dari satu tipe Vectorizable masih agak menyusahkan (dan agak tidak elegan) ... Tapi setidaknya itu akan menghapus salah satu pengulangan yang mengganggu (1) di Julia (harus mendaftarkan _setiap_ yang baru ditambahkan fungsi dengan panggilan ke @vectorize_Xarg).

(1) Itu dengan asumsi Julia mendukung pewarisan pada fungsi (mis: myfunction(...)<: Vectorizable ) - yang tampaknya tidak, pada v0.4.0. Solusi yang saya dapatkan di Julia 0.4.0 hanyalah peretasan... Anda masih harus mendaftarkan fungsi Anda... tidak jauh lebih baik daripada menelepon @vectorize_Xarg

MA

Saya masih berpikir itu semacam abstraksi yang salah. Fungsi yang dapat atau harus "divektorkan" bukanlah jenis fungsi tertentu. _Every_ function dapat diteruskan ke map , memberikannya perilaku ini.

BTW, dengan perubahan yang sedang saya kerjakan di cabang jb/functions, Anda akan dapat melakukan function f(x) <: T (meskipun, jelas, hanya untuk definisi pertama f ).

Oke, saya rasa saya lebih mengerti apa yang Anda cari... dan bukan itu yang saya sarankan. Saya pikir itu mungkin menjadi bagian dari masalah yang dimiliki @johnmyleswhite dengan saran saya juga...

...Tetapi jika sekarang saya mengerti apa masalahnya, solusinya tampak lebih sederhana bagi saya:

function call{T1,T2}(fn::Function, v1::Vector{T1}, v2::Vector{T2})
    RT = promote_type(T1,T2) #For type stability!
    return RT[fn(v1[i],v2[i]) for i in 1:length(v1)]
end

myadd(x::Number, y::Number) = x+y+1

Karena myadd bertipe Function , ia harus terjebak oleh fungsi call ... yang dilakukannya:

call(myadd,collect(1:10),collect(21:30.0)) #No problem

Tetapi call tidak mengirim otomatis pada fungsi, untuk beberapa alasan (tidak yakin mengapa):

myadd(collect(1:10),collect(21:30.0)) #Hmm... Julia v0.4.0 does not dispatch this to call...

Tapi saya membayangkan perilaku ini seharusnya tidak terlalu sulit untuk diubah . Secara pribadi, saya tidak tahu bagaimana perasaan saya tentang membuat fungsi catch-all yang mencakup semua, tetapi sepertinya itulah yang Anda inginkan.

Sesuatu yang aneh yang saya perhatikan: Julia sudah memvektorisasi fungsi secara otomatis jika tidak diketik:

myadd(x,y) = x+y+1 #This gets vectorized automatically, for some reason

RE: BTW...:
Dingin! Saya ingin tahu hal-hal rapi apa yang dapat saya lakukan dengan membuat subtipe fungsi :).

Julia sudah memvektorisasi fungsi secara otomatis jika tidak diketik

Tampaknya seperti itu karena operator + yang digunakan di dalam fungsi divektorkan.

Tapi saya membayangkan perilaku ini seharusnya tidak terlalu sulit untuk diubah. Secara pribadi, saya tidak tahu bagaimana perasaan saya tentang membuat fungsi catch-all yang mencakup semua, tetapi sepertinya itulah yang Anda inginkan.

Saya berbagi reservasi Anda. Anda tidak dapat secara masuk akal memiliki definisi yang mengatakan "inilah cara memanggil fungsi apa pun", karena setiap fungsi itu sendiri mengatakan apa yang harus dilakukan ketika dipanggil --- itulah fungsinya!

Saya seharusnya mengatakan: ... operator infix non-unicode yang ditentukan pengguna, karena menurut saya kami tidak ingin mengharuskan pengguna untuk mengetik karakter unicode untuk mengakses fungsionalitas inti seperti itu. Meskipun saya melihat bahwa $ sebenarnya adalah salah satu dari yang ditambahkan, jadi terima kasih untuk itu! Wow, jadi ini benar-benar berfungsi di Julia hari ini (walaupun belum "cepat"...):

julia> ($) = peta
julia>sin $ (0,5 * (abs2 $ (xy)))

@binarybana bagaimana dengan / \mapsto ?

julia> x, y = rand(3), rand(3);

julia> ↦ = map    # \mapsto<TAB>
map (generic function with 39 methods)

julia> sin ↦ (0.5 * (abs2 ↦ (x-y)))
3-element Array{Float64,1}:
 0.271196
 0.0927406
 0.0632608

Ada juga:

FWIW, saya setidaknya awalnya berasumsi bahwa \mapsto adalah sintaks alternatif untuk lambdas, seperti yang biasa digunakan dalam matematika dan, pada dasarnya (dalam inkarnasi ASCII, -> ) di Julia, juga . Saya pikir ini akan agak membingungkan.

Berbicara tentang matematika ... Dalam teori model saya telah melihat map diekspresikan dengan menerapkan fungsi ke tuple tanpa tanda kurung. Yaitu, jika \bar{a}=(a_1, \dots, a_n) , maka f(\bar{a}) adalah f(a_1, \dots, a_n) (yaitu, pada dasarnya, apply ) dan f\bar{a} adalah (f(a_1), \dots, f(a_n)) (yaitu, map ). Sintaks yang berguna untuk mendefinisikan homomorfisme, dll., tetapi tidak semudah itu dapat ditransfer ke bahasa pemrograman :-}

Bagaimana dengan alternatif lain seperti \Mapsto , apakah Anda akan bingung dengan => (Pair)? Saya pikir kedua simbol dapat dibedakan di sini berdampingan:

  • ->

Ada banyak simbol yang mirip, apa alasan untuk memiliki begitu banyak jika kita hanya menggunakan yang terlihat sangat berbeda atau ASCII murni?

Saya pikir ini akan agak membingungkan.

Saya pikir dokumentasi dan pengalaman menyelesaikan ini, apakah Anda setuju?

Ada juga banyak simbol seperti panah lainnya, sejujurnya saya tidak tahu untuk apa mereka digunakan dalam matematika atau yang lain, saya hanya mengusulkan yang ini karena mereka memiliki map dalam nama mereka! :senyum:

Saya kira maksud saya adalah bahwa -> adalah upaya Julia untuk mewakili di ASCII. Jadi menggunakan berarti sesuatu yang lain tampaknya keliru. Bukannya saya tidak bisa membedakan mereka secara visual :-)

Perasaan saya adalah jika kita akan menggunakan simbol matematika yang sudah mapan, kita mungkin ingin setidaknya memikirkan bagaimana penggunaan Julia berbeda dengan penggunaan yang sudah ada. Tampaknya logis untuk memilih simbol dengan map dalam nama mereka, tetapi dalam kasus ini mengacu pada definisi peta (atau peta dari jenis yang berbeda, atau jenis peta tersebut). Itu juga berlaku untuk penggunaan di Pair, kurang lebih, di mana daripada mendefinisikan fungsi penuh dengan mendefinisikan apa yang dipetakan oleh parameter, Anda sebenarnya mencantumkan apa yang dipetakan oleh argumen (nilai parameter) yang diberikan – yaitu, itu adalah elemen dari secara eksplisit fungsi enumerasi, secara konseptual (misalnya, kamus).

@Ismael-VC Masalah dengan saran Anda adalah bahwa Anda perlu menelepon map dua kali, sedangkan solusi ideal tidak akan melibatkan array sementara dan dikurangi menjadi map((a, b) -> sin(0.5 * abs2(a-b)), x, y) . Selain itu, mengulangi dua kali tidak bagus baik secara visual maupun untuk mengetik (yang setara dengan ASCII akan lebih baik untuk dimiliki).

Pengguna R mungkin tidak menyukai ide ini, tetapi jika kita beralih ke penghentian penguraian kasus khusus makro-infix saat ini dari ~ (paket seperti GLM dan DataFrames perlu diubah ke penguraian makro formula DSL mereka, ref https:/ /github.com/JuliaStats/GLM.jl/issues/116), yang akan membebaskan komoditas langka dari operator ascii infix.

a ~ b dapat didefinisikan dalam basis sebagai map(a, b) , dan mungkin a .~ b dapat didefinisikan sebagai broadcast(a, b) ? Jika diurai sebagai operator infix konvensional maka DSL makro seperti emulasi dari antarmuka rumus R akan bebas mengimplementasikan interpretasi mereka sendiri terhadap operator di dalam makro, seperti yang dilakukan JuMP dengan <= dan == .

Ini mungkin bukan saran yang paling bagus, tetapi juga bukan steno di Mathematica jika Anda menggunakannya secara berlebihan... favorit saya adalah .#&/@

:+1: untuk casing yang kurang khusus dan lebih umum dan konsisten, arti yang Anda usulkan untuk ~ dan .~ tampak hebat bagi saya.

+1 Toni.

@tkelman Untuk lebih jelasnya, bagaimana Anda menulis misalnya sin(0.5 * abs2(a-b)) dengan cara yang sepenuhnya di-vektor?

Dengan pemahaman, mungkin. Saya tidak berpikir peta infix akan berfungsi untuk varargs atau di tempat, jadi kemungkinan sintaks gratis tidak menyelesaikan semua masalah.

Jadi itu tidak akan benar-benar memperbaiki masalah ini. :-/

Sejauh ini sintaks sin(0.5 * abs2(a-b)) over (a, b) (atau varian, mungkin menggunakan operator infix) adalah yang paling menarik.

Judul masalah ini adalah "Sintaks alternatif untuk map(func, x) ". Menggunakan operator infix tidak menyelesaikan fusi peta/loop untuk menghilangkan sementara tapi saya pikir itu mungkin masalah yang lebih luas, terkait tetapi terpisah secara teknis daripada sintaks.

Ya, saya setuju dengan @tkelman , intinya adalah memiliki sintaks alternatif untuk map , itulah sebabnya saya menyarankan menggunakan \mapsto , . Apa yang @nalimilan sebutkan tampaknya lebih luas dan lebih cocok untuk masalah lain IMHO, How to fully vecotrize expressions ?

<rambling>
Masalah ini telah berlangsung selama lebih dari satu tahun sekarang (dan dapat berlanjut tanpa batas seperti banyak masalah lainnya sekarang)! Tapi kita bisa memiliki Alternative syntax for map(func, x) sekarang . Dari ±450 kontributor julian hanya 41 yang dapat menemukan masalah ini dan/atau bersedia untuk berbagi pendapat (itu banyak untuk masalah github tetapi jelas tidak cukup dalam kasus ini), secara keseluruhan tidak ada banyak saran yang berbeda (yang bukan hanya variasi kecil dari konsep yang sama).

Saya tahu beberapa dari Anda tidak menyukai ide atau melihat nilai dalam membuat survei/jajak pendapat (: kaget :), tetapi karena saya tidak perlu meminta izin dari siapa pun untuk hal seperti ini, saya akan tetap melakukannya. Agak menyedihkan cara kita tidak mengambil pengaruh penuh dari komunitas dan jejaring sosial kita dan komunitas lain juga bahkan lebih sedih lagi karena kita tidak melihat nilainya, mari kita lihat apakah saya dapat mengumpulkan lebih banyak pendapat berbeda dan segar , atau setidaknya periksa apa pendapat mayoritas tentang pendapat saat ini dalam masalah khusus ini, sebagai eksperimen dan lihat bagaimana kelanjutannya. Mungkin memang tidak ada gunanya, mungkin juga tidak, hanya ada satu cara untuk benar-benar tahu.
</rambling>

@Ismael-VC: Jika Anda benar-benar ingin membuat polling, hal pertama yang harus Anda lakukan adalah mempertimbangkan dengan cermat pertanyaan yang ingin Anda ajukan. Anda tidak dapat mengharapkan semua orang untuk membaca seluruh utas dan meringkas opsi yang telah didiskusikan satu per satu.

map(func, x) juga mencakup hal-hal seperti map(v -> sin(0.5 * abs2(v)), x) , dan inilah yang dibahas dalam utas ini. Jangan pindahkan ini ke utas lain, karena akan membuat lebih sulit untuk mengingat semua proposal yang dibahas di atas.

Saya tidak menentang menambahkan sintaks untuk kasus sederhana penerapan fungsi generik menggunakan map , tetapi jika kita melakukannya, saya pikir itu akan menjadi ide yang baik untuk mempertimbangkan gambaran yang lebih luas pada saat yang sama. Jika bukan karena itu, masalahnya mungkin sudah diperbaiki sejak lama.

@Ismael-VC Polling tidak mungkin membantu di sini IMHO. Kami tidak mencoba mencari tahu mana dari beberapa solusi yang terbaik, melainkan untuk menemukan solusi yang belum pernah ditemukan oleh siapa pun. Diskusi ini sudah lama dan melibatkan banyak orang, saya rasa menambahkan lebih banyak tidak akan membantu.

@Ismael-VC Tidak apa-apa, jangan ragu untuk membuat polling. Sebenarnya saya telah melakukan beberapa polling doodle tentang isu-isu di masa lalu (misalnya http://doodle.com/poll/s8734pcue8yxv6t4). Dalam pengalaman saya, orang yang sama atau lebih sedikit memilih dalam jajak pendapat daripada berdiskusi di utas masalah. Masuk akal untuk masalah yang sangat spesifik, seringkali dangkal/sintaks. Tapi bagaimana jajak pendapat akan menghasilkan ide-ide segar ketika yang bisa Anda lakukan hanyalah memilih dari opsi yang ada?

Tentu saja tujuan sebenarnya dari masalah ini adalah untuk menghilangkan fungsi vektor secara implisit. Secara teori, sintaks untuk map sudah cukup untuk itu, karena semua fungsi ini hanya melakukan map dalam setiap kasus.

Saya sudah mencoba mencari notasi matematika yang ada untuk ini, tetapi Anda cenderung melihat komentar yang menyatakan bahwa operasinya terlalu tidak penting untuk memiliki notasi! Dalam kasus fungsi arbitrer dalam konteks matematika saya hampir bisa mempercayai ini. Namun hal yang paling dekat tampaknya adalah notasi produk Hadamard, yang memiliki beberapa generalisasi: https://en.wikipedia.org/wiki/Hadamard_product_ (matriks)#Analogous_Operations

Itu memberi kita sin∘x seperti yang disarankan @rfourquet . Tampaknya tidak terlalu membantu, karena memerlukan unicode dan tidak diketahui secara luas.

@nalimilan , saya pikir Anda hanya akan melakukan sin(0.5 * abs2(a-b)) ~ (a,b) yang akan diterjemahkan menjadi map((a,b)->sin(0.5 * abs2(a-b)), (a,b)) . Tidak yakin apakah itu benar, tapi saya pikir itu akan berhasil.

Saya juga berhati-hati untuk menggali terlalu banyak ke dalam masalah let-me-give-you-a-complicated-ekspresi-dan-anda-sempurna-otomatis-vektorisasi-untuk-saya. Saya pikir solusi pamungkas dalam hal itu adalah membangun DAG penuh ekspresi/tugas + perencanaan kueri, dll. Tapi saya pikir itu ikan yang jauh lebih besar untuk digoreng daripada hanya memiliki sintaks yang nyaman untuk map .

@quinnj Ya, itu pada dasarnya sintaks over yang diusulkan di atas, kecuali dengan operator infix.

Komentar serius: Saya pikir Anda mungkin akan menemukan kembali SQL jika Anda mengejar ide ini cukup jauh karena SQL pada dasarnya adalah bahasa untuk menyusun fungsi elemen-bijaksana dari banyak variabel yang kemudian diterapkan melalui "vektorisasi" baris-bijaksana.

@johnmyleswhite setuju, mulai terlihat seperti DSL alias Linq

Pada topik utas yang diposting, Anda dapat mengkhususkan operator |> 'pipa' dan mendapatkan fungsionalitas gaya peta. Anda dapat membacanya sebagai pipa fungsi ke data. Sebagai bonus tambahan, Anda dapat menggunakan yang sama untuk melakukan komposisi fungsi.

julia> (|>)(x::Function, y...) = map(x, y... )
|> (generic function with 8 methods)

julia> (|>)(x::Function, y::Function) = (z...)->x(y(z...))
|> (generic function with 8 methods)

julia> sin |> cos |> [ 1,2,3 ]
3-element Array{Float64,1}:
  0.514395
 -0.404239
 -0.836022

julia> x,y = rand(3), rand(3)
([0.8883630054185454,0.32542923024720194,0.6022157767415313],    [0.35274912207468145,0.2331784754319688,0.9262490059844113])

julia> sin |> ( 0.5 *( abs( x - y ) ) )
3-element Array{Float64,1}:
 0.264617
 0.046109
 0.161309

@johnmyleswhite Itu benar, tetapi ada tujuan perantara yang bermanfaat yang cukup sederhana. Di cabang saya, ekspresi vektor multi-operasi versi map sudah lebih cepat dari yang kita miliki sekarang. Jadi mencari tahu bagaimana transisi dengan lancar ke sana agak mendesak.

@johnmyleswhite Tidak yakin. Banyak dari SQL adalah tentang memilih, memesan, dan menggabungkan baris. Di sini kita hanya berbicara tentang menerapkan fungsi elemen-bijaksana. Selain itu, SQL tidak menyediakan sintaks untuk membedakan pengurangan (misalnya SUM ) dari operasi elemen-bijaksana (misalnya > , LN ). Yang terakhir secara otomatis di-vektorisasi seperti di Julia saat ini.

@JeffBezanson Keindahan penggunaan \circ adalah jika Anda menafsirkan keluarga yang diindeks sebagai fungsi dari kumpulan indeks (yang merupakan "implementasi" matematika standar), kemudian memetakan _is_ hanya komposisi. Jadi (sin ∘ x)(i)=sin(x(i)) , atau, lebih tepatnya sin(x[i]) .

Penggunaan pipa, seperti yang disebutkan @mdcfrancis , pada dasarnya hanya menjadi komposisi "urutan diagram", yang sering dilakukan dengan titik koma (mungkin gemuk) dalam matematika (atau terutama aplikasi teori kategori CS) — tetapi kita sudah memiliki pipa operator, tentu saja.

Jika tidak satu pun dari operator komposisi ini baik-baik saja, maka salah satu dapat menggunakan beberapa yang lain. Sebagai contoh, setidaknya beberapa penulis menggunakan \cdot rendah untuk komposisi panah abstrak/morfisme, karena pada dasarnya adalah "perkalian" dari groupoid (kurang lebih) panah.

Dan jika seseorang menginginkan analog ASCII: Ada juga penulis yang benar-benar menggunakan titik untuk menunjukkan perkalian. (Saya mungkin telah melihat beberapa menggunakannya untuk komposisi juga; tidak ingat.)

Jadi seseorang bisa memiliki sin . x … tapi saya rasa itu akan membingungkan :-}

Namun … analogi terakhir itu mungkin menjadi argumen untuk salah satu proposal yang sangat awal, yaitu sin.(x) . (Atau mungkin itu terlalu mengada-ada.)

Mari kita coba dari sudut yang berbeda, jangan tembak saya.

Jika kita mendefinisikan .. oleh collect(..(A,B)) == ((a[1],..., a[n]), (b[1], ...,b[n])) == zip(A,B) , maka menggunakan T[x,y,z] = [T(x), T(y), T(z)] secara formal menyatakan bahwa

map(f,A,B) = [f(a[1],b[1]), ..., f(a[n],b[n])] = f[zip(A,B)...] = f[..(A,B)]

Itu memotivasi setidaknya satu sintaks untuk peta yang tidak mengganggu sintaks untuk konstruksi array. Dengan :: atau table ekstensi f[::(A,B)] = [f(a[i], b[j]) for i in 1:n, j in 1:n] mengarah setidaknya ke kasus penggunaan kedua yang menarik.

hati-hati mempertimbangkan pertanyaan yang ingin Anda tanyakan.

@toivoh Terima kasih, saya akan. Saat ini saya sedang mengevaluasi beberapa perangkat lunak jajak pendapat/survei. Saya juga hanya akan melakukan polling tentang sintaks yang disukai, mereka yang ingin membaca seluruh utas hanya akan melakukannya, jangan berasumsi tidak ada orang lain yang tertarik untuk melakukan itu.

temukan solusi yang belum pernah ditemukan oleh siapa pun

@nalimilan tidak ada di antara kita, itu. :senyum:

bagaimana jajak pendapat akan menghasilkan ide-ide segar ketika yang dapat Anda lakukan hanyalah memilih dari opsi yang ada?
orang yang sama atau lebih sedikit memberikan suara dalam jajak pendapat daripada berdiskusi di utas masalah.

@JeffBezanson Saya senang mendengar bahwa Anda telah melakukan polling, pertahankan!

  • Bagaimana Anda telah mempromosikan jajak pendapat Anda?
  • Dari perangkat lunak jajak pendapat/survei yang telah saya evaluasi sejauh ini kwiksurveys.com biarkan pengguna menambahkan pendapat mereka sendiri alih-alih opsi _tidak ada opsi ini untuk saya_.

sin∘x Tampaknya tidak terlalu membantu, karena memerlukan Unicode dan toh tidak diketahui secara luas.

Unicode kita banyak banget, ayo kita manfaatkan, bahkan kita punya cara yang bagus untuk menggunakannya dengan penyelesaian tab, lalu apa salahnya menggunakannya?, Jika tidak tahu, mari dokumentasikan dan edukasi, jika tidak ada, apa salahnya menciptakannya? Apakah kita benar-benar perlu menunggu orang lain untuk menemukan dan menggunakannya sehingga kita dapat mengambilnya sebagai preseden dan baru setelah itu mempertimbangkannya?

memiliki preseden, jadi masalahnya adalah Unicode? mengapa? kapan kita akan mulai menggunakan sisa Unicode yang tidak banyak dikenal? tidak pernah?

Dengan logika itu, Julia memang tidak banyak dikenal orang, tapi siapa yang mau belajar bisa. Itu tidak masuk akal bagi saya, menurut pendapat saya yang sangat sederhana.

Cukup adil, saya tidak sepenuhnya menentang . Memerlukan unicode untuk menggunakan fitur yang cukup mendasar hanyalah satu tanda menentangnya. Belum tentu cukup untuk menenggelamkannya sepenuhnya.

Apakah benar-benar gila menggunakan/membebani * sebagai alternatif ASCII? Saya akan mengatakan itu dapat dikatakan masuk akal secara matematis, tetapi saya kira mungkin sulit untuk membedakan artinya di kali… (Kemudian lagi, jika dibatasi pada fungsi map , maka map sudah menjadi alternatif ASCII, bukan?)

Keindahan penggunaan \circ adalah jika Anda menafsirkan keluarga yang diindeks sebagai fungsi dari kumpulan indeks (yang merupakan "implementasi" matematika standar), maka pemetaan _is_ hanyalah komposisi.

Saya tidak yakin saya membeli ini.

@hayd Bagian yang mana? Bahwa keluarga yang diindeks (misalnya, urutan) dapat dilihat sebagai fungsi dari himpunan indeks, atau pemetaan di atasnya menjadi komposisi? Atau bahwa ini adalah perspektif yang berguna dalam kasus ini?

Dua poin (matematis) pertama cukup tidak kontroversial, saya pikir. Tapi, ya, saya tidak akan menganjurkan dengan kuat untuk menggunakan ini di sini – sebagian besar adalah "Ah, itu agak cocok!" reaksi.

@mlhetland |> cukup dekat dengan -> dan berfungsi hari ini - ia juga memiliki 'keuntungan' karena asosiatif yang benar.

x = parse( "sin |> cos |> [1,2]" )
:((sin |> cos) |> [1,2])

@mdcfrancis Tentu. Tapi ternyata interpretasi komposisi yang saya uraikan di atas kepalanya. Artinya, sin∘x akan setara dengan x |> sin , bukan?

PS: Mungkin hilang dalam "aljabar", tetapi hanya mengizinkan fungsi dalam konstruksi array yang diketik T[x,y,z] sedemikian rupa sehingga f[x,y,z] adalah [f(x),f(y),f(z)] memberikan secara langsung

map(f,A) == f[A...]

yang cukup mudah dibaca dan dapat diperlakukan sebagai sintaks ..

Itu pintar. Tapi saya menduga bahwa jika kita bisa membuatnya bekerja, sin[x...] benar-benar kalah bertele-tele menjadi sin(x) atau sin~x dll.

Bagaimana dengan sintaks [sin xs] ?

Ini mirip dalam sintaks untuk pemahaman array [sin(x) for x in xs] .

@mlhetland sin |> x === map( sin, x )

Itu akan menjadi urutan terbalik dari arti rantai fungsi saat ini . Bukannya saya tidak keberatan menemukan penggunaan yang lebih baik untuk operator itu, tetapi akan membutuhkan masa transisi.

@mdcfrancis Ya, saya mengerti itulah yang Anda tuju. Yang membalikkan keadaan (seperti @tkelman mengulangi) wrt. interpretasi komposisi yang saya uraikan.

Saya pikir mengintegrasikan vektorisasi dan rantai akan sangat keren. Saya ingin tahu apakah kata-kata akan menjadi operator yang paling jelas.
Sesuatu seperti:

[1, 2] mapall
  +([2, 3]) map
  ^(2, _) chain
  { a = _ + 1
    b = _ - 1
    [a..., b...] } chain
  sum chain
  [ _, 2, 3] chain
  reduce(+, _)

Beberapa peta dalam satu baris dapat digabungkan secara otomatis menjadi satu peta untuk meningkatkan kinerja. Perhatikan juga bahwa saya berasumsi peta akan memiliki semacam fitur siaran otomatis. Mengganti [1, 2] dengan _ di awal dapat membangun fungsi anonim. Catatan Saya menggunakan aturan magrittr R untuk rantai (lihat posting saya di utas rantai).

Mungkin ini mulai terlihat lebih seperti DSL.

Saya telah mengikuti masalah ini untuk waktu yang lama, dan belum berkomentar sampai sekarang, tetapi ini mulai tidak terkendali IMHO.

Saya sangat mendukung gagasan sintaks yang bersih untuk peta. Saya paling suka saran @tkelman tentang ~ karena tetap dalam ASCII untuk fungsionalitas dasar seperti itu, dan saya cukup suka sin~x . Ini akan memungkinkan pemetaan gaya satu baris yang cukup canggih seperti yang dibahas di atas. Penggunaan sin∘x juga boleh. Untuk sesuatu yang lebih rumit, saya cenderung berpikir loop yang tepat jauh lebih jelas (dan biasanya kinerja terbaik). Saya tidak terlalu suka siaran 'ajaib', itu membuat kode lebih sulit untuk diikuti. Sebuah loop eksplisit biasanya lebih jelas.

Itu bukan untuk mengatakan fungsionalitas seperti itu tidak boleh ditambahkan, tetapi mari kita buat sintaks map yang bagus dan ringkas terlebih dahulu, terutama karena ini akan menjadi sangat cepat (dari pengujian saya pada cabang jb/functions ) .

Perhatikan bahwa salah satu efek jb/functions adalah bahwa broadcast(op, x, y) memiliki kinerja yang sama baiknya dengan versi kustom x .op y yang secara manual mengkhususkan penyiaran pada op .

Untuk sesuatu yang lebih rumit, saya cenderung berpikir loop yang tepat jauh lebih jelas (dan biasanya kinerja terbaik). Saya tidak terlalu suka siaran 'ajaib', itu membuat kode lebih sulit untuk diikuti. Sebuah loop eksplisit biasanya lebih jelas.

Saya tidak setuju. exp(2 * x.^2) sangat mudah dibaca, dan kurang verbose dari [exp(2 * v^2) for v in x] . Tantangannya di sini IMHO adalah untuk menghindari menjebak orang dengan membiarkan mereka menggunakan yang pertama (yang mengalokasikan salinan dan tidak menggabungkan operasi): untuk ini, kita perlu menemukan sintaks yang cukup pendek sehingga bentuk lambat dapat ditinggalkan.

Lebih banyak pikiran. Ada beberapa hal yang mungkin ingin Anda lakukan saat memanggil suatu fungsi:

loop tanpa argumen (rantai)
loop hanya melalui argumen yang dirantai (peta)
loop melalui semua argumen (mapall)

Masing-masing di atas dapat dimodifikasi, dengan:
Menandai item untuk diulang (~)
Menandai item untuk tidak diulang ( satu set tambahan [ ] )

Item yang dapat digunakan harus ditangani secara otomatis, mengabaikan sintaks.
Memperluas dimensi tunggal harus terjadi secara otomatis jika setidaknya ada dua argumen yang diulang

Penyiaran hanya membuat perbedaan ketika akan ada dimensi
ketidakcocokan sebaliknya. Jadi ketika Anda mengatakan tidak menyiarkan, Anda bermaksud memberikan
kesalahan sebagai gantinya jika ukuran argumen tidak cocok?

sin[x...] benar-benar kalah dengan sin(x) atau sin~x dll.

Juga, melanjutkan pemikiran, peta sin[x...] adalah versi yang kurang bersemangat pada [f(x...)] .
Sintaksnya

[exp(2 * (...x)^2)]

atau sesuatu yang serupa seperti [exp(2 * (x..)^2)] akan tersedia dan menjelaskan sendiri jika rantai fungsi tacit yang sebenarnya diperkenalkan.

@nalimilan ya tapi itu cocok dengan kategori 'one-liner' saya yang saya katakan baik-baik saja tanpa loop.

Sementara kami mencantumkan semua keinginan kami: jauh lebih penting bagi saya adalah agar hasil map dapat dialihkan tanpa alokasi atau penyalinan. Ini adalah alasan lain saya masih lebih suka loop untuk kode kritis kinerja, tetapi jika ini dapat dikurangi (#249 saat ini tidak mencari ATM yang penuh harapan) maka ini semua menjadi jauh lebih menarik.

hasil peta dapat dialihkan tanpa alokasi atau penyalinan

Bisakah Anda memperluas ini sedikit? Anda pasti dapat mengubah hasil map .

Saya kira maksudnya menyimpan output map ke array yang telah dialokasikan sebelumnya.

Iya benar sekali. Mohon maaf jika itu sudah memungkinkan.

Tentu saja. Kami memiliki map! , tetapi seperti yang Anda amati #249 meminta beberapa cara yang lebih baik untuk melakukannya.

@jtravs Saya mengusulkan solusi di atas dengan LazyArray (https://github.com/JuliaLang/julia/issues/8450#issuecomment-65106563), tetapi sejauh ini kinerjanya tidak ideal.

@toivoh Saya membuat beberapa pengeditan pada posting itu setelah saya mempostingnya. Pertanyaan yang saya khawatirkan adalah bagaimana mencari tahu argumen mana yang harus dilewati dan argumen mana yang tidak (jadi mapall mungkin lebih jelas daripada disiarkan). Saya pikir jika Anda mengulang lebih dari satu argumen, memperluas dimensi tunggal untuk menghasilkan array yang sebanding harus selalu dilakukan jika perlu, saya pikir.

Ya map! benar sekali. Akan lebih baik jika ada gula sintaksis yang bagus yang berhasil di sini juga mencakup kasus itu. Tidak bisakah kita membuat x := ... secara implisit memetakan RHS ke x .

Saya memasang paket bernama ChainMap yang mengintegrasikan pemetaan dan rantai.

Berikut ini contoh singkatnya:

<strong i="7">@chain</strong> begin
  [1, 2]
  -(1)
  (_, _)
  map_all(+)
  <strong i="8">@chain_map</strong> begin
    -(1)
    ^(2. , _)
  end
  begin
    a = _ - 1
    b = _ + 1
    [a, b]
  end
  sum
end

Saya terus memikirkannya dan saya pikir saya akhirnya menemukan sintaksis yang konsisten dan Julian untuk memetakan array yang berasal dari pemahaman array. Proposal berikut adalah julian karena dibangun di atas bahasa pemahaman array yang sudah ada.

  1. Mulai dari f[a...] yang sebenarnya diusulkan oleh @Jutho , konvensi
    bahwa untuk vektor a, b
f[a...] == map(f, a[:])
f[a..., b...] == map(f, a[:], b[:])
etc

yang tidak memperkenalkan simbol-simbol baru.

2.) Selain itu, saya akan mengusulkan pengenalan satu operator tambahan: operator _shape melestarikan_ percikan .. (katakanlah). Ini karena ... adalah operator spatting _flat_, jadi f[a...] harus mengembalikan vektor dan bukan array meskipun a adalah n -dimensi. Jika .. dipilih, maka dalam konteks ini,

f[a.., ] == map(f, a)
f[a.., b..] == map(f, a, b)

dan hasilnya mewarisi bentuk argumen. Mengizinkan penyiaran

f[a.., b..] == broadcast(f, a, b)

akan memungkinkan menulis berpikir seperti

sum(*[v.., v'..]) == dot(v,v)

Heureka?

Ini tidak membantu dengan pemetaan ekspresi, bukan? Salah satu keuntungan dari sintaks over adalah cara kerjanya dengan ekspresi:

sin(x * (y - 2)) over x, y  == map((x, y) -> sin(x * (y - 2)), x, y) 

Yah, mungkin melalui [sin(x.. * y..)] atau sin[x.. * y..] di atas jika Anda ingin mengizinkannya. Saya suka itu sedikit lebih dari sintaks yang berlebihan karena memberikan petunjuk visual bahwa operator fungsi pada elemen dan bukan pada wadah.

Tetapi bisakah Anda tidak menyederhanakannya sehingga x.. hanya memetakan x ? Jadi contoh @johansigfrids adalah:

sin(x.. * (y.. - 2))  == map((x, y) -> sin(x * (y - 2)), x, y)

@jtravs Karena pelingkupan ( [println(g(x..))] vs println([g(x..)]) ) dan konsistensi [x..] = x .

Satu kemungkinan lebih lanjut adalah untuk mengambil x.. = x[:, 1], x[:, 2], etc. sebagai percikan parsial dari subarray (kolom) terkemuka dan ..y sebagai percikan parsial dari subarray yang mengikuti ..y = y[1,:], y[2,:] . Jika keduanya melewati indeks yang berbeda, ini mencakup banyak kasus menarik

[f(v..)] == [f(v[i]) for i in 1:m ]
[v.. * v..] == [v[i] * v[i] for 1:m]
[v.. * ..v] == [v[i] * v[j] for i in 1:m, j in 1:n]
[f(..A)] == [f(A[:, j]) for j in 1:n]
[f(A..)] == [f(A[i, :]) for i in 1:m]
[dot(A.., ..A)] == [dot(A[:,i], A[j,:]) for i in 1:m, j in 1:n] == A*A
[f(..A..)] == [f(A[i,j]) for i in 1:m, j in 1:n]
[v..] == [..v] = v
[..A..] == A

( v sebuah Vektor, A sebuah matriks)

Saya lebih suka over , karena memungkinkan Anda untuk menulis ekspresi dalam sintaks normal daripada memperkenalkan banyak tanda kurung siku dan titik.

Anda benar tentang kekacauan itu, saya pikir dan saya mencoba untuk mengadaptasi dan mensistematisasikan proposal saya. Untuk tidak membebani kesabaran semua orang lebih jauh, saya menulis pemikiran saya tentang peta dan indeks, dll. di Gist https://Gist.github.com/mschauer/b04e000e9d0963e40058 .

Setelah membaca utas ini, preferensi saya sejauh ini adalah memiliki _both_ f.(x) untuk hal-hal sederhana dan orang-orang yang terbiasa dengan fungsi vektor (" . = vektorisasi" idiom cukup umum), dan f(x^2)-x over x untuk ekspresi yang lebih rumit.

Ada terlalu banyak orang yang datang dari Matlab, Numpy, dan sebagainya, untuk sepenuhnya meninggalkan sintaks fungsi vektor; menyuruh mereka menambahkan titik mudah diingat. Sintaks seperti over yang bagus untuk membuat vektorisasi ekspresi kompleks menjadi satu loop juga sangat berguna.

Sintaks over benar-benar membuat saya salah jalan. Baru terpikir oleh saya mengapa: itu menganggap bahwa semua penggunaan setiap variabel dalam ekspresi adalah vektor atau non-vektor, yang mungkin tidak demikian. Misalnya, log(A) .- sum(A,1) – asumsikan bahwa kita menghapus vektorisasi log . Anda juga tidak dapat membuat vektorisasi fungsi di atas ekspresi, yang tampaknya merupakan kelemahan yang cukup besar, bagaimana jika saya ingin menulis exp(log(A) .- sum(A,1)) dan membuat exp dan log di-vektorkan dan sum bukan?

@StefanKarpinski , maka Anda harus melakukan exp.(log.(A) .- sum(A,1)) dan menerima temporer tambahan (misalnya dalam penggunaan interaktif di mana kinerja tidak kritis), atau s = sum(A, 1); exp(log(A) - s) over A (walaupun itu tidak benar jika sum(A,1) adalah vektor dan Anda ingin menyiarkan); Anda mungkin hanya perlu menggunakan pemahaman. Apa pun sintaks yang kami buat, kami tidak akan membahas semua kemungkinan kasus, dan contoh Anda sangat bermasalah karena semua sintaks "otomatis" harus mengetahui bahwa sum murni dan dapat diangkat keluar dari loop/peta.

Bagi saya, prioritas pertama adalah sintaks f.(x...) untuk broadcast(f, x...) atau map(f, x...) sehingga kita dapat menyingkirkan @vectorize . Setelah itu, kita dapat melanjutkan mengerjakan sintaks seperti over (atau apa pun) untuk menyingkat penggunaan yang lebih umum dari map dan pemahaman.

@stevenngj Saya rasa contoh kedua tidak berfungsi, karena - tidak akan disiarkan. Dengan asumsi A adalah sebuah matriks, output akan menjadi matriks matriks baris tunggal, yang masing-masing adalah log dari elemen A dikurangi vektor jumlah di sepanjang dimensi pertama. Anda membutuhkan broadcast((x, y)->exp(log(x)-y), A, sum(A, 1)) . Tapi saya pikir memiliki sintaks yang ringkas untuk map berguna dan tidak harus berupa sintaks yang ringkas untuk broadcast juga.

Apakah fungsi yang secara historis telah di-vektor otomatis seperti sin akan terus demikian dengan sintaks baru, atau apakah itu akan ditinggalkan? Saya khawatir bahkan sintaks f. akan terasa seperti 'mendapatkan' bagi sebagian besar programmer ilmiah yang tidak termotivasi oleh argumen keanggunan konseptual.

Perasaan saya adalah bahwa fungsi-fungsi yang divektorkan secara historis seperti sin harus ditinggalkan demi sin. , tetapi mereka harus ditinggalkan secara kuasi-permanen (sebagai lawan dari dihapus seluruhnya dalam rilis berikutnya) untuk manfaat pengguna dari bahasa ilmiah lainnya.

Satu masalah kecil(?) dengan f.(args...) : meskipun sintaks object.(field) sebagian besar jarang digunakan dan mungkin dapat diganti dengan getfield(object, field) tanpa terlalu banyak kesulitan, ada _lot_ definisi/referensi metode dari formulir Base.(:+)(....) = .... , dan akan sulit untuk mengubahnya menjadi getfield .

Salah satu solusinya adalah:

  • Membuat Base.(:+) berubah menjadi map(Base, :+) seperti semua f.(args...) lainnya, tetapi tentukan metode usang map(m::Module, s::Symbol) = getfield(m, s) untuk kompatibilitas mundur
  • dukung sintaks Base.:+ (yang saat ini gagal) dan rekomendasikan ini dalam peringatan penghentian untuk Base.(:+)

Saya ingin bertanya lagi - apakah ini sesuatu yang bisa kita lakukan di 0.5.0? Saya pikir ini penting karena penghentian banyak konstruktor vektor. Saya pikir saya akan baik-baik saja dengan ini, tetapi saya menemukan map(Int32, a) , bukannya int32(a) agak membosankan.

Apakah ini pada dasarnya hanya masalah memilih sintaks pada saat ini?

Apakah ini pada dasarnya hanya masalah memilih sintaks pada saat ini?

Saya pikir @stevenngj memberikan argumen yang bagus untuk mendukung penulisan sin.(x) daripada .sin(x) dalam PR-nya https://github.com/JuliaLang/julia/pull/15032. Jadi saya akan mengatakan jalan telah dibersihkan.

Saya memiliki satu reservasi tentang fakta bahwa kami belum memiliki solusi untuk menggeneralisasi sintaks ini ke ekspresi gabungan secara efisien. Tapi saya pikir pada tahap ini kita sebaiknya menggabungkan fitur ini yang mencakup sebagian besar kasus penggunaan daripada membiarkan diskusi ini tidak terselesaikan tanpa batas.

@JeffBezanson Saya mengembalikan tonggak sejarah ini menjadi 0,5.0 untuk memunculkannya selama diskusi triase - terutama untuk memastikan saya tidak lupa.

Apakah #15032 juga berfungsi untuk call - misalnya Int32.(x) ?

@ViralBShah , ya. Setiap f.(x...) diubah menjadi map(f, broadcast, x...) pada tingkat sintaks, terlepas dari jenis f .

Itulah keuntungan utama . dibandingkan sesuatu seperti f[x...] , yang sebaliknya menarik (dan tidak memerlukan perubahan parser) tetapi hanya akan berfungsi untuk f::Function . f[x...] juga sedikit berbenturan secara konseptual dengan pemahaman array T[...] . Meskipun saya pikir @StefanKarpinski menyukai sintaks braket?

(Untuk memilih contoh lain, objek o::PyObject di PyCall dapat dipanggil, menggunakan metode __call__ dari objek Python o , tetapi objek yang sama juga dapat mendukung o[...] pengindeksan. Ini akan sedikit berbenturan dengan penyiaran f[x...] , tetapi akan bekerja dengan baik dengan penyiaran o.(x...) .)

call tidak ada lagi.

(Saya juga menyukai argumen @nalimilan bahwa f.(x...) menjadikan .( analog dari .+ dll.)

Ya analogi titik bijaksana adalah yang paling saya sukai juga. Bisakah kita maju dan bergabung?

Haruskah getfield dengan modul menjadi penghentian yang sebenarnya?

@tkelman , sebagai lawan dari apa? Namun, peringatan penghentian untuk Base.(:+) (yaitu argumen simbol literal) harus menyarankan Base.:+ , bukan getfield . (_Update_: memerlukan penghentian sintaks juga untuk menangani definisi metode.)

@ViralBShah , apakah ada keputusan tentang ini pada diskusi triase hari Kamis? #15032 dalam kondisi yang cukup bagus untuk digabungkan, saya pikir.

Saya pikir Viral melewatkan bagian dari panggilan itu. Kesan saya adalah bahwa banyak orang masih memiliki keraguan tentang estetika f.(x) dan mungkin lebih memilih keduanya

  1. operator infix yang akan lebih sederhana secara konseptual dan dalam implementasinya, tetapi kami tidak memiliki ascii yang tersedia dari apa yang dapat saya lihat. Gagasan saya sebelumnya untuk menghentikan penguraian makro ~ akan membutuhkan pekerjaan untuk mengganti dalam paket dan mungkin sudah terlambat untuk mencoba melakukannya dalam siklus ini.
  2. atau sintaks baru alternatif yang membuatnya lebih mudah untuk menggabungkan loop dan menghilangkan sementara. Tidak ada alternatif lain yang diterapkan untuk mendekati level #15032, jadi sepertinya kita harus menggabungkannya dan mencobanya meskipun ada reservasi yang tersisa.

Ya, saya memiliki beberapa reservasi tetapi saya tidak dapat melihat opsi yang lebih baik daripada f.(x) sekarang. Tampaknya lebih baik daripada memilih simbol arbitrer seperti ~ , dan saya yakin banyak orang yang terbiasa dengan .* (dll.) bahkan dapat langsung menebak apa artinya.

Satu hal yang ingin saya pahami adalah apakah orang setuju dengan _mengganti_ definisi vektor yang ada dengan .( . Jika orang tidak cukup menyukainya untuk melakukan penggantian, saya akan ragu lagi.

Sebagai pengguna yang mengintai diskusi ini, saya SANGAT ingin menggunakan ini untuk mengganti kode vektor saya yang ada.

Saya sebagian besar menggunakan vektorisasi di julia untuk keterbacaan karena loop cepat. Jadi saya sangat suka menggunakannya untuk exp, sin, dll seperti yang telah disebutkan sebelumnya. Seperti yang sudah saya gunakan .^, .* dalam ekspresi seperti itu menambahkan titik ekstra ke sin. ex. dll terasa sangat alami, dan bahkan lebih eksplisit, bagi saya ... terutama ketika saya dapat dengan mudah melipat fungsi saya sendiri dengan notasi umum alih-alih mencampur sin(x) dan map(f, x).

Semua untuk mengatakan, sebagai pengguna biasa, saya sangat, sangat berharap ini digabungkan!

Saya lebih menyukai sintaks fun[vec] yang diusulkan daripada fun.(vec) .
Apa pendapat Anda tentang [fun vec] ? Ini seperti pemahaman daftar tetapi dengan variabel implisit. Itu bisa memungkinkan untuk melakukan T[fun vec]

Sintaks itu gratis di Julia 0.4 untuk vektor dengan panjang> 1:

julia> [sin rand(1)]
1x2 Array{Any,2}:
 sin  0.0976151

julia> [sin rand(10)]
ERROR: DimensionMismatch("mismatch in dimension 1 (expected 1 got 10)")
 in cat_t at abstractarray.jl:850
 in hcat at abstractarray.jl:875

Sesuatu seperti [fun over vec] dapat diubah pada tingkat sintaks dan mungkin layak untuk disederhanakan [fun(x) for x in vec] tetapi tidak lebih sederhana dari map(fun,vec) .

Sintaks yang mirip dengan [fun vec] : Sintaks (fun vec) gratis dan {fun vec} tidak digunakan lagi.

julia> (fun vec)
ERROR: syntax: missing separator in tuple

julia> {fun vec}

WARNING: deprecated syntax "{a b ...}".
Use "Any[a b ...]" instead.
1x2 Array{Any,2}:
 fun  [0.3231600663395422,0.10208482721149204,0.7964663210635679,0.5064134055014935,0.7606900072242995,0.29583012284224064,0.5501131920491444,0.35466150455688483,0.6117729165962635,0.7138111929010424]

@diegozea , fun[vec] dikesampingkan karena bertentangan dengan T[vec] . (fun vec) pada dasarnya adalah sintaks Skema, dengan kasus multi-argumen mungkin adalah (fun vec1 vec2 ...) ... ini sangat berbeda dengan sintaks Julia lainnya. Atau apakah Anda bermaksud (fun vec1, vec2, ...) , yang bertentangan dengan sintaks Tuple? Juga tidak jelas apa keuntungannya dibandingkan fun.(vecs...) .

Selanjutnya, ingat bahwa tujuan utamanya adalah untuk akhirnya memiliki sintaks untuk menggantikan fungsi @vectorized (sehingga kita tidak memiliki subset fungsi yang "diberkati" yang "bekerja pada vektor"), dan ini berarti bahwa sintaks harus sesuai/intuitif/nyaman bagi orang yang terbiasa dengan fungsi vektor di Matlab, Numpy, dan sebagainya. Itu juga harus mudah dikomposisi untuk ekspresi seperti sin(A .+ cos(B[:,1])) . Persyaratan ini mengesampingkan banyak proposal yang lebih "kreatif".

sin.(A .+ cos.(B[:,1])) tampaknya tidak terlalu buruk. Ini akan membutuhkan dokumentasi yang baik. Apakah f.(x) akan didokumentasikan sebagai .( mirip dengan .+ ?
Bisakah .+ tidak digunakan lagi demi +. ?

# Since 
sin.(A .+ cos.(B[:,1]))
# could be written as
sin.(.+(A, cos.(B[:,1])))
# +.
sin.(+.(A, cos.(B[:,1]))) #  will be more coherent.

@diegozea , #15032 sudah menyertakan dokumentasi, tetapi saran tambahan apa pun dipersilakan.

.+ akan terus dieja .+ . Pertama, penempatan titik ini terlalu mengakar, dan tidak ada cukup keuntungan dengan mengubah ejaan di sini. Kedua, seperti yang ditunjukkan oleh @nalimilan , Anda dapat menganggap .( sebagai "operator panggilan fungsi yang divektorkan", dan dari perspektif ini sintaksnya sudah konsisten.

(Setelah kesulitan dengan type-computation di broadcast (#4883) teratasi, harapan saya adalah membuat PR lain sehingga a .⧆ b untuk semua operator hanyalah gula untuk panggilan ke broadcast(⧆, a, b) Dengan begitu, kita tidak perlu lagi mengimplementasikan .+ dan sebagainya secara eksplisit — Anda akan mendapatkan operator penyiaran secara otomatis hanya dengan mendefinisikan + dll. masih dapat menerapkan metode khusus, misalnya panggilan ke BLAS, dengan membebani broadcast untuk operator tertentu.)

Itu juga harus mudah dikomposisi untuk ekspresi seperti sin(A .+ cos(B[:,1])) .

Apakah mungkin untuk mengurai f1.(x, f2.(y .+ z)) sebagai broadcast((a, b, c)->(f1(a, f2(b + c))), x, y, z) ?

Sunting: Saya melihatnya sudah disebutkan di atas ... di komentar disembunyikan secara default oleh @github ..

@yuyichao , loop fusion sepertinya harus dimungkinkan jika fungsinya ditandai sebagai @pure (setidaknya jika eltypes tidak dapat diubah), seperti yang saya komentari di #15032, tetapi ini adalah tugas untuk kompiler, bukan pengurai. (Tetapi sintaksis vektor seperti ini lebih untuk kenyamanan daripada untuk memeras siklus terakhir dari loop dalam yang kritis.)

Ingat bahwa tujuan utama di sini adalah untuk menghilangkan kebutuhan akan fungsi @vectorized ; ini membutuhkan sintaks setidaknya sama umum, hampir sama nyamannya, dan setidaknya secepat. Itu tidak memerlukan fusi loop otomatis, meskipun bagus untuk mengekspos niat broadcast pengguna ke kompiler untuk membuka kemungkinan fusi loop di masa mendatang.

Apakah ada kekurangannya jika melakukan loop fusion juga?

@yuyichao , loop fusion adalah masalah yang jauh lebih sulit, dan itu tidak selalu mungkin bahkan mengesampingkan fungsi non-murni (misalnya lihat contoh exp(log(A) .- sum(A,1)) @StefanKarpinski di atas). Menahan agar itu diimplementasikan mungkin akan mengakibatkannya _tidak pernah_ diimplementasikan, menurut pendapat saya — kita harus melakukan ini secara bertahap. Mulailah dengan mengekspos niat pengguna. Jika kita dapat lebih mengoptimalkan di masa depan, bagus. Jika tidak, kami masih memiliki pengganti umum untuk beberapa fungsi "divektorkan" yang tersedia sekarang.

Kendala lain adalah bahwa .+ dll. saat ini tidak diekspos ke parser sebagai operasi broadcast ; .+ hanyalah fungsi lain. Rencana saya adalah mengubahnya (buat .+ gula untuk broadcast(+, ...) ), seperti yang disebutkan di atas. Tetapi sekali lagi, jauh lebih mudah untuk membuat kemajuan jika perubahannya bertahap.

Maksud saya adalah melakukan loop fusion dengan membuktikan bahwa hal itu valid itu sulit, jadi kita bisa membiarkan parser melakukan transformasi sebagai bagian dari skema. Pada contoh di atas, dapat ditulis sebagai. exp.(log.(A) .- sum(A,1)) dan diuraikan sebagai broadcast((x, y)->exp(log(x) - y), A, sum(A, 1)) .

Ini juga baik-baik saja jika .+ belum termasuk dalam kategori yang sama (seperti halnya panggilan fungsi non-boardcast akan dimasukkan ke dalam argumen) dan itu bahkan baik-baik saja jika kita hanya akan melakukan ini (loop fusion) dalam a versi yang lebih baru. Saya terutama bertanya apakah memiliki skema seperti itu dimungkinkan (yaitu tidak ambigu) di parser dan jika ada penarikan kembali dengan mengizinkan loop vektor dan fusi tertulis dengan cara ini ..

melakukan loop fusion dengan membuktikan bahwa hal itu valid itu sulit

Maksud saya melakukan itu di kompiler itu sulit (mungkin bukan tidak mungkin), terutama karena kompiler perlu melihat ke dalam implementasi rumit broadcast , kecuali jika kita kasus khusus broadcast di kompiler, yaitu mungkin ide yang buruk dan kita harus menghindarinya jika memungkinkan...

Mungkin? Ini adalah ide yang menarik, dan sepertinya tidak mustahil untuk mendefinisikan sintaks .( sebagai "sekering" dengan cara ini, dan menyerahkannya kepada pemanggil untuk tidak menggunakannya untuk fungsi yang tidak murni. Hal terbaik adalah mencobanya dan melihat apakah ada kasus sulit (saya tidak melihat masalah yang jelas sekarang), tetapi saya cenderung melakukan ini setelah PR "tidak menyatu".

Saya cenderung melakukan ini setelah PR "non-sekering".

Sangat setuju, terutama karena .+ tidak ditangani.

Saya tidak ingin menggagalkan ini, tetapi saran @yuyichao memberi saya beberapa ide. Penekanannya di sini adalah pada fungsi mana yang divektorkan, tetapi itu selalu tampak agak salah tempat bagi saya – pertanyaan sebenarnya adalah variabel mana yang akan divektorkan, yang sepenuhnya menentukan bentuk hasil. Inilah sebabnya mengapa saya cenderung menandai argumen untuk vektorisasi, daripada menandai fungsi untuk vektorisasi. Menandai argumen juga memungkinkan fungsi yang membuat vektor pada satu argumen tetapi tidak pada argumen lainnya. Yang mengatakan, kita dapat memiliki keduanya dan PR ini melayani tujuan langsung untuk mengganti fungsi vektor bawaan.

@StefanKarpinski , ketika Anda memanggil f.(args...) atau broadcast(f, args...) , itu membuat vektor di atas _semua_ argumen. (Untuk tujuan ini, ingatlah bahwa skalar diperlakukan sebagai array 0-dimensi.) Dalam saran @yuyichao f.(args...) = _fused broadcast syntax_ (yang semakin saya sukai), saya pikir fusion akan " stop" pada ekspresi apa pun yang bukan func.(args...) (untuk menyertakan .+ dll. di masa mendatang).

Jadi, misalnya, sin.(x .+ cos.(x .^ sum(x.^2))) akan berubah (dalam julia-syntax.scm ) menjadi broadcast((x, _s_) -> sin(x + cos(x^_s_)), x, sum(broacast(^, x, 2))) . Perhatikan bahwa fungsi sum akan menjadi "batas fusi." Penelepon akan bertanggung jawab untuk tidak menggunakan f.(args...) dalam kasus di mana fusi akan mengacaukan efek samping.

Apakah Anda memiliki contoh dalam pikiran di mana ini tidak akan cukup?

yang semakin saya sukai

Aku senang kau menyukainya. =)

Hanya ekstensi lain yang mungkin tidak termasuk dalam babak yang sama, dimungkinkan untuk menggunakan .= , .*= atau serupa untuk menyelesaikan masalah penugasan di tempat (dengan membuatnya berbeda dari tugas biasa)

Ya, kurangnya fusi untuk operasi lain adalah keberatan utama saya untuk .+= dan seterusnya di #7052, tetapi saya pikir itu akan diselesaikan dengan memiliki .= fusi dengan panggilan func.(args...) lainnya . Atau cukup gabungkan x[:] = ... .

:thumbsup: Ada dua konsep yang dirangkai dalam diskusi ini yang sebenarnya cukup ortogonal:
matlab'y "operasi penyiaran menyatu" atau x .* y .+ z dan apl'y "peta pada produk dan ritsleting" seperti f[product(I,J)...] dan f[zip(I,J)...] . Pembicaraan melewati satu sama lain mungkin ada hubungannya dengan itu juga.

@mschauer , f.(I, J) sudah (dalam #15032) setara dengan map(x -> f(x...), zip(I, J) jika I dan J memiliki bentuk yang sama. Dan jika I adalah vektor baris dan J adalah vektor kolom atau sebaliknya, maka broadcast memang memetakan set produk (atau Anda dapat melakukan f.(I, J') jika keduanya adalah array 1d). Jadi saya tidak mengerti mengapa menurut Anda konsepnya "cukup ortogonal."

Ortogonal bukanlah kata yang tepat, mereka hanya cukup berbeda untuk hidup berdampingan.

Namun, intinya adalah bahwa kita tidak memerlukan sintaks terpisah untuk kedua kasus tersebut. func.(args...) dapat mendukung keduanya.

Setelah anggota dari tiga serangkai (Stefan, Jeff, Viral) bergabung #15032 (yang menurut saya sudah siap untuk digabungkan), saya akan menutup ini dan mengajukan masalah peta jalan untuk menguraikan sisa perubahan yang diusulkan: perbaiki perhitungan tipe siaran, tidak digunakan lagi @vectorize , ubah .op menjadi gula siaran, tambahkan "broadcast-fusion" tingkat sintaksis, dan terakhir gabungkan dengan penugasan di tempat. Dua yang terakhir mungkin tidak akan berhasil menjadi 0,5.

Hei, saya sangat senang dan berterima kasih tentang 15032. Saya tidak akan mengabaikan diskusi ini. Misalnya vektor vektor dan objek serupa masih sangat canggung untuk digunakan dalam julia tetapi dapat tumbuh seperti rumput liar sebagai hasil pemahaman. Notasi implisit yang baik tidak didasarkan pada pengkodean iterasi ke dalam dimensi tunggal memiliki potensi untuk banyak memudahkan hal ini, misalnya dengan iterator tertekuk dan ekspresi generator baru.

Saya pikir ini bisa ditutup sekarang demi #16285.

Apakah halaman ini membantu?
0 / 5 - 0 peringkat