Как работает эта часть запутанного кода Haskell?

При чтении http://uncyclopedia.wikia.com/wiki/Haskell (и игнорируя все "оскорбительные" вещи), я наткнулся на следующий фрагмент запутанного кода:

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

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

Как работает этот кусок кода?

Ответ 1

Начнем с того, что у нас есть прекрасное определение

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

Мы можем с помощью point-free-ify заменить (.) на (<$>), а затем добавить некоторые паразитные разделы:

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

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

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

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

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

Ответ 2

Написал длинный ответ с полным прохождением моих журналов 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 вплоть до самой последней версии, где я сжал (*2) и изменил $2 в конце на $1, "Сделать карту ненужно сложным" шаг не произошел (что рано).
  • Я использовал liftM2 вместо liftA2
  • Запущенная функция map была вставлена ​​перед заменой liftM2 с помощью аппликативных комбинаторов. Это также, когда все пробелы исчезли.
  • Даже у моей "финальной" версии было много . для композиции функций. Замена всех тех, у кого <$>, по-видимому, произошло некоторое время в месяцах между этим и некробликой.

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

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