Đoạn mã Haskell bị xáo trộn này hoạt động như thế nào?


91

Trong khi đọc https://en.uncyclopedia.co/wiki/Haskell (và bỏ qua tất cả những thứ "gây khó chịu"), tôi tình cờ gặp đoạn mã khó hiểu sau:

fix$(<$>)<$>(:)<*>((<$>((:[{- thor's mother -}])<$>))(=<<)<$>(*)<$>(*2))$1

Khi tôi chạy đoạn mã đó trong ghci(sau khi nhập Data.FunctionControl.Applicative), ghciin danh sách tất cả các lũy thừa của 2.

Đoạn mã này hoạt động như thế nào?


3
Tôi tự hỏi liệu câu trả lời có phải là điều gì đó xúc phạm hay không ... nếu đúng, thật mỉa mai khi xem xét nỗ lực của bạn để tránh những lời thô tục.
Meredith

31
Bạn đã thử những gì? Những điều rõ ràng cần thử là (a) xóa nhận xét, (b) định dạng lại / nhập lại mã, (c) tìm ra các trường hợp Functor / Ứng dụng / Đơn nguyên nào đang được sử dụng (có thể là tất cả danh sách, nhưng đừng giả sử .. . không có gì có thể ngăn cản một lập trình viên đủ mất trí nhớ sử dụng năm phiên bản Monad khác nhau trong một dòng mã), (d) hãy đơn giản hóa hết mức có thể. Sau đó, xem những gì bạn còn lại.
dave4420 30/09/12

10
Haskell là ngôn ngữ lập trình yêu thích của tôi cho đến nay, nhưng tuy nhiên, Uncyclopedia.wikia.com/wiki/Haskell đã làm tôi cười rất nhiều!
AndrewC


5
Nó thực sự làm phiền tôi khi ai đó tìm thấy đoạn mã khó hiểu vô cớ nhất mà họ có thể tìm thấy trong ngôn ngữ XYZ, và sau đó khẳng định như thực tế rằng "hầu như không thể viết mã có thể đọc được bằng ngôn ngữ XYZ". Nhưng đó chỉ là tôi ...
MathematicalOrchid

Câu trả lời:


219

Để bắt đầu, chúng ta có định nghĩa đáng yêu

x = 1 : map (2*) x

bản thân nó có một chút suy nghĩ nếu bạn chưa từng thấy nó trước đây. Dù sao thì đó cũng là một mẹo khá tiêu chuẩn của sự lười biếng và đệ quy. Bây giờ, chúng ta sẽ loại bỏ đệ quy rõ ràng bằng cách sử dụng fixvà point-free-ify.

x = fix (\vs -> 1 : map (2*) vs)
x = fix ((1:) . map (2*))

Điều tiếp theo chúng tôi sẽ làm là mở rộng :phần và làm cho phần mapphức tạp không cần thiết.

x = fix ((:) 1 . (map . (*) . (*2)) 1)

Bây giờ chúng ta có hai bản sao của hằng số đó 1. Điều đó sẽ không bao giờ xảy ra, vì vậy chúng tôi sẽ sử dụng ứng dụng người đọc để loại bỏ trùng lặp. Ngoài ra, thành phần chức năng là một chút rác, vì vậy hãy thay thế nó bằng (<$>)bất cứ nơi nào chúng ta có thể.

x = fix (liftA2 (.) (:) (map . (*) . (*2)) 1)
x = fix (((.) <$> (:) <*> (map . (*) . (*2))) 1)
x = fix (((<$>) <$> (:) <*> (map <$> (*) <$> (*2))) 1)

Tiếp theo: cuộc gọi đến đó mapquá khó đọc. Nhưng không có gì phải lo sợ: chúng ta có thể sử dụng luật đơn nguyên để mở rộng nó một chút. Đặc biệt fmap f x = x >>= return . f, vì vậy

map f x = x >>= return . f
map f x = ((:[]) <$> f) =<< x

Chúng tôi có thể point-free-ify, thay thế (.)bằng (<$>)và sau đó thêm một số phần giả mạo:

map = (=<<) . ((:[]) <$>)
map = (=<<) <$> ((:[]) <$>)
map = (<$> ((:[]) <$>)) (=<<)

Thay thế phương trình này trong bước trước của chúng tôi:

x = fix (((<$>) <$> (:) <*> ((<$> ((:[]) <$>)) (=<<) <$> (*) <$> (*2))) 1)

Cuối cùng, bạn phá vỡ phím cách và tạo ra phương trình cuối cùng tuyệt vời

x=fix(((<$>)<$>(:)<*>((<$>((:[])<$>))(=<<)<$>(*)<$>(*2)))1)

4
Bạn bỏ ra ngoài {- thor's mother -}!
Simon Shine

14

Đang viết một câu trả lời dài với bản xem qua đầy đủ nhật ký IRC của tôi về các thử nghiệm dẫn đến mã cuối cùng (đây là vào đầu năm 2008), nhưng tôi vô tình viết tất cả văn bản :) Tuy nhiên, không có nhiều sự mất mát - đối với phần lớn phân tích của Daniel là đúng.

Đây là những gì tôi bắt đầu với:

Jan 25 23:47:23 <olsner>        @pl let q = 2 : map (2*) q in q
Jan 25 23:47:23 <lambdabot>     fix ((2 :) . map (2 *))

Sự khác biệt chủ yếu phụ thuộc vào thứ tự mà việc tái cấu trúc xảy ra.

  • Thay vì x = 1 : map (2*) xtôi bắt đầu với 2 : map ..., và tôi giữ nguyên 2 đầu tiên đó cho đến phiên bản cuối cùng, nơi tôi nhấn vào a (*2)và thay đổi $2ở cuối thành $1. Bước "tạo bản đồ không cần phức tạp" đã không xảy ra (sớm như vậy).
  • Tôi đã sử dụng liftM2 thay vì liftA2
  • mapChức năng xáo trộn đã được đưa vào trước khi thay thế liftM2 bằng các tổ hợp ứng dụng. Đó cũng là lúc tất cả các khoảng trống biến mất.
  • Ngay cả phiên bản "cuối cùng" của tôi .còn sót lại rất nhiều thành phần chức năng. Việc thay thế tất cả những thứ đó bằng <$>dường như đã xảy ra một thời gian trong những tháng giữa từ đó và chưa có từ điển bách khoa.

BTW, đây là phiên bản cập nhật không còn đề cập đến số 2:

fix$(<$>)<$>(:)<*>((<$>((:[{- Jörð -}])<$>))(=<<)<$>(*)<$>(>>=)(+)($))$1

10
Việc bỏ từ “đã xóa” ở đoạn đầu có phải là cố ý không? Nếu vậy tôi ngả mũ chào ông.
Jake Brownson

3
@JakeBrownson Đó là một thành ngữ phổ biến trên internet , mặc dù tôi cũng không chắc liệu nó có cố ý hay không.
Lambda Fairy

1

Cả hai câu trả lời đều lấy ra đoạn mã bị xáo trộn từ đoạn mã gốc ngắn được đưa ra ngoài màu xanh lam, nhưng câu hỏi thực sự đặt ra câu hỏi làm thế nào để đoạn mã dài bị xáo trộn thực hiện công việc của nó.

Đây là cách thực hiện:

fix$(<$>)<$>(:)<*>((<$>((:[{- thor's mother -}])<$>))(=<<)<$>(*)<$>(*2))$1 
= {- add spaces, remove comment -}
fix $ (<$>) <$> (:) <*> ( (<$> ((:[]) <$>) ) (=<<)  <$>  (*)  <$>  (*2) ) $ 1 
--                      \__\______________/_____________________________/
= {-    A   <$> B   <*> C                          $ x   =   A (B x) (C x) -}
fix $ (<$>) (1 :)     ( ( (<$> ((:[]) <$>) ) (=<<)  <$>  (*)  <$>  (*2) ) 1 )
--                      \__\______________/____________________________/
= {- op f g = (f `op` g) ; (`op` g) f = (f `op` g) -}
fix $ (1 :) <$>  ( (((=<<) <$> ((:[]) <$>) )        <$>  (*)  <$>  (*2) ) 1 )
--                  \\____________________/____________________________/
= {- <$> is left associative anyway -}
fix $ (1 :) <$>  ( ( (=<<) <$> ((:[]) <$>)          <$>  (*)  <$>  (*2) ) 1 )
--                  \__________________________________________________/
= {- A <$> foo = A . foo when foo is a function -}
fix $ (1 :) <$>  ( ( (=<<) <$> ((:[]) <$>)           .   (*)   .   (*2) ) 1 )
--                  \__________________________________________________/
= {- ((:[]) <$>) = (<$>) (:[]) = fmap (:[])  is a function -}
fix $ (1 :) <$>  ( ( (=<<)  .  ((:[]) <$>)           .   (*)   .   (*2) ) 1 )
--                  \__________________________________________________/
= {- (a . b . c . d) x = a (b (c (d x))) -}
fix $ (1 :) <$>      (=<<)  (  ((:[]) <$>)           (   (*)   (   (*2)   1 )))
= {- (`op` y) x = (x `op` y) -}
fix $ (1 :) <$>      (=<<)  (  ((:[]) <$>)           (   (*)   2             ))
= {- op x = (x `op`) -}
fix $ (1 :) <$>      (=<<)  (  ((:[]) <$>)              (2*)                  )
= {-  (f `op`) g = (f `op` g) -}
fix $ (1 :) <$>      (=<<)  (   (:[]) <$>               (2*)                  )
= {-  A <$> foo = A . foo when foo is a function -}
fix $ (1 :) <$>      (=<<)  (   (:[])  .                (2*)                  )
= {-  (f . g) = (\ x -> f (g x)) -}
fix $ (1 :) <$>      (=<<)  (\ x -> [2*x]  )
= {- op f = (f `op`)  -}
fix $ (1 :) <$>           ( (\ x -> [2*x]  )  =<<)

Dưới đây ( (\ x -> [2*x]) =<<) = (>>= (\ x -> [2*x])) = concatMap (\ x -> [2*x]) = map (2*)là một chức năng, vì vậy một lần nữa, <$> = .:

= 
fix $ (1 :)  .  map (2*)
= {- substitute the definition of fix -}
let xs = (1 :) . map (2*) $ xs in xs
=
let xs = 1 : [ 2*x | x <- xs] in xs
= {- xs = 1 : ys -}
let ys =     [ 2*x | x <- 1:ys] in 1:ys
= {- ys = 2 : zs -}
let zs =     [ 2*x | x <- 2:zs] in 1:2:zs
= {- zs = 4 : ws -}
let ws =     [ 2*x | x <- 4:ws] in 1:2:4:ws
=
iterate (2*) 1
= 
[2^n | n <- [0..]]

là tất cả các lũy thừa của 2 , theo thứ tự tăng dần.


Điều này sử dụng

  • A <$> B <*> C $ x = liftA2 A B C xvà kể từ khi liftA2 A B Cđược áp dụng cho xnó là một chức năng, một chức năng nó có nghĩa là liftA2 A B C x = A (B x) (C x).
  • (f `op` g) = op f g = (f `op`) g = (`op` g) f là ba luật của phần toán tử
  • >>=là ràng buộc đơn nguyên, và kể từ khi (`op` g) f = op f gvà các loại

    (>>=)                :: Monad m => m a -> (a -> m b ) -> m b
    (\ x -> [2*x])       :: Num t   =>         t -> [ t]
    (>>= (\ x -> [2*x])) :: Num t   => [ t]               -> [ t]

    theo loại ứng dụng và thay thế, chúng tôi thấy rằng đơn nguyên được đề cập là []dành cho cái nào (>>= g) = concatMap g.

  • concatMap (\ x -> [2*x]) xs được đơn giản hóa thành

    concat $ map (\ x -> [2*x]) 
    =
    concat $ [ [2*x] | x <- xs]
    =
             [  2*x  | x <- xs]
    =
             map (\ x ->  2*x )
  • và theo định nghĩa,

    (f . g) x  =  f (g x)
    
    fix f  =  let x = f x in x
    
    iterate f x  =  x : iterate f (f x)
                 =  x : let y = f x in 
                        y : iterate f (f y)
                 =  x : let y = f x in 
                        y : let z = f y in 
                            z : iterate f (f z)
                 = ...
                 = [ (f^n) x | n <- [0..]]

    Ở đâu

            f^n  =  f  .  f  .  ...  . f
            --     \_____n_times _______/

    vậy nên

    ((2*)^n) 1  =  ((2*) . (2*) .  ...  . (2*)) 1
                =    2*  (  2*  (  ...  (  2*   1 )...)) 
                =    2^n   ,  for n in [0..]
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.