Keuntungan dari sintaks bahasa kiri ke kanan

18

Saya telah menonton wawancara dengan Herb Sutter di Channel9 dan dia menyebutkan di akhir video bahwa sintaks bahasa kiri ke kanan akan menjadi yang teratas di whishlist untuk standar C ++ masa depan (meskipun dia mengakui bahwa memodifikasi C ++ dengan cara itu) akan cukup banyak membuat binatang yang sama sekali berbeda).

Selain:

  • lebih bisa dimengerti oleh manusia, lebih jelas dengan mata telanjang, mis

    //C syntax
    
    /*pointer to function taking a pointer to function(which takes 2 integers as 
    
    arguments and returns an int), and an int as arguments and returning an int*/
    
    int (*fp)(int (*ff)(int x, int y), int b)
    
    //Go analogous syntax which is left to write
    
    f func(func(int,int) int, int) int
    
  • lebih mudah diurai (mengarah ke dukungan alat yang lebih baik seperti yang disebutkan dalam video - misalnya kode refactoring)

apa keuntungan lain yang ada dengan sintaks "kiri ke kanan" dalam bahasa pemrograman. Saya hanya tahu Pascal dan Go yang menggunakan sintaksis semacam itu (dan Go bahkan tidak berjalan sepenuhnya seperti yang saya mengerti dari posting blog ini dari mana saya mengambil juga contoh-contohnya) Apakah mungkin untuk memiliki sistem bahasa pemrograman sistem dengan jenis itu dari sintaks?

celavek
sumber
1
haskell menggunakan kiri ke kanan:f :: (Int -> Int -> Int) -> Int -> Int
Karoly Horvath
1
ActionScript juga: function strlen(s:String):int {...}. Juga, ketik lambda calculus (karenanya, Haskell).
outis
2
Adakah yang bisa menjelaskan tolong suara penutup :)? Saya tidak dapat melihat alasan untuk menutupnya, tetapi mungkin saya mengajukan pertanyaan "salah".
1
Saya tidak memilih untuk menutup, tetapi, komentar oleh @Devjosh relevan, lebih tepat untuk Programmer, semoga seseorang akan memigrasikannya ....
Nim
3
@ Frank: dan jangan lupa, jika ada fungsi pointer, bahwa sintaksnya canggung karena tipe aktualnya terpecah ! Itu pukulan rendah ...
Matthieu M.

Jawaban:

12

Keuntungan dasarnya adalah penguraian lebih sederhana dan unik. Perhatikan bahwa setelah baris diuraikan, kompiler akan tahu apa tipe yang tepat, jadi dari situ, bagaimana tipe itu didefinisikan tidak relevan.

Setiap fungsi yang mengembalikan argumen tipe array, atau tipe pointer fungsi sulit dibaca saat ini:

// common way of obtaining the static size in elements of an array:
template <typename T, int N>
char (&array_size_impl( T (&)[N] ))[N];
// alternative parser:
template <typename T, int N>               // this would probably be changed also
array_simple_impl function( T [N] & ) char [N] &;

Dan akan ada sedikit kesempatan untuk kesalahpahaman (sebagai yang paling menjengkelkan ):

// Current C++
type x( another_type() );      // create an instance x of type passing a
                               // default constructed another_type temporary?
                               // or declare a function x that returns type and takes as argument
                               // a function that has no arguments and returns another_type
// How the compiler reads it:
x function( function() another_type ) type;

// What many people mean:
x type { another_type{} };

Menggunakan pendekatan yang mirip dengan inisialisasi seragam dalam C ++ 0x (yaitu {}untuk mengidentifikasi inisialisasi). Perhatikan bahwa dengan pendekatan kiri ke kanan jauh lebih jelas apa yang kita definisikan. Banyak orang (saya pasti) pernah digigit oleh kesalahan parsing ini di masa lalu (lebih dari sekali), dan itu tidak akan menjadi kasus dengan sintaks kiri-ke-kanan.

David Rodríguez - dribeas
sumber
Bagaimana dengan ekspresi? Bagaimana "sintaksis kiri ke kanan" ini memengaruhi prioritas dan urutan evaluasi operator?
1
@celavek: Jika Anda kembali ke wawancara, Anda akan perhatikan bahwa dia tidak ingin mengubah seluruh sintaks bahasa, hanya deklarasi dan definisi s. Tentu saja, itu mungkin meresap dalam ekspresi lain, saya tidak terlalu yakin bahwa baris terakhir dalam contoh di atas adalah dari kiri ke kanan yang tepat (pikirkan tentang bagaimana sementara dibuat, yang mungkin perlu diubah ... dalam C # yang diselesaikan dengan memberikan dua semantik kepada newoperator untuk structatau class, yang tidak berlaku untuk C ++ karena dalam C ++ tidak ada perbedaan pada tipe nilai / referensi.
David Rodríguez - dribeas
5

Bagaimana Kami Sampai di Sini

Sintaks C untuk menyatakan titik fungsi dimaksudkan untuk mencerminkan penggunaan. Pertimbangkan deklarasi fungsi reguler seperti ini dari <math.h>:

double round(double number);

Untuk memiliki variabel titik Anda dapat menetapkan bahwa dengan menggunakan jenis keamanan

fp = round;

Anda harus mendeklarasikan fpvariabel titik itu dengan cara ini:

double (*fp)(double number);

Jadi yang harus Anda lakukan adalah melihat bagaimana Anda akan menggunakan fungsi tersebut, dan ganti nama fungsi itu dengan referensi pointer, membuatnya roundmenjadi *fp. Namun, Anda memerlukan seperangkat paren tambahan, yang menurut beberapa orang membuatnya agak berantakan.

Bisa dibilang, ini dulu lebih mudah di C asli, yang bahkan tidak memiliki fungsi tanda tangan, tapi jangan kembali ke sana, ok?

Tempat yang menjadi sangat jahat adalah mencari tahu cara mendeklarasikan fungsi yang diambil sebagai argumen atau mengembalikan pointer ke fungsi, atau keduanya.

Jika Anda memiliki fungsi:

void myhandler(int signo);

Anda bisa meneruskannya ke fungsi sinyal (3) dengan cara ini:

signal(SIGHUP, myhandler);

atau jika Anda ingin mempertahankan pawang lama, maka

old_handler = signal(SIGHUP, new_handler);

yang cukup mudah. Apa yang cukup mudah - atau tidak cantik, tidak mudah - adalah mendapatkan deklarasi yang benar.

signal(int signo, ???)

Nah, Anda cukup kembali ke deklarasi fungsi dan bertukar nama untuk referensi titik:

signal(int sendsig, void (*hisfunc)(int gotsig));

Karena Anda tidak mendeklarasikan gotsig, Anda mungkin merasa lebih mudah dibaca jika Anda menghilangkan:

signal(int sendsig, void (*hisfunc)(int));

Atau mungkin tidak. :(

Kecuali itu tidak cukup baik, karena sinyal (3) juga mengembalikan handler lama, seperti pada:

old_handler = signal(SIGHUP, new_handler);

Jadi sekarang Anda harus mencari cara untuk mendeklarasikan semua itu.

void (*old_handler)(int gotsig);

sudah cukup untuk variabel yang akan Anda tetapkan. Perhatikan bahwa Anda tidak benar-benar mendeklarasikan di gotsigsini, hanya old_handler. Jadi ini sudah cukup:

void (*old_handler)(int);

Itu membawa kita ke definisi yang benar untuk sinyal (3):

void (*signal(int signo, void (*handler)(int)))(int);

Typedefs to the Rescue

Pada saat ini, saya pikir semua orang akan setuju bahwa itu berantakan. Terkadang lebih baik memberi nama abstraksi Anda; sering, sungguh. Dengan benar typedef, ini menjadi lebih mudah untuk dipahami:

typedef void (*sig_t) (int);

Sekarang variabel handler Anda menjadi

sig_t old_handler, new_handler;

dan deklarasi Anda untuk sinyal (3) menjadi adil

sig_t signal(int signo, sig_t handler);

yang tiba-tiba bisa dipahami. Menyingkirkan tanda * juga menghilangkan beberapa tanda kurung yang membingungkan (dan mereka mengatakan bahwa orangtua selalu membuat segala sesuatu lebih mudah untuk dipahami - hah!). Penggunaan Anda masih sama:

old_handler = signal(SIGHUP, new_handler);

tapi sekarang Anda memiliki kesempatan untuk memahami deklarasi untuk old_handler, new_handler, dan bahkan signalketika Anda pertama kali bertemu dengan mereka atau kebutuhan untuk menulis mereka.

Kesimpulan

Ternyata sangat sedikit programmer C yang mampu merancang deklarasi yang benar untuk hal-hal ini sendiri tanpa berkonsultasi dengan bahan referensi.

Saya tahu, karena kami pernah memiliki pertanyaan tentang pertanyaan wawancara ini untuk orang-orang yang melakukan pekerjaan kernel dan driver perangkat. :) Tentu, kami kehilangan banyak kandidat seperti itu karena mereka jatuh dan terbakar di papan tulis. Tetapi kami juga menghindari mempekerjakan orang yang mengklaim bahwa mereka memiliki pengalaman sebelumnya di bidang ini tetapi sebenarnya tidak dapat melakukan pekerjaan itu.

Karena kesulitan yang meluas ini, mungkin tidak hanya masuk akal tetapi juga masuk akal untuk membahas semua deklarasi yang tidak lagi mengharuskan Anda menjadi programmer geek triple-alpha dengan tiga sigma di atas rata-rata hanya untuk menggunakan ini. semacam itu dengan nyaman.

tchrist
sumber
1
Dibuat, sulit untuk diikuti ... +1 untuk upaya ini karena menggambarkan poin bahwa kadang-kadang sulit untuk mendapatkan yang benar dalam C.
celavek
4

Saya pikir Anda agak merindukan titik ketika Anda fokus pada bit dari kiri ke kanan.

Masalah C dan C ++ adalah tata bahasa menghebohkan yang mereka miliki, yang sulit dibaca (manusia) dan diurai (alat).

Memiliki tata bahasa yang lebih konsisten (atau teratur ) memudahkan keduanya. Dan penguraian yang lebih mudah berarti perkakas yang lebih mudah: sebagian besar alat saat ini tidak mendapatkan C ++ dengan benar, bahkan plugin Eclipse terbaru tidak berusaha mencari kembali roda ... dan gagal, dan mereka mungkin memiliki lebih banyak orang daripada proyek OS rata-rata.

Jadi Anda mungkin berhasil ketika fokus pada membaca dan mengurai ... dan itu masalah besar :)

Matthieu M.
sumber
Itu kejutan besar tentang Eclipse yang masih tidak dapat menguraikan hal-hal seperti deklarasi di atas. Kenapa mereka tidak menggunakan parser C asli seperti dari gcc?
tchrist
@tchrist: Saya telah mendeteksi dua masalah dengan Eclipse, dan keduanya sepertinya terkait dengan makro. Mungkin lebih merupakan masalah preprosesor daripada generasi AST.
Matthieu M.