Читая https://en.uncyclopedia.co/wiki/Haskell (и игнорируя все «оскорбительные» вещи), я наткнулся на следующий фрагмент запутанного кода:
fix$(<$>)<$>(:)<*>((<$>((:[{- thor's mother -}])<$>))(=<<)<$>(*)<$>(*2))$1
Когда я запускаю этот фрагмент кода ghci
(после импорта Data.Function
и Control.Applicative
), ghci
распечатывает список всех степеней двойки.
Как работает этот фрагмент кода?
Ответы:
Для начала у нас есть прекрасное определение
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)
источник
{- thor's mother -}
!Писал длинный ответ с полным прогоном моих 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
. Шага «сделать карту излишне сложной» не произошло (так рано).map
функция была добавлена до замены liftM2 комбинаторами Applicative. Тогда же исчезли все пробелы..
для композиции функций. Замена всех тех на, по<$>
всей видимости, произошла через какое-то время между этим и анциклопедией.Кстати, вот обновленная версия, в которой больше не упоминается номер
2
:fix$(<$>)<$>(:)<*>((<$>((:[{- Jörð -}])<$>))(=<<)<$>(*)<$>(>>=)(+)($))$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..]
источник