Як працює цей фрагмент затуманеного коду Haskell?


91

Читаючи https://en.uncyclopedia.co/wiki/Haskell (і ігноруючи всі "образливі речі"), я натрапив на такий заплутаний код:

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

Коли я запускаю цей фрагмент коду в ghci(після імпорту Data.Functionта Control.Applicative), ghciдрукує список усіх повноважень з 2.

Як працює цей шматок коду?


3
Цікаво, чи відповідь може бути чимось огидним ... образливим, іронічним, враховуючи ваші зусилля, щоб уникнути вульгарності.
Мередіт

31
Що ви пробували? Очевидні речі, які слід спробувати: (а) видалити коментар, (б) переформатувати / повторно ввести код, (в) визначити, які екземпляри Functor / Applicative / Monad використовуються (можливо, усі перелічені, але не припускайте .. (ніщо не завадило б достатньо розумовому програмісту використовувати п'ять різних екземплярів Monad в одному рядку коду), (d) максимально спростити. Тоді подивіться, що з вами залишилось.
dave4420

10
Haskell - моя улюблена мова програмування, безумовно, але тим не менше uncyclopedia.wikia.com/wiki/Haskell так сильно насмішив мене!
AndrewC


5
Мене дуже дратує, коли хтось знаходить найбільш беззаперечно фрагмент кодового коду, який можна знайти в мові XYZ, а потім стверджує як факт, що "практично неможливо написати читабельний код на мові XYZ". Але це лише я ...
MathematicalOrchid

Відповіді:


219

Для початку ми маємо прекрасне визначення

x = 1 : map (2*) x

що саме по собі трохи збиває з розуму, якщо ви ніколи раніше цього не бачили. У будь-якому випадку це досить стандартний трюк ліні та рекурсії. Тепер ми позбудемося явної рекурсії за допомогою fixі point-free-ify.

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

Наступне, що ми збираємося зробити, це розширити :розділ і зробити mapнадмірно складним.

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

Ну, тепер у нас є дві копії цієї константи 1. Це ніколи не допоможе, тому ми використаємо програму читання, щоб видалити її. Крім того, склад функцій - це трохи сміття, тому давайте замінимо його на те, (<$>)де ми можемо.

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

Далі: цей дзвінок mapзанадто читабельний. Але боятися нічого: ми можемо використовувати закони монади, щоб трохи розширити їх. Зокрема fmap f x = x >>= return . f, так

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

Ми можемо вказати ify-free, замінити (.)на (<$>), а потім додати кілька помилкових розділів:

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

Підставивши це рівняння на нашому попередньому кроці:

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

Нарешті, ви розбиваєте пробіл і створюєте чудове остаточне рівняння

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

4
Ви пропустили {- thor's mother -}!
Саймон Шайн

14

Писав довгу відповідь із повним прогоном моїх журналів IRC експериментів, що приводили до остаточного коду (це було на початку 2008 р.), Але випадково я прочитав увесь текст :) Хоча це не така велика втрата - для здебільшого аналіз Даніеля є на місці.

Ось з чого я почав:

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 *))

Відмінності здебільшого зводяться до порядку, в якому відбувалися рефакторинг.

  • Замість того, x = 1 : map (2*) xщоб почати з 2 : map ..., і я зберігав це початкове 2 аж до останньої версії, де я втиснув a (*2)і змінив $2наприкінці на $1. Крок "робити карту непотрібно складною" не відбувся (настільки рано).
  • Я використовував liftM2 замість liftA2
  • Затуманена mapфункція була введена до заміни liftM2 на додаткові комбінатори. Тоді ж усі простори зникли.
  • Навіть у моїй "остаточній" версії залишилося багато .для складу функцій. Заміна всіх цих на, <$>мабуть, відбулася деякий час у місяці між цим та унциклопедією.

До речі, ось оновлена ​​версія, яка більше не згадує номер 2:

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

10
Чи є упущення слова "видалено" в першому абзаці навмисним? Якщо так, то капелюх вам, сер.
Джейк Браунсон,

3
@JakeBrownson Це загальна ідіома в Інтернеті , хоча я також не впевнений, чи це було навмисно .
Лямбда-фея

1

Обидві відповіді отримують заплутаний фрагмент коду з короткого оригіналу, виданого несподівано, але питання насправді задає питання, як довгий затуманений код виконує свою роботу.

Ось як:

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]  )  =<<)

Ось ( (\ x -> [2*x]) =<<) = (>>= (\ x -> [2*x])) = concatMap (\ x -> [2*x]) = map (2*)функція, тому ще раз <$> = .:

= 
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..]]

це всі повноваження 2 , у зростаючому порядку.


Це використовує

  • A <$> B <*> C $ x = liftA2 A B C xа оскільки liftA2 A B Cзастосовується до xцієї функції, то як функція це означає liftA2 A B C x = A (B x) (C x).
  • (f `op` g) = op f g = (f `op`) g = (`op` g) f три закони розділів оператора
  • >>=є монадичним зв'язком, а оскільки (`op` g) f = op f gі типи є

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

    за типом застосування та заміни ми бачимо, що монада, про яку йде мова, []для якої (>>= g) = concatMap g.

  • concatMap (\ x -> [2*x]) xs спрощується як

    concat $ map (\ x -> [2*x]) 
    =
    concat $ [ [2*x] | x <- xs]
    =
             [  2*x  | x <- xs]
    =
             map (\ x ->  2*x )
    
  • і за визначенням,

    (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..]]
    

    де

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

    так що

    ((2*)^n) 1  =  ((2*) . (2*) .  ...  . (2*)) 1
                =    2*  (  2*  (  ...  (  2*   1 )...)) 
                =    2^n   ,  for n in [0..]
    
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.