Apa yang membuat macro Lisp begitu istimewa?

297

Membaca esai Paul Graham tentang bahasa pemrograman orang akan berpikir bahwa macro Lisp adalah satu-satunya cara untuk pergi. Sebagai pengembang yang sibuk, bekerja pada platform lain, saya belum memiliki hak istimewa untuk menggunakan macro Lisp. Sebagai seseorang yang ingin memahami buzz, tolong jelaskan apa yang membuat fitur ini begitu kuat.

Tolong juga hubungkan ini dengan sesuatu yang saya akan mengerti dari dunia pengembangan Python, Java, C # atau C.

minty
sumber
2
By the way, ada LISP-gaya prosesor makro untuk C # disebut Lemp: ecsharp.net/lemp ... JavaScript juga memiliki satu disebut Sweet.js: sweetjs.org
Qwertie
@ Qwertie Apakah sweetjs bahkan berfungsi hari ini?
fredrik.hjarner
Saya belum menggunakannya tetapi komit terbaru adalah enam bulan yang lalu ... cukup baik untuk saya!
Qwertie

Jawaban:

308

Untuk memberikan jawaban singkat, makro digunakan untuk mendefinisikan ekstensi sintaksis bahasa ke Common Lisp atau Domain Specific Languages ​​(DSLs). Bahasa-bahasa ini tertanam langsung ke dalam kode Lisp yang ada. Sekarang, DSL dapat memiliki sintaksis yang mirip dengan Lisp (seperti Prolog Interpreter Peter Norvig untuk Common Lisp) atau yang sama sekali berbeda (mis. Infix Notation Math for Clojure).

Berikut adalah contoh yang lebih konkret:
Python memiliki daftar pemahaman yang dibangun ke dalam bahasa. Ini memberikan sintaksis sederhana untuk kasus umum. Garis

divisibleByTwo = [x for x in range(10) if x % 2 == 0]

menghasilkan daftar yang berisi semua angka genap antara 0 dan 9. Kembali di Python 1,5 hari tidak ada sintaksis seperti itu; Anda akan menggunakan sesuatu yang lebih seperti ini:

divisibleByTwo = []
for x in range( 10 ):
   if x % 2 == 0:
      divisibleByTwo.append( x )

Keduanya setara secara fungsional. Mari kita memohon penangguhan ketidakpercayaan kita dan berpura-pura Lisp memiliki makro loop yang sangat terbatas yang hanya melakukan iterasi dan tidak ada cara mudah untuk melakukan yang setara dengan pemahaman daftar.

Di Lisp Anda bisa menulis yang berikut ini. Saya harus mencatat contoh yang dibuat ini dipilih untuk menjadi identik dengan kode Python bukan contoh yang baik dari kode Lisp.

;; the following two functions just make equivalent of Python's range function
;; you can safely ignore them unless you are running this code
(defun range-helper (x)
  (if (= x 0)
      (list x)
      (cons x (range-helper (- x 1)))))

(defun range (x)
  (reverse (range-helper (- x 1))))

;; equivalent to the python example:
;; define a variable
(defvar divisibleByTwo nil)

;; loop from 0 upto and including 9
(loop for x in (range 10)
   ;; test for divisibility by two
   if (= (mod x 2) 0) 
   ;; append to the list
   do (setq divisibleByTwo (append divisibleByTwo (list x))))

Sebelum saya melangkah lebih jauh, saya harus menjelaskan apa itu makro. Ini adalah transformasi yang dilakukan pada kode dengan kode. Yaitu, sepotong kode, dibaca oleh interpreter (atau kompiler), yang mengambil kode sebagai argumen, memanipulasi dan mengembalikan hasilnya, yang kemudian dijalankan di tempat.

Tentu saja itu banyak mengetik dan programmer malas. Jadi kita bisa mendefinisikan DSL untuk melakukan pemahaman daftar. Bahkan, kami sudah menggunakan satu makro (loop makro).

Lisp mendefinisikan beberapa bentuk sintaks khusus. Kutipan ( ') menunjukkan token berikutnya adalah literal. Kuasiquote atau backtick ( `) menunjukkan token berikutnya adalah literal dengan lolos. Lolos ditunjukkan oleh operator koma. Secara literal '(1 2 3)adalah setara dengan Python [1, 2, 3]. Anda dapat menetapkannya ke variabel lain atau menggunakannya di tempat. Anda dapat menganggapnya `(1 2 ,x)sebagai yang ekivalen dengan Python di [1, 2, x]mana xvariabel yang sebelumnya didefinisikan. Notasi daftar ini adalah bagian dari keajaiban yang masuk ke makro. Bagian kedua adalah pembaca Lisp yang secara cerdas mengganti makro untuk kode tetapi yang paling baik digambarkan di bawah ini:

Jadi kita bisa mendefinisikan makro yang disebut lcomp(kependekan dari daftar pemahaman). Sintaksnya akan persis seperti python yang kita gunakan dalam contoh [x for x in range(10) if x % 2 == 0] -(lcomp x for x in (range 10) if (= (% x 2) 0))

(defmacro lcomp (expression for var in list conditional conditional-test)
  ;; create a unique variable name for the result
  (let ((result (gensym)))
    ;; the arguments are really code so we can substitute them 
    ;; store nil in the unique variable name generated above
    `(let ((,result nil))
       ;; var is a variable name
       ;; list is the list literal we are suppose to iterate over
       (loop for ,var in ,list
            ;; conditional is if or unless
            ;; conditional-test is (= (mod x 2) 0) in our examples
            ,conditional ,conditional-test
            ;; and this is the action from the earlier lisp example
            ;; result = result + [x] in python
            do (setq ,result (append ,result (list ,expression))))
           ;; return the result 
       ,result)))

Sekarang kita bisa mengeksekusi di baris perintah:

CL-USER> (lcomp x for x in (range 10) if (= (mod x 2) 0))
(0 2 4 6 8)

Cukup rapi, ya? Sekarang tidak berhenti di situ. Anda memiliki mekanisme, atau kuas, jika Anda mau. Anda dapat memiliki sintaks yang mungkin Anda inginkan. Seperti withsintaksis Python atau C # . Atau sintaks. LINQ .NET Pada akhirnya, inilah yang menarik orang ke Lisp - fleksibilitas tertinggi.

gte525u
sumber
51
+1 untuk menerapkan pemahaman daftar di Lisp, karena mengapa tidak?
ckb
9
@ckb Sebenarnya LISP sudah memiliki makro pemahaman daftar di perpustakaan standar: (loop for x from 0 below 10 when (evenp x) collect x), contoh lebih lanjut di sini . Tapi memang, loop adalah "hanya makro" (Saya benar - benar menerapkannya kembali dari awal beberapa waktu lalu )
Suzanne Dupéron
8
Saya tahu ini sangat tidak berhubungan, tapi saya bertanya-tanya tentang sintaks dan bagaimana parsing sebenarnya bekerja ... Katakanlah saya memanggil lcomp dengan cara itu (mengubah item haus dari "untuk" menjadi "azertyuiop"): (lcomp x azertyuiop x in ( kisaran 10) jika (= (% x 2) 0)) apakah makro masih berfungsi seperti yang diharapkan? Atau apakah parameter "for" digunakan dalam loop sehingga harus berupa string "for" saat dipanggil?
ayah
2
@dader Jadi, loop sebenarnya adalah makro lain dan for adalah simbol khusus yang digunakan dalam makro itu. Saya bisa dengan mudah mendefinisikan makro tanpa loop makro. Namun, jika saya punya, posting akan jauh lebih lama. Makro loop dan semua sintaksnya didefinisikan dalam CLtL .
gte525u
3
Satu hal yang saya bingung dengan makro bahasa lain, adalah bahwa makro mereka dibatasi oleh sintaks bahasa host. Bisakah Lispy makro menafsirkan sintaksis non-Lispy. Maksud saya suka membayangkan membuat haskell seperti sintaks (tidak ada paranthese) dan menafsirkannya menggunakan macro Lisp. Apakah ini mungkin, dan apa pro / kontra dari penggunaan makro dibandingkan dengan menggunakan lexer dan parser secara langsung?
CMCDragonkai
105

Anda akan menemukan perdebatan komprehensif seputar makro makro di sini .

Subset menarik dari artikel itu:

Dalam kebanyakan bahasa pemrograman, sintaksisnya kompleks. Macro harus membongkar sintaks program, menganalisisnya, dan menyusunnya kembali. Mereka tidak memiliki akses ke parser program, sehingga mereka harus bergantung pada heuristik dan tebakan terbaik. Terkadang analisis cut-rate mereka salah, dan kemudian mereka pecah.

Tetapi Lisp berbeda. Macro Lisp memang memiliki akses ke parser, dan itu adalah parser yang sangat sederhana. Makro Lisp tidak diberikan string, tetapi potongan kode sumber yang disiapkan dalam bentuk daftar, karena sumber program Lisp bukanlah string; ini adalah daftar. Dan program Lisp benar-benar pandai memisahkan daftar dan menyatukannya kembali. Mereka melakukan ini dengan andal, setiap hari.

Ini adalah contoh yang diperluas. Lisp memiliki makro, yang disebut "setf", yang melakukan tugas. Bentuk paling sederhana dari setf adalah

  (setf x whatever)

yang menetapkan nilai simbol "x" ke nilai ekspresi "apa pun".

Lisp juga memiliki daftar; Anda dapat menggunakan fungsi "mobil" dan "cdr" untuk mendapatkan masing-masing elemen pertama dari daftar atau sisa daftar.

Sekarang bagaimana jika Anda ingin mengganti elemen pertama dari daftar dengan nilai baru? Ada fungsi standar untuk melakukan itu, dan hebatnya, namanya bahkan lebih buruk daripada "mobil". Itu adalah "rplaca". Tetapi Anda tidak harus mengingat "rplaca", karena Anda dapat menulis

  (setf (car somelist) whatever)

untuk mengatur mobil somelist.

Apa yang sebenarnya terjadi di sini adalah "setf" adalah makro. Pada waktu kompilasi, ia memeriksa argumennya, dan ia melihat bahwa yang pertama memiliki bentuk (mobil SESUATU). Ia berkata pada dirinya sendiri, "Oh, programmer mencoba mengatur mobil sesuatu. Fungsi yang digunakan untuk itu adalah 'rplaca'." Dan diam-diam menulis ulang kode di tempat untuk:

  (rplaca somelist whatever)
VONC
sumber
5
setf adalah ilustrasi yang bagus tentang kekuatan makro, terima kasih sudah memasukkannya.
Joel
Saya suka highlight .. karena sumber program Lisp bukan string; ini adalah daftar. ! Apakah itu alasan utama mengapa makro LISP lebih unggul daripada kebanyakan yang lain karena tanda kurung?
Siswa
@Student Saya kira begitu: books.google.fr/… menyarankan Anda benar.
VonC
55

Makro Lisp umum pada dasarnya memperluas "primitif sintaksis" dari kode Anda.

Misalnya, dalam C, konstruksi sakelar / kasus hanya berfungsi dengan tipe integral dan jika Anda ingin menggunakannya untuk pelampung atau string, Anda dibiarkan dengan pernyataan bersarang jika dan perbandingan eksplisit. Anda juga tidak bisa menulis makro C untuk melakukan pekerjaan untuk Anda.

Tapi, karena makro lisp (pada dasarnya) adalah program lisp yang mengambil potongan kode sebagai input dan mengembalikan kode untuk menggantikan "doa" makro, Anda dapat memperpanjang repertoar "primitif" sejauh yang Anda inginkan, biasanya berakhir dengan program yang lebih mudah dibaca.

Untuk melakukan hal yang sama dalam C, Anda harus menulis pra-prosesor kustom yang memakan sumber awal Anda (tidak-cukup-C) dan mengeluarkan sesuatu yang dapat dipahami oleh kompiler C. Ini bukan cara yang salah untuk melakukannya, tetapi itu belum tentu yang termudah.

Vatine
sumber
41

Lisp macro memungkinkan Anda untuk memutuskan kapan (jika ada) bagian atau ekspresi apa pun akan dievaluasi. Untuk memberikan contoh sederhana, pikirkan C:

expr1 && expr2 && expr3 ...

Apa yang dikatakannya adalah: Evaluasi expr1, dan, jika benar, evaluasi expr2, dll.

Sekarang coba buat ini &&menjadi fungsi ... itu benar, Anda tidak bisa. Memanggil sesuatu seperti:

and(expr1, expr2, expr3)

Akan mengevaluasi ketiganya exprssebelum memberikan jawaban terlepas dari apakah expr1itu salah!

Dengan macro lisp Anda dapat mengkodekan sesuatu seperti:

(defmacro && (expr1 &rest exprs)
    `(if ,expr1                     ;` Warning: I have not tested
         (&& ,@exprs)               ;   this and might be wrong!
         nil))

sekarang Anda memiliki &&, yang dapat Anda panggil seperti fungsi dan tidak akan mengevaluasi bentuk apa pun yang Anda berikan kepadanya kecuali semuanya benar.

Untuk melihat manfaatnya, kontras:

(&& (very-cheap-operation)
    (very-expensive-operation)
    (operation-with-serious-side-effects))

dan:

and(very_cheap_operation(),
    very_expensive_operation(),
    operation_with_serious_side_effects());

Hal lain yang dapat Anda lakukan dengan makro adalah membuat kata kunci baru dan / atau bahasa mini (lihat (loop ...)makro untuk contoh), mengintegrasikan bahasa lain ke dalam lisp, misalnya, Anda bisa menulis makro yang memungkinkan Anda mengatakan sesuatu seperti:

(setvar *rows* (sql select count(*)
                      from some-table
                     where column1 = "Yes"
                       and column2 like "some%string%")

Dan itu bahkan tidak masuk ke makro Reader .

Semoga ini membantu.

dsm
sumber
Saya pikir seharusnya: "(melamar &&, @ exprs); ini dan mungkin salah!"
Svante
1
@svante - dengan dua hal: pertama, && adalah makro, bukan fungsi; berlaku hanya berfungsi pada fungsi. kedua, terapkan ambil daftar argumen untuk dilewati, jadi Anda ingin salah satu dari "(funcall fn, @ exprs)", "(berlaku fn (daftar, @ exprs)" atau "(berlaku fn, @ exprs nil)", tidak "(melamar fn, @ exprs)".
Aaron
(and ...akan mengevaluasi ekspresi sampai seseorang mengevaluasi ke false, perhatikan bahwa efek samping yang dihasilkan oleh evaluasi palsu akan terjadi, hanya ekspresi berikutnya yang akan dilewati.
ocodo
31

Saya tidak berpikir saya pernah melihat Lisp macro dijelaskan lebih baik daripada oleh orang ini: http://www.defmacro.org/ramblings/lisp.html

Rayne
sumber
3
Terutama jika Anda memiliki latar belakang Java / XML.
matahari terbenam
1
Alangkah senangnya membaca kebohongan ini di sofa saya pada Sabtu sore! Sangat jelas ditulis dan diatur.
Colin
10

Pikirkan apa yang dapat Anda lakukan di C atau C ++ dengan makro dan template. Mereka adalah alat yang sangat berguna untuk mengelola kode berulang, tetapi mereka terbatas dalam cara yang cukup parah.

  • Sintaks makro / template terbatas membatasi penggunaannya. Misalnya, Anda tidak bisa menulis templat yang diperluas ke sesuatu selain kelas atau fungsi. Makro dan templat tidak dapat dengan mudah memelihara data internal.
  • Sintaksis kompleks dan sangat tidak teratur dari C dan C ++ membuatnya sulit untuk menulis makro yang sangat umum.

Lisp dan Lisp macro menyelesaikan masalah ini.

  • Makro Lisp ditulis dalam Lisp. Anda memiliki kekuatan penuh Lisp untuk menulis makro.
  • Lisp memiliki sintaks yang sangat teratur.

Bicaralah dengan siapa pun yang menguasai C ++ dan tanyakan kepada mereka berapa lama mereka menghabiskan mempelajari semua templat yang mereka butuhkan untuk melakukan metaprogramming templat. Atau semua trik gila dalam buku-buku (luar biasa) seperti Modern C ++ Design , yang masih sulit untuk di-debug dan (dalam praktiknya) non-portable antara kompiler dunia nyata meskipun bahasa tersebut telah distandarisasi selama satu dekade. Semua itu mencair jika bahasa yang Anda gunakan untuk metaprogramming adalah bahasa yang sama yang Anda gunakan untuk pemrograman!

Matt Curtis
sumber
11
Yah, agar adil, masalah dengan metaprogramming template C ++ bukanlah bahasa metaprogramming yang berbeda , tapi itu mengerikan - itu tidak begitu banyak dirancang seperti yang ditemukan dalam apa yang dimaksudkan sebagai fungsi template yang jauh lebih sederhana.
Brooks Moses
@ Brooks Sure. Fitur yang muncul tidak selalu buruk. Sayangnya, dalam bahasa yang didorong oleh komite yang bergerak lambat, sulit untuk memperbaikinya saat itu. Ini memalukan begitu banyak fitur baru C ++ yang modern dan berguna ditulis dalam beberapa bahasa yang bahkan bisa dibaca, dan ada celah besar antara programmer rata-rata dan "imam besar".
Matt Curtis
2
@ downvoter: jika ada yang salah dengan jawaban saya, silakan tinggalkan komentar sehingga kita semua bisa berbagi pengetahuan.
Matt Curtis
10

Makro lisp mengambil fragmen program sebagai input. Fragmen program ini mewakili struktur data yang dapat dimanipulasi dan diubah dengan cara apa pun yang Anda suka. Pada akhirnya makro menghasilkan fragmen program lain, dan fragmen ini adalah apa yang dieksekusi saat runtime.

C # tidak memiliki fasilitas makro, akan tetapi setara jika kompilator mengurai kode menjadi pohon CodeDOM, dan meneruskannya ke metode, yang mengubahnya menjadi CodeDOM lain, yang kemudian dikompilasi menjadi IL.

Ini dapat digunakan untuk mengimplementasikan sintaksis "gula" seperti for each-kurang- usingklausa, linq -ekspresi selectdan sebagainya, sebagai makro yang mentransformasikannya menjadi kode yang mendasarinya.

Jika Java memiliki makro, Anda bisa mengimplementasikan sintaks Linq di Java, tanpa perlu Sun untuk mengubah bahasa dasar.

Berikut ini adalah pseudo-code untuk tampilan makro gaya lisp di C # untuk implementasi using:

define macro "using":
    using ($type $varname = $expression) $block
into:
    $type $varname;
    try {
       $varname = $expression;
       $block;
    } finally {
       $varname.Dispose();
    }
JacquesB
sumber
Sekarang ada sebenarnya adalah sebuah Lisp gaya prosesor makro untuk C #, saya akan menunjukkan bahwa makro untuk usingakan terlihat seperti ini ;)
Qwertie
9

Karena jawaban yang ada memberikan contoh konkret yang baik menjelaskan apa yang dicapai makro dan bagaimana, mungkin akan membantu untuk mengumpulkan bersama beberapa pemikiran tentang mengapa fasilitas makro adalah keuntungan yang signifikan dalam kaitannya dengan bahasa lain ; pertama dari jawaban ini, lalu yang besar dari tempat lain:

... di C, Anda harus menulis pra-prosesor kustom [yang mungkin akan memenuhi syarat sebagai program C yang cukup rumit ] ...

- Vatine

Bicaralah dengan siapa pun yang menguasai C ++ dan tanyakan kepada mereka berapa lama mereka menghabiskan mempelajari semua templat yang mereka butuhkan untuk melakukan metaprogramming templat [yang masih belum sekuat].

- Matt Curtis

... di Java Anda harus meretas jalan Anda dengan tenun bytecode, meskipun beberapa kerangka kerja seperti AspectJ memungkinkan Anda melakukan ini menggunakan pendekatan yang berbeda, ini pada dasarnya adalah peretasan.

- Miguel Ping

DOLIST mirip dengan foreach Perl atau for Python. Java menambahkan sejenis loop konstruksi dengan "ditingkatkan" untuk loop di Java 1.5, sebagai bagian dari JSR-201. Perhatikan perbedaan makro. Seorang programmer Lisp yang memperhatikan pola umum dalam kode mereka dapat menulis makro untuk memberikan diri mereka abstraksi tingkat sumber dari pola itu. Seorang programmer Java yang memperhatikan pola yang sama harus meyakinkan Sun bahwa abstraksi khusus ini layak ditambahkan ke bahasa. Kemudian Sun harus menerbitkan JSR dan membentuk "kelompok ahli" di seluruh industri untuk menyelesaikan semuanya. Proses itu - menurut Sun - memakan waktu rata-rata 18 bulan. Setelah itu, semua penulis kompiler harus memutakhirkan kompiler mereka untuk mendukung fitur baru. Dan bahkan setelah kompiler favorit programmer Java mendukung versi Java yang baru, mereka mungkin 'masih' tidak dapat menggunakan fitur baru sampai mereka diizinkan untuk memutus kompatibilitas sumber dengan versi Java yang lebih lama. Jadi jengkel bahwa programmer Common Lisp dapat menyelesaikan sendiri dalam waktu lima menit mengganggu programmer Java selama bertahun-tahun.

- Peter Seibel, dalam "Praktis Masalah Umum"

ZakW
sumber
8

Saya tidak yakin dapat menambahkan beberapa wawasan ke posting semua orang (luar biasa), tetapi ...

Makro Lisp berfungsi dengan baik karena sifat sintaks Lisp.

Lisp adalah bahasa yang sangat teratur (anggap semuanya adalah daftar ); makro memungkinkan Anda untuk memperlakukan data dan kode sebagai sama (tidak diperlukan penguraian string atau peretasan lainnya untuk memodifikasi ekspresi lisp). Anda menggabungkan kedua fitur ini dan Anda memiliki cara yang sangat bersih untuk memodifikasi kode.

Sunting: Apa yang saya coba katakan adalah bahwa Lisp adalah homoikonik , yang berarti bahwa struktur data untuk program lisp ditulis dalam lisp itu sendiri.

Jadi, Anda berakhir dengan cara membuat generator kode Anda sendiri di atas bahasa menggunakan bahasa itu sendiri dengan semua kekuatannya (misalnya di Jawa Anda harus meretas jalan Anda dengan tenun bytecode, meskipun beberapa kerangka kerja seperti AspectJ memungkinkan Anda untuk lakukan ini dengan menggunakan pendekatan yang berbeda, ini pada dasarnya hack).

Dalam praktiknya, dengan makro Anda akhirnya membangun bahasa mini Anda sendiri di atas lisp, tanpa perlu belajar bahasa atau alat tambahan, dan dengan menggunakan kekuatan penuh dari bahasa itu sendiri.

Miguel Ping
sumber
1
Namun, ini adalah komentar yang mendalam, gagasan bahwa "semuanya adalah daftar" dapat menakuti pendatang baru. untuk memahami daftar, Anda perlu memahami kontra, mobil, cdrs, sel. Lebih tepatnya, Lisp terbuat dari S-expressions, bukan daftar.
ribamar
6

Macro Lisp mewakili pola yang terjadi di hampir semua proyek pemrograman yang cukup besar. Akhirnya dalam program besar Anda memiliki bagian kode tertentu di mana Anda menyadari itu akan lebih sederhana dan lebih sedikit kesalahan cenderung bagi Anda untuk menulis sebuah program yang menampilkan kode sumber sebagai teks yang kemudian bisa Anda tempelkan.

Dalam objek Python memiliki dua metode __repr__dan __str__. __str__hanyalah representasi yang dapat dibaca manusia. __repr__mengembalikan representasi yang merupakan kode Python yang valid, artinya, sesuatu yang dapat dimasukkan ke dalam interpreter sebagai Python yang valid. Dengan cara ini Anda dapat membuat potongan kecil Python yang menghasilkan kode yang valid yang dapat ditempelkan ke sumber Anda yang sebenarnya.

Dalam Lisp seluruh proses ini telah diformalkan oleh sistem makro. Tentu itu memungkinkan Anda untuk membuat ekstensi ke sintaks dan melakukan segala macam hal mewah, tetapi kegunaan sebenarnya diringkas oleh di atas. Tentu saja itu membantu bahwa sistem makro Lisp memungkinkan Anda untuk memanipulasi "cuplikan" ini dengan kekuatan penuh dari seluruh bahasa.

dnolen
sumber
1
Paragraf pertama Anda sangat jelas bagi orang luar Lisp, yang penting.
Wildcard
5

Singkatnya, makro adalah transformasi kode. Mereka memungkinkan untuk memperkenalkan banyak konstruksi sintaksis baru. Misalnya, pertimbangkan LINQ dalam C #. Di lisp, ada ekstensi bahasa yang serupa yang diterapkan oleh makro (misalnya, built-in loop construct, iterate). Makro secara signifikan mengurangi duplikasi kode. Macro memungkinkan menanamkan «sedikit bahasa» (misalnya, di mana di c # / java satu akan menggunakan xml untuk mengkonfigurasi, dalam lisp hal yang sama dapat dicapai dengan makro). Macro mungkin menyembunyikan kesulitan menggunakan penggunaan perpustakaan.

Misal, di lisp Anda bisa menulis

(iter (for (id name) in-clsql-query "select id, name from users" on-database *users-database*)
      (format t "User with ID of ~A has name ~A.~%" id name))

dan ini menyembunyikan semua basis data (transaksi, penutupan koneksi yang tepat, mengambil data, dll.) sedangkan di C # ini membutuhkan pembuatan SqlConnections, SqlCommands, menambahkan SqlParameters ke SqlCommands, mengulang pada SqlDataReaders, menutupnya dengan benar.

dmitry_vk
sumber
3

Sementara yang di atas menjelaskan makro apa dan bahkan memiliki contoh keren, saya pikir perbedaan utama antara fungsi makro dan normal adalah bahwa LISP mengevaluasi semua parameter terlebih dahulu sebelum memanggil fungsi. Dengan makro sebaliknya, LISP meneruskan parameter yang tidak dievaluasi ke makro. Misalnya, jika Anda meneruskan (+ 1 2) ke suatu fungsi, fungsi tersebut akan menerima nilai 3. Jika Anda meneruskan ini ke makro, itu akan menerima Daftar (+ 1 2). Ini dapat digunakan untuk melakukan semua jenis hal yang sangat berguna.

  • Menambahkan struktur kontrol baru, misalnya lingkaran atau dekonstruksi daftar
  • Ukur waktu yang diperlukan untuk menjalankan fungsi yang dilewati. Dengan fungsi, parameter akan dievaluasi sebelum kontrol dilewatkan ke fungsi. Dengan makro, Anda dapat memisahkan kode Anda antara mulai dan berhenti dari stopwatch Anda. Di bawah ini memiliki kode yang sama persis di makro dan fungsi dan output sangat berbeda. Catatan: Ini adalah contoh yang dibuat-buat dan implementasinya dipilih sehingga identik untuk menyoroti perbedaan dengan lebih baik.

    (defmacro working-timer (b) 
      (let (
            (start (get-universal-time))
            (result (eval b))) ;; not splicing here to keep stuff simple
        ((- (get-universal-time) start))))
    
    (defun my-broken-timer (b)
      (let (
            (start (get-universal-time))
            (result (eval b)))    ;; doesn't even need eval
        ((- (get-universal-time) start))))
    
    (working-timer (sleep 10)) => 10
    
    (broken-timer (sleep 10)) => 0
Joerg Schmuecker
sumber
Btw, Scala telah menambahkan makro ke bahasa. Meskipun mereka tidak memiliki keindahan dari Lisp macro karena bahasanya tidak homoiconic, mereka layak untuk melihatnya dan pohon-pohon sintaksis abstrak yang mereka sediakan mungkin lebih mudah digunakan pada akhirnya. Masih terlalu dini bagi saya untuk mengatakan sistem makro mana yang saya sukai.
Joerg Schmuecker
2
"LISP meneruskan parameter yang tidak dievaluasi ke makro" akhirnya jawaban yang mengatakannya dengan jelas. tetapi Anda lupa bagian kedua dari itu: dan hasil makro adalah kode yang diubah yang akan dievaluasi secara keseluruhan oleh sistem di tempat aslinya seolah-olah ada di tempat pertama (kecuali jika itu sendiri lagi panggilan untuk makro, yang juga akan bisa berubah, oleh yang makro saat ini).
Will Ness
0

Saya mendapatkan ini dari buku resep The Common Lisp tapi saya pikir itu menjelaskan mengapa macro lisp baik dalam cara yang baik.

"Makro adalah bagian biasa dari kode Lisp yang beroperasi pada sepotong kode Lisp putatif lainnya, menerjemahkannya menjadi (versi lebih dekat dengan) Lisp yang dapat dieksekusi. Itu mungkin terdengar agak rumit, jadi mari kita berikan contoh sederhana. Misalkan Anda ingin sebuah versi setq yang menetapkan dua variabel ke nilai yang sama. Jadi jika Anda menulis

(setq2 x y (+ z 3))

ketika z=8kedua x dan y diatur ke 11. (Saya tidak bisa memikirkan kegunaan untuk ini, tapi itu hanya sebuah contoh.)

Seharusnya jelas bahwa kita tidak dapat mendefinisikan setq2 sebagai fungsi. Jika x=50dan y=-5, fungsi ini akan menerima nilai 50, -5, dan 11; itu tidak akan memiliki pengetahuan tentang variabel apa yang seharusnya ditetapkan. Apa yang benar-benar ingin kami katakan adalah, Ketika Anda (sistem Lisp) melihatnya (setq2 v1 v2 e), perlakukan itu setara dengan (progn (setq v1 e) (setq v2 e)). Sebenarnya, ini tidak benar, tetapi akan dilakukan untuk saat ini. Makro memungkinkan kita untuk melakukan ini dengan tepat, dengan menentukan program untuk mengubah pola input (setq2 v1 v2 e)"menjadi pola output (progn ...)."

Jika Anda pikir ini bagus, Anda dapat terus membaca di sini: http://cl-cookbook.sourceforge.net/macros.html

nilsi
sumber
1
Dimungkinkan untuk mendefinisikan setq2sebagai fungsi jika xdan ydilewatkan dengan referensi. Saya tidak tahu apakah itu mungkin di CL, namun. Jadi bagi seseorang yang tidak tahu Lisps atau CL khususnya ini bukan contoh yang sangat ilustratif IMO
neoascetic
Kelulusan argumen @neoascetic CL hanya berdasarkan nilai (itulah sebabnya ia membutuhkan makro sejak awal). beberapa nilai adalah pointer (seperti daftar).
Will Ness
-5

Dalam python Anda memiliki dekorator, Anda pada dasarnya memiliki fungsi yang mengambil fungsi lain sebagai input. Anda dapat melakukan apa yang Anda inginkan: panggil fungsi, lakukan sesuatu yang lain, bungkus panggilan fungsi dalam rilis perolehan sumber daya, dll. Tetapi Anda tidak bisa mengintip ke dalam fungsi itu. Katakanlah kami ingin membuatnya lebih kuat, misalkan dekorator Anda menerima kode fungsi sebagai daftar maka Anda tidak hanya dapat menjalankan fungsinya, tetapi kini Anda dapat menjalankan bagiannya, menyusun ulang garis fungsi, dll.

stonemetal
sumber