Kapan pelingkupan dinamis bermanfaat?

9

Dengan pelingkupan dinamis, callee dapat mengakses variabel peneleponnya. Kode Pseudo C:

void foo()
{
    print(x);
}

void bar()
{
    int x = 42;
    foo();
}

Karena saya belum pernah memprogram dalam bahasa yang mendukung pelingkupan dinamis, saya ingin tahu seperti apa beberapa kasus penggunaan dunia nyata untuk pelingkupan dinamis nantinya.

fredoverflow
sumber
mungkin lebih mudah diimplementasikan ketika penerjemah diimplementasikan dalam beberapa bahasa lain?
Alf P. Steinbach
2
Tidak banyak gunanya sehingga sebagian besar bahasa yang pernah menggunakannya (misalnya, Lisp) tidak lagi. Hanya untuk satu contoh nyata, implementasi Lisp paling awal menggunakan pelingkupan dinamis, tetapi sekarang semua varian utama (misalnya, CL, Skema) menggunakan pelingkupan leksikal.
Jerry Coffin
1
@JerryCoffin: Pengecualian penting termasuk Perl dan Emacs Lisp — keduanya awalnya menggunakan pelingkupan dinamis, dan sekarang (Perl 5, Emacs 24) memiliki dukungan untuk pelingkupan dinamis dan leksikal. Senang bisa memilih.
Jon Purdy
@ JerryCoffin Tidak persis cocok dengan contoh kode tetapi javascript masih menggunakan pelingkupan dinamis secara luas jika saya mengerti pertanyaannya dengan benar. Masih mencoba memikirkan keuntungan umum yang disediakannya yang tidak hanya menggantikan bahasa yang pendek.
Adrian
@JerryCoffin Masih ada beberapa pelingkupan dinamis yang aktif digunakan dalam Common Lisp, sebagian besar di sekitar kontrol dinamis membaca dan mencetak.
Vatine

Jawaban:

15

Aplikasi yang sangat berguna untuk pelingkupan dinamis adalah untuk melewatkan parameter kontekstual tanpa harus menambahkan parameter baru secara eksplisit ke setiap fungsi dalam tumpukan panggilan

Misalnya, Clojure mendukung pelingkupan dinamis melalui penjilidan , yang dapat digunakan untuk menetapkan kembali sementara nilai *out*untuk pencetakan. Jika Anda mengikat kembali *out*maka setiap panggilan untuk mencetak dalam lingkup dinamis dari penjilidan akan mencetak ke aliran output baru Anda. Sangat berguna jika, misalnya, Anda ingin mengalihkan semua hasil cetak ke beberapa jenis log debugging.

Contoh: dalam kode di bawah ini, fungsi do-stuff akan mencetak ke output debug daripada standar keluar, tetapi perhatikan bahwa saya tidak perlu menambahkan parameter output untuk melakukan-hal untuk mengaktifkan ini ....

(defn do-stuff [] 
  (do-other-stuff)
  (print "stuff done!"))

(binding [*out* my-debug-output-writer]
  (do-stuff))

Perhatikan bahwa binding Clojure juga bersifat lokal, jadi Anda tidak memiliki masalah dengan penggunaan bersamaan dari kemampuan ini. Ini membuat binding jauh lebih aman daripada (ab) menggunakan variabel global untuk tujuan yang sama.

mikera
sumber
2

(Penafian: Saya tidak pernah memprogram dalam bahasa pelingkupan dinamis)

Pelingkupan jauh lebih mudah diimplementasikan dan berpotensi lebih cepat. Dengan pelingkupan dinamis, hanya satu tabel simbol yang diperlukan (variabel saat ini tersedia). Itu hanya membaca dari tabel simbol ini untuk semuanya.

Bayangkan dalam Python fungsi yang sama.

def bar():
    x = 42;
    foo(42)

def foo(x):
    print x

Ketika saya memanggil bilah, saya memasukkan x ke tabel simbol. Ketika saya menelepon foo, saya mengambil tabel simbol yang saat ini digunakan untuk bar dan mendorongnya ke tumpukan. Saya kemudian memanggil foo, yang telah x diteruskan ke sana (kemungkinan telah dimasukkan ke dalam tabel simbol baru pada pemanggilan fungsi). Setelah keluar dari fungsi, saya harus menghancurkan ruang lingkup baru dan mengembalikan yang lama.

Dengan pelingkupan dinamis, ini tidak diperlukan. Saya hanya perlu mengetahui instruksi yang saya perlukan untuk kembali ketika fungsi berakhir karena tidak ada yang harus dilakukan ke tabel simbol.

jsternberg
sumber
"Berpotensi lebih cepat" hanya dalam juru bahasa (naif); kompiler dapat melakukan jauh lebih baik dengan pelingkupan leksikal daripada pelingkupan dinamis. Juga, "Dengan pelingkupan dinamis, ini tidak diperlukan ...." salah: dengan pelingkupan dinamis, ketika ruang lingkup variabel berakhir (misalnya, fungsi kembali), Anda perlu memperbarui tabel simbol untuk mengembalikan nilai sebelumnya. Sebenarnya, saya pikir kode biasanya akan merujuk langsung ke "objek simbol" yang akan memiliki bidang yang bisa berubah-ubah untuk nilai saat ini variabel, jauh lebih cepat daripada melakukan pencarian tabel setiap kali. Tapi tetap saja, pekerjaan pembaruan tidak hanya hilang.
Ryan Culpepper
Ah, saya tidak tahu pelingkupan dinamis masih akan mengembalikan nilai dari sebelum fungsi dipanggil. Saya pikir mereka semua merujuk pada nilai yang sama.
jsternberg
2
ya, masih ada sarang. Kalau tidak, itu akan sama dengan membuat setiap variabel global dan hanya melakukan tugas sederhana untuk itu.
Ryan Culpepper
2

Penanganan pengecualian di sebagian besar bahasa menggunakan pelingkupan dinamis; ketika pengecualian terjadi kontrol akan ditransfer kembali ke penangan terdekat pada tumpukan aktivasi (dinamis).

Eyvind
sumber
Berikan komentar tentang alasan downvote. Terima kasih!
Eyvind
Ini hal yang menarik. Apakah Anda juga mengatakan bahwa returnpernyataan dalam kebanyakan bahasa menggunakan pelingkupan dinamis, karena mereka mengembalikan kontrol ke penelepon di tumpukan?
ruakh
1

Tidak 100% yakin jika ini adalah pasangan yang tepat, tapi saya pikir setidaknya sudah cukup dekat dalam arti umum untuk menunjukkan di mana itu mungkin berguna untuk melanggar atau mengubah aturan pelingkupan.

Bahasa Ruby datang dengan ERB templating class, yang misalnya dalam Rails digunakan untuk menghasilkan file html. Jika Anda menggunakannya terlihat seperti ini:

require 'erb'

x = 42
template = ERB.new <<-EOF
  The value of x is: <%= x %>
EOF
puts template.result(binding)

The bindingtangan akses ke variabel lokal ke ERB pemanggilan metode, sehingga dapat mengaksesnya dan menggunakannya untuk mengisi template. (Kode antara EOF adalah string, bagian antara <% =%> dievaluasi sebagai kode Ruby oleh ERB dan akan menyatakan ruang lingkupnya sendiri seperti fungsi)

Contoh Rails lebih baik menunjukkan hal ini. Di pengontrol artikel, Anda akan menemukan sesuatu seperti ini:

def index
  @articles = Article.all

  respond_to do |format|
    format.html
    format.xml  { render :xml => @posts }
  end
end

File index.html.erb kemudian dapat menggunakan variabel lokal @articlesseperti ini (dalam hal ini pembuatan objek ERB dan pengikatan ditangani oleh kerangka Rails, jadi Anda tidak melihatnya di sini):

<ul>
<% @articles.each do |article| %>
  <li><%= article.name</li>
<% end %>
</ul>

Jadi dengan menggunakan variabel yang mengikat, Ruby memungkinkan untuk menjalankan satu dan kode templat yang sama dalam konteks yang berbeda.

Kelas ERB hanya satu contoh penggunaan. Ruby memungkinkan secara umum untuk mendapatkan status eksekusi aktual dengan pengikatan variabel dan metode dengan menggunakan pengikatan Kernel #, yang sangat berguna dalam konteks apa pun di mana Anda ingin mengevaluasi suatu metode dalam konteks yang berbeda atau ingin menyimpan konteks untuk penggunaan nanti.

thorsten müller
sumber
1

Kasus penggunaan untuk pelingkupan dinamis adalah IHMO sama dengan variabel global. Pelingkupan dinamis menghindari beberapa masalah dengan variabel global yang memungkinkan pembaruan variabel yang lebih terkontrol.

Beberapa menggunakan case yang dapat saya pikirkan:

  • Logging : Masuk akal untuk mengelompokkan log berdasarkan konteks runtime yang dari konteks leksikal. Anda secara dinamis dapat mengikat logger di penangan permintaan sehingga semua fungsi yang dipanggil dari sana berbagi "requestID" yang sama dalam entri log.
  • Pengalihan output
  • Resource Allocators : Untuk melacak sumber daya yang dialokasikan untuk tindakan / permintaan tertentu.
  • Penanganan pengecualian .
  • Perilaku kontekstual secara umum, misalnya Emacs mengubah pemetaan kunci dalam konteks tertentu .

Tentu saja pelingkupan dinamis tidak "mutlak diperlukan" tetapi meringankan beban karena harus meneruskan data gelandangan di sepanjang rantai panggilan atau harus menerapkan proksi global pintar dan manajer konteks.

Tetapi sekali lagi, pelingkupan dinamis berguna ketika berhadapan dengan jenis elemen yang sering kali diperlakukan sebagai global (logging, output, alokasi / manajemen sumber daya, dll).

ecerulm
sumber
-2

Ada cukup banyak tidak ada. Saya harap Anda, misalnya, tidak pernah menggunakan variabel yang sama dua kali.

void foo() {
    print_int(x);
}
void bar() {
    print_string(x);
}

Sekarang bagaimana Anda memanggil foo dan bar dari fungsi yang sama?

Ini secara efektif mirip dengan hanya menggunakan variabel global, dan itu buruk untuk semua alasan yang sama.

DeadMG
sumber
2
x = 42; foo(); x = '42'; bar();?
Luc Danton