Como funciona esse código Haskell ofuscado?

91

Ao ler https://en.uncyclopedia.co/wiki/Haskell (e ignorando todas as coisas "ofensivas"), me deparei com a seguinte parte do código ofuscado:

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

Quando executo esse trecho de código em ghci(após importar Data.Functione Control.Applicative), ghciimprime a lista de todas as potências de 2.

Como funciona esse código?

Alexandros
fonte
3
Eu me pergunto se a resposta seria algo hubristicamente ofensivo ... se for verdade, irônico considerando seus esforços para evitar a vulgaridade.
Meredith
31
O que você tentou? As coisas óbvias a tentar são (a) remover o comentário, (b) reformatar / reindentar o código, (c) descobrir quais instâncias de Functor / Applicative / Monad estão sendo usadas (provavelmente todas as listas, mas não suponha .. nada impediria um programador suficientemente demente de usar cinco instâncias diferentes do Monad em uma única linha de código), (d) simplificar o máximo que puder. Então veja o que resta.
dave4420
10
Haskell é minha linguagem de programação favorita, de longe, mas mesmo assim uncyclopedia.wikia.com/wiki/Haskell me fez rir muito!
AndrewC de
5
Realmente me irrita quando alguém encontra o fragmento de código mais criptografado que pode encontrar na linguagem XYZ, e então afirma que é "virtualmente impossível escrever código legível na linguagem XYZ". Mas isso sou só eu ...
Orquídea Matemática

Respostas:

219

Para começar, temos a bela definição

x = 1 : map (2*) x

o que por si só é um pouco alucinante, se você nunca viu antes. De qualquer forma, é um truque bastante comum de preguiça e recursão. Agora, vamos nos livrar da recursão explícita usando fixe point-free-ify.

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

A próxima coisa que vamos fazer é expandir a :seção e tornar o mapdesnecessariamente complexo.

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

Bem, agora temos duas cópias dessa constante 1. Isso nunca vai funcionar, então usaremos o aplicativo do leitor para eliminar a duplicação. Além disso, a composição de funções é um pouco lixo, então vamos substituí-la (<$>)sempre que pudermos.

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

A seguir: essa chamada para mapé muito legível. Mas não há nada a temer: podemos usar as leis da mônada para expandi-lo um pouco. Em particular fmap f x = x >>= return . f, então

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

Podemos apontar livremente, substituir (.)por (<$>)e, em seguida, adicionar algumas seções espúrias:

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

Substituindo esta equação em nossa etapa anterior:

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

Finalmente, você quebra a barra de espaço e produz a maravilhosa equação final

x=fix(((<$>)<$>(:)<*>((<$>((:[])<$>))(=<<)<$>(*)<$>(*2)))1)
Daniel Wagner
fonte
4
Você deixou de fora {- thor's mother -}!
Simon Shine,
14

Estava escrevendo uma longa resposta com uma análise completa dos meus logs de IRC dos experimentos que levaram ao código final (isso foi no início de 2008), mas eu acidentalmente todo o texto :) Não é uma grande perda - para o a maior parte da análise de Daniel está correta.

Aqui está o que eu comecei:

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

As diferenças se resumem principalmente à ordem em que as refatorações aconteceram.

  • Ao invés de x = 1 : map (2*) xeu comecei com 2 : map ..., e mantive aquele 2 inicial até a última versão, onde apertei um (*2)e mudei o $2no final para $1. A etapa "tornar o mapa desnecessariamente complexo" não aconteceu (tão cedo).
  • Usei liftM2 em vez de liftA2
  • A mapfunção ofuscada foi colocada antes de substituir liftM2 por combinadores Aplicativos. Foi então que todos os espaços desapareceram.
  • Até a minha versão "final" tinha muito .para composição de funções sobrando. A substituição de todos por <$>aparentemente aconteceu algum tempo nos meses entre isso e a desciclopédia.

BTW, aqui está uma versão atualizada que não menciona mais o número 2:

fix$(<$>)<$>(:)<*>((<$>((:[{- Jörð -}])<$>))(=<<)<$>(*)<$>(>>=)(+)($))$1
Olsner
fonte
10
A omissão da palavra "excluído" no primeiro parágrafo é intencional? Se assim for, tiro o chapéu para o senhor.
Jake Brownson
3
@JakeBrownson É um idioma comum na Internet , embora eu também não tenha certeza se foi de propósito.
Lambda Fairy
1

Ambas as respostas derivam o trecho de código ofuscado do original curto fornecido do nada, mas a questão realmente pergunta como o código ofuscado faz seu trabalho.

Veja como:

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

Aqui ( (\ x -> [2*x]) =<<) = (>>= (\ x -> [2*x])) = concatMap (\ x -> [2*x]) = map (2*)está uma função, então, novamente <$> = .:

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

são todas as potências de 2 , em ordem crescente.


Isso usa

  • A <$> B <*> C $ x = liftA2 A B C xe, uma vez que liftA2 A B Cé aplicado a xuma função, significa uma função liftA2 A B C x = A (B x) (C x).
  • (f `op` g) = op f g = (f `op`) g = (`op` g) f são as três leis das seções de operação
  • >>=é ligação monádica, e desde que (`op` g) f = op f ge os tipos são

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

    pela aplicação e substituição de tipo, vemos que a mônada em questão é []para qual (>>= g) = concatMap g.

  • concatMap (\ x -> [2*x]) xs é simplificado como

    concat $ map (\ x -> [2*x]) 
    =
    concat $ [ [2*x] | x <- xs]
    =
             [  2*x  | x <- xs]
    =
             map (\ x ->  2*x )
  • e por definição,

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

    Onde

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

    de modo a

    ((2*)^n) 1  =  ((2*) . (2*) .  ...  . (2*)) 1
                =    2*  (  2*  (  ...  (  2*   1 )...)) 
                =    2^n   ,  for n in [0..]
Will Ness
fonte