Một ngôn ngữ dựa trên việc giới hạn số lượng đối số được truyền cho các hàm


16

Ý tưởng được lấy cảm hứng từ các toán tử thực tế như +, -,%, v.v. có thể được xem là các hàm với một hoặc hai đối số được truyền và không có tác dụng phụ. Giả sử tôi hoặc ai đó viết một ngôn ngữ ngăn chặn nhiều hơn hai đối số được thông qua và cũng chỉ hoạt động thông qua giá trị trả về:

a) một ngôn ngữ như vậy sẽ dẫn đến dễ hiểu mã hơn?

b) dòng chảy của mã sẽ rõ ràng hơn? (buộc phải thực hiện nhiều bước hơn, với khả năng tương tác ít hơn 'ẩn'

c) các hạn chế sẽ làm cho ngôn ngữ trở nên cồng kềnh cho các chương trình phức tạp hơn.

d) (tiền thưởng) bất kỳ ý kiến ​​khác về ưu / nhược điểm

Ghi chú:

Hai quyết định vẫn sẽ phải được đưa ra - đầu tiên là liệu có cho phép người dùng nhập bên ngoài main () hoặc tương đương hay không, và quy tắc sẽ liên quan đến những gì xảy ra khi truyền mảng / cấu trúc. Ví dụ, nếu ai đó muốn một hàm duy nhất để thêm nhiều giá trị, anh ta có thể vượt qua giới hạn bằng cách gói nó vào một mảng. Điều này có thể được dừng lại bằng cách không cho phép một mảng hoặc cấu trúc tương tác với chính nó, ví dụ, vẫn sẽ cho phép bạn chia mỗi số cho một số lượng khác nhau, tùy thuộc vào vị trí của nó.


4
Chào. Danh sách ưu và nhược điểm có xu hướng đưa ra câu trả lời xấu. Có cách nào bạn có thể viết lại câu hỏi của bạn để vẫn có được thông tin bạn cần nhưng ở định dạng khác không?
MetaFight

22
Lý luận của bạn thậm chí không bắt đầu có ý nghĩa với tôi. Một số hàm có vài đối số vì vậy hãy giới hạn tất cả các hàm? Thông thường khi một người đề xuất các hạn chế tùy ý, có một lý do, một cái gì đó để đạt được. Tôi không thể thấy những gì điều này có thể có được bạn.

2
Không phải là có bất cứ điều gì sai với câu hỏi 'what if' (mặc dù đôi khi chúng khó trả lời như @MetaFight đã nói), nhưng ngay cả khi bạn, người nghĩ về điều đó và quan tâm đủ để đặt câu hỏi, thực sự không thể đặt tên lợi ích, sau đó tôi khá chắc chắn phản ứng ban đầu của tôi về "cái gì? không! thật ngu ngốc tại sao bạn lại làm thế" là chính xác.

6
Có khá nhiều ngôn ngữ chỉ cho phép một đối số duy nhất cho mỗi hàm: mọi thứ dựa trên phép tính lambda. Kết quả thường là một hàm lấy một đối số danh sách hoặc một hàm trả về một hàm lấy đối số tiếp theo cho đến khi tất cả các đối số đã được xử lý : result = f(a)(b)…(z). Đây là trường hợp trong họ ngôn ngữ ML như Haskell, nhưng cũng có khái niệm trong các ngôn ngữ khác như Lisp, JavaScript hoặc Perl.
amon

3
@Orangesandlemons: Được rồi, sau đó tôi có thể mã hóa một số nguyên tùy ý trong một số nguyên chỉ bằng cách nhân và cộng (để mã hóa) và chia và trừ (để giải mã). Vì vậy, bạn cũng không cho phép các số nguyên, hoặc ít nhất là nhân, cộng, chia và trừ. (Một hậu quả của sức mạnh của lập trình là bạn có thể mã hóa hầu hết mọi thứ bằng cách sử dụng hầu hết mọi thứ, và do đó hạn chế mọi thứ thực sự rất khó khăn. Nói chung, các hạn chế không thực sự "hạn chế" bất cứ điều gì, chúng chỉ gây khó chịu cho các lập trình viên.)
Jörg Wagag

Câu trả lời:


40

Robert C. Martin trong cuốn sách "Clean Code" khuyến nghị sử dụng tối đa các hàm với tối đa 0, 1 hoặc 2 tham số, do đó, ít nhất có một tác giả sách có kinh nghiệm nghĩ rằng mã trở nên sạch hơn bằng cách sử dụng kiểu này (tuy nhiên, ông là chắc chắn không phải là cơ quan tối cao ở đây, và ý kiến ​​của ông đang gây tranh cãi).

Trong đó Bob Martin là IMHO đúng là: các hàm có từ 3 tham số trở lên thường là các chỉ báo cho mùi mã. Trong nhiều trường hợp, các tham số có thể được nhóm lại với nhau để tạo thành một kiểu dữ liệu kết hợp, trong các trường hợp khác, nó có thể là một chỉ báo cho hàm đơn giản là làm quá nhiều.

Tuy nhiên, tôi không nghĩ rằng sẽ là một ý tưởng tốt để phát minh ra một ngôn ngữ mới cho việc này:

  • nếu bạn thực sự muốn thực thi quy tắc như vậy trong toàn bộ mã của mình, bạn chỉ cần một công cụ phân tích mã cho ngôn ngữ hiện có, không cần phải phát minh một ngôn ngữ hoàn toàn mới cho điều này (ví dụ, đối với C #, một cái gì đó như 'fxcop' có thể được sử dụng ).

  • đôi khi, việc kết hợp các tham số thành một loại mới dường như không có giá trị rắc rối, hoặc nó sẽ trở thành một sự kết hợp nhân tạo thuần túy. Xem, ví dụ, phương pháp nàyFile.Open từ khung .Net. Phải mất bốn tham số và tôi khá chắc chắn rằng các nhà thiết kế API đó đã cố tình làm điều này, bởi vì họ nghĩ rằng đó sẽ là cách thực tế nhất để cung cấp các tham số khác nhau cho hàm.

  • đôi khi có các kịch bản trong thế giới thực trong đó có nhiều hơn 2 tham số làm cho mọi thứ đơn giản hơn vì lý do kỹ thuật (ví dụ: khi bạn cần ánh xạ 1: 1 đến API hiện có, nơi bạn bị ràng buộc với việc sử dụng các kiểu dữ liệu đơn giản và không thể kết hợp khác nhau tham số vào một đối tượng tùy chỉnh)


16
Mùi với nhiều thông số thường là các thông số khác nhau thực sự thuộc về nhau. Lấy ví dụ tính toán chỉ số khối cơ thể, BMI. Đó là một chức năng của chiều dài và trọng lượng của một người. f (chiều dài, cân nặng), nhưng hai tham số đó thực sự thuộc về nhau bởi vì bạn có bao giờ thực hiện phép tính này với chiều cao của một người và cân nặng của người khác không? Vì vậy, để thể hiện điều đó tốt hơn, bạn sẽ có được f (người) nơi người đó có thể có giao diện trọng lượng, chiều dài.
Pieter B

@PieterB: tất nhiên, xem chỉnh sửa của tôi.
Doc Brown

5
Ngôn ngữ nhỏ xíu , nhỏ xíu : "có thể chỉ ra mùi mã" Không phải là mùi mã theo định nghĩa chỉ là một dấu hiệu cho thấy bạn nên xem xét lại một cái gì đó, ngay cả khi cuối cùng bạn không thay đổi mã? Vì vậy, một mùi mã sẽ không được "chỉ định." Nếu một khía cạnh cụ thể chỉ ra khả năng của một vấn đề, thì đó mùi mã. Không?
jpmc26

6
@ jpmc26: Từ quan điểm khác, mùi mã là một vấn đề có thể xảy ra, không phải là dấu hiệu của một ;-) Tất cả phụ thuộc vào định nghĩa chính xác của mùi mã (và một khi nó ngửi thấy, nó đã trở nên tồi tệ ?)
hoffmale

3
@PieterB Có ai thực sự làm điều này mặc dù? Bây giờ bạn phải tạo một cá thể Người mới mỗi lần bạn chỉ muốn tính chỉ số BMI với hai giá trị tùy ý. Chắc chắn, nếu ứng dụng của bạn đã sử dụng Người bắt đầu và bạn thấy mình thường xuyên làm một việc gì đó như f (person.length, person.height), bạn có thể dọn sạch nó một chút nhưng tạo các đối tượng mới cụ thể cho các tham số nhóm có vẻ quá mức.
Luật sư

47

Có rất nhiều ngôn ngữ đã hoạt động theo cách này, ví dụ Haskell. Trong Haskell, mọi hàm lấy chính xác một đối số và trả về chính xác một giá trị.

Luôn luôn có thể thay thế một hàm lấy n đối số bằng một hàm lấy các đối số n-1 và trả về một hàm lấy đối số cuối cùng. Áp dụng đệ quy này, luôn có thể thay thế một hàm lấy số lượng đối số tùy ý bằng một hàm có chính xác một đối số. Và sự chuyển đổi này có thể được thực hiện một cách cơ học, bằng một thuật toán.

Cái này được gọi là Frege-Schönfinkeling, Schönfinkeling, Schönfinkel-Currying, hay Currying, sau khi Haskell Curry, người đã nghiên cứu nó rộng rãi vào những năm 1950, Moses Schönfinkel, người đã mô tả nó vào năm 1924, và Gottlob Frege.

Nói cách khác, việc hạn chế số lượng đối số có tác động chính xác bằng không.


2
Đó là nếu bạn cho phép các chức năng được trả lại, tất nhiên. Thậm chí nếu không, tất cả những gì bạn phải làm là có chức năng tiếp theo được gọi trong chương trình chính. Tức là bạn có thể có 1 + 1 + 1 trong đó phép cộng đầu tiên là hàm trả về hàm cho phép cộng bổ sung hoặc đơn giản có thể được gọi hai lần. Về mặt lý thuyết, phong cách thứ hai sẽ sạch hơn, hay tôi nhầm?

5
"có tác động chính xác bằng không" - Câu hỏi của OP là liệu khả năng đọc mã sẽ tăng hay giảm và tôi đoán bạn không yêu cầu quyết định thiết kế như vậy đối với ngôn ngữ không ảnh hưởng đến điều đó, phải không?
Doc Brown

3
@DocBrown Với sức mạnh khá và nhà điều hành quá tải, tôi có thể sử dụng ngôn ngữ cà ri và làm cho nó trông giống như một ngôn ngữ chưa được xử lý. Ví dụ: f *(call_with: a,b,c,d,e) Quá tải call_with :để bắt đầu một chuỗi, ,để mở rộng chuỗi và *trên LHS để gọi fbằng cách chuyển từng chuỗi nội dung của chuỗi một lần. Một hệ thống nạp chồng toán tử đủ yếu chỉ làm cho cú pháp trở nên cồng kềnh, nhưng đó là lỗi của hệ thống nạp chồng toán tử nhiều hơn bất cứ điều gì.
Yakk

Trên thực tế, currying của Haskell làm giảm một hàm với n đối số thành một hàm với một đối số trả về một hàm khác với n - 1 đối số.
Ryan Reich

2
@RyanReich Bạn nói đúng rằng bạn có thể thấy một "bóng ma" của "số lượng đối số" của hàm Haskell trong chữ ký loại của nó. Đó là một con ma chứ không phải là một chữ ký thật bởi vì nói chung bạn không có cách nào để biết liệu biến loại cuối cùng trong chữ ký cũng là một loại hàm. Ở bất kỳ giá nào, con ma này ở đó không làm mất hiệu lực thực tế là trong Haskell, tất cả các hàm đều lấy 1 đối số và trả về giá trị không phải hàm hoặc hàm khác cũng có 1 đối số. Điều này được tích hợp vào tính kết hợp của ->: a-> b-> c là a -> (b-> c). Vì vậy, bạn chủ yếu là sai ở đây.
Ian

7

Tôi đã dành một chút thời gian trong vài tuần qua để cố gắng học ngôn ngữ máy tính J. Trong J, hầu hết mọi thứ đều là toán tử, vì vậy bạn chỉ nhận được "monads" (các hàm chỉ có một đối số) và "dyads" (các hàm có chính xác hai đối số). Nếu bạn cần thêm đối số, bạn phải cung cấp chúng trong một mảng hoặc cung cấp chúng trong "hộp".

J có thể rất súc tích, nhưng giống như APL tiền nhiệm của nó, nó cũng có thể rất khó hiểu - nhưng điều này chủ yếu là kết quả của mục tiêu của người sáng tạo để mô phỏng tính cô đọng toán học. Có thể làm cho chương trình J dễ đọc hơn bằng cách sử dụng tên thay vì ký tự để tạo toán tử.


ah, vì vậy nó cho phép các mảng tương tác với chính họ.

5

Một ngôn ngữ dựa trên cách nó ràng buộc nhà phát triển phụ thuộc vào giả định rằng nhà phát triển ngôn ngữ hiểu nhu cầu của từng lập trình viên tốt hơn là lập trình viên hiểu chính những nhu cầu đó. Có những trường hợp điều này thực sự hợp lệ. Ví dụ, các ràng buộc đối với lập trình đa luồng yêu cầu đồng bộ hóa bằng cách sử dụng mutexes và semaphores được nhiều người coi là "tốt" bởi vì hầu hết các lập trình viên hoàn toàn không biết về các phức tạp cụ thể của máy mà những ràng buộc đó ẩn giấu khỏi chúng. Tương tự như vậy, ít người muốn nắm bắt hoàn toàn các sắc thái của thuật toán thu gom rác đa luồng; một ngôn ngữ đơn giản là không cho phép bạn phá vỡ thuật toán GC được ưu tiên hơn một ngôn ngữ buộc người lập trình phải nhận thức được quá nhiều sắc thái.

Bạn sẽ phải đưa ra một lập luận hợp lệ về lý do tại sao, với tư cách là một nhà phát triển ngôn ngữ, bạn hiểu đối số vượt qua tốt hơn nhiều so với các lập trình viên sử dụng ngôn ngữ của bạn rằng có giá trị trong việc ngăn họ làm những việc mà bạn cho là có hại. Tôi nghĩ rằng đó sẽ là một lập luận khó khăn để thực hiện.

Bạn cũng phải biết rằng các lập trình viên sẽ làm việc xung quanh các ràng buộc của bạn. Nếu họ cần 3 đối số trở lên, họ sẽ sử dụng các kỹ thuật như currying để biến chúng thành các cuộc gọi ít đối số hơn. Tuy nhiên, điều này thường đi kèm với chi phí dễ đọc, hơn là cải thiện nó.

Hầu hết các ngôn ngữ tôi biết với loại quy tắc này là esolang, ngôn ngữ được thiết kế để chứng minh rằng bạn thực sự có thể hoạt động với một bộ chức năng hạn chế. Cụ thể, các esolang nơi mỗi ký tự là một opcode có xu hướng giới hạn số lượng đối số, đơn giản vì chúng cần giữ danh sách các opcodes ngắn.


Đây là câu trả lời tốt nhất.
Jared Smith

1

Bạn sẽ cần hai điều:

  • Khép kín
  • Kiểu dữ liệu tổng hợp

Tôi sẽ thêm một ví dụ toán học để giải thích câu trả lời được viết bởi Jörg W Mittag .

Xét hàm Gaussian .

Hàm Gaussian có hai tham số cho hình dạng của nó, đó là giá trị trung bình (vị trí trung tâm của đường cong) và phương sai (liên quan đến độ rộng xung của đường cong). Ngoài hai tham số, người ta cũng cần cung cấp giá trị của biến miễn phí xđể đánh giá nó.

Trong bước đầu tiên, chúng ta sẽ thiết kế hàm Gaussian có cả ba tham số, đó là giá trị trung bình, phương sai và biến tự do.

Trong bước thứ hai, chúng tôi tạo ra một kiểu dữ liệu tổng hợp kết hợp giá trị trung bình và phương sai thành một thứ.

Trong bước thứ ba, chúng ta tạo một tham số hóa của hàm Gaussian bằng cách tạo một bao đóng của hàm Gaussian liên kết với kiểu dữ liệu tổng hợp mà chúng ta đã tạo trong bước thứ hai.

Cuối cùng, chúng tôi đánh giá việc đóng được tạo ở bước thứ ba bằng cách chuyển giá trị của biến miễn phí xcho nó.

Do đó, cấu trúc là:

  • Đánh giá (tính toán)
    • ParameterizedGaussian (bao đóng: công thức, cộng với một số biến bị ràng buộc)
      • GaussianParameter (kiểu dữ liệu tổng hợp)
        • Giá trị trung bình)
        • Phương sai (giá trị)
    • X (giá trị của biến miễn phí)

1
  1. Chỉ trong bất kỳ ngôn ngữ lập trình nào, bạn có thể chuyển một số loại danh sách, mảng, tuple, bản ghi hoặc đối tượng làm đối số duy nhất. Mục đích duy nhất của nó là giữ các vật phẩm khác thay vì chuyển chúng đến một chức năng riêng lẻ. Một số Java IDE thậm chí còn có tính năng " Trích xuất đối tượng tham số " để làm việc đó. Trong nội bộ, Java thực hiện số lượng đối số biến bằng cách tạo và truyền một mảng.

  2. Nếu bạn thực sự muốn làm những gì bạn đang nói ở dạng tinh khiết nhất, bạn cần xem xét tính toán lambda. Đó chính xác là những gì bạn mô tả. Bạn có thể tìm kiếm trên web cho nó, nhưng mô tả có ý nghĩa với tôi là trong Các loại và Ngôn ngữ lập trình .

  3. Nhìn vào ngôn ngữ lập trình HaskellML (ML đơn giản hơn). Cả hai đều dựa trên tính toán lambda và về mặt khái niệm chỉ có một tham số cho mỗi hàm (nếu bạn nheo mắt một chút).

  4. Mục 2 của Josh Bloch là: "Hãy xem xét một người xây dựng khi phải đối mặt với nhiều tham số của hàm tạo." Bạn có thể thấy điều này dài dòng như thế nào , nhưng thật vui khi làm việc với một API được viết theo cách này.

  5. Một số ngôn ngữ đã đặt tên tham số là một cách tiếp cận khác để làm cho chữ ký phương thức lớn dễ dàng điều hướng hơn nhiều. Kotlin đã đặt tên đối số chẳng hạn.

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.