Как работает этот запутанный код Haskell?

90

Читая https://en.uncyclopedia.co/wiki/Haskell (и игнорируя все «оскорбительные» вещи), я наткнулся на следующий фрагмент запутанного кода:

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

Когда я запускаю этот фрагмент кода ghci(после импорта Data.Functionи Control.Applicative), ghciраспечатывает список всех степеней двойки.

Как работает этот фрагмент кода?

Александрос
источник
3
Интересно, будет ли ответ чем-то высокомерно оскорбительным ... если это правда, то ироничным, учитывая ваши попытки избежать пошлости.
Мередит
31
Что ты пробовал? Очевидные вещи, которые следует попробовать, это (а) удалить комментарий, (б) переформатировать / переназначить код, (в) выяснить, какие экземпляры Functor / Applicative / Monad используются (возможно, весь список, но не предполагайте .. ... ничто не помешает достаточно безумному программисту использовать пять разных экземпляров Monad в одной строке кода), (d) упростить, насколько это возможно. Тогда посмотри, что у тебя осталось.
dave4420
10
Haskell, безусловно, мой любимый язык программирования, но тем не менее uncyclopedia.wikia.com/wiki/Haskell меня так рассмешил!
AndrewC 01
5
Меня действительно раздражает, когда кто-то находит самый необоснованно загадочный фрагмент кода, который они могут найти на языке XYZ, а затем утверждает как факт, что «практически невозможно написать читаемый код на языке XYZ». Но это только я ...
MathematicalOrchid

Ответы:

218

Для начала у нас есть прекрасное определение

x = 1 : map (2*) x

что само по себе немного умопомрачительно, если вы никогда его раньше не видели. В любом случае это довольно стандартный трюк лени и рекурсии. Теперь мы избавимся от явной рекурсии с использованием fixи без точки.

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

Мы можем избавиться от точек, заменить (.)на (<$>), а затем добавить несколько ложных секций:

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

Подставив это уравнение на наш предыдущий шаг:

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

Наконец, вы ломаете пробел и получаете чудесное окончательное уравнение

x=fix(((<$>)<$>(:)<*>((<$>((:[])<$>))(=<<)<$>(*)<$>(*2)))1)
Даниэль Вагнер
источник
4
Вы ушли {- thor's mother -}!
Саймон Шайн
13

Писал длинный ответ с полным прогоном моих 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 комбинаторами Applicative. Тогда же исчезли все пробелы.
  • Даже в моей «последней» версии оставалось много работы .для композиции функций. Замена всех тех на, по <$>всей видимости, произошла через какое-то время между этим и анциклопедией.

Кстати, вот обновленная версия, в которой больше не упоминается номер 2:

fix$(<$>)<$>(:)<*>((<$>((:[{- Jörð -}])<$>))(=<<)<$>(*)<$>(>>=)(+)($))$1
olsner
источник
10
Умышленно ли опущено слово «удален» в первом абзаце? В таком случае снимаю перед вами шляпу, сэр.
Джейк Браунсон
3
@JakeBrownson Это обычная интернет-идиома , хотя я также не уверен, было ли это намеренно.
Lambda Fairy
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..]]

все степени двойки в порядке возрастания.


Это использует

  • 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]
    

    по типу application и подстановке мы видим, что рассматриваемая монада предназначена []для (>>= 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..]
    
Уилл Несс
источник