Nghịch lý blub của người Viking và c ++


37

Tôi đã đọc bài viết ở đây: http://www.paulgraham.com/avg.html và phần về "nghịch lý blub" đặc biệt thú vị. Là một người chủ yếu viết mã bằng c ++ nhưng tiếp xúc với các ngôn ngữ khác (chủ yếu là Haskell) Tôi nhận thấy một vài điều hữu ích trong các ngôn ngữ này khó có thể sao chép trong c ++. Câu hỏi chủ yếu dành cho những người thành thạo cả c ++ và một số ngôn ngữ khác, có một tính năng ngôn ngữ hay thành ngữ nào đó mà bạn sử dụng trong một ngôn ngữ khó có thể khái niệm hóa hoặc thực hiện nếu bạn chỉ viết bằng c ++?

Đặc biệt trích dẫn này đã thu hút sự chú ý của tôi:

Bằng cách cảm ứng, các lập trình viên duy nhất ở một vị trí để thấy tất cả sự khác biệt về sức mạnh giữa các ngôn ngữ khác nhau là những người hiểu ngôn ngữ mạnh nhất. (Đây có lẽ là ý nghĩa của Eric Raymond về việc Lisp biến bạn thành một lập trình viên giỏi hơn.) Bạn không thể tin vào ý kiến ​​của người khác, vì nghịch lý Blub: họ hài lòng với bất kỳ ngôn ngữ nào họ sử dụng, bởi vì nó ra lệnh cách họ nghĩ về các chương trình.

Nếu hóa ra tôi tương đương với lập trình viên "Blub" nhờ sử dụng c ++ thì điều này đặt ra câu hỏi sau: Có bất kỳ khái niệm hay kỹ thuật hữu ích nào mà bạn gặp phải trong các ngôn ngữ khác mà bạn sẽ gặp khó khăn khi khái niệm hóa không? đã viết hoặc "suy nghĩ" trong c ++?

Ví dụ, mô hình lập trình logic được thấy trong các ngôn ngữ như Prolog và Mercury có thể được triển khai trong c ++ bằng thư viện castor nhưng cuối cùng tôi thấy rằng về mặt khái niệm tôi đang nghĩ về mã Prolog và dịch sang c ++ tương đương khi sử dụng. Như một cách để mở rộng kiến ​​thức lập trình của tôi, tôi đang cố gắng tìm hiểu xem có những ví dụ tương tự khác về thành ngữ hữu ích / mạnh mẽ được thể hiện hiệu quả hơn trong các ngôn ngữ khác mà tôi có thể không biết là nhà phát triển c ++. Một ví dụ khác xuất hiện trong tâm trí là hệ thống macro không thể thiếu, việc tạo mã chương trình từ bên trong chương trình dường như có nhiều lợi ích cho một số vấn đề. Điều này có vẻ khó thực hiện và suy nghĩ từ bên trong c ++.

Câu hỏi này không nhằm mục đích trở thành một cuộc tranh luận "c ++ vs lisp" hay bất kỳ cuộc tranh luận kiểu chiến tranh ngôn ngữ nào. Đặt một câu hỏi như thế này là cách duy nhất tôi có thể thấy có thể để tìm hiểu về những điều mà tôi không biết tôi không biết.



2
Tôi đồng ý. Chừng nào điều này không biến thành cuộc tranh luận giữa C ++ và Lisp, tôi nghĩ có điều gì đó cần phải học ở đây.
jeffythedragonslayer

@MasonWheeler: there are things that other languages can do that Lisp can't- Không thể, vì Lisp đã hoàn thành Turing. Có lẽ bạn muốn nói rằng có một số điều không thực tế để làm ở Lisp? Tôi có thể nói điều tương tự về bất kỳ ngôn ngữ lập trình nào .
Robert Harvey

2
@RobertHarvey: "Tất cả các ngôn ngữ đều mạnh như nhau theo nghĩa tương đương Turing, nhưng đó không phải là ý nghĩa của các lập trình viên quan tâm. (Không ai muốn lập trình một máy Turing.) Loại lập trình viên quyền lực quan tâm có thể không phải là chính thức có thể xác định được, nhưng một cách để giải thích là nói rằng nó đề cập đến các tính năng mà bạn chỉ có thể có trong ngôn ngữ ít mạnh hơn bằng cách viết một trình thông dịch cho ngôn ngữ mạnh hơn trong đó. " - Paul Graham, trong một chú thích cho trollpost trong câu hỏi. (Xem ý tôi là gì?)
Mason Wheeler

@Mason Wheeler: (Không thực sự.)
Robert Harvey

Câu trả lời:


16

Chà, kể từ khi bạn đề cập đến Haskell:

  1. Kết hợp mẫu. Tôi thấy khớp mẫu để dễ đọc và viết hơn nhiều. Xem xét định nghĩa của bản đồ và suy nghĩ về cách nó sẽ được thực hiện bằng ngôn ngữ mà không khớp mẫu.

    map :: (a -> b) -> [a] -> [b]
    map f [] = []
    map f (x:xs) = f x : map f xs
  2. Các loại hệ thống. Nó có thể là một nỗi đau đôi khi nhưng nó cực kỳ hữu ích. Bạn phải lập trình với nó để thực sự hiểu nó và có bao nhiêu lỗi nó bắt được. Ngoài ra, minh bạch tham chiếu là tuyệt vời. Nó chỉ trở nên rõ ràng sau khi lập trình trong Haskell trong một thời gian có bao nhiêu lỗi gây ra bởi việc quản lý trạng thái trong một ngôn ngữ bắt buộc.

  3. Lập trình chức năng nói chung. Sử dụng bản đồ và nếp gấp thay vì lặp. Đệ quy. Đó là về suy nghĩ ở cấp độ cao hơn.

  4. Đánh giá lười biếng. Một lần nữa, đó là suy nghĩ ở cấp độ cao hơn và để hệ thống xử lý đánh giá.

  5. Cabal, gói, và mô-đun. Có các gói tải xuống Cabal đối với tôi thuận tiện hơn nhiều so với việc tìm mã nguồn, viết một tệp tạo tệp, v.v.


2
Một lưu ý về so khớp mẫu: Tôi sẽ không nói rằng việc viết chung dễ dàng hơn, nhưng sau khi bạn đọc một chút về vấn đề biểu thức, rõ ràng là những thứ như if và switch câu lệnh, enums và mẫu quan sát đều là các triển khai kém hơn của các kiểu dữ liệu đại số + Khớp mẫu. (Và thậm chí không cho phép bắt đầu về cách Có thể làm cho ngoại lệ con trỏ null trở nên lỗi thời)
hugomg

Những gì bạn nói là đúng, nhưng vấn đề biểu hiện là về các hạn chế của các loại dữ liệu đại số (và về các giới hạn kép của OOP tiêu chuẩn).
Blaisorblade

@hugomg Ý của bạn là mẫu Người truy cập thay vì Người quan sát?
Sebastian Redl

Vâng. Tôi luôn chuyển đổi hai tên đó :)
hugomg

@hugomg Không phải là có Maybe(đối với C ++ xem std::optional), đó là về việc phải đánh dấu rõ ràng mọi thứ là tùy chọn / nullable / có thể.
Ded repeatator

7

Ghi nhớ!

Hãy thử viết nó bằng C ++. Không phải với C ++ 0x.

Quá cồng kềnh? Được rồi, hãy thử nó với C ++ 0x.

Xem bạn có thể đánh bại phiên bản biên dịch 4 dòng (hoặc 5 dòng, bất cứ điều gì: P) trong D:

auto memoize(alias Fn, T...)(T args) {
    auto key = tuple(args);                               //Key is all the args
    static typeof(Fn(args))[typeof(key)] cache;           //Hashtable!
    return key in cache ? cache[key] : (cache[key] = Fn(args));
}

Tất cả những gì bạn cần làm để gọi nó là một cái gì đó như:

int fib(int n) { return n > 1 ? memoize!(fib)(n - 1) + memoize!(fib)(n - 2) : 1;}
fib(60);

Bạn cũng có thể thử một cái gì đó tương tự trong Scheme, mặc dù nó chậm hơn một chút vì nó xảy ra vào thời gian chạy và vì việc tra cứu ở đây là tuyến tính thay vì băm (và tốt, vì đó là Scheme):

(define (memoize f)
    (let ((table (list)))
        (lambda args
            (cdr
                (or (assoc args table)
                    (let ((entry (cons args (apply f args))))
                        (set! table (cons entry table))
                        entry))))))
(define (fib n)
        (if (<= n 1)
            1
            (+ (fib (1- n))
                (fib (- n 2)))))))
(set! fib (memoize fib))

1
Vì vậy, bạn yêu thích APL nơi bạn có thể viết bất cứ điều gì trong một dòng duy nhất? Kích thước không thành vấn đề!
Bo Persson

@Bo: Tôi chưa sử dụng APL. Tôi không chắc ý của bạn là "kích thước không thành vấn đề", nhưng có gì sai với mã của tôi khiến bạn nói vậy không? Và có một số lợi thế cho cách bạn sẽ làm điều này bằng một ngôn ngữ khác (như C ++) mà tôi không biết? (Tôi đã chỉnh sửa tên biến một chút, trong trường hợp đó là những gì bạn đang đề cập đến.)
Mehrdad

1
@Mehrdad - Nhận xét của tôi là về chương trình nhỏ gọn nhất không phải là dấu hiệu của ngôn ngữ lập trình tốt nhất. Trong trường hợp đó, APL sẽ thắng, vì bạn làm hầu hết mọi thứ với một toán tử char duy nhất. Chỉ có vấn đề là nó không thể đọc được.
Bo Persson

@Bo: Như tôi đã nói, tôi đã không giới thiệu APL; Tôi thậm chí chưa bao giờ nhìn thấy nó. Kích thước là một tiêu chí (mặc dù là một tiêu chí quan trọng, như có thể thấy nếu bạn thử điều này với C ++) ... nhưng có điều gì sai với mã này không?
Mehrdad

1
@Matt: Mã của bạn ghi nhớ một chức năng, nhưng mã này có thể ghi nhớ bất kỳ chức năng nào . Chúng không thực sự tương đương ở tất cả. Nếu bạn thực sự thử viết một hàm bậc cao hơn như thế này trong C ++ 0x, thì sẽ tẻ nhạt hơn nhiều so với trong D (mặc dù điều đó vẫn hoàn toàn có thể ... mặc dù điều đó không thể thực hiện được trong C ++ 03).
Mehrdad

5

C ++ là một ngôn ngữ đa âm, có nghĩa là nó cố gắng hỗ trợ nhiều cách suy nghĩ. Đôi khi, một tính năng C ++ khó xử hoặc kém trôi chảy hơn so với việc triển khai ngôn ngữ khác, như trường hợp lập trình chức năng.

Điều đó nói rằng, tôi không thể nghĩ ra khỏi đầu một tính năng ngôn ngữ C ++ bản địa làm những gì yieldtrong Python hoặc JavaScript.

Một ví dụ khác là lập trình đồng thời . C ++ 0x sẽ có tiếng nói về nó, nhưng tiêu chuẩn hiện tại thì không, và đồng thời là một cách suy nghĩ hoàn toàn mới.

Ngoài ra, phát triển nhanh chóng - ngay cả lập trình shell - là điều bạn sẽ không bao giờ học nếu bạn không bao giờ rời khỏi miền lập trình C ++.


Tôi thậm chí không thể nghĩ rằng việc tạo các trình tạo trong C ++ được đưa ra C ++ 2003 sẽ khó khăn như thế nào. C ++ 2011 sẽ làm cho nó dễ dàng hơn, nhưng vẫn không tầm thường. Làm việc thường xuyên với C ++, C # và Python, các trình tạo dễ dàng là tính năng tôi dễ nhớ nhất trong C ++ (bây giờ C ++ 2011 đã thêm lambdas).

Tôi biết tôi sẽ bị bắn vì điều này, nhưng nếu tôi hoàn toàn phải thực hiện các trình tạo trong C ++, tôi sẽ phải sử dụng ... setjmplongjmp. Tôi không biết bao nhiêu đó phá vỡ, nhưng tôi đoán ngoại lệ sẽ là người đầu tiên đi. Bây giờ, nếu bạn tha thứ cho tôi, tôi cần đọc lại Modern C ++ Design để lấy nó ra khỏi đầu.
Mike DeSimone

@Mike DeSimone, bạn có thể giải thích (breifly) về cách bạn thử giải pháp với setjmp và longjmp không?

1
Coroutines là đẳng cấu với functor.
GManNickG

1
@Xeo, @Mike: xkcd.com/292
Mehrdad

5

Coroutines là một tính năng ngôn ngữ vô cùng hữu ích, làm nền tảng cho rất nhiều lợi ích hữu hình hơn của các ngôn ngữ khác so với C ++. Về cơ bản, chúng cung cấp các ngăn xếp bổ sung để các chức năng có thể bị gián đoạn và tiếp tục, cung cấp các phương tiện giống như đường ống cho ngôn ngữ dễ dàng cung cấp kết quả của các hoạt động thông qua các bộ lọc cho các hoạt động khác. Thật tuyệt vời, và trong Ruby tôi thấy nó rất trực quan và thanh lịch. Đánh giá lười biếng quan hệ trong này là tốt.

Nội suy và biên dịch / thực thi / đánh giá mã thời gian chạy / bất cứ điều gì là các tính năng mạnh mẽ mà C ++ thiếu.


Các coroutines có sẵn trong FreeRTOS ( xem tại đây ), được triển khai trong C. Tôi tự hỏi sẽ cần những gì để làm cho chúng hoạt động trong C ++?
Mike DeSimone

Co-Routines là một hack khó chịu để mô phỏng các đối tượng trong C. Trong C ++, các đối tượng được sử dụng để bó mã và dữ liệu. Nhưng trong C, bạn không thể. Vì vậy, bạn sử dụng ngăn xếp đồng quy để lưu trữ dữ liệu và hàm co-thường dùng để giữ mã.
MSalters


1
@Ferruccio: Cảm ơn vì liên kết ... có một vài trong bài viết Wikipedia. @MSalters: điều gì khiến bạn mô tả các đồng nghiệp là "một vụ hack khó chịu"? Có vẻ như một quan điểm rất độc đoán với tôi. Sử dụng một ngăn xếp để lưu trữ trạng thái cũng được thực hiện bằng các thuật toán đệ quy - chúng có bị hack quá không? FWIW, coroutines và OOP đã xuất hiện cùng một lúc (đầu những năm 1960) ... để nói rằng kẻ tấn công các đối tượng trong C có vẻ kỳ quái ... Tôi tưởng tượng vài lập trình viên C hồi đó quan tâm đến việc mô phỏng các đối tượng, > 15 năm trước C ++.
Tony

4

Đã triển khai hệ thống đại số máy tính ở cả Lisp và C ++, tôi có thể nói với bạn rằng nhiệm vụ này dễ dàng hơn nhiều ở Lisp, mặc dù tôi là một người mới hoàn toàn với ngôn ngữ. Bản chất đơn giản này của mọi thứ được liệt kê đơn giản hóa rất nhiều thuật toán. Cấp, phiên bản C ++ nhanh hơn gấp trăm lần. Vâng, tôi đã có thể làm cho phiên bản lisp nhanh hơn, nhưng mã sẽ không được lispy. Kịch bản là một thứ khác luôn luôn dễ dàng hơn là điều tất yếu. Đó là tất cả về việc sử dụng công cụ phù hợp cho công việc.


Sự khác biệt về tốc độ là gì?
quant_dev

1
@quant_dev: dĩ nhiên là bội số của một tỷ!
Matt Ellen

Tôi chưa bao giờ thực sự đo nó nhưng tôi có cảm giác O lớn thật khác biệt. Ban đầu tôi đã viết phiên bản C ++ theo phong cách chức năng và nó cũng có vấn đề về tốc độ cho đến khi tôi dạy nó sửa đổi cấu trúc dữ liệu thay vì tạo mới, thay đổi. Nhưng điều đó cũng làm cho mã khó đọc hơn ...
jeffythedragonslayer

2

Ý chúng ta là gì khi chúng ta nói rằng một ngôn ngữ "mạnh hơn" ngôn ngữ khác? Khi chúng ta nói rằng một ngôn ngữ là "biểu cảm?" Hay "giàu?" Tôi nghĩ rằng chúng tôi muốn nói rằng một ngôn ngữ có được sức mạnh khi tầm nhìn của nó thu hẹp đủ để làm cho nó dễ dàng và tự nhiên để mô tả một vấn đề - thực sự là một sự chuyển đổi trạng thái, phải không? - sống trong quan điểm đó. Tuy nhiên, ngôn ngữ đó ít mạnh mẽ hơn, ít biểu cảm hơn và ít hữu ích hơn khi lĩnh vực quan điểm của chúng tôi mở rộng.

Ngôn ngữ càng "mạnh mẽ" và "biểu cảm" thì việc sử dụng nó càng bị hạn chế. Vì vậy, có lẽ "mạnh mẽ" và "biểu cảm" là những từ sai để sử dụng cho một công cụ tiện ích hẹp. Có lẽ "thích hợp" hoặc "trừu tượng" là những từ tốt hơn cho những điều đó.

Tôi bắt đầu lập trình bằng cách viết cả đống thứ cấp thấp: trình điều khiển thiết bị với thói quen ngắt quãng của chúng; chương trình nhúng; mã hệ điều hành. Mã này rất thân với phần cứng và tôi đã viết tất cả bằng ngôn ngữ lắp ráp. Chúng tôi sẽ không nói rằng trình biên dịch là ít trừu tượng nhất, nhưng nó đã và là ngôn ngữ mạnh mẽ và biểu cảm nhất trong tất cả chúng. Tôi có thể diễn đạt bất kỳ vấn đề nào trong ngôn ngữ lắp ráp; nó mạnh đến mức tôi có thể làm bất cứ điều gì tôi muốn với bất kỳ máy nào.

Và tất cả những hiểu biết sau này của tôi về ngôn ngữ cấp cao hơn đều nợ mọi thứ đối với kinh nghiệm của tôi với trình biên dịch. Mọi thứ tôi học được sau này đều dễ dàng bởi vì, bạn thấy đấy, mọi thứ - dù trừu tượng đến đâu - cuối cùng cũng phải phù hợp với phần cứng.

Bạn có thể muốn quên đi mức độ trừu tượng ngày càng cao hơn - nghĩa là các trường nhìn hẹp hơn và hẹp hơn. Bạn luôn có thể chọn nó sau. Đó là một snap để tìm hiểu, một vấn đề của ngày. Theo tôi, tốt hơn hết là bạn nên học ngôn ngữ của phần cứng 1 , để đến gần nhất có thể.


1 Có lẽ không hoàn toàn Gecman, nhưng carcdrlấy tên của họ từ các phần cứng: các Lisp đầu tiên chạy trên một máy tính mà đã có một Decrement Đăng ký thực tế và một địa chỉ thực tế Đăng ký. Thế nào?


Bạn thấy rằng sự lựa chọn là một thanh kiếm hai lưỡi. Tất cả chúng ta đều tìm kiếm nó, nhưng có một mặt tối với nó và nó khiến chúng ta không vui. Tốt hơn là nên có một cái nhìn rất rõ ràng về thế giới và các ranh giới xác định mà bạn có thể vận hành. Mọi người là những sinh vật rất sáng tạo và có thể làm những điều tuyệt vời với các công cụ hạn chế. Vì vậy, để tóm tắt, tôi đang nói nó không phải là ngôn ngữ lập trình nhưng có những người tài năng có thể làm cho bất kỳ ngôn ngữ nào hát!
Chad

"Đề xuất là tạo ra; định nghĩa là hủy diệt." Nghĩ rằng bạn có thể làm cho cuộc sống dễ dàng hơn với một ngôn ngữ khác cảm thấy tốt, nhưng một khi bạn thực hiện bước nhảy, thì bạn phải đối phó với mụn cóc của ngôn ngữ mới.
Mike DeSimone

2
Tôi có thể nói rằng tiếng Anh mạnh mẽ và biểu cảm hơn bất kỳ ngôn ngữ lập trình nào, nhưng giới hạn về tính hữu dụng của nó mở rộng mỗi ngày và tiện ích của nó là vô cùng lớn. Một phần của sức mạnh và tính biểu cảm đến từ việc có thể giao tiếp ở mức độ trừu tượng thích hợp khả năng phát minh ra mức độ trừu tượng mới khi được kết hợp.
molbdnilo

@ Giống như sau đó bạn phải đối phó với việc giao tiếp với ngôn ngữ trước đó trong ngôn ngữ mới;)
Chad

2

Mảng liên kết

Một cách xử lý dữ liệu điển hình là:

  • đọc đầu vào và xây dựng một cấu trúc phân cấp từ nó,
  • tạo các chỉ mục cho cấu trúc đó (ví dụ: thứ tự khác nhau),
  • tạo chiết xuất (phần được lọc) của chúng,
  • tìm một giá trị hoặc một nhóm giá trị (nút),
  • sắp xếp lại cấu trúc (xóa các nút, thêm, nối, xóa các thành phần phụ dựa trên quy tắc, v.v.),
  • quét qua cây và in ra hoặc lưu một số phần của chúng.

Công cụ phù hợp cho nó là mảng kết hợp .

  • Hỗ trợ ngôn ngữ tốt nhất cho các mảng kết hợp mà tôi đã thấy là MUMPS , trong đó các mảng kết hợp là: 1. luôn được sắp xếp 2. chúng có thể được tạo trên đĩa (được gọi là cơ sở dữ liệu), với cú pháp rất giống nhau. (Hiệu ứng phụ: nó cực kỳ mạnh mẽ như cơ sở dữ liệu, lập trình viên có quyền truy cập vào btree gốc. Hệ thống NoQuery tốt nhất từng có.)
  • Giải nhì của tôi thuộc về PHP , tôi thích foreach và cú pháp dễ dàng, như $ a [] = x hoặc $ a [x] [y] [z] ++ .

Tôi thực sự không thích cú pháp mảng kết hợp của JavaScript, vì tôi không thể tạo, giả sử [x] [y] [z] = 8 , trước tiên tôi phải tạo [x][x] [y] .

Được rồi, trong C ++ (và trong Java) có danh mục các lớp container, Map , Multimap , dù sao đi nữa, nhưng nếu tôi muốn quét qua, tôi phải tạo một trình vòng lặp và khi tôi muốn chèn một phần tử mức sâu mới, tôi phải tạo ra tất cả các cấp trên, vv Không thoải mái.

Tôi không nói rằng không có mảng kết hợp có thể sử dụng được trong C ++ (và Java), nhưng các ngôn ngữ kịch bản không gõ (hoặc không gõ nghiêm ngặt) đánh bại các ngôn ngữ được biên dịch, bởi vì chúng là ngôn ngữ kịch bản không chữ.

Tuyên bố miễn trừ trách nhiệm: Tôi không quen thuộc với C # và các langug .NET khác, AFAIK họ có khả năng xử lý mảng kết hợp tốt.


1
Tiết lộ đầy đủ: MUMPS không dành cho tất cả mọi người. Trích dẫn: Để cung cấp thêm một chút ví dụ về thế giới thực của thế giới về kinh dị đó là MUMPS, hãy bắt đầu bằng cách tham gia một phần Cuộc thi mã Cffatedated quốc tế, một chút về Perl, hai biện pháp nặng nề của FORTRAN và SNOBOL, và những đóng góp độc lập và không liên kết của Hàng chục nhà nghiên cứu y tế, và bạn đi.
Mike DeSimone

1
Trong Python, bạn có thể sử dụng dictloại tích hợp (ví dụ: x = {0: 5, 1: "foo", None: 500e3}lưu ý rằng không có yêu cầu nào cho các khóa hoặc giá trị là cùng loại). Cố gắng làm một cái gì đó giống như a[x][y][z] = 8là khó vì ngôn ngữ phải nhìn vào tương lai để xem liệu bạn sẽ đặt một giá trị hoặc tạo một cấp độ khác; biểu hiện a[x][y]của chính nó không nói với bạn.
Mike DeSimone

MUMPS ban đầu là một ngôn ngữ giống như Cơ bản với các mảng kết hợp (có thể được lưu trữ trực tiếp trên đĩa!). Các phiên bản sau này chứa các phần mở rộng thủ tục, làm cho nó rất giống với PHP lõi. Một người sợ Basic và PHP sẽ thấy Quai bị đáng sợ, nhưng những người khác thì không. Lập trình viên không. Và hãy nhớ rằng, đó là một hệ thống rất cũ, tất cả những điều kỳ lạ, như hướng dẫn một chữ cái (altough, bạn có thể sử dụng tên đầy đủ), thứ tự đánh giá LR, v.v. - cũng như các giải pháp không lạ - chỉ có một mục tiêu: tối ưu hóa .
ern0

"Chúng ta nên quên đi những hiệu quả nhỏ, nói khoảng 97% thời gian: tối ưu hóa sớm là gốc rễ của mọi tội lỗi. Tuy nhiên, chúng ta không nên bỏ qua cơ hội của mình trong 3% quan trọng đó." - Donald Knuth. Những gì bạn mô tả âm thanh với tôi như một ngôn ngữ kế thừa có điểm nhấn là khả năng tương thích ngược, và điều đó không sao cả. Cá nhân, trong các ứng dụng đó, tôi coi khả năng bảo trì quan trọng hơn tối ưu hóa và một ngôn ngữ có biểu thức không đại số và các lệnh một chữ cái nghe có vẻ phản tác dụng. Tôi phục vụ khách hàng, không phải ngôn ngữ.
Mike DeSimone

@Mike: các liên kết rất thú vị mà bạn đã đăng, tôi đã có một tiếng cười vui vẻ khi đọc chúng.
đưa đón87

2

Tôi không học Java, C \ C ++, hội và Java Script. Tôi sử dụng C ++ để kiếm sống.

Mặc dù vậy, tôi sẽ phải nói rằng tôi thích lập trình hội và lập trình C hơn. Đây là nội tuyến chủ yếu với lập trình mệnh lệnh.

Tôi biết rằng các Paradigms lập trình rất quan trọng để phân loại các loại dữ liệu và đưa ra các khái niệm trừu tượng hóa lập trình cao hơn để cho phép các thiết kế mạnh mẽ và chính thức hóa mã. Mặc dù theo một nghĩa nào đó, mỗi Paradigms là một tập hợp các mẫu và bộ sưu tập để trừu tượng hóa lớp phần cứng bên dưới, do đó bạn không cần phải suy nghĩ về EAX hoặc IP bên trong máy.

Vấn đề duy nhất của tôi với điều này, là nó cho phép các khái niệm và khái niệm về cách thức hoạt động của cỗ máy biến thành tư tưởng và mơ hồ về những gì đang diễn ra. Bánh mì này là tất cả các loại trừu tượng tuyệt vời trên đầu trang tóm tắt cho một số mục tiêu ý thức hệ của lập trình viên.

Vào cuối ngày, tốt hơn hết là bạn nên có một tư duy rõ ràng và ranh giới rõ ràng về CPU là gì và cách thức các máy tính hoạt động dưới mui xe. Tất cả các CPU quan tâm là thực hiện một loạt các hướng dẫn di chuyển dữ liệu vào và ra khỏi bộ nhớ vào một thanh ghi và thực hiện một lệnh. Nó không có khái niệm về kiểu dữ liệu, hoặc bất kỳ khái niệm lập trình cao hơn. Nó chỉ di chuyển dữ liệu xung quanh.

Nó trở nên phức tạp hơn khi bạn thêm các mô hình lập trình vào hỗn hợp vì quan điểm của chúng ta về thế giới hoàn toàn khác nhau.


2

Có một số tính năng hoặc thành ngữ ngôn ngữ mạnh mẽ mà bạn sử dụng trong một ngôn ngữ sẽ khó khái niệm hóa hoặc thực hiện nếu bạn chỉ viết bằng c ++?

Có bất kỳ khái niệm hoặc kỹ thuật hữu ích nào mà bạn gặp phải trong các ngôn ngữ khác mà bạn cảm thấy khó khái niệm hóa nếu bạn đã viết hoặc "suy nghĩ" trong c ++ không?

C ++ làm cho nhiều cách tiếp cận khó hiểu. Tôi sẽ đi xa hơn để nói rằng hầu hết các chương trình khó có thể khái niệm hóa nếu bạn giới hạn bản thân mình trong C ++. Dưới đây là một số ví dụ về các vấn đề dễ giải quyết hơn nhiều theo cách mà C ++ gây khó khăn.

Đăng ký phân bổ và gọi công ước

Nhiều người nghĩ về C ++ như một ngôn ngữ cấp thấp bằng kim loại nhưng thực sự không phải vậy. Bằng cách trừu tượng hóa các chi tiết quan trọng của máy, C ++ khiến bạn khó có thể khái niệm hóa thực tiễn như phân bổ đăng ký và gọi các quy ước.

Để tìm hiểu về các khái niệm như thế này, tôi khuyên bạn nên tham gia một số chương trình ngôn ngữ lắp ráp và xem bài viết này về chất lượng tạo mã ARM .

Tạo mã thời gian chạy

Nếu bạn chỉ biết C ++ thì có lẽ bạn nghĩ rằng các mẫu là tất cả và cuối cùng của siêu lập trình. Họ không. Trong thực tế, chúng là một công cụ khách quan xấu cho siêu lập trình. Bất kỳ chương trình nào thao túng một chương trình khác là một siêu dữ liệu, bao gồm trình thông dịch, trình biên dịch, hệ thống đại số máy tính và các định lý định lý. Tạo mã thời gian chạy là một tính năng hữu ích cho việc này.

Tôi khuyên bạn nên kích hoạt triển khai Đề án và chơi với EVALđể tìm hiểu về đánh giá siêu vòng tròn.

Thao tác trên cây

Cây cối ở khắp mọi nơi trong lập trình. Trong phân tích cú pháp bạn có cây cú pháp trừu tượng. Trong trình biên dịch, bạn có IR là cây. Trong lập trình đồ họa và GUI, bạn có cây cảnh.

Đây "Ridiculously Simple JSON Parser cho C ++" nặng trong lúc chỉ 484 LỘC mà là rất nhỏ so với C ++. Bây giờ hãy so sánh nó với trình phân tích cú pháp JSON đơn giản của riêng tôi , chỉ nặng 60 LỘC F #. Sự khác biệt chủ yếu là do các kiểu dữ liệu đại số và khớp mẫu của ML (bao gồm cả các mẫu hoạt động) làm cho việc điều khiển cây dễ dàng hơn rất nhiều.

Kiểm tra cây đỏ-đen ở OCaml quá.

Cấu trúc dữ liệu hoàn toàn chức năng

Thiếu GC trong C ++ khiến cho thực tế không thể áp dụng một số phương pháp hữu ích. Cấu trúc dữ liệu chức năng hoàn toàn là một công cụ như vậy.

Ví dụ: kiểm tra trình so khớp biểu thức chính quy 47 dòng này trong OCaml. Sự ngắn gọn này chủ yếu là do việc sử dụng rộng rãi các cấu trúc dữ liệu chức năng thuần túy. Đặc biệt, việc sử dụng từ điển với các khóa được đặt. Điều đó thực sự khó thực hiện trong C ++ vì các từ điển và bộ stdlib đều có thể thay đổi nhưng bạn không thể thay đổi các khóa của từ điển hoặc bạn phá vỡ bộ sưu tập.

Lập trình logic và hoàn tác bộ đệm là những ví dụ thực tế khác trong đó các cấu trúc dữ liệu chức năng thuần túy làm cho một thứ gì đó khó trong C ++ thực sự dễ dàng trong các ngôn ngữ khác.

Các cuộc gọi đuôi

C ++ không chỉ không đảm bảo các cuộc gọi đuôi mà về cơ bản RAII còn mâu thuẫn với nó bởi vì các kẻ hủy diệt cản trở cuộc gọi ở vị trí đuôi. Các cuộc gọi đuôi cho phép bạn thực hiện một số lượng các cuộc gọi chức năng không giới hạn chỉ bằng một lượng không gian ngăn xếp bị giới hạn. Điều này là tuyệt vời để thực hiện các máy trạng thái, bao gồm cả các máy trạng thái có thể mở rộng và nó là một thẻ "thoát khỏi tù" tuyệt vời trong nhiều trường hợp khó xử.

Ví dụ: kiểm tra việc triển khai vấn đề về chiếc ba lô 0-1 này bằng cách sử dụng kiểu tiếp tục với sự ghi nhớ trong F # từ ngành tài chính. Khi bạn có các cuộc gọi đuôi, kiểu chuyển tiếp tiếp tục có thể là một giải pháp rõ ràng nhưng C ++ làm cho nó không thể truy cập được.

Đồng thời

Một ví dụ rõ ràng khác là lập trình đồng thời. Mặc dù điều này là hoàn toàn có thể có trong C ++ nhưng nó rất dễ bị lỗi so với các công cụ khác, đáng chú ý nhất là truyền đạt các quy trình tuần tự như đã thấy trong các ngôn ngữ như Erlang, Scala và F #.


1

Đây là một câu hỏi cũ, nhưng vì không ai đề cập đến nó, tôi sẽ thêm danh sách (và bây giờ là chính tả). Thật dễ dàng để viết một lớp lót trong Haskell hoặc Python để giải quyết vấn đề Fizz-Buzz. Hãy thử làm điều đó trong C ++.

Mặc dù C ++ đã có những bước chuyển lớn sang hiện đại với C ++ 11, nhưng thật khó để gọi nó là ngôn ngữ "hiện đại". C ++ 17 (chưa được phát hành) đang tạo ra nhiều động thái hơn để đạt được các tiêu chuẩn hiện đại, miễn là "hiện đại" có nghĩa là "không phải từ thiên niên kỷ trước".

Ngay cả những cách hiểu đơn giản nhất mà người ta có thể viết bằng một dòng trong Python (và tuân theo giới hạn độ dài 79 ký tự của Guido) trở thành rất nhiều dòng mã khi được dịch sang C ++ và một số dòng mã C ++ đó khá phức tạp.


Lưu ý tốt: Hầu hết các chương trình của tôi là trong C ++. Tôi thích ngôn ngữ
David Hammen

Tôi nghĩ rằng Đề xuất Ranges có nghĩa vụ phải giải quyết vấn đề này? (Tôi thậm chí không ở C ++ 17)
Martin Ba

2
"Chuyển động lớn đến hiện đại": C ++ 11 cung cấp các tính năng "hiện đại" nào được phát minh trong thiên niên kỷ hiện tại?
Giorgio

@MartinBa - Sự hiểu biết của tôi về đề xuất "phạm vi" là nó thay thế cho các trình vòng lặp dễ làm việc hơn và ít bị lỗi hơn. Tôi chưa thấy bất kỳ đề xuất nào cho phép họ cho phép mọi thứ thú vị như việc hiểu danh sách.
Jules

2
@Giorgio - những tính năng của bất kỳ ngôn ngữ phổ biến hiện nay đã được phát minh trong thiên niên kỷ hiện tại?
Jules

0

Một thư viện được biên dịch gọi một cuộc gọi lại, là một hàm thành viên do người dùng định nghĩa của một lớp do người dùng định nghĩa.


Điều này có thể có trong Objective-C và nó làm cho việc lập trình giao diện người dùng trở nên dễ dàng. Bạn có thể nói với một nút: "Xin vui lòng, gọi phương thức này cho đối tượng này khi bạn được nhấn" và nút sẽ làm như vậy. Bạn có thể sử dụng bất kỳ tên phương thức nào cho cuộc gọi lại mà bạn thích, nó không bị đóng băng trong mã thư viện, bạn không phải kế thừa từ một bộ điều hợp để nó hoạt động, trình biên dịch cũng không muốn giải quyết cuộc gọi trong thời gian biên dịch, và, không kém phần quan trọng, bạn có thể yêu cầu hai nút gọi hai phương thức khác nhau của cùng một đối tượng.

Tôi chưa thấy một cách linh hoạt tương tự để xác định một cuộc gọi lại bằng bất kỳ ngôn ngữ nào khác (mặc dù tôi rất muốn nghe về chúng!). Tương đương gần nhất trong C ++ có lẽ là truyền một hàm lambda thực hiện cuộc gọi được yêu cầu, một lần nữa hạn chế mã thư viện là một mẫu.

Chính tính năng này của Objective-C đã dạy tôi đánh giá khả năng của ngôn ngữ để vượt qua bất kỳ loại đối tượng / chức năng / bất cứ điều gì quan trọng-khái niệm-ngôn ngữ-chứa xung quanh một cách tự do, cùng với sức mạnh để cứu chúng biến. Bất kỳ điểm nào trong ngôn ngữ xác định bất kỳ loại khái niệm nào, nhưng không cung cấp phương tiện để lưu trữ nó (hoặc tham chiếu đến nó) trong tất cả các loại biến có sẵn, là một trở ngại đáng kể và có thể là một nguồn rất xấu, mã trùng lặp. Thật không may, các ngôn ngữ lập trình baroque có xu hướng thể hiện một số điểm sau:

  • Trong C ++, bạn không thể viết ra loại VLA, cũng không lưu con trỏ vào đó. Điều này thực sự nghiêm cấm các mảng đa chiều thực sự có kích thước động (có sẵn trong C kể từ C99).

  • Trong C ++, bạn không thể viết loại lambda. Bạn thậm chí không thể gõ nó. Vì vậy, không có cách nào để vượt qua lambda, hoặc lưu trữ một tham chiếu đến nó trong một đối tượng. Các hàm Lambda chỉ có thể được truyền cho các mẫu.

  • Trong Fortran, bạn không thể viết ra loại bảng tên. Đơn giản là không có cách nào để vượt qua một người đặt tên cho bất kỳ loại thói quen nào. Vì vậy, nếu bạn có một thuật toán phức tạp có thể xử lý hai tên khác nhau, bạn sẽ không gặp may. Bạn không thể chỉ viết thuật toán một lần và chuyển các bảng tên có liên quan đến nó.

Đây chỉ là một vài ví dụ, nhưng bạn thấy điểm chung: Bất cứ khi nào bạn thấy hạn chế như vậy lần đầu tiên, bạn thường không quan tâm vì dường như đó là một ý tưởng điên rồ khi làm điều cấm. Tuy nhiên, khi bạn thực hiện một số chương trình nghiêm túc bằng ngôn ngữ đó, cuối cùng bạn sẽ đến lúc hạn chế chính xác này trở thành một mối phiền toái thực sự.


1
I have not seen a similarly flexible way to define a callback in any other language yet (though I'd be very interested to hear about them!) Những gì bạn vừa mô tả âm thanh chính xác như cách mã UI hướng sự kiện hoạt động trong Delphi. (Và trong .NET WinForms, chịu ảnh hưởng lớn từ Delphi.)
Mason Wheeler

2
"Trong C ++, bạn không thể viết loại VLA" [...] - trong C ++, các VLA kiểu C99 là không cần thiết, bởi vì chúng tôi có std::vector. Mặc dù nó kém hiệu quả hơn một chút do không sử dụng phân bổ ngăn xếp, nhưng nó có cấu trúc đồng nhất với VLA, do đó, không thực sự được coi là một vấn đề kiểu "blub": Các lập trình viên C ++ có thể nhìn vào cách nó hoạt động và chỉ cần nói, "ah vâng , C làm điều đó hiệu quả hơn C ++ ".
Jules

2
"Trong C ++, bạn không thể viết ra loại lambda. Thậm chí bạn không thể gõ nó. Vì vậy, không có cách nào để vượt qua lambda, hoặc lưu trữ một tham chiếu đến nó trong một đối tượng" - đó là những gì std::functiondành cho.
Jules

3
"Tôi chưa thấy một cách linh hoạt tương tự để xác định một cuộc gọi lại bằng bất kỳ ngôn ngữ nào khác (mặc dù tôi rất muốn nghe về chúng!)." - trong Java, bạn có thể viết object::methodvà nó sẽ được chuyển đổi thành một thể hiện của bất kỳ giao diện nào mà mã nhận được mong đợi. C # có đại biểu. Mọi ngôn ngữ chức năng đối tượng đều có tính năng này bởi vì về cơ bản, nó là điểm của mặt cắt ngang của hai mô hình.
Jules

@Jules Đối số của bạn chính xác là những gì Blub-Paradox nói về: Là một lập trình viên C ++ thành thạo, bạn không xem đây là những hạn chế. Tuy nhiên, chúng là những hạn chế và các ngôn ngữ khác như C99 mạnh hơn ở những điểm cụ thể này. Đến điểm cuối cùng của bạn: Có nhiều cách giải quyết có thể có trong nhiều ngôn ngữ, nhưng tôi không biết một ngôn ngữ thực sự cho phép bạn chuyển tên của bất kỳ phương thức nào cho một lớp khác và gọi nó trên một số đối tượng mà bạn cung cấp.
cmaster
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.