Apa tanggapan Haskell terhadap Node.js?

217

Saya percaya komunitas Erlang tidak iri pada Node.js karena tidak memblokir I / O secara asli dan memiliki cara untuk mengukur penyebaran dengan mudah ke lebih dari satu prosesor (sesuatu yang bahkan tidak ada di Node.js). Lebih detail di http://journal.dedasys.com/2010/04/29/erlang-vs-node-js dan Node.js atau Erlang

Bagaimana dengan Haskell? Bisakah Haskell memberikan beberapa manfaat dari Node.js, yaitu solusi bersih untuk menghindari pemblokiran I / O tanpa meminta bantuan pemrograman multi-thread?


Ada banyak hal yang menarik dengan Node.js

  1. Acara: Tanpa manipulasi utas, programmer hanya menyediakan panggilan balik (seperti pada kerangka kerja Snap)
  2. Panggilan balik dijamin dijalankan dalam utas tunggal: tidak ada kondisi balapan yang mungkin.
  3. API ramah-UNIX yang bagus dan sederhana. Bonus: Dukungan HTTP luar biasa. DNS juga tersedia.
  4. Setiap I / O secara default tidak sinkron. Ini membuatnya lebih mudah untuk menghindari kunci. Namun, terlalu banyak pemrosesan CPU dalam panggilan balik akan berdampak pada koneksi lain (dalam hal ini, tugas harus dipecah menjadi sub-tugas yang lebih kecil dan dijadwalkan ulang).
  5. Bahasa yang sama untuk sisi klien dan sisi server. (Saya tidak melihat terlalu banyak nilai dalam yang satu ini. Namun, jQuery dan Node.js berbagi model acara pemrograman tetapi sisanya sangat berbeda. Saya hanya tidak bisa melihat bagaimana berbagi kode antara sisi server dan sisi klien bisa berguna dalam latihan.)
  6. Semua ini dikemas dalam satu produk.
gawi
sumber
17
Saya pikir Anda harus mengajukan pertanyaan ini pada Programer sebagai gantinya.
Jonas
47
Tidak termasuk sepotong kode tidak membuatnya menjadi pertanyaan subjektif.
gawi
20
Saya tidak tahu banyak tentang node.js, tetapi satu hal yang mengejutkan saya tentang pertanyaan Anda: mengapa Anda menemukan prospek utas sangat tidak menyenangkan? Utas harus menjadi solusi tepat untuk multiplexing I / O. Saya menggunakan istilah utas secara luas di sini, termasuk proses Erlang. Mungkin Anda khawatir tentang kunci dan status yang bisa berubah? Anda tidak harus melakukan hal-hal seperti itu - menggunakan pesan-lewat atau transaksi jika itu lebih masuk akal untuk aplikasi Anda.
Simon Marlow
9
@gawi Saya rasa itu tidak mudah diprogram - tanpa preemption, Anda harus berhadapan dengan kemungkinan kelaparan dan latensi panjang. Pada dasarnya utas adalah abstraksi yang tepat untuk server web - tidak perlu berurusan dengan I / O asinkron dan semua kesulitan yang menyertainya, lakukan saja dalam utas. Kebetulan, saya menulis makalah tentang server web di Haskell yang mungkin menarik bagi Anda: haskell.org/~simonmar/papers/web-server-jfp.pdf
Simon Marlow
3
"Callback dijamin dijalankan dalam satu utas: tidak ada kondisi balapan yang mungkin." Salah. Anda dapat dengan mudah memiliki kondisi balapan di Node.js; anggap saja satu tindakan I / O akan selesai sebelum yang lain, dan BOOM. Apa yang memang mustahil adalah satu jenis kondisi ras tertentu, yaitu akses bersamaan yang tidak disinkronkan ke byte yang sama dalam memori.
sayap kanan

Jawaban:

219

Ok, jadi setelah menyaksikan sedikit presentasi node.js yang ditunjukkan @gawi kepada saya, saya bisa mengatakan sedikit lebih banyak tentang bagaimana Haskell dibandingkan dengan node.js. Dalam presentasi tersebut, Ryan menjelaskan beberapa manfaat dari Green Threads, tetapi kemudian mengatakan bahwa ia tidak menemukan kurangnya abstraksi thread menjadi kerugian. Saya tidak setuju dengan posisinya, terutama dalam konteks Haskell: Saya pikir abstraksi yang disediakan thread sangat penting untuk membuat kode server lebih mudah untuk mendapatkan yang benar, dan lebih kuat. Khususnya:

  • menggunakan satu utas per koneksi memungkinkan Anda menulis kode yang mengekspresikan komunikasi dengan satu klien, alih-alih menulis kode yang berhubungan dengan semua klien secara bersamaan. Pikirkan seperti ini: server yang menangani banyak klien dengan utas terlihat hampir sama dengan yang menangani satu klien; perbedaan utama adalah ada suatu forktempat di bekas. Jika protokol yang Anda implementasikan sangat rumit, mengelola mesin negara untuk banyak klien secara bersamaan menjadi cukup rumit, sedangkan utas memungkinkan Anda membuat skrip komunikasi dengan satu klien saja. Kode lebih mudah untuk diperbaiki, dan lebih mudah dipahami dan dipelihara.

  • panggilan balik pada utas OS tunggal adalah multitasking kooperatif, tidak seperti multitasking preemptive, yang adalah apa yang Anda dapatkan dengan utas. Kerugian utama dengan multitasking kooperatif adalah bahwa programmer bertanggung jawab untuk memastikan bahwa tidak ada kelaparan. Kehilangan modularitas: membuat kesalahan di satu tempat, dan itu dapat mengacaukan seluruh sistem. Ini benar-benar sesuatu yang tidak ingin Anda khawatirkan, dan preemption adalah solusi sederhana. Selain itu, komunikasi antara panggilan balik tidak dimungkinkan (itu akan menemui jalan buntu).

  • concurrency tidak sulit di Haskell, karena sebagian besar kode murni dan aman untuk konstruksi. Ada primitif komunikasi sederhana. Jauh lebih sulit menembak diri sendiri dengan konkurensi di Haskell daripada dalam bahasa dengan efek samping yang tidak terbatas.

Simon Marlow
sumber
42
Ok, jadi saya mendapatkan node.js itu solusi untuk 2 masalah: 1- concurrency sulit di sebagian besar bahasa, 2 - menggunakan thread OS ekspansif. Solusi Node.js adalah menggunakan konkurensi berbasis peristiwa (w / libev) untuk menghindari komunikasi antara utas dan untuk menghindari masalah skalabilitas utas OS. Haskell tidak memiliki masalah # 1 karena kemurnian. Untuk # 2, Haskell memiliki thread + manajer acara ringan yang dioptimalkan baru-baru ini di GHC untuk konteks skala besar. Juga, menggunakan Javascript tidak dapat dianggap sebagai nilai tambah bagi pengembang Haskell. Untuk beberapa orang yang menggunakan Kerangka Snap, Node.js adalah "hanya buruk".
gawi
4
Pemrosesan permintaan sebagian besar merupakan urutan operasi yang saling tergantung. Saya cenderung setuju bahwa menggunakan panggilan balik untuk setiap operasi pemblokiran dapat menjadi masalah. Utas lebih cocok daripada panggilan balik untuk ini.
gawi
10
Ya! Dan multiplexing I / O baru di GHC 7 membuat server penulisan di Haskell lebih baik.
andreypopp
3
Poin pertama Anda tidak masuk akal bagi saya (sebagai orang luar) ... Saat memproses permintaan di node.js panggilan balik Anda berurusan dengan satu klien. Mengelola status hanya menjadi sesuatu yang perlu dikhawatirkan saat melakukan penskalaan ke beberapa proses, dan meskipun begitu cukup mudah menggunakan perpustakaan yang tersedia.
Ricardo Tomasi
12
Itu bukan masalah yang terpisah. Jika pertanyaan ini adalah pencarian yang tulus untuk alat terbaik untuk pekerjaan di Haskell, atau memeriksa apakah alat yang sangat baik untuk pekerjaan itu ada di Haskell, maka asumsi implisit bahwa pemrograman multi-utas tidak sesuai perlu ditantang, karena Haskell tidak utasnya agak berbeda, seperti yang ditunjukkan Don Stewart. Jawaban yang menjelaskan mengapa komunitas Haskell juga tidak cemburu pada Node.js sangat memperhatikan topik untuk pertanyaan ini. Tanggapan gawi menunjukkan bahwa itu adalah jawaban yang tepat untuk pertanyaannya.
AndrewC
154

Bisakah Haskell memberikan beberapa manfaat dari Node.js, yaitu solusi bersih untuk menghindari pemblokiran I / O tanpa meminta bantuan pemrograman multi-thread?

Ya, sebenarnya acara dan utas disatukan di Haskell.

  • Anda dapat memprogram dalam utas ringan eksplisit (mis. Jutaan utas pada satu laptop).
  • Atau; Anda dapat memprogram dalam gaya yang didorong oleh acara async, berdasarkan pemberitahuan acara yang dapat diskalakan.

Thread sebenarnya diimplementasikan dalam hal peristiwa , dan dijalankan di beberapa inti, dengan migrasi thread yang mulus, dengan kinerja yang terdokumentasi, dan aplikasi.

Misalnya untuk

Koleksi serentak nbody di 32 core

teks alternatif

Di Haskell Anda memiliki kedua acara dan utas, dan seperti semua acara di bawah tenda.

Baca makalah yang menjelaskan implementasi.

Don Stewart
sumber
2
Terima kasih. Saya perlu mencerna semua ini ... Ini sepertinya spesifik GHC. Saya kira tidak apa-apa. Bahasa Haskell kadang-kadang bisa dikompilasi oleh GHC. Dengan cara yang sama, "platform" Haskell kurang lebih merupakan run-time GHC.
gawi
1
@gawi: Itu dan semua paket lainnya yang dibundel ke dalamnya sehingga berguna langsung dari kotak. Dan ini adalah gambar yang sama yang saya lihat di kursus CS saya; dan bagian terbaiknya adalah bahwa tidak sulit di Haskell untuk mencapai hasil luar biasa serupa di program Anda sendiri.
Robert Massaioli
1
Hai Don, apakah Anda pikir Anda dapat menautkan ke server web haskell yang melakukan yang terbaik (Warp) ketika menjawab pertanyaan seperti ini? Berikut ini adalah patokan yang cukup relevan terhadap Node.js: yesodweb.com/blog/2011/03/…
Greg Weber
4
Secara teori. "Benang ringan" Haskell tidak seberat yang Anda kira. Jauh lebih murah untuk mendaftarkan panggilan balik pada antarmuka epoll daripada menjadwalkan thread hijau yang disebut, mereka tentu saja lebih murah daripada thread OS tetapi mereka tidak gratis. Membuat 100.000 dari mereka menggunakan sekitar. Memori 350 MB dan perlu waktu. Coba 100.000 koneksi dengan node.js. Tidak masalah sama sekali. Akan ajaib jika tidak lebih cepat karena ghc menggunakan epoll di bawah tenda sehingga mereka tidak bisa lebih cepat daripada menggunakan epoll secara langsung. Pemrograman dengan antarmuka utas cukup bagus.
Kr0e
3
Selain itu: Manajer IO baru (ghc) menggunakan algoritma penjadwalan yang memiliki kompleksitas (m log n) (di mana m adalah jumlah utas yang dapat dijalankan dan n total jumlah utas). Epoll memiliki kompleksitas k (k adalah jumlah read / writeable fd's =. Jadi ghc memiliki O (k * m log n) atas semua kompleksitas yang tidak terlalu baik jika Anda menghadapi koneksi lalu lintas yang tinggi. Node.js hanya memiliki kompleksitas linear yang disebabkan oleh epoll. Dan mari kita jangan bicara tentang kinerja windows ... Node.js jauh lebih cepat karena menggunakan IOCP
Kr0e
20

Pertama, saya tidak berpendapat bahwa node.js melakukan hal yang benar untuk mengekspos semua panggilan balik itu. Anda akhirnya menulis program Anda dalam CPS (kelanjutan lewat gaya) dan saya pikir itu harus menjadi tugas kompiler untuk melakukan transformasi itu.

Acara: Tanpa manipulasi utas, programmer hanya menyediakan panggilan balik (seperti pada kerangka kerja Snap)

Jadi dengan mengingat hal ini, Anda dapat menulis menggunakan gaya asinkron jika Anda menginginkannya, tetapi dengan melakukannya Anda tidak akan menulis dengan gaya sinkron yang efisien, dengan satu utas per permintaan. Haskell sangat efisien dalam kode sinkron, terutama jika dibandingkan dengan bahasa lain. Itu semua kejadian di bawahnya.

Panggilan balik dijamin dijalankan dalam utas tunggal: tidak ada kondisi balapan yang mungkin.

Anda masih bisa memiliki kondisi balapan di node.js, tetapi ini lebih sulit.

Setiap permintaan ada di utasnya sendiri. Ketika Anda menulis kode yang harus berkomunikasi dengan utas lain, sangat mudah untuk membuatnya utas karena terima kasih kepada concurrency concurrency haskell.

API ramah-UNIX yang bagus dan sederhana. Bonus: Dukungan HTTP luar biasa. DNS juga tersedia.

Lihatlah hackage dan lihat sendiri.

Setiap I / O secara default tidak sinkron (kadang-kadang ini bisa mengganggu). Ini membuatnya lebih mudah untuk menghindari kunci. Namun, terlalu banyak pemrosesan CPU dalam panggilan balik akan berdampak pada koneksi lain (dalam hal ini, tugas harus dipecah menjadi sub-tugas yang lebih kecil dan dijadwalkan ulang).

Anda tidak memiliki masalah seperti itu, ghc akan mendistribusikan pekerjaan Anda di antara utas OS nyata.

Bahasa yang sama untuk sisi klien dan sisi server. (Saya tidak melihat terlalu banyak nilai dalam yang satu ini, namun. JQuery dan Node.js berbagi model pemrograman acara tetapi sisanya sangat berbeda. Saya hanya tidak bisa melihat bagaimana berbagi kode antara sisi server dan sisi klien bisa berguna dalam latihan.)

Haskell tidak mungkin menang di sini ... kan? Pikirkan lagi, http://www.haskell.org/haskellwiki/Haskell_in_web_browser .

Semua ini dikemas dalam satu produk.

Unduh ghc, jalankan komplotan rahasia. Ada paket untuk setiap kebutuhan.

dan_waterworth
sumber
Saya hanya berperan sebagai penasihat iblis. Jadi, ya saya setuju dengan poin Anda. Kecuali penyatuan bahasa sisi klien dan sisi server. Meskipun saya pikir ini layak secara teknis, saya tidak berpikir itu pada akhirnya dapat menggantikan semua ekosistem Javascript di tempat hari ini (JQuery dan teman-teman). Meskipun ini adalah argumen yang dikemukakan oleh pendukung Node.js, saya tidak berpikir itu adalah argumen yang sangat penting. Apakah Anda benar-benar perlu membagikan kode sebanyak itu antara lapisan presentasi dan backend Anda? Apakah kita benar-benar bertujuan membuat pemrogram tahu hanya satu bahasa?
gawi
Kemenangan sebenarnya adalah Anda dapat merender halaman di server dan sisi klien sehingga membuat halaman waktu nyata lebih mudah dibuat.
dan_waterworth
@dan_waterworth tepatnya, lihat meteor atau derby.js
mb21
1
@gawi Kami memiliki layanan produksi di mana 85% kode dibagi antara klien dan server. Ini dikenal sebagai JavaScript universal di komunitas. Kami menggunakan Bereaksi untuk merender konten secara dinamis di server untuk mengurangi waktu render pertama yang bermanfaat di klien. Meskipun saya sadar bahwa Anda dapat menjalankan Haskell di browser, saya tidak mengetahui praktik terbaik "Haskell universal" yang memungkinkan rendering sisi-server dan sisi klien menggunakan basis kode yang sama.
Eric Elliott
8

Saya pribadi melihat Node.js dan pemrograman dengan callback sebagai level rendah yang tidak perlu dan sedikit hal yang tidak wajar. Mengapa memprogram dengan callback ketika runtime yang bagus seperti yang ditemukan di GHC dapat menangani callback untuk Anda dan melakukannya dengan cukup efisien?

Sementara itu, runtime GHC telah meningkat pesat: sekarang fitur "manajer IO baru baru" yang disebut MIO di mana "M" adalah singkatan dari multicore yang saya percaya. Itu dibangun di atas dasar manajer IO yang ada dan tujuan utamanya adalah untuk mengatasi penyebab penurunan kinerja 4+ core. Angka kinerja yang disediakan dalam makalah ini cukup mengesankan. Lihat dirimu sendiri:

Dengan Mio, server HTTP realistis dalam skala Haskell hingga 20 core CPU, mencapai kinerja puncak hingga faktor 6,5x dibandingkan dengan server yang sama menggunakan versi GHC sebelumnya. Latensi server Haskell juga ditingkatkan: [...] di bawah beban sedang, mengurangi waktu respons yang diharapkan sebesar 5,7x bila dibandingkan dengan versi GHC sebelumnya

Dan:

Kami juga menunjukkan bahwa dengan Mio, McNettle (pengontrol SDN yang ditulis dalam Haskell) dapat menskalakan secara efektif hingga 40+ core, mencapai jumlah lebih dari 20 juta permintaan baru per detik pada satu mesin, dan karenanya menjadi yang tercepat dari semua pengontrol SDN yang ada .

Mio telah membuatnya menjadi rilis GHC 7.8.1. Saya pribadi melihat ini sebagai langkah maju dalam kinerja Haskell. Akan sangat menarik untuk membandingkan kinerja aplikasi web yang ada yang disusun oleh versi GHC sebelumnya dan 7.8.1.

vlprans
sumber
6

Peristiwa IMHO baik, tetapi pemrograman dengan cara callback tidak.

Sebagian besar masalah yang membuat pengkodean dan debugging aplikasi web khusus berasal dari apa yang membuatnya terukur dan fleksibel. Yang paling penting, sifat stateless HTTP. Ini meningkatkan kemampuan navigasi, tetapi ini memaksakan inversi kontrol di mana elemen IO (server web dalam hal ini) memanggil penangan yang berbeda dalam kode aplikasi. Model peristiwa ini - atau model panggilan balik, lebih tepatnya dikatakan - adalah mimpi buruk, karena panggilan balik tidak berbagi ruang lingkup variabel, dan tampilan navigasi yang intuitif hilang. Sangat sulit untuk mencegah semua kemungkinan perubahan status saat pengguna menavigasi bolak-balik, di antara masalah lainnya.

Dapat dikatakan bahwa masalahnya mirip dengan pemrograman GUI di mana model acara berfungsi dengan baik, tetapi GUI tidak memiliki navigasi dan tidak ada tombol kembali. Yang melipatgandakan transisi keadaan mungkin dalam aplikasi web. Hasil dari upaya untuk memecahkan masalah ini adalah kerangka kerja berat dengan konfigurasi rumit banyak pengidentifikasi sihir meresap tanpa mempertanyakan akar masalah: model callback dan kurangnya berbagi dari lingkup variabel, dan tidak ada urutan, sehingga urutan harus dibangun dengan menghubungkan pengidentifikasi.

Ada kerangka kerja berbasis sekuensial seperti ocsigen (ocaml) seaside (smalltalk) WASH (dihentikan, Haskell) dan mflow (Haskell) yang memecahkan masalah manajemen negara dengan tetap menjaga kemampuan navigasi dan keutuhan REST. dalam kerangka kerja ini, programmer dapat mengekspresikan navigasi sebagai urutan imperatif di mana program mengirim halaman dan menunggu tanggapan dalam satu utas, variabel dalam ruang lingkup dan tombol kembali bekerja secara otomatis. Ini secara inheren menghasilkan kode yang lebih pendek, lebih aman, lebih mudah dibaca di mana navigasi terlihat jelas oleh programmer. (peringatan wajar: Saya pengembang mflow)

agocorona
sumber
Dalam node.js, panggilan balik digunakan untuk menangani async I / O, misalnya ke basis data. Anda berbicara tentang sesuatu yang berbeda yang, meskipun menarik, tidak menjawab pertanyaan.
Robin Green
Kamu benar. Butuh tiga tahun untuk memiliki jawaban yang, saya harap, memenuhi keberatan Anda: github.com/transient-haskell
agocorona
Node sekarang mendukung fungsi async, yang berarti Anda dapat menulis kode gaya imperatif yang sebenarnya tidak sinkron. Itu menggunakan janji di bawah tenda.
Eric Elliott
5

Pertanyaannya cukup konyol karena 1) Haskell telah memecahkan masalah ini dengan cara yang jauh lebih baik dan 2) dengan cara yang kira-kira sama dengan Erlang. Inilah patokan terhadap simpul: http://www.yesodweb.com/blog/2011/03/preliminary-warp-cross-language-benchmarks

Berikan Haskell 4 core dan dapat melakukan permintaan 100r (sederhana) per detik dalam satu aplikasi. Node tidak dapat melakukan banyak hal, dan tidak dapat mengatur skala aplikasi tunggal lintas inti. Dan Anda tidak perlu melakukan apa pun untuk menuai ini karena runtime Haskell non-blocking. Satu-satunya bahasa (relatif umum) lainnya yang memiliki IO non-pemblokiran yang dibangun ke dalam runtime adalah Erlang.

Greg Weber
sumber
14
Konyol? Pertanyaannya bukan "apakah Haskell memiliki respons" melainkan "apa respons Haskell". Pada saat pertanyaan diajukan, GHC 7 bahkan belum dirilis sehingga Haskell belum "dalam permainan" (kecuali mungkin untuk kerangka kerja menggunakan libev seperti Snap). Selain itu, saya setuju.
gawi
1
Saya tidak tahu apakah ini benar ketika Anda memposting jawaban ini, tetapi sekarang ada, pada kenyataannya, modul simpul yang memungkinkan aplikasi simpul untuk dengan mudah skala lintas core. Juga, tautan itu membandingkan node.js yang berjalan pada satu inti dengan haskell yang berjalan pada 4 inti. Saya ingin melihatnya berjalan lagi dalam konfigurasi yang lebih adil, tetapi sayangnya, repo github hilang.
Tim Gautier
2
Haskell menggunakan lebih dari 4 core menurunkan kinerja aplikasi. Ada makalah tentang masalah ini, ini aktif dikerjakan tetapi masih merupakan masalah. Jadi menjalankan 16 instance Node.js pada 16 server inti kemungkinan besar akan jauh lebih baik daripada aplikasi ghc tunggal menggunakan + RTS -N16 yang memang akan lebih lambat daripada + RTS -N1 karena bug runtime ini. Itu karena mereka menggunakan hanya satu IOManager yang akan melambat ketika digunakan dengan banyak utas OS. Saya berharap mereka akan memperbaiki bug ini tetapi sudah ada sejak saat itu jadi saya tidak akan memiliki banyak harapan ...
Kr0e
Siapa pun yang melihat jawaban ini harus menyadari bahwa Node dapat dengan mudah memproses 100rb permintaan sederhana pada satu inti dan itu mudah untuk skala aplikasi Node stateless di banyak core. pm2 -i max path/to/app.jsakan secara otomatis skala ke jumlah instance optimal berdasarkan core yang tersedia. Selain itu, Node juga non-pemblokiran secara default.
Eric Elliott
1

Sama seperti nodejs telah menjatuhkan libev , Kerangka Web Snap Haskell juga telah menjatuhkan libev .

Chawathe Vipul S
sumber
1
Bagaimana ini menjawab pertanyaan?
dfeuer
1
@ PDFeuer Tautan harus dibaca sebagai, Kerangka Web Snap Haskell telah menjatuhkan libev, saya tidak tahu mengapa pemformatan gagal. Server runtime node adalah semua tentang Linux libev ketika dimulai, & begitu juga Snap Web FrameWork. Haskell dengan Snap seperti ECMAscript dengan nodejs, jadi bagaimana Snap berkembang bersama nodejs lebih relevan daripada Haskell, yang lebih tepat dibandingkan dengan ECMAscript dalam konteks ini.
Chawathe Vipul S