这段模糊的Haskell代码如何工作?


91

在阅读https://en.uncyclopedia.co/wiki/Haskell(并忽略所有“令人反感”的东西)时,我偶然发现了以下混淆代码:

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

当我在ghciData.Function和导入后Control.Applicative)中运行该代码时,ghci输出2的所有幂的列表。

此段代码如何工作?


3
我想知道答案是否会是一种冒犯性的冒犯……如果是正确的话,具有讽刺意味的是,考虑到您为避免庸俗所作的努力。
Meredith 2012年

31
你尝试了什么?可以尝试的明显方法是(a)删除注释,(b)重新格式化/重新插入代码,(c)确定正在使用哪个Functor / Applicative / Monad实例(可能是所有列表,但不要假设)。 。没有什么可以阻止一个痴呆的程序员在一行代码中使用Monad的五个不同实例),(d)尽可能地简化。然后看看剩下的东西。
dave4420

10
到目前为止,Haskell是我最喜欢的编程语言,但是uncyclopedia.wikia.com/wiki/Haskell令我大笑!
AndrewC


5
当有人找到他们可以用XYZ语言找到的最令人费解的密码片段,然后断言“实际上用XYZ语言编写可读代码几乎是不可能的”这一事实时,我真的很烦。但这只是我...
MathematicalOrchid

Answers:


219

首先,我们有一个可爱的定义

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太易读了。但是没有什么可担心的:我们可以使用monad法则来对其进行扩展。特别是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 -}
Simon Shine '18

14

我写了一个很长的答案,并完整地浏览了我的IRC日志,直到最终代码(这是在2008年初),最后我还是把所有的文本:) Daniel的大部分分析都是当场的。

这是我的开始:

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在使用Appliative组合器替换liftM2之前,请使用混淆功能。那时所有的空间都消失了。
  • 甚至我的“最终版本”也有很多.功能组成部分。用这些替换所有这些<$>显然在那和非百科全书之间的几个月中发生了一段时间。

顺便说一句,这是一个更新的版本,不再提及数字2

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

10
是否有意省略第一段中的“删除”一词?如果是这样,先生,我要戴上帽子。
杰克·布朗森

3
@JakeBrownson这是一个常见的互联网习惯用法,尽管我也不确定它是否是故意的。
Lambda Fairy 2014年

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]

    通过类型应用和替换,我们可以看到所讨论的monad是[]针对哪个的(>>= 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..]
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.