Có gì tệ về Mẫu Haskell?


252

Có vẻ như Template Haskell thường được cộng đồng Haskell xem là một sự tiện lợi đáng tiếc. Thật khó để diễn tả chính xác những gì tôi đã quan sát về vấn đề này, nhưng hãy xem xét một vài ví dụ

Tôi đã thấy nhiều bài đăng trên blog khác nhau, nơi mọi người thực hiện công cụ khá gọn gàng với Template Haskell, cho phép cú pháp đẹp hơn mà đơn giản là không thể thực hiện được trong Haskell thông thường, cũng như giảm mức độ nồi hơi cực lớn. Vậy tại sao Mẫu Haskell lại bị xem thường theo cách này? Điều gì làm cho nó không mong muốn? Trong trường hợp nào thì nên tránh Mẫu Haskell, và tại sao?


56
Tôi không đồng ý với việc bỏ phiếu để di chuyển; Tôi đang hỏi câu hỏi này với tinh thần tương tự mà tôi đã hỏi Điều gì tệ về I / O lười biếng? và tôi mong đợi để xem câu trả lời của cùng một thời trang. Tôi sẵn sàng viết lại câu hỏi nếu điều đó có ích.
Dan Burton

51
@ErikPhilips Tại sao bạn không để những người thường xuyên sử dụng thẻ này quyết định xem nó có thuộc về nơi này không? Có vẻ như sự tương tác duy nhất của bạn với cộng đồng Haskell là đặt câu hỏi xuống.
Gabriel Gonzalez

5
@GabrielGonzalez rõ ràng với câu hỏi và câu trả lời hiện tại rằng nó không tuân theo Câu hỏi thường gặp theo loại câu hỏi nào tôi không nên hỏi ở đây: không có vấn đề thực sự nào cần giải quyết . Câu hỏi không có vấn đề cụ thể về mã nào được giải quyết và chỉ mang tính khái niệm. Và dựa trên câu hỏi tôi có nên tránh mẫu haskell không, nó cũng rơi vào Stack Overflow không phải là Công cụ đề xuất .
Erik Philips

27
@ErikPhilips Khía cạnh của Công cụ đề xuất không liên quan đến câu hỏi này, bởi vì bạn sẽ nhận thấy rằng đó là đề cập đến quyết định giữa các công cụ khác nhau (ví dụ: "cho tôi biết tôi nên sử dụng ngôn ngữ nào"). Ngược lại, tôi chỉ đơn thuần yêu cầu giải thích về Mẫu Haskell một cách cụ thể và Câu hỏi thường gặp "nếu động lực của bạn là Cấm tôi muốn người khác giải thích [trống] cho tôi, thì có lẽ bạn vẫn ổn." So sánh với, ví dụ, GOTO vẫn được coi là có hại?
Dan Burton

29
Bỏ phiếu để mở lại. Chỉ vì đó là một câu hỏi cấp cao hơn, không có nghĩa nó không phải là một câu hỏi hay.
Gyorgy Andottok

Câu trả lời:


171

Một lý do để tránh Bản mẫu Haskell là vì toàn bộ nó không an toàn về loại, do đó, đi ngược lại với phần lớn "tinh thần của Haskell". Dưới đây là một số ví dụ về điều này:

  • Bạn không có quyền kiểm soát đối với loại Haskell AST mà một đoạn mã TH sẽ tạo ra, ngoài nơi nó sẽ xuất hiện; bạn có thể có một giá trị của loại Exp, nhưng bạn không biết nếu đó là một biểu thức đại diện cho một [Char]hoặc một (a -> (forall b . b -> c))hoặc bất cứ điều gì. TH sẽ đáng tin cậy hơn nếu người ta có thể diễn tả rằng một hàm chỉ có thể tạo các biểu thức của một loại nhất định hoặc chỉ khai báo hàm hoặc chỉ các mẫu khớp với hàm tạo dữ liệu, v.v.
  • Bạn có thể tạo các biểu thức không biên dịch. Bạn đã tạo một biểu thức tham chiếu một biến miễn phí fookhông tồn tại? Thật may mắn, bạn sẽ chỉ thấy rằng khi thực sự sử dụng trình tạo mã của mình và chỉ trong các trường hợp kích hoạt việc tạo mã cụ thể đó. Nó rất khó để kiểm tra đơn vị, quá.

TH cũng hoàn toàn nguy hiểm:

  • Mã chạy vào thời gian biên dịch có thể làm tùy ý IO , bao gồm phóng tên lửa hoặc đánh cắp thẻ tín dụng của bạn. Bạn không muốn phải xem qua mọi gói cabal bạn từng tải xuống để tìm kiếm khai thác TH.
  • TH có thể truy cập các định nghĩa và chức năng "mô-đun riêng tư", phá vỡ hoàn toàn việc đóng gói trong một số trường hợp.

Sau đó, có một số vấn đề khiến các chức năng TH trở nên kém thú vị hơn khi sử dụng làm nhà phát triển thư viện:

  • Mã TH không phải lúc nào cũng có thể ghép được. Giả sử ai đó tạo ra một máy phát cho ống kính, và thường xuyên hơn không, máy phát đó sẽ được cấu trúc theo cách mà nó chỉ có thể được gọi trực tiếp bởi "người dùng cuối" chứ không phải bởi mã TH khác, ví dụ như lấy một danh sách các hàm tạo để tạo các thấu kính làm tham số. Thật khó để tạo danh sách đó trong mã, trong khi người dùng chỉ phải viếtgenerateLenses [''Foo, ''Bar] .
  • Các nhà phát triển thậm chí không biết rằng mã TH có thể được soạn thảo. Bạn có biết rằng bạn có thể viết forM_ [''Foo, ''Bar] generateLens? Qchỉ là một đơn nguyên, vì vậy bạn có thể sử dụng tất cả các chức năng thông thường trên nó. Một số người không biết điều này và do đó, họ tạo ra nhiều phiên bản quá tải về cơ bản cùng các chức năng có cùng chức năng và các chức năng này dẫn đến một hiệu ứng phình to nhất định. Ngoài ra, hầu hết mọi người viết máy phát điện của họ trong Qđơn nguyên ngay cả khi họ không phải, giống như viết bla :: IO Int; bla = return 3; bạn đang cung cấp cho một chức năng nhiều "môi trường" hơn nhu cầu của nó và khách hàng của chức năng được yêu cầu cung cấp môi trường đó như là một hiệu ứng của điều đó.

Cuối cùng, có một số điều làm cho các chức năng TH trở nên ít thú vị hơn khi sử dụng như người dùng cuối:

  • Độ mờ đục. Khi một hàm TH có kiểu Q Dec, nó có thể tạo hoàn toàn mọi thứ ở cấp cao nhất của mô-đun và bạn hoàn toàn không kiểm soát được những gì sẽ được tạo.
  • Nguyên khối. Bạn không thể kiểm soát số lượng hàm TH tạo ra trừ khi nhà phát triển cho phép; nếu bạn tìm thấy một hàm tạo giao diện cơ sở dữ liệu giao diện tuần tự hóa JSON, bạn không thể nói "Không, tôi chỉ muốn giao diện cơ sở dữ liệu, cảm ơn; Tôi sẽ cuộn giao diện JSON của riêng tôi"
  • Thời gian chạy. Mã TH mất một thời gian tương đối dài để chạy. Mã được diễn giải một lần nữa mỗi khi tập tin được biên dịch và thông thường, một tấn gói được yêu cầu bởi mã TH đang chạy, phải được tải. Điều này làm chậm thời gian biên dịch đáng kể.

4
Thêm vào đó là thực tế rằng mẫu-haskell trong lịch sử đã được ghi chép lại rất kém. . Hai điều này đã góp phần khiến tôi cố tình không bận tâm để hiểu TH khi tôi là người mới bắt đầu Haskell.
mightybyte

14
Đừng quên rằng việc sử dụng Mẫu Haskell hoàn toàn có nghĩa là thứ tự khai báo có vấn đề! TH chỉ không được tích hợp chặt chẽ như người ta có thể hy vọng sẽ có được sự đánh bóng mượt mà của Haskell (có thể là 1.4, '98, 2010 hoặc thậm chí là Glasgow).
Thomas M. DuBuisson

13
Bạn có thể suy luận về Haskell mà không gặp quá nhiều khó khăn, không có gì đảm bảo như vậy về Template Haskell.
tăng

15
Và điều gì đã xảy ra với lời hứa của Oleg về một loại thay thế an toàn cho TH? Tôi đang đề cập đến công việc của anh ấy dựa trên bài báo "Cuối cùng không có thẻ, được đánh giá một phần" và nhiều ghi chú của anh ấy ở đây . Nó trông rất hứa hẹn khi họ công bố nó và sau đó tôi không bao giờ nghe một từ nào khác về nó.
Gabriel Gonzalez


53

Đây chỉ là ý kiến ​​của riêng tôi.

  • Thật là xấu khi sử dụng. $(fooBar ''Asdf)trông không đẹp Hời hợt, chắc chắn, nhưng nó góp phần.

  • Nó thậm chí còn xấu hơn để viết. Báo giá đôi khi hoạt động, nhưng rất nhiều thời gian bạn phải thực hiện ghép AST thủ công và hệ thống ống nước. Các API là lớn và cồng kềnh, luôn có rất nhiều trường hợp, bạn không quan tâm đến nhưng vẫn cần phải công văn, và các trường hợp bạn làm chăm sóc về xu hướng có mặt trong nhiều hình thức tương tự nhưng không giống hệt nhau (dữ liệu vs Newtype, kỷ lục -style so với các nhà xây dựng bình thường, v.v.). Thật nhàm chán và lặp đi lặp lại để viết và đủ phức tạp để không bị máy móc. Các cải cách đề nghị các địa chỉ một số này (làm dấu ngoặc kép nhiều ứng dụng rộng rãi).

  • Giới hạn sân khấu là địa ngục. Không thể ghép các hàm được xác định trong cùng một mô-đun là phần nhỏ hơn của nó: hậu quả khác là nếu bạn có một mối nối cấp cao nhất, mọi thứ sau mô-đun sẽ nằm ngoài phạm vi của bất kỳ thứ gì trước nó. Các ngôn ngữ khác có thuộc tính này (C, C ++) làm cho nó hoạt động được bằng cách cho phép bạn chuyển tiếp khai báo mọi thứ, nhưng Haskell thì không. Nếu bạn cần tham chiếu theo chu kỳ giữa các khai báo được ghép hoặc các phụ thuộc và phụ thuộc của chúng, bạn thường chỉ bị vặn.

  • Nó vô kỷ luật. Điều tôi muốn nói là điều này là hầu hết thời gian khi bạn thể hiện một sự trừu tượng, có một số loại nguyên tắc hoặc khái niệm đằng sau sự trừu tượng đó. Đối với nhiều khái niệm trừu tượng, nguyên tắc đằng sau chúng có thể được thể hiện trong các loại của chúng. Đối với các lớp loại, bạn thường có thể xây dựng luật mà các trường hợp nên tuân theo và khách hàng có thể giả định. Nếu bạn sử dụng tính năng tổng quát mới của GHC để trừu tượng hóa hình thức khai báo cá thể trên bất kỳ kiểu dữ liệu nào (trong giới hạn), bạn có thể nói "đối với các loại tổng, nó hoạt động như thế này, đối với các loại sản phẩm, nó hoạt động như thế". Mặt khác, mẫu Haskell chỉ là macro. Đó không phải là sự trừu tượng ở cấp độ ý tưởng, mà là sự trừu tượng hóa ở cấp độ AST, tốt hơn, nhưng chỉ khiêm tốn, hơn là trừu tượng hóa ở cấp độ văn bản thuần túy. *

  • Nó ràng buộc bạn với GHC. Về lý thuyết, một trình biên dịch khác có thể thực hiện nó, nhưng trong thực tế tôi nghi ngờ điều này sẽ xảy ra. (Điều này trái ngược với các phần mở rộng hệ thống loại khác nhau, mặc dù chúng chỉ có thể được GHC triển khai vào lúc này, tôi có thể dễ dàng tưởng tượng việc được các trình biên dịch khác sử dụng trên đường và cuối cùng được chuẩn hóa.)

  • API không ổn định. Khi các tính năng ngôn ngữ mới được thêm vào GHC và gói template-haskell được cập nhật để hỗ trợ chúng, điều này thường liên quan đến các thay đổi không tương thích ngược với các kiểu dữ liệu TH. Nếu bạn muốn mã TH của bạn tương thích với nhiều hơn một phiên bản GHC, bạn cần phải rất cẩn thận và có thể sử dụngCPP .

  • Có một nguyên tắc chung là bạn nên sử dụng công cụ phù hợp cho công việc và công cụ nhỏ nhất sẽ đủ, và trong đó mẫu tương tự Haskell là một cái gì đó như thế này . Nếu có một cách để làm điều đó không phải là Mẫu Haskell, thì nói chung là tốt hơn.

Ưu điểm của Template Haskell là bạn có thể làm mọi thứ với nó mà bạn không thể làm bất kỳ cách nào khác, và đó là một cách lớn. Hầu hết thời gian những thứ TH được sử dụng cho chỉ có thể được thực hiện nếu chúng được thực hiện trực tiếp như các tính năng của trình biên dịch. TH cực kỳ có lợi khi có cả hai vì nó cho phép bạn thực hiện những điều này và vì nó cho phép bạn tạo các phần mở rộng trình biên dịch tiềm năng theo cách nhẹ hơn và có thể sử dụng lại nhiều hơn (ví dụ: xem các gói ống kính khác nhau).

Để tóm tắt lý do tại sao tôi nghĩ rằng có những cảm xúc tiêu cực đối với Template Haskell: Nó giải quyết được rất nhiều vấn đề, nhưng đối với bất kỳ vấn đề nào mà nó giải quyết, cảm giác như cần có một giải pháp tốt hơn, thanh lịch hơn, kỷ luật hơn phù hợp hơn để giải quyết vấn đề đó, một vấn đề không giải quyết được vấn đề bằng cách tự động tạo ra bản mẫu, nhưng bằng cách loại bỏ sự cần thiết phải bản mẫu.

* Mặc dù tôi thường cảm thấy rằng CPPcó tỷ lệ công suất trên trọng lượng tốt hơn cho những vấn đề mà nó có thể giải quyết.

EDIT 23-04-14: Điều tôi thường cố gắng đạt được ở phần trên và chỉ mới nhận được chính xác gần đây, đó là một sự khác biệt quan trọng giữa sự trừu tượng và sự trùng lặp. Sự trừu tượng đúng cách thường dẫn đến sự trùng lặp như là một tác dụng phụ, và sự trùng lặp thường là một dấu hiệu nhận biết về sự trừu tượng không đầy đủ, nhưng đó không phải là lý do tại sao nó có giá trị. Trừu tượng đúng là những gì làm cho mã chính xác, dễ hiểu và có thể duy trì. Sự trùng lặp chỉ làm cho nó ngắn hơn. Mẫu Haskell, giống như macro nói chung, là một công cụ để chống trùng lặp.


"CPP có tỷ lệ công suất trên trọng lượng tốt hơn cho những vấn đề mà nó có thể giải quyết". Thật. Và trong C99 nó có thể giải quyết bất cứ điều gì bạn muốn. Hãy xem xét những điều này: COS , Chaos . Tôi cũng không hiểu tại sao mọi người nghĩ thế hệ AST tốt hơn. Nó chỉ mơ hồ hơn và ít trực giao hơn với các tính năng ngôn ngữ khác
Britton Kerin

31

Tôi muốn giải quyết một vài điểm mà dflemstr đưa ra.

Tôi không thấy thực tế là bạn không thể đánh máy TH để trở nên đáng lo ngại. Tại sao? Bởi vì ngay cả khi có lỗi, nó vẫn sẽ là thời gian biên dịch. Tôi không chắc điều này có củng cố lập luận của tôi không, nhưng điều này tương tự với các lỗi mà bạn nhận được khi sử dụng các mẫu trong C ++. Tôi nghĩ rằng các lỗi này dễ hiểu hơn các lỗi của C ++, vì bạn sẽ nhận được một phiên bản in đẹp của mã được tạo.

Nếu một biểu thức / quasi-quote của TH làm điều gì đó tiến bộ đến mức các góc khó có thể che giấu, thì có lẽ đó là điều không nên?

Tôi phá vỡ quy tắc này khá nhiều với các trích dẫn gần đây mà tôi đã làm việc gần đây (sử dụng haskell-src-exts / meta) - https://github.com/mgsloan/quasi-extras/tree/master/examples . Tôi biết điều này giới thiệu một số lỗi như không thể ghép nối trong phần hiểu danh sách tổng quát. Tuy nhiên, tôi nghĩ rằng có một cơ hội tốt để một số ý tưởng trong http://hackage.haskell.org/trac/ghc/blog/Template%20Haskell%20Proposal sẽ kết thúc trong trình biên dịch. Cho đến lúc đó, các thư viện để phân tích cú pháp Haskell sang cây TH là một xấp xỉ gần như hoàn hảo.

Về tốc độ biên dịch / phụ thuộc, chúng ta có thể sử dụng gói "zeroth" để nội tuyến mã được tạo. Điều này ít nhất là tốt cho người dùng của một thư viện nhất định, nhưng chúng ta không thể làm tốt hơn nhiều cho trường hợp chỉnh sửa thư viện. TH phụ thuộc có thể phình to nhị phân tạo ra? Tôi nghĩ rằng nó bỏ qua mọi thứ không được tham chiếu bởi mã được biên dịch.

Hạn chế dàn / tách các bước biên dịch của mô-đun Haskell không hút.

Độ mờ RE: Điều này giống với bất kỳ chức năng thư viện nào bạn gọi. Bạn không có quyền kiểm soát đối với những gì Data.List.groupBy sẽ làm. Bạn chỉ cần có một "bảo đảm" / quy ước hợp lý rằng các số phiên bản cho bạn biết điều gì đó về tính tương thích. Đó là một phần của một vấn đề khác nhau của sự thay đổi khi.

Đây là nơi sử dụng zeroth trả hết - bạn đã phiên bản các tệp được tạo - vì vậy bạn sẽ luôn biết khi nào hình thức của mã được tạo đã thay đổi. Tuy nhiên, nhìn vào các khác biệt có thể là một chút sởn gai ốc, đối với số lượng lớn mã được tạo, vì vậy đó là một nơi mà giao diện nhà phát triển tốt hơn sẽ có ích.

RE Monolithism: Bạn chắc chắn có thể xử lý hậu quả của biểu thức TH, sử dụng mã thời gian biên dịch của riêng bạn. Sẽ không có nhiều mã để lọc về loại / tên khai báo cấp cao nhất. Heck, bạn có thể tưởng tượng viết một chức năng làm điều này một cách khái quát. Để sửa đổi / khử nguyên khối các quasiquoters, bạn có thể tạo mẫu khớp với "QuasiQuoter" và trích xuất các biến đổi được sử dụng hoặc tạo một biến đổi mới theo cách cũ.


1
Về Opacity / Monolithism: Tất nhiên bạn có thể đi qua [Dec]và xóa những thứ bạn không muốn, nhưng giả sử rằng hàm đọc một tệp định nghĩa bên ngoài khi tạo giao diện JSON. Chỉ vì bạn không sử dụng mà Deckhông làm cho trình tạo ngừng tìm tệp định nghĩa, khiến cho quá trình biên dịch thất bại. Vì lý do đó, thật tuyệt khi có một phiên bản hạn chế hơn của Qđơn nguyên cho phép bạn tạo tên mới (và những thứ như vậy) nhưng không cho phép IO, vì vậy, như bạn nói, người ta có thể lọc kết quả của nó / làm các tác phẩm khác .
dflemstr

1
Tôi đồng ý, nên có một phiên bản không có IO của Q / quote! Điều này cũng sẽ giúp giải quyết - stackoverflow.com/questions/7107308/ . Khi bạn đảm bảo rằng mã thời gian biên dịch không làm gì không an toàn, có vẻ như bạn chỉ có thể chạy trình kiểm tra an toàn trên mã kết quả và đảm bảo rằng nó không tham chiếu nội dung riêng tư.
mgsloan

1
Bạn đã bao giờ viết lên phản biện của bạn? Vẫn đang chờ liên kết hứa hẹn của bạn. Đối với những người tìm kiếm nội dung cũ của câu trả lời này: stackoverflow.com/revutions/10859441/1 và cho những người có thể xem nội dung đã bị xóa: stackoverflow.com/revutions/10913718/6
Dan Burton

1
Có phiên bản cập nhật nào của zeroth so với phiên bản hack không? Cái đó (và darcs repo) đã được cập nhật lần cuối vào năm 2009. Cả hai bản sao không được xây dựng với ghc hiện tại (7.6).
aavogt

1
@aavogt tgeeky đang cố gắng sửa nó, nhưng tôi không nghĩ anh ấy đã hoàn thành: github.com/technogeeky/zeroth Nếu không có ai làm điều đó / cuối cùng tôi sẽ làm được.
mgsloan

16

Câu trả lời này là để đáp ứng với các vấn đề được đưa ra bởi illissius, từng điểm một:

  • Thật là xấu khi sử dụng. $ (fooBar '' Asdf) trông không đẹp mắt. Hời hợt, chắc chắn, nhưng nó góp phần.

Tôi đồng ý. Tôi cảm thấy như $ () đã được chọn để trông giống như nó là một phần của ngôn ngữ - sử dụng biểu tượng pallet quen thuộc của Haskell. Tuy nhiên, đó chính xác là những gì bạn / không / muốn trong các biểu tượng được sử dụng cho ghép nối macro của bạn. Họ chắc chắn pha trộn quá nhiều, và khía cạnh mỹ phẩm này là khá quan trọng. Tôi thích giao diện của {{}} cho các mối nối, vì chúng khá khác biệt về mặt trực quan.

  • Nó thậm chí còn xấu hơn để viết. Báo giá đôi khi hoạt động, nhưng rất nhiều thời gian bạn phải thực hiện ghép AST thủ công và hệ thống ống nước. [API] [1] rất lớn và khó sử dụng, luôn có rất nhiều trường hợp bạn không quan tâm nhưng vẫn cần gửi đi và các trường hợp bạn quan tâm có xu hướng xuất hiện ở nhiều dạng tương tự nhưng không giống nhau (dữ liệu so với newtype, kiểu ghi so với các nhà xây dựng bình thường, v.v.). Thật nhàm chán và lặp đi lặp lại để viết và đủ phức tạp để không bị máy móc. [Đề xuất cải cách] [2] đề cập đến một số điều này (làm cho trích dẫn được áp dụng rộng rãi hơn).

Tuy nhiên, tôi cũng đồng ý với điều này, vì một số ý kiến ​​trong "Hướng đi mới cho TH" quan sát, việc thiếu trích dẫn AST ngoài luồng tốt không phải là một lỗ hổng nghiêm trọng. Trong gói WIP này, tôi tìm cách giải quyết những vấn đề này ở dạng thư viện: https://github.com/mgsloan/quasi-extras . Cho đến nay tôi cho phép ghép nối ở một vài nơi hơn bình thường và có thể khớp mẫu trên AST.

  • Giới hạn sân khấu là địa ngục. Không thể ghép các hàm được xác định trong cùng một mô-đun là phần nhỏ hơn của nó: hậu quả khác là nếu bạn có một mối nối cấp cao nhất, mọi thứ sau mô-đun sẽ nằm ngoài phạm vi của bất kỳ thứ gì trước nó. Các ngôn ngữ khác có thuộc tính này (C, C ++) làm cho nó hoạt động được bằng cách cho phép bạn chuyển tiếp khai báo mọi thứ, nhưng Haskell thì không. Nếu bạn cần tham chiếu theo chu kỳ giữa các khai báo được ghép hoặc các phụ thuộc và phụ thuộc của chúng, bạn thường chỉ bị vặn.

Tôi đã gặp phải vấn đề định nghĩa TH tuần hoàn là không thể trước đây ... Nó khá khó chịu. Có một giải pháp, nhưng nó thật xấu xí - bao bọc những thứ liên quan đến sự phụ thuộc theo chu kỳ trong một biểu thức TH kết hợp tất cả các khai báo được tạo. Một trong những trình tạo khai báo này có thể chỉ là một bộ trích dẫn chấp nhận mã Haskell.

  • Nó không được cung cấp. Điều tôi muốn nói là điều này là hầu hết thời gian khi bạn thể hiện một sự trừu tượng, có một số loại nguyên tắc hoặc khái niệm đằng sau sự trừu tượng đó. Đối với nhiều khái niệm trừu tượng, nguyên tắc đằng sau chúng có thể được thể hiện trong các loại của chúng. Khi bạn định nghĩa một lớp loại, bạn thường có thể xây dựng luật mà các trường hợp nên tuân theo và khách hàng có thể giả định. Nếu bạn sử dụng [tính năng tổng quát mới] của GHC để trừu tượng hóa hình thức khai báo cá thể trên bất kỳ kiểu dữ liệu nào (trong giới hạn), bạn có thể nói "đối với các loại tổng, nó hoạt động như thế này, đối với các loại sản phẩm, nó hoạt động như thế ". Nhưng Template Haskell chỉ là macro ngu ngốc. Nó không trừu tượng ở cấp độ ý tưởng, nhưng trừu tượng hóa ở cấp độ AST, tốt hơn, nhưng chỉ khiêm tốn, hơn là trừu tượng ở cấp độ văn bản thuần túy.

Nó chỉ là không có đối thủ nếu bạn làm những điều không được thực hiện với nó. Sự khác biệt duy nhất là với trình biên dịch triển khai các cơ chế trừu tượng hóa, bạn có thêm niềm tin rằng sự trừu tượng hóa không bị rò rỉ. Có lẽ thiết kế ngôn ngữ dân chủ hóa nghe có vẻ hơi đáng sợ! Người tạo thư viện TH cần ghi chép tốt và xác định rõ ràng ý nghĩa và kết quả của các công cụ họ cung cấp. Một ví dụ điển hình của TH nguyên tắc là gói phái sinh: http://hackage.haskell.org/package/derive - nó sử dụng DSL sao cho ví dụ về nhiều đạo hàm / chỉ định / đạo hàm thực tế.

  • Nó ràng buộc bạn với GHC. Về lý thuyết, một trình biên dịch khác có thể thực hiện nó, nhưng trong thực tế tôi nghi ngờ điều này sẽ xảy ra. (Điều này trái ngược với các phần mở rộng hệ thống loại khác nhau, mặc dù chúng chỉ có thể được GHC triển khai vào lúc này, tôi có thể dễ dàng tưởng tượng việc được các trình biên dịch khác sử dụng trên đường và cuối cùng được chuẩn hóa.)

Đó là một điểm khá tốt - API TH khá lớn và cồng kềnh. Thực hiện lại có vẻ như nó có thể khó khăn. Tuy nhiên, thực sự chỉ có một vài cách để giải quyết vấn đề đại diện cho Haskell ASTs. Tôi tưởng tượng rằng việc sao chép TH ADT và viết một trình chuyển đổi sang biểu diễn AST bên trong sẽ giúp bạn giải quyết tốt vấn đề đó. Điều này sẽ tương đương với nỗ lực (không đáng kể) của việc tạo haskell-src-meta. Nó cũng có thể được thực hiện lại một cách đơn giản bằng cách in đẹp TH AST và sử dụng trình phân tích cú pháp nội bộ của trình biên dịch.

Mặc dù tôi có thể sai, tôi không thấy TH là phức tạp của phần mở rộng trình biên dịch, từ góc độ thực hiện. Đây thực sự là một trong những lợi ích của việc "giữ cho nó đơn giản" và không có lớp cơ bản là một hệ thống tạo khuôn mẫu có thể kiểm chứng tĩnh, hấp dẫn về mặt lý thuyết.

  • API không ổn định. Khi các tính năng ngôn ngữ mới được thêm vào GHC và gói template-haskell được cập nhật để hỗ trợ chúng, điều này thường liên quan đến các thay đổi không tương thích ngược với các kiểu dữ liệu TH. Nếu bạn muốn mã TH của bạn tương thích với nhiều hơn một phiên bản GHC, bạn cần phải rất cẩn thận và có thể sử dụngCPP .

Đây cũng là một điểm tốt, nhưng hơi kịch tính. Mặc dù gần đây đã có các bổ sung API, nhưng chúng vẫn chưa bị phá vỡ gây ra. Ngoài ra, tôi nghĩ rằng với trích dẫn AST vượt trội mà tôi đã đề cập trước đó, API thực sự cần được sử dụng có thể được giảm đáng kể. Nếu không có cấu trúc / kết hợp nào cần các hàm riêng biệt và thay vào đó được biểu thị bằng chữ, thì hầu hết các API sẽ biến mất. Hơn nữa, mã bạn viết sẽ chuyển dễ dàng hơn tới các biểu diễn AST cho các ngôn ngữ tương tự như Haskell.


Tóm lại, tôi nghĩ rằng TH là một công cụ mạnh mẽ, bị bỏ quên. Ít ghét hơn có thể dẫn đến một hệ thống thư viện sinh thái sống động hơn, khuyến khích thực hiện các nguyên mẫu tính năng ngôn ngữ nhiều hơn. Người ta đã quan sát thấy TH là một công cụ quá sức, có thể cho phép bạn / làm / hầu hết mọi thứ. Tình trạng hỗn loạn! Vâng, theo ý kiến ​​của tôi, sức mạnh này có thể cho phép bạn vượt qua hầu hết các hạn chế của nó và xây dựng các hệ thống có khả năng tiếp cận lập trình meta khá nguyên tắc. Thật đáng để sử dụng các bản hack xấu xí để mô phỏng việc thực hiện "đúng", vì cách này thiết kế của việc thực hiện "đúng" sẽ dần trở nên rõ ràng.

Trong phiên bản lý tưởng cá nhân của tôi về niết bàn, phần lớn ngôn ngữ sẽ thực sự chuyển ra khỏi trình biên dịch, vào các thư viện thuộc loại này. Thực tế là các tính năng được triển khai như các thư viện không ảnh hưởng nhiều đến khả năng trừu tượng trung thực của chúng.

Câu trả lời điển hình của Haskell cho mã soạn sẵn là gì? Trừu tượng. Trừu tượng yêu thích của chúng tôi là gì? Chức năng và kiểu chữ!

Máy đánh chữ cho phép chúng ta định nghĩa một tập hợp các phương thức, sau đó có thể được sử dụng theo tất cả các cách của các hàm chung trên lớp đó. Tuy nhiên, ngoài điều này, cách duy nhất các lớp giúp tránh nồi hơi là bằng cách cung cấp "định nghĩa mặc định". Bây giờ đây là một ví dụ về một tính năng chưa được cung cấp!

  • Bộ ràng buộc tối thiểu không thể khai báo / trình biên dịch có thể kiểm tra. Điều này có thể dẫn đến các định nghĩa vô tình mang lại đáy do đệ quy lẫn nhau.

  • Mặc dù sự tiện lợi và sức mạnh này sẽ mang lại, bạn không thể chỉ định mặc định của siêu lớp, do trường hợp mồ côi http://lukepalmer.wordpress.com/2009/01/11/a-world-without-orphans/ Những điều này sẽ cho phép chúng tôi khắc phục thứ bậc số duyên dáng!

  • Đi sau các khả năng giống như TH cho các mặc định của phương thức đã dẫn đến http://www.haskell.org/haskellwiki/GHC.Generics . Mặc dù đây là công cụ tuyệt vời, nhưng trải nghiệm mã gỡ lỗi duy nhất của tôi khi sử dụng các tổng quát này là không thể, do kích thước của loại gây ra và ADT phức tạp như AST. https://github.com/mgsloan/th-extra/commit/d7784d95d394eb3abdb409a24360beb03731c88c

    Nói cách khác, điều này đi sau các tính năng do TH cung cấp, nhưng nó phải nâng toàn bộ một miền của ngôn ngữ, ngôn ngữ xây dựng, thành một đại diện hệ thống loại. Mặc dù tôi có thể thấy nó hoạt động tốt cho vấn đề chung của bạn, nhưng đối với những vấn đề phức tạp, có vẻ như nó mang lại một đống biểu tượng đáng sợ hơn nhiều so với TH hack.

    TH cung cấp cho bạn tính toán thời gian biên dịch mức giá trị của mã đầu ra, trong khi đó tổng quát buộc bạn phải nâng phần khớp mẫu / đệ quy của mã vào hệ thống loại. Mặc dù điều này hạn chế người dùng theo một số cách khá hữu ích, tôi không nghĩ rằng sự phức tạp là đáng giá.

Tôi nghĩ rằng việc từ chối TH và lập trình siêu dữ liệu giống như lisp đã dẫn đến sự ưu tiên đối với những thứ như mặc định phương thức thay vì mở rộng macro, linh hoạt hơn như khai báo các thể hiện. Kỷ luật tránh những thứ có thể dẫn đến kết quả không được cải thiện là khôn ngoan, tuy nhiên, chúng ta không nên bỏ qua rằng hệ thống loại có khả năng của Haskell cho phép lập trình siêu dữ liệu đáng tin cậy hơn trong nhiều môi trường khác (bằng cách kiểm tra mã được tạo).


4
Câu trả lời này không thể tự mình đứng vững: bạn đang tạo ra một loạt các tài liệu tham khảo cho một câu trả lời khác mà tôi phải đi và tìm trước khi tôi có thể đọc đúng của bạn.
Ben Millwood

Đúng rồi. Tôi đã cố gắng để làm rõ những gì đang được nói về mặc dù. Có lẽ tôi sẽ chỉnh sửa các điểm của illisuis.
mgsloan

Tôi nên nói rằng có lẽ "unincipled" là một từ mạnh hơn tôi nên sử dụng, với một số ý nghĩa mà tôi không có ý định - nó chắc chắn không phải là vô đạo đức! Điểm đạn đó là thứ tôi gặp rắc rối nhất, bởi vì tôi có cảm giác này hoặc ý tưởng không được định dạng trong đầu và rắc rối khi nói ra, và "unipipled" là một trong những từ tôi nắm được ở đâu đó xung quanh. "Kỷ luật" có lẽ gần hơn. Không có một công thức rõ ràng và succint tôi đã đi cho một số ví dụ thay thế. Nếu ai đó có thể giải thích cho tôi rõ hơn những gì tôi có thể có nghĩa, tôi sẽ đánh giá cao nó!
glaebhoerl

(Ngoài ra tôi nghĩ rằng bạn có thể đã tăng giới hạn dàn dựng và trích dẫn xấu để viết.)
glaebhoerl

1
Đừng! Cảm ơn đã chỉ ra rằng! Đã sửa. Vâng, vô kỷ luật có lẽ sẽ là một cách tốt hơn để đặt nó. Tôi nghĩ rằng sự khác biệt ở đây thực sự là giữa các cơ chế trừu tượng hóa "tích hợp" và do đó "được hiểu rõ", so với "ad-hoc" hoặc các cơ chế trừu tượng do người dùng định nghĩa. Tôi cho rằng điều tôi muốn nói là bạn có thể hình dung một cách dễ hiểu một thư viện TH đã triển khai một cái gì đó khá giống với công văn kiểu chữ (mặc dù tại thời gian biên dịch không phải thời gian chạy)
mgsloan

8

Một vấn đề khá thực tế với Template Haskell là nó chỉ hoạt động khi có trình thông dịch mã byte của GHC, đây không phải là trường hợp trên tất cả các kiến ​​trúc. Vì vậy, nếu chương trình của bạn sử dụng Template Haskell hoặc dựa vào các thư viện sử dụng nó, nó sẽ không chạy trên các máy có CPU ARM, MIPS, S390 hoặc PowerPC.

Điều này có liên quan trong thực tế: git-annex là một công cụ được viết bằng Haskell có ý nghĩa để chạy trên các máy lo lắng về lưu trữ, những máy như vậy thường có CPU không phải i386. Cá nhân, tôi chạy git-annex trên NSLU 2 (RAM 32 MB, CPU ; bạn có biết Haskell hoạt động tốt trên phần cứng như vậy không?) Nếu nó sử dụng Template Haskell, điều này là không thể.

(Tình hình về GHC trên ARM ngày nay đã được cải thiện rất nhiều và tôi nghĩ 7.4.2 thậm chí còn hoạt động, nhưng quan điểm vẫn đứng vững).


1
Bản mẫu của Haskell dựa vào trình biên dịch và trình thông dịch mã byte tích hợp sẵn của GHC để chạy các biểu thức mối nối. - haskell.org/ghc/docs/7.6.2/html/usftimefide/ide
Joachim Breitner

2
à, tôi thì - không, TH sẽ không hoạt động nếu không có trình thông dịch mã byte, nhưng nó khác với (mặc dù có liên quan đến) ghci. Tôi sẽ không ngạc nhiên nếu có một mối quan hệ hoàn hảo giữa tính sẵn có của ghci và tính khả dụng của trình thông dịch mã byte, vì ghci không phụ thuộc vào trình thông dịch mã byte, nhưng vấn đề là thiếu trình thông dịch mã byte, chứ không phải thiếu trình thông báo ghci đặc biệt.
muhmuhten

1
(tình cờ, ghci có sự hỗ trợ đáng kinh ngạc cho việc sử dụng tương tác của TH. thử ghci -XTemplateHaskell <<< '$(do Language.Haskell.TH.runIO $ (System.Random.randomIO :: IO Int) >>= print; [| 1 |] )')
muhmuhten

Ok, với ghci tôi đã đề cập đến khả năng GHC diễn giải mã (thay vì biên dịch), không phụ thuộc vào việc mã có tương tác trong nhị phân ghci hay từ mối nối TH.
Joachim Breitner

6

Tại sao TH xấu? Đối với tôi, nó đi xuống này:

Nếu bạn cần tạo ra nhiều mã lặp đi lặp lại đến mức bạn thấy mình đang cố gắng sử dụng TH để tự động tạo mã, bạn đã làm sai!

Hãy suy nghĩ về nó. Một nửa sự hấp dẫn của Haskell là thiết kế cấp cao của nó cho phép bạn tránh được một lượng lớn mã soạn sẵn vô dụng mà bạn phải viết bằng các ngôn ngữ khác. Nếu bạn cần tạo mã thời gian biên dịch, về cơ bản, bạn nói rằng ngôn ngữ hoặc thiết kế ứng dụng của bạn đã làm bạn thất bại. Và chúng tôi lập trình viên không muốn thất bại.

Đôi khi, tất nhiên, nó là cần thiết. Nhưng đôi khi bạn có thể tránh việc cần TH bằng cách khéo léo hơn một chút với các thiết kế của mình.

(Một điều nữa là TH ở mức độ khá thấp. Không có thiết kế cấp cao nào; rất nhiều chi tiết triển khai nội bộ của GHC bị lộ. Và điều đó khiến API dễ bị thay đổi ...)


Tôi không nghĩ rằng điều đó có nghĩa là ngôn ngữ hoặc ứng dụng của bạn đã làm bạn thất vọng, đặc biệt nếu chúng tôi xem xét QuasiQuotes. Luôn có sự đánh đổi khi nói về cú pháp. Một số cú pháp mô tả tốt hơn một tên miền nhất định, vì vậy đôi khi bạn muốn có thể chuyển sang một cú pháp khác. QuasiQuotes cho phép bạn chuyển đổi thanh lịch giữa các cú pháp. Điều này rất mạnh mẽ và được Yesod và các ứng dụng khác sử dụng. Có thể viết mã tạo HTML bằng cú pháp có cảm giác như HTML là một tính năng tuyệt vời.
CoolCodeBro

@CoolCodeBro Vâng, trích dẫn khá hay và tôi cho rằng hơi trực giao với TH. (Rõ ràng là nó được triển khai trên TH.) Tôi đã suy nghĩ nhiều hơn về việc sử dụng TH để tạo các thể hiện của lớp hoặc xây dựng các chức năng của nhiều loại hoặc nhiều thứ tương tự.
Toán học,
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.