2009-10-10 29 views
11

Là một newbie trong các ngôn ngữ chức năng (tôi bắt đầu chạm vào Erlang một vài tuần trước - ngôn ngữ chức năng đầu tiên tôi có thể nhận được bàn tay của tôi trên).Lời khuyên về Học tập "Cách suy nghĩ về chức năng"?

Tôi bắt đầu viết một số thuật toán nhỏ (chẳng hạn như left_rotate_list, bubble_sort,merge_sort v.v.). Tôi thấy mình thường bị lạc trong các quyết định như "tôi có nên sử dụng Danh sách trợ giúp để lưu trữ kết quả trung gian không?" và "tôi có nên tạo một hàm trợ giúp để làm điều này không?" Sau một thời gian, tôi thấy rằng lập trình chức năng (chịu với tôi nếu những gì tôi đang nói không có ý nghĩa gì cả) khuyến khích một "trên xuống" thiết kế: tức là, khi tôi làm merge_sort, trước tiên bạn viết xuống tất cả các hợp nhất các bước sắp xếp và đặt tên chúng là các hàm trợ giúp riêng lẻ; và sau đó bạn thực hiện các hàm trợ giúp đó từng cái một (và nếu bạn cần phân chia thêm các hàm trợ giúp đó, hãy thực hiện nó theo cùng cách tiếp cận).

Điều này dường như mâu thuẫn với thiết kế OO một chút, trong đó bạn có thể bắt đầu từ phía dưới để xây dựng cấu trúc dữ liệu cơ bản, sau đó lắp ráp cấu trúc dữ liệu và thuật toán vào những gì bạn muốn.

Cảm ơn nhận xét. Vâng, tôi muốn nhận được lời khuyên về cách "suy nghĩ trong ngôn ngữ chức năng" (giống như "suy nghĩ trong Java", "suy nghĩ trong C++").

+0

Câu hỏi là gì? :-) – csl

+0

Bạn muốn gợi ý cho "hướng dẫn lập trình chức năng cho các lập trình viên OOP?" – Mahin

+0

Xin chào, hãy cập nhật câu hỏi. Cảm ơn bạn đã trả lời – chen

Trả lời

4

Sau một thời gian, tôi thấy rằng lập trình chức năng […] khuyến khích thiết kế "từ trên xuống".

Tôi không chắc đây là tuyên bố chính xác hay không. Gần đây tôi đã cố gắng tự học lập trình chức năng, và tôi thấy rằng kiểu lập trình "từ dưới lên" thực sự giúp tôi. Để sử dụng ví dụ về loại hợp nhất của bạn:

  • Đầu tiên hãy xem trường hợp cơ sở. Làm thế nào để bạn sắp xếp một mảng của 0/1 yếu tố?
  • Tiếp theo, hãy xem các trường hợp cơ bản + 1, cơ sở + 2,…. Cuối cùng, bạn sẽ thấy một mẫu (phân tách thành các vấn đề con, giải các bài toán con, kết hợp các phân đoạn) cho phép bạn viết một trường hợp đệ quy chung hơn là cuối cùng đạt đến trường hợp cơ bản.
  • Chia nhỏ thành các bài toán con rất dễ dàng, nhưng việc kết hợp các giải pháp này khó hơn một chút. Bạn cần một cách để hợp nhất hai mảng được sắp xếp thành một mảng được sắp xếp.
  • Bây giờ, hãy đặt mọi thứ lại với nhau. Xin chúc mừng, bạn vừa viết loại hợp nhất. :)

Tôi có thể sử dụng sai thuật ngữ này, nhưng điều này giống như thiết kế từ dưới lên đối với tôi. Lập trình chức năng khác với lập trình hướng đối tượng, nhưng bạn không cần phải bỏ hoàn toàn các kỹ thuật thiết kế hiện có khi chuyển đổi giữa hai kỹ thuật.

10

Câu trả lời là lập trình chức năng là chương trình sử dụng các hàm, vì chúng được định nghĩa trong toán học (trong ngắn hạn, các tác dụng phụ miễn phí ánh xạ giá trị từ miền đến tên miền). Để thực sự dịch thành "cách suy nghĩ" là phần vẫy tay khó hiểu, nhưng tôi sẽ lấy mẫu một số suy nghĩ của tôi:

  1. Định nghĩa quan trọng hơn hiệu quả. Đó là, việc thực hiện chính xác một chức năng rõ ràng mà người ta có thể hiểu tất cả hành vi của nó là tốt hơn so với một hành vi được tối ưu hóa phức tạp mà khó có thể giải thích được. (Và nên được ưa thích càng lâu càng tốt, cho đến khi có bằng chứng người ta phải phá vỡ tài sản tốt đẹp này.)
  2. Một hàm toán học không có tác dụng phụ. Một chương trình hữu ích phải có tác dụng phụ. Một lập trình viên chức năng nhận thức được các tác dụng phụ, như một điều rất nguy hiểm và phức tạp, và thiết kế chương trình như một loạt các hàm lấy giá trị đầu ra từ một tác dụng phụ và tạo ra các giá trị đầu vào cho tác dụng phụ tiếp theo.

Số một có liên quan đến mơ hồ: "mã trang nhã". Sự hiểu biết danh sách có thể trình bày rất ngắn gọn và toán học-phương trình như định nghĩa của hàm. Chỉ cần nhìn vào việc sắp xếp nhanh được triển khai với LC. Đây là cách tôi xác định sự thanh lịch, gọn gàng và làm cho mọi hành vi rõ ràng. Không phải là trò chơi mã hóa perl nơi bạn thường xuyên nhất và khó hiểu.

Số hai là thứ mà tôi sử dụng hàng ngày trong tất cả các chương trình. Chia mã thành các hàm (các phương thức, các thường trình, vv ..) của trạng thái hiện tại là các phép tính miễn phí có hiệu lực phụ cho đầu vào cho hành động tiếp theo cần thực hiện (ngay cả hành động tiếp theo cần thực hiện). Khi giá trị được trả về, hãy đưa nó vào một thường trình thực hiện hành động được mô tả, sau đó bắt đầu lại. Trong đầu tôi, tôi vẽ sơ đồ một quy trình Erlang dưới dạng đồ thị máy trạng thái, trong đó mỗi đỉnh là một tác dụng phụ và một hàm có đầu ra là cạnh để chọn ra khỏi đỉnh. Vấn đề cao về tác dụng phụ là một cái gì đó mà mô hình lập trình chức năng đã dạy tôi. Đặc biệt là ở Erlang, vì các tác dụng phụ thực sự quan trọng trong đồng thời, và Erlang làm cho đồng thời rất có sẵn.

Giống như cách một số bộ tộc bị cô lập chỉ có một từ cho số trên 3 hoặc không có từ nào cho "của tôi"/"của bạn". Nó cảm thấy như ngôn ngữ phổ biến không có từ ngữ cho "điều này sẽ gây ra một tác dụng phụ", nhưng lập trình chức năng có nó. Nó buộc bạn phải luôn ý thức được điều đó, và đó là một điều tốt.

2

Tôi thấy mình thường bị lạc trong các quyết định như "tôi có nên sử dụng Danh sách trợ giúp để lưu trữ kết quả trung gian không?" và "tôi có nên tạo một hàm trợ giúp để làm điều này không?"

Lời khuyên của tôi cho điều này: đọc The Little Schemer. Bạn có thể theo dõi nó trong Erlang. Đó là một cuốn sách tốt để khoan một cảm giác này vào bạn.

5

Sau một thời gian, tôi thấy rằng lập trình chức năng [...] khuyến khích thiết kế "từ trên xuống".

Vâng, đó không phải là thiết kế "từ trên xuống" hoặc "dưới lên" thực sự. Đó là về tập trung vào "những gì" của vấn đề ở bàn tay, chứ không phải là "làm thế nào". Khi tôi bắt đầu với lập trình hàm, tôi thấy rằng tôi đã nhớ lại các cấu trúc bắt buộc như vòng lặp for lồng nhau trong C. Sau đó, tôi nhanh chóng phát hiện ra rằng cố gắng dịch suy nghĩ bắt buộc của tôi thành các cấu trúc hàm là rất khó. Tôi sẽ cố gắng cung cấp cho bạn một ví dụ cụ thể hơn. Tôi sẽ thực hiện một chương trình tương đương trong C và Haskell và cố gắng theo dõi quá trình suy nghĩ của tôi trong cả hai trường hợp. Lưu ý rằng tôi đã rõ ràng tiết lộ cho mục đích giải thích.

Trong C:

#include <stdio.h> 

int main(void) 
{ 
    int i, inputNumber, primeFlag = 1; 
    scanf("%d", &inputNumber); 
    for(i = 2; i <= inputNumber/2; i ++) 
    { 
     if (inputNumber % i == 0) 
     { 
      primeFlag = 0; 
      break; 
     } 
    } 
    if (primeFlag == 0) printf("False\n"); 
    else printf ("True\n"); 
    return 0; 
} 

Dấu vết của quá trình suy nghĩ của tôi:

  1. suy nghĩ trong các bước. Đầu tiên, chấp nhận một số từ người dùng. Hãy để số này được gọi là inputNumber. scanf() được viết.
  2. Thuật toán cơ bản: Một số là số nguyên tố trừ khi được chứng minh khác.primeFlag được khai báo và đặt bằng 1.
  3. Kiểm tra primeNumber với mọi số từ 2 đến primeNumber/2. for bắt đầu vòng lặp. Đã khai báo biến vòng lặp i để kiểm tra primeNumber chống lại.
  4. Để bác bỏ xác nhận ban đầu của chúng tôi rằng số đó là số nguyên tố, hãy kiểm tra primeNumber với mỗi số i. Thời điểm chúng tôi tìm thấy ngay cả một số i chia primeNumber, đặt primeFlag thành 0break. Cơ thể vòng lặp được viết.
  5. Sau khi trải qua quá trình kiểm tra nghiêm ngặt của chúng tôi trong vòng lặp for, hãy kiểm tra giá trị primeFlag và báo cáo cho người dùng. printf() được viết.

Trong Haskell:

assertPrime :: (Integral a) => a -> Bool 
assertPrime x = null divisors 
    where divisors = takeWhile (<= div x 2) [y | y <- [2..], mod x y == 0] 

Dấu vết của quá trình suy nghĩ của tôi:

  1. Một số là số nguyên tố nếu nó không có ước nhưng một và chính nó. Vì vậy, null divisors.
  2. Làm cách nào để chúng tôi xây dựng divisors? Đầu tiên, hãy viết ra một danh sách các ứng cử viên có thể. Đã viết xuống phạm vi Texas từ 2 đến số/2.
  3. Bây giờ, hãy lọc danh sách và chỉ chọn các mục thực sự là ước số của số. Viết lọc mod x y == 0

tôi muốn tìm lời khuyên về cách "nghĩ trong ngôn ngữ chức năng"

Ok, đầu tiên và trước hết, suy nghĩ "cái gì", không phải "làm thế nào". Điều này có thể mất rất nhiều thực hành để làm quen. Ngoài ra, nếu trước đây bạn là một lập trình viên C/C++ như tôi, đừng lo lắng về bộ nhớ! Các ngôn ngữ hiện đại có một bộ thu gom rác và nó được viết cho bạn sử dụng - vì vậy thậm chí không cố gắng sửa đổi các biến tại chỗ. Một điều khác đã giúp tôi: viết ra các định nghĩa giống như tiếng Anh trong chương trình của bạn để tóm tắt các chức năng làm việc nặng nhọc.

+0

Chẳng phải ví dụ chức năng của bạn là một đoạn mã bắt buộc trong ngụy trang chức năng sao? một cái gì đó như thế này mô tả ý định của bạn hơn? 'assertPrime x = x elem primes x',' primes x = sàng [1..x] ', nơi sàng là máy phát điện thủ tướng ngây thơ? – Zed

+2

Tôi không hiểu - tác phẩm của tôi bắt buộc như thế nào? Cách tiếp cận của chúng tôi đối với vấn đề là khác nhau, nhưng mã của tôi là Haskell thuần túy không đơn thuần. Không có bước, và không có tác dụng phụ. Cách tiếp cận của bạn trước tiên tạo danh sách các số nguyên tố và sau đó kiểm tra xem phần tử đã cho có được tìm thấy trong danh sách hay không. Mặt khác, cách tiếp cận chức năng của tôi tương tự như cách tiếp cận bắt buộc của tôi - tôi đã làm điều đó nhằm mục đích so sánh và minh họa. – artagnon

0

Nhập liệu để quen với việc nghĩ rằng dữ liệu có thể được sử dụng làm mã và ngược lại.

Thông thường bạn xây dựng chương trình (dữ liệu) sử dụng một số hoạt động nguyên thủy (gấp, lồng, luồng, phân phối, ... và một số là sản phẩm bên trong tổng quát, sản phẩm bên ngoài, v.v.) và sử dụng chương trình này (dữ liệu) để thao tác dữ liệu khác.

Sau một thời gian, tôi thấy rằng chức năng lập trình […] khuyến khích thiết kế "top ".

Tôi đồng ý.

Các vấn đề liên quan