Các khái niệm nâng cao của Haskell như Monads và Applicative Functor quan trọng như thế nào đối với hầu hết các nhiệm vụ lập trình thông thường?


16

Tôi đã đọc cuốn sách Tìm hiểu về một cuốn sách Haskell cho đến khi họ giới thiệu Monads và những thứ như Just a. Những thứ này quá trực quan đối với tôi đến nỗi tôi chỉ cảm thấy muốn từ bỏ việc cố gắng học nó.

Nhưng tôi thực sự muốn thử.

Tôi tự hỏi liệu ít nhất tôi có thể tránh các khái niệm nâng cao trong một thời gian không và chỉ bắt đầu sử dụng các phần khác của ngôn ngữ để thực hiện nhiều tác vụ thực tế với các phần cơ bản hơn của Haskell, tức là các chức năng, IO chuẩn, xử lý tệp, cơ sở dữ liệu giao thoa và tương tự.

Đây có phải là một cách tiếp cận hợp lý để học cách sử dụng Haskell?


2
Tôi không biết bạn sẽ đi được bao xa. Không có đơn vị, bạn không thể làm bất cứ điều gì hữu ích trong Haskell, vì làm bất cứ điều gì hữu ích trong lập trình (như, viết một chương trình nghiêm túc mà ai đó thực sự muốn sử dụng) yêu cầu I / O và trạng thái, cả hai chỉ có thể được quản lý trong Haskell thông qua việc sử dụng các đơn nguyên.
Mason Wheeler

1
@dan - Cuối cùng tôi đã hiểu các đơn vị Haskell khi đọc hai tài liệu - "Bạn có thể đã phát minh ra các đơn vị" và "Xử lý Biệt đội vụng về". Tôi không thể nói liệu chúng có tốt hơn "Tìm hiểu bạn một Haskell", mà tôi chưa đọc, nhưng chúng đã làm việc cho tôi. Để sử dụng ký hiệu với đơn vị IO, bạn có thể hiểu được rất ít về các đơn vị là gì - bạn sẽ làm một số điều sẽ khiến các chuyên gia cười hoặc khóc, nhưng ít nhất, bề ngoài, nó không khác xa so với sử dụng một ngôn ngữ mệnh lệnh thông thường. Nhưng nó giá trị nhận được một số hiểu biết sâu sắc hơn.
Steve314

2
@dan, tôi đã mất gần một thập kỷ để chán OOP và bắt đầu đánh giá cao / tò mò về các ngôn ngữ chức năng. Tôi vẫn sẽ học F # và Clojure trước khi tôi thử Haskell một cách nghiêm túc. Trong khi những thách thức cá nhân là tốt đẹp, có một điều gì đó để nói về cảm giác ruột của bạn và con đường ít kháng cự nhất. Rất nhiều phụ thuộc vào bạn là ai. Nếu bạn là một người giỏi toán, thì có lẽ bạn sẽ thích Haskell / Schem ngay từ đầu. Nếu bạn là một người đàn ông 'hoàn thành công việc' thì cũng không sao. Đừng tiếp cận Haskell với thái độ 'làm hay chết'. Tiếp tục học những thứ khác, và khi chán trở lại.
Công việc

Cảm ơn lời khuyên @Job. Tôi đã thử Clojure một chút, nhưng cú pháp Lisp thực sự không hiệu quả với tôi, cho cả đọc và gõ. Tôi thấy cú pháp của Haskell tự nhiên hơn. Tôi rất vui khi sử dụng Ruby cho các dự án trong thời gian này, nhưng tôi hy vọng sẽ bắt đầu thực hiện một số dự án thực tế tại Haskell.
dan

@dan, Buồn cười, tôi thấy cú pháp của Clojure khá thân thiện, nhưng đó có lẽ là do tôi đã được tiếp xúc với Scheme trước đây. Có lẽ sau F # tôi sẽ quen với cách Haskell làm mọi việc.
Công việc

Câu trả lời:


16

Trước tiên chúng ta hãy phân biệt giữa việc học các khái niệm trừu tượng và học các ví dụ cụ thể về chúng.

Bạn sẽ không đi quá xa mà bỏ qua tất cả các ví dụ cụ thể, vì lý do đơn giản là chúng hoàn toàn phổ biến. Trong thực tế, sự trừu tượng tồn tại phần lớn bởi vì chúng thống nhất những điều bạn sẽ làm bằng mọi cách với các ví dụ cụ thể.

Mặt khác, bản tóm tắt chắc chắn rất hữu ích , nhưng chúng không cần thiết ngay lập tức. Bạn có thể nhận được khá nhiều bỏ qua hoàn toàn trừu tượng và chỉ sử dụng các loại khác nhau trực tiếp. Cuối cùng bạn sẽ muốn hiểu chúng, nhưng bạn luôn có thể quay lại với nó sau. Trên thực tế, tôi gần như có thể đảm bảo rằng nếu bạn làm điều đó, khi bạn quay lại, bạn sẽ tát vào trán mình và tự hỏi tại sao bạn lại dành toàn bộ thời gian để làm mọi việc một cách khó khăn thay vì sử dụng các công cụ đa năng tiện lợi.

Lấy Maybe amột ví dụ. Nó chỉ là một kiểu dữ liệu:

data Maybe a = Just a | Nothing

Đó là tất cả nhưng tự viết tài liệu; đó là một giá trị tùy chọn. Hoặc bạn có "chỉ" một cái gì đó thuộc loại a, hoặc bạn không có gì. Giả sử bạn có một chức năng tra cứu nào đó, trả về Maybe Stringđể đại diện cho việc tìm kiếm một Stringgiá trị có thể không có. Vì vậy, bạn khớp mẫu trên giá trị để xem đó là:

case lookupFunc key of
    Just val -> ...
    Nothing  -> ...

Đó là tất cả!

Thực sự, không có gì khác bạn cần. Không có Functors hoặc Monads hoặc bất cứ điều gì khác. Những cách này thể hiện những cách sử dụng Maybe agiá trị phổ biến ... nhưng chúng chỉ là thành ngữ, "mẫu thiết kế", bất cứ điều gì bạn có thể muốn gọi nó.

Nơi mà bạn thực sự không thể tránh được hoàn toàn là với IO, nhưng dù sao đó cũng là một hộp đen bí ẩn, vì vậy không đáng để cố gắng hiểu ý nghĩa của nó là Monadgì hay bất cứ điều gì.

Trên thực tế, đây là một bảng cheat cho tất cả những gì bạn thực sự cần biết IObây giờ:

  • Nếu một cái gì đó có một loại IO a, điều đó có nghĩa là nó là một thủ tục làm một cái gì đó và phun ra một agiá trị.

  • Khi bạn có một khối mã bằng cách sử dụng doký hiệu, hãy viết một cái gì đó như thế này:

    do -- ...
       inp <- getLine
       -- etc...
    

    ... Có nghĩa là thực hiện thủ tục ở bên phải <-và gán kết quả cho tên bên trái.

  • Trong khi đó nếu bạn có một cái gì đó như thế này:

    do -- ...
       let x = [foo, bar]
       -- etc...
    

    ... Nó có nghĩa là gán giá trị của biểu thức đơn giản (không phải là thủ tục) ở bên phải của =tên bên trái.

  • Nếu bạn đặt một cái gì đó ở đó mà không gán giá trị, như thế này:

    do putStrLn "blah blah, fishcakes"
    

    ... Nó có nghĩa là thực hiện một thủ tục và bỏ qua bất cứ điều gì nó trả về. Một số thủ tục có loại IO ()- ()loại là loại giữ chỗ không nói gì, vì vậy điều đó chỉ có nghĩa là thủ tục làm gì đó và không trả về giá trị. Sắp xếp giống như một voidchức năng trong các ngôn ngữ khác.

  • Thực hiện cùng một quy trình nhiều lần có thể cho kết quả khác nhau; Đó là loại ý tưởng. Đây là lý do tại sao không có cách nào để "loại bỏ" IOkhỏi một giá trị, bởi vì thứ gì đó IOkhông phải là một giá trị, đó là một thủ tục để có được một giá trị.

  • Dòng cuối cùng trong một dokhối phải là một thủ tục đơn giản không có phép gán, trong đó giá trị trả về của thủ tục đó trở thành giá trị trả về cho toàn bộ khối. Nếu bạn muốn giá trị trả về sử dụng một số giá trị đã được gán, returnhàm sẽ lấy một giá trị đơn giản và cung cấp cho bạn một quy trình không trả về giá trị đó.

  • Ngoài ra, không có gì đặc biệt IO; các thủ tục này thực sự là các giá trị đơn giản và bạn có thể vượt qua chúng và kết hợp chúng theo các cách khác nhau. Chỉ khi họ bị xử tử trong một dokhối được gọi ở đâu mainđó thì họ mới làm gì.

Vì vậy, trong một cái gì đó như chương trình ví dụ hoàn toàn nhàm chán, rập khuôn này:

hello = do putStrLn "What's your name?"
           name <- getLine
           let msg = "Hi, " ++ name ++ "!"
           putStrLn msg
           return name

... bạn có thể đọc nó giống như một chương trình bắt buộc. Chúng tôi đang xác định một thủ tục được đặt tên hello. Khi được thực thi, đầu tiên nó thực hiện một thủ tục để in một thông báo hỏi tên của bạn; tiếp theo nó thực hiện một thủ tục đọc một dòng đầu vào và gán kết quả cho name; sau đó nó gán một biểu thức cho tên msg; sau đó nó in thông điệp; sau đó nó trả về tên người dùng là kết quả của toàn bộ khối. Vì namelà a String, điều này có nghĩa hellolà thủ tục trả về a String, vì vậy nó có kiểu IO String. Và bây giờ bạn có thể thực hiện thủ tục này ở nơi khác, giống như nó thực thi getLine.

Pfff, đơn nguyên. Ai cần họ?


2
@dan: Bạn rất hoan nghênh! Nếu bạn có bất kỳ câu hỏi cụ thể nào trên đường đi, đừng ngần ngại hỏi về Stack Overflow. Thẻ [haskell] thường hoạt động khá tốt và nếu bạn giải thích bạn đến từ đâu thì mọi người khá giỏi trong việc giúp bạn có được sự hiểu biết thay vì chỉ ném những câu trả lời khó hiểu.
CA McCann

9

Các khái niệm nâng cao của Haskell như Monads và Applicative Functor quan trọng như thế nào đối với hầu hết các nhiệm vụ lập trình thông thường?

Chúng rất cần thiết. Không có chúng, thật dễ dàng để sử dụng Haskell như một ngôn ngữ bắt buộc khác, và mặc dù Simon Peyton Jones đã từng gọi Haskell, "ngôn ngữ lập trình mệnh lệnh tốt nhất thế giới", bạn vẫn đang bỏ lỡ phần hay nhất.

Monads, Monad Transforms, Monoids, Functor, Applicative Functor, Contravarient Functor, Arrow, Category, v.v., là những mẫu thiết kế. Một khi bạn phù hợp với điều cụ thể mà bạn thực hiện thành một trong số chúng, bạn có thể rút ra được rất nhiều chức năng được viết một cách trừu tượng. Những lớp học này không chỉ đơn giản là ở đó để làm bạn bối rối, chúng ở đó để làm cho cuộc sống của bạn dễ dàng hơn và giúp bạn làm việc hiệu quả hơn. Theo tôi, chúng là lý do lớn nhất cho sự đồng nhất của mã Haskell tốt.


1
+1: Cảm ơn bạn đã chỉ ra rằng "Monads, Monad Transforms, Monoids, Functor, Applicative Functor, Contravarient Functor, Arbow, Category, v.v., là các mẫu thiết kế." !
Giorgio

7

Có thực sự cần thiết nếu bạn chỉ muốn có được một cái gì đó chạy? Không.

Có cần thiết nếu bạn muốn viết các chương trình Haskell thành ngữ đẹp? Chắc chắn rồi.

Khi lập trình trong Haskell, nó giúp ghi nhớ hai điều:

  1. Hệ thống loại là có để giúp bạn. Nếu bạn soạn chương trình của mình ra khỏi mã nặng kiểu chữ đa hình, rất thường xuyên bạn có thể gặp tình huống tuyệt vời trong đó loại hàm bạn muốn sẽ hướng dẫn bạn thực hiện đúng (một!) .

  2. Các thư viện có sẵn khác nhau được phát triển theo nguyên tắc # 1.

Vì vậy, khi viết một chương trình bằng Haskell, tôi bắt đầu bằng cách viết các loại. Sau đó, tôi bắt đầu lại và viết ra một số loại tổng quát hơn (và nếu chúng có thể là phiên bản của các kiểu chữ hiện có, thì tốt hơn). Sau đó, tôi viết một số hàm lấy cho tôi các giá trị của các loại mà tôi muốn. (Sau đó, tôi chơi với họ ghcivà có thể viết một số bài kiểm tra quickCheck.) Và cuối cùng tôi dán tất cả lại với nhau main.

Có thể viết Haskell xấu xí mà chỉ cần một cái gì đó chạy. Nhưng còn rất nhiều điều để tìm hiểu khi bạn đạt đến giai đoạn đó.

Chúc may mắn!


1
Cảm ơn bạn! Tôi đã dao động giữa Clojure và Haskell, nhưng bây giờ tôi đang nghiêng về Haskell vì những người hữu ích như bạn đã từng rất hữu ích.
dan

1
Cộng đồng Haskell dường như rất tốt đẹp và hữu ích. Và dường như đó là nơi có rất nhiều ý tưởng thú vị đến từ
Zachary K
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.