Tại sao các ngôn ngữ lập trình, đặc biệt là C, sử dụng dấu ngoặc nhọn và không phải là hình vuông?


96

Định nghĩa của "ngôn ngữ kiểu C" trên thực tế có thể được đơn giản hóa thành "sử dụng dấu ngoặc nhọn ( {})." Tại sao chúng ta sử dụng ký tự cụ thể đó (và tại sao không phải là thứ gì đó hợp lý hơn, như [], không yêu cầu phím shift ít nhất trên bàn phím Hoa Kỳ)?

Có bất kỳ lợi ích thực tế nào đối với năng suất lập trình viên đến từ các niềng răng này, hoặc các nhà thiết kế ngôn ngữ mới nên tìm kiếm các lựa chọn thay thế (tức là những kẻ đứng sau Python)?

Wikipedia cho chúng ta biết rằng C sử dụng niềng răng nói, nhưng không phải tại sao. Một tuyên bố trong bài viết Wikipedia về Danh sách các ngôn ngữ lập trình dựa trên C cho thấy phần tử cú pháp này có phần đặc biệt:

Nói rộng ra, ngôn ngữ gia đình C là những ngôn ngữ sử dụng cú pháp khối giống như C (bao gồm cả dấu ngoặc nhọn để bắt đầu và kết thúc khối) ...


35
Người duy nhất có thể trả lời điều này là Dennis Ritchie và anh ta đã chết. Một dự đoán hợp lý là [] đã được thực hiện cho các mảng.
Dirk Holsopple

2
@DirkHolsopple Vì vậy, anh không để lại lý do? Drat. Ngoài ra: hai downvote về một cái gì đó tôi thực sự tò mò về? Cảm ơn các bạn ....
Một số Mèo con

1
Vui lòng tiếp tục thảo luận về câu hỏi này trong câu hỏi Meta này .
Thomas Owens

2
Tôi đã mở khóa bài này. Hãy giữ bất kỳ ý kiến ​​về câu hỏi và thảo luận về sự phù hợp trên câu hỏi Meta .
Thomas Owens

5
Có lẽ nó cũng có liên quan đến thực tế là các dấu ngoặc nhọn được sử dụng trong ký hiệu tập hợp trong toán học, khiến chúng hơi khó sử dụng để truy cập phần tử mảng, thay vì những thứ như khai báo "set" -ish những thứ như structs, mảng, v.v. Ngay cả các ngôn ngữ hiện đại như Python cũng sử dụng dấu ngoặc nhọn để khai báo các bộ và từ điển. Câu hỏi sau đó là tại sao C cũng sử dụng dấu ngoặc nhọn để khai báo phạm vi? Có lẽ bởi vì các nhà thiết kế không thích các lựa chọn thay thế đã biết, như BEGIN / END và ký hiệu truy cập mảng quá tải ([]) được coi là kém thẩm mỹ hơn so với ký hiệu đặt.
Charles Salvia

Câu trả lời:


102

Hai trong số những ảnh hưởng chính đến C là họ ngôn ngữ Algol (Algol 60 và Algol 68) và BCPL (từ đó C lấy tên của nó).

BCPL là ngôn ngữ lập trình khung cong đầu tiên và dấu ngoặc nhọn tồn tại trong các thay đổi cú pháp và đã trở thành một phương tiện phổ biến để biểu thị các câu lệnh mã nguồn chương trình. Trong thực tế, trên các bàn phím hạn chế trong ngày, các chương trình nguồn thường sử dụng các chuỗi $ (và $) thay cho các ký hiệu {và}. Các nhận xét '//' một dòng của BCPL, không được đưa lên trong C, đã xuất hiện lại trong C ++ và sau đó là C99.

Từ http://www.princeton.edu/~achaney/tmve/wiki100k/docs/BCPL.html

BCPL đã giới thiệu và thực hiện một số đổi mới đã trở thành các yếu tố khá phổ biến trong thiết kế các ngôn ngữ sau này. Do đó, đây là ngôn ngữ lập trình khung cong đầu tiên (một ngôn ngữ sử dụng {} làm dấu phân cách khối) và là ngôn ngữ đầu tiên sử dụng // để đánh dấu các nhận xét nội tuyến.

Từ http://progopedia.com/lingu/bcpl/

Trong BCPL, người ta thường thấy niềng răng xoăn, nhưng không phải lúc nào cũng vậy. Đây là một hạn chế của bàn phím tại thời điểm đó. Các ký tự $($)từ vựng tương đương với {}. Các bản đồ và chữ viết được duy trì trong C (mặc dù một bộ khác để thay thế nẹp xoăn - ??<??>).

Việc sử dụng niềng răng được cải tiến thêm ở B (trước C).

Từ tài liệu tham khảo của người dùng đến B của Ken Thompson:

/* The following function will print a non-negative number, n, to
  the base b, where 2<=b<=10,  This routine uses the fact that
  in the ASCII character set, the digits 0 to 9 have sequential
  code values.  */

printn(n,b) {
        extern putchar;
        auto a;

        if(a=n/b) /* assignment, not test for equality */
                printn(a, b); /* recursive */
        putchar(n%b + '0');
}

Có dấu hiệu cho thấy niềng răng xoăn được sử dụng như bàn tay ngắn beginendtrong Algol.

Tôi nhớ rằng bạn cũng đã đưa chúng vào mã thẻ 256 ký tự mà bạn đã xuất bản trong CACM, vì tôi thấy thú vị khi bạn đề xuất rằng chúng có thể được sử dụng thay cho các từ khóa 'bắt đầu' và 'kết thúc' của Algol, chính xác là sau này chúng được sử dụng như thế nào trong ngôn ngữ C.

Từ http://www.bobbemer.com/BRACES.HTM


Việc sử dụng dấu ngoặc vuông (như một sự thay thế được đề xuất trong câu hỏi) sẽ còn đi xa hơn nữa. Như đã đề cập, họ Algol ảnh hưởng C. Trong Algol 60 và 68 (C được viết vào năm 1972 và BCPL năm 1966), dấu ngoặc vuông được sử dụng để chỉ định một chỉ mục thành một mảng hoặc ma trận.

BEGIN
  FILE F(KIND=REMOTE);
  EBCDIC ARRAY E[0:11];
  REPLACE E BY "HELLO WORLD!";
  WRITE(F, *, E);
END.

Vì các lập trình viên đã quen thuộc với dấu ngoặc vuông cho mảng trong Algol và BCPL, và dấu ngoặc nhọn cho các khối trong BCPL, nên có rất ít nhu cầu hoặc mong muốn thay đổi điều này khi tạo ngôn ngữ khác.


Câu hỏi cập nhật bao gồm phần phụ lục năng suất cho việc sử dụng nẹp xoăn và đề cập đến trăn. Có một số tài nguyên khác thực hiện nghiên cứu này mặc dù câu trả lời rút gọn thành "giai thoại của nó, và những gì bạn đã quen là những gì bạn làm việc hiệu quả nhất". Bởi vì các kỹ năng khác nhau trong lập trình và làm quen với các ngôn ngữ khác nhau, những điều này trở nên khó khăn.

Xem thêm: Stack Overflow Có nghiên cứu thống kê nào chỉ ra rằng Python có năng suất cao hơn không?

Phần lớn lợi nhuận sẽ phụ thuộc vào IDE (hoặc thiếu) được sử dụng. Trong các trình soạn thảo dựa trên vi, đặt con trỏ lên một lần mở / đóng phù hợp và %sau đó nhấn sẽ di chuyển con trỏ đến ký tự khớp khác. Điều này rất hiệu quả với các ngôn ngữ dựa trên C trở lại thời xưa - ít hơn bây giờ.

Một so sánh tốt hơn sẽ là giữa {}begin/ endđó là các tùy chọn trong ngày (không gian ngang là quý giá). Nhiều ngôn ngữ Wirth dựa trên a beginendphong cách (Algol (đã đề cập ở trên), pascal (nhiều người quen thuộc) và gia đình Modula).

Tôi gặp khó khăn trong việc tìm kiếm bất kỳ cách ly tính năng ngôn ngữ cụ thể này - tốt nhất tôi có thể làm là chỉ ra rằng các ngôn ngữ dấu ngoặc nhọn phổ biến hơn nhiều so với ngôn ngữ bắt đầu và đó là một cấu trúc phổ biến. Như đã đề cập trong liên kết Bob Bemer ở ​​trên, nẹp xoăn đã được sử dụng để giúp lập trình dễ dàng hơn như tốc ký.

Từ tại sao Pascal không phải là ngôn ngữ lập trình yêu thích của tôi

Các lập trình viên C và Ratfor thấy 'bắt đầu' và 'kết thúc' cồng kềnh so với {và}.

Đó là về tất cả những gì có thể nói - sự quen thuộc và sở thích của nó.


14
Bây giờ mọi người ở đây đang học BCPL thay vì làm việc :)
Denys Séguret

Các bộ ba (được giới thiệu trong tiêu chuẩn ISO C năm 1989) cho {}đang ??<??>. Các bản tóm tắt (được giới thiệu bởi sửa đổi năm 1995) là <%%>. Các phân đoạn được mở rộng trong tất cả các bối cảnh, trong giai đoạn dịch rất sớm. Digcript là mã thông báo và không được mở rộng bằng chuỗi ký tự, hằng ký tự hoặc nhận xét.
Keith Thompson

Có tồn tại một cái gì đó trước năm 1989 cho điều này trong C (Tôi phải khai thác cuốn sách ấn bản đầu tiên của mình để có được một ngày về điều đó). Không phải tất cả các trang mã EBCDIC đều có dấu ngoặc nhọn (hoặc dấu ngoặc vuông) trong đó và có các quy định cho điều này trong trình biên dịch C sớm nhất.

@NevilleDNZ BCPL đã sử dụng niềng răng xoăn vào năm 1966. Trường hợp Algol68 có ý tưởng từ đó sẽ là điều cần khám phá - nhưng BCPL đã không nhận được nó từ Algo68. Toán tử ternary là điều mà tôi quan tâm và đã theo dõi nó trở lại CPL (1963) (tiền thân của BCPL) đã mượn khái niệm từ Lisp (1958).

Năm 1968: Algol68 cho phép dấu ngoặc tròn (~) làm tốc ký bắt đầu ~ kết thúc các khối biểu tượng in đậm . Chúng được gọi là các ký hiệu ngắn gọn , cf wp: Algol68 Các ký hiệu đậm , điều này cho phép các khối mã được xử lý giống như các biểu thức . A68 cũng có ngắn gọn shorthands như C : ternary nhà điều hành ví dụ như x:=(c|s1|s2)thay vì C x=c?s1|s2. Tương tự điều này áp dụng cho câu lệnh if & case . BTW: A68 là từ nơi chiếc vỏ có được esac & fi ¢
NevilleDNZ

24

Niềng răng vuông []dễ gõ hơn, kể từ khi thiết bị đầu cuối IBM 2741 được sử dụng rộng rãi trên hệ điều hành Multics " , đến lượt nó có Dennis Ritchie, một trong những người tạo ngôn ngữ C với tư cách là thành viên nhóm phát triển .

http://upload.wik hè.org/wikipedia/commons/thumb/9/9f/APL-keybd2.svg/600px-APL-keybd2.svg.png

Lưu ý sự vắng mặt của dấu ngoặc nhọn ở bố cục IBM 2741!

Trong C, dấu ngoặc vuông được "lấy" vì chúng được sử dụng cho mảng và con trỏ . Nếu các nhà thiết kế ngôn ngữ mong đợi các mảng và con trỏ quan trọng hơn / được sử dụng thường xuyên hơn các khối mã (nghe có vẻ như một giả định hợp lý ở bên cạnh họ, nhiều hơn về bối cảnh lịch sử của phong cách mã hóa bên dưới), điều đó có nghĩa là các dấu ngoặc nhọn sẽ "ít quan trọng hơn" "Cú pháp.

Tầm quan trọng của mảng là khá rõ ràng trong bài viết Sự phát triển của ngôn ngữ C của Ritchie. Thậm chí còn có một giả định được tuyên bố rõ ràng về "sự phổ biến của con trỏ trong các chương trình C" .

... ngôn ngữ mới giữ lại một lời giải thích mạch lạc và khả thi (nếu không bình thường) về ngữ nghĩa của mảng ... Hai ý tưởng là đặc trưng nhất của C giữa các ngôn ngữ của lớp: mối quan hệ giữa mảng và con trỏ ... Đặc điểm khác của C, điều trị mảng của nó ... có những đức tính thực sự . Mặc dù mối quan hệ giữa con trỏ và mảng là không bình thường, nó có thể được học. Hơn nữa, ngôn ngữ cho thấy sức mạnh đáng kể để mô tả các khái niệm quan trọng, ví dụ, các vectơ có chiều dài thay đổi theo thời gian chạy, chỉ với một vài quy tắc và quy ước cơ bản ...


Để hiểu rõ hơn về bối cảnh lịch sử và phong cách mã hóa của thời điểm khi ngôn ngữ C được tạo ra, người ta cần phải tính đến việc "nguồn gốc của C gắn liền với sự phát triển của Unix" và cụ thể là chuyển hệ điều hành sang PDP- 11 "dẫn đến sự phát triển của phiên bản đầu của C" ( nguồn trích dẫn ). Theo Wikipedia , "vào năm 1972, Unix đã được viết lại bằng ngôn ngữ lập trình C" .

Mã nguồn của các phiên bản cũ khác nhau của Unix có sẵn trực tuyến, ví dụ như tại trang web Unix Tree . Trong số các phiên bản khác nhau được trình bày ở đó, hầu hết có liên quan dường như là Phiên bản thứ hai Unix ngày 1972-06:

Phiên bản thứ hai của Unix được phát triển cho PDP-11 tại Bell Labs bởi Ken Thompson, Dennis Ritchie và những người khác. Nó mở rộng Phiên bản đầu tiên với nhiều cuộc gọi hệ thống hơn và nhiều lệnh hơn. Phiên bản này cũng chứng kiến ​​sự khởi đầu của ngôn ngữ C, được sử dụng để viết một số lệnh ...

Bạn có thể duyệt và nghiên cứu mã nguồn C từ trang Unix (V2) phiên bản thứ hai để có ý tưởng về phong cách mã hóa điển hình của thời đại.

Một ví dụ nổi bật hỗ trợ cho ý tưởng hồi đó khá quan trọng đối với lập trình viên để có thể nhập dấu ngoặc vuông dễ dàng có thể được tìm thấy trong mã nguồn V2 / c / ncc.c :

/* C command */

main(argc, argv)
char argv[][]; {
    extern callsys, printf, unlink, link, nodup;
    extern getsuf, setsuf, copy;
    extern tsp;
    extern tmp0, tmp1, tmp2, tmp3;
    char tmp0[], tmp1[], tmp2[], tmp3[];
    char glotch[100][], clist[50][], llist[50][], ts[500];
    char tsp[], av[50][], t[];
    auto nc, nl, cflag, i, j, c;

    tmp0 = tmp1 = tmp2 = tmp3 = "//";
    tsp = ts;
    i = nc = nl = cflag = 0;
    while(++i < argc) {
        if(*argv[i] == '-' & argv[i][1]=='c')
            cflag++;
        else {
            t = copy(argv[i]);
            if((c=getsuf(t))=='c') {
                clist[nc++] = t;
                llist[nl++] = setsuf(copy(t));
            } else {
            if (nodup(llist, t))
                llist[nl++] = t;
            }
        }
    }
    if(nc==0)
        goto nocom;
    tmp0 = copy("/tmp/ctm0a");
    while((c=open(tmp0, 0))>=0) {
        close(c);
        tmp0[9]++;
    }
    while((creat(tmp0, 012))<0)
        tmp0[9]++;
    intr(delfil);
    (tmp1 = copy(tmp0))[8] = '1';
    (tmp2 = copy(tmp0))[8] = '2';
    (tmp3 = copy(tmp0))[8] = '3';
    i = 0;
    while(i<nc) {
        if (nc>1)
            printf("%s:\n", clist[i]);
        av[0] = "c0";
        av[1] = clist[i];
        av[2] = tmp1;
        av[3] = tmp2;
        av[4] = 0;
        if (callsys("/usr/lib/c0", av)) {
            cflag++;
            goto loop;
        }
        av[0] = "c1";
        av[1] = tmp1;
        av[2] = tmp2;
        av[3] = tmp3;
        av[4] = 0;
        if(callsys("/usr/lib/c1", av)) {
            cflag++;
            goto loop;
        }
        av[0] = "as";
        av[1] = "-";
        av[2] = tmp3;
        av[3] = 0;
        callsys("/bin/as", av);
        t = setsuf(clist[i]);
        unlink(t);
        if(link("a.out", t) | unlink("a.out")) {
            printf("move failed: %s\n", t);
            cflag++;
        }
loop:;
        i++;
    }
nocom:
    if (cflag==0 & nl!=0) {
        i = 0;
        av[0] = "ld";
        av[1] = "/usr/lib/crt0.o";
        j = 2;
        while(i<nl)
            av[j++] = llist[i++];
        av[j++] = "-lc";
        av[j++] = "-l";
        av[j++] = 0;
        callsys("/bin/ld", av);
    }
delfil:
    dexit();
}
dexit()
{
    extern tmp0, tmp1, tmp2, tmp3;

    unlink(tmp1);
    unlink(tmp2);
    unlink(tmp3);
    unlink(tmp0);
    exit();
}

getsuf(s)
char s[];
{
    extern exit, printf;
    auto c;
    char t, os[];

    c = 0;
    os = s;
    while(t = *s++)
        if (t=='/')
            c = 0;
        else
            c++;
    s =- 3;
    if (c<=8 & c>2 & *s++=='.' & *s=='c')
        return('c');
    return(0);
}

setsuf(s)
char s[];
{
    char os[];

    os = s;
    while(*s++);
    s[-2] = 'o';
    return(os);
}

callsys(f, v)
char f[], v[][]; {

    extern fork, execv, wait, printf;
    auto t, status;

    if ((t=fork())==0) {
        execv(f, v);
        printf("Can't find %s\n", f);
        exit(1);
    } else
        if (t == -1) {
            printf("Try again\n");
            return(1);
        }
    while(t!=wait(&status));
    if ((t=(status&0377)) != 0) {
        if (t!=9)       /* interrupt */
            printf("Fatal error in %s\n", f);
        dexit();
    }
    return((status>>8) & 0377);
}

copy(s)
char s[]; {
    extern tsp;
    char tsp[], otsp[];

    otsp = tsp;
    while(*tsp++ = *s++);
    return(otsp);
}

nodup(l, s)
char l[][], s[]; {

    char t[], os[], c;

    os = s;
    while(t = *l++) {
        s = os;
        while(c = *s++)
            if (c != *t++) goto ll;
        if (*t++ == '\0') return (0);
ll:;
    }
    return(1);
}

tsp;
tmp0;
tmp1;
tmp2;
tmp3;

Thật thú vị khi lưu ý cách động lực thực tế của việc chọn các ký tự để biểu thị các yếu tố cú pháp ngôn ngữ dựa trên việc sử dụng chúng trong các ứng dụng thực tế được nhắm mục tiêu giống như Luật của Zipf như được giải thích trong câu trả lời tuyệt vời này ...

mối quan hệ được quan sát giữa tần suất và độ dài được gọi là Luật của Zipf

... với sự khác biệt duy nhất là độ dài trong câu lệnh trên được thay thế bằng / khái quát là tốc độ gõ.


5
Bất cứ điều gì hỗ trợ cho kỳ vọng "rõ ràng" này của các nhà thiết kế ngôn ngữ? Không cần lập trình nhiều trong C để nhận thấy rằng dấu ngoặc nhọn phổ biến hơn nhiều so với khai báo mảng. Điều này thực sự không thay đổi nhiều kể từ thời xa xưa - hãy xem K & R.

1
Tôi bằng cách nào đó nghi ngờ lời giải thích này. Chúng tôi không biết những gì được mong đợi và họ có thể dễ dàng chọn nó theo cách khác vì họ cũng là người quyết định ký hiệu mảng. Chúng tôi thậm chí không biết liệu họ có nghĩ rằng niềng răng xoăn là lựa chọn "ít quan trọng" hơn không, có lẽ họ thích niềng răng xoăn hơn.
thorsten müller

3
@gnat: Niềng răng vuông dễ gõ hơn trên bàn phím hiện đại, điều này có áp dụng cho bàn phím xung quanh khi unix và c lần đầu tiên được thực hiện không? Tôi không có lý do để nghi ngờ rằng họ đang sử dụng cùng một bàn phím, hoặc họ sẽ cho rằng các bàn phím khác sẽ giống như bàn phím của họ, hoặc họ sẽ nghĩ tốc độ gõ sẽ đáng để tối ưu hóa bởi một ký tự.
Michael Shaw

1
Ngoài ra, luật của Zipf là một khái quát về những gì kết thúc bằng ngôn ngữ tự nhiên. C được xây dựng nhân tạo, vì vậy không có lý do gì để nghĩ rằng nó sẽ được áp dụng ở đây trừ khi các nhà thiết kế của C có ý định quyết định áp dụng nó một cách có chủ ý. Nếu nó được áp dụng, không có lý do gì để cho rằng nó sẽ đơn giản hóa một cái gì đó ngắn như một ký tự.
Michael Shaw

1
@gnat FWIW, grep -Fonói với tôi những *.cfile của mã nguồn CPython (rev. 4b42d7f288c5 bởi vì đó là những gì tôi có trong tay), trong đó bao gồm libffi, chứa 39.511 {(39.508 {, dunno tại sao hai niềng răng không khép kín), nhưng chỉ 13.718 [(13.702 [). Đó là số lần xuất hiện trong chuỗi và trong bối cảnh không liên quan đến câu hỏi này, vì vậy điều này không thực sự chính xác, ngay cả khi chúng tôi bỏ qua rằng cơ sở mã có thể không đại diện (lưu ý rằng sự thiên vị này có thể đi theo một trong hai hướng). Tuy nhiên, hệ số 2,8?

1

C (và sau đó là C ++ và C #) đã thừa hưởng phong cách giằng của nó từ người tiền nhiệm B , được viết bởi Ken Thompson (với sự đóng góp từ Dennis Ritchie) vào năm 1969.

Ví dụ này là từ Tài liệu tham khảo của người dùng đến B của Ken Thompson (thông qua Wikipedia ):

/* The following function will print a non-negative number, n, to
   the base b, where 2<=b<=10,  This routine uses the fact that
   in the ASCII character set, the digits 0 to 9 have sequential
   code values.  */

printn(n,b) {
        extern putchar;
        auto a;

        if(a=n/b) /* assignment, not test for equality */
                printn(a, b); /* recursive */
        putchar(n%b + '0');
}

Bản thân B một lần nữa dựa trên BCPL , một ngôn ngữ được viết bởi Martin Richards vào năm 1966 cho hệ điều hành Multics. Hệ thống niềng răng của B chỉ sử dụng niềng răng tròn, được sửa đổi bởi các ký tự bổ sung (ví dụ về giai thừa in của Martin Richards, thông qua Wikipedia ):

GET "LIBHDR"

LET START() = VALOF $(
        FOR I = 1 TO 5 DO
                WRITEF("%N! = %I4*N", I, FACT(I))
        RESULTIS 0
)$

AND FACT(N) = N = 0 -> 1, N * FACT(N - 1)

Các dấu ngoặc nhọn được sử dụng trong B và các ngôn ngữ tiếp theo "{...}" là một cải tiến mà Ken Thompson đã thực hiện theo kiểu niềng răng hỗn hợp ban đầu trong BCPL "$ (...) $".


1
Không. Có vẻ như Bob Bemer ( en.wikipedia.org/wiki/Bob_Bemer ) chịu trách nhiệm cho việc này - "... bạn đã đề xuất rằng chúng có thể được sử dụng thay cho các từ khóa 'bắt đầu' và 'kết thúc' của Algol, chính xác là sau này chúng được sử dụng như thế nào trong ngôn ngữ C. " (từ bobbemer.com/BRACES.HTM )
SChepurin

1
Các $( ... $)định dạng tương đương với { ... }trong lexer trong BCPL, cũng giống như ??< ... ??>là tương đương với { ... }bằng C. Sự cải thiện giữa hai phong cách là trong phần cứng bàn phím - không phải là ngôn ngữ.
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.