[英]How does this piece of obfuscated Haskell code work?
While reading https://en.uncyclopedia.co/wiki/Haskell (and ignoring all the "offensive" stuff), I stumbled upon the following piece of obfuscated code:在阅读https://en.uncyclopedia.co/wiki/Haskell (并忽略所有“令人反感”的东西)时,我偶然发现了以下一段混淆代码:
fix$(<$>)<$>(:)<*>((<$>((:[{- thor's mother -}])<$>))(=<<)<$>(*)<$>(*2))$1
When I run that piece of code in ghci
(after importing Data.Function
and Control.Applicative
), ghci
prints the list of all powers of 2.当我在
ghci
运行那段代码时(在导入Data.Function
和Control.Applicative
), ghci
打印出 2 的所有幂的列表。
How does this piece of code work?这段代码是如何工作的?
To begin with, we have the lovely definition首先,我们有一个可爱的定义
x = 1 : map (2*) x
which by itself is a bit mind-bending if you've never seen it before.如果您以前从未见过它,这本身就有点令人费解。 Anyway it's a fairly standard trick of laziness and recursion.
无论如何,这是一个相当标准的懒惰和递归技巧。 Now, we'll get rid of the explicit recursion using
fix
, and point-free-ify.现在,我们将使用
fix
和 point-free-ify 摆脱显式递归。
x = fix (\vs -> 1 : map (2*) vs)
x = fix ((1:) . map (2*))
The next thing we're going to do is expand the :
section and make the map
needlessly complex.我们要做的下一件事是扩展
:
部分并使map
变得不必要地复杂。
x = fix ((:) 1 . (map . (*) . (*2)) 1)
Well, now we have two copies of that constant 1
.好吧,现在我们有这个常量
1
两个副本。 That will never do, so we'll use the reader applicative to de-duplicate that.那永远不会,所以我们将使用 reader applicative 去重复它。 Also, function composition is a bit rubbish, so let's replace that with
(<$>)
wherever we can.此外,函数组合有点垃圾,所以让我们尽可能用
(<$>)
替换它。
x = fix (liftA2 (.) (:) (map . (*) . (*2)) 1)
x = fix (((.) <$> (:) <*> (map . (*) . (*2))) 1)
x = fix (((<$>) <$> (:) <*> (map <$> (*) <$> (*2))) 1)
Next up: that call to map
is much too readable.接下来:对
map
调用太易读了。 But there's nothing to fear: we can use the monad laws to expand it a bit.但是没有什么可害怕的:我们可以使用单子定律来扩展它。 In particular,
fmap fx = x >>= return . f
特别是,
fmap fx = x >>= return . f
fmap fx = x >>= return . f
, so fmap fx = x >>= return . f
, 所以
map f x = x >>= return . f
map f x = ((:[]) <$> f) =<< x
We can point-free-ify, replace (.)
with (<$>)
, and then add some spurious sections:我们可以点自由化,将
(.)
替换为(<$>)
,然后添加一些虚假部分:
map = (=<<) . ((:[]) <$>)
map = (=<<) <$> ((:[]) <$>)
map = (<$> ((:[]) <$>)) (=<<)
Substituting this equation in our previous step:代入我们上一步中的这个等式:
x = fix (((<$>) <$> (:) <*> ((<$> ((:[]) <$>)) (=<<) <$> (*) <$> (*2))) 1)
Finally, you break your spacebar and produce the wonderful final equation最后,你打破你的空格键并产生美妙的最终方程
x=fix(((<$>)<$>(:)<*>((<$>((:[])<$>))(=<<)<$>(*)<$>(*2)))1)
Was writing a long answer with a full run-through of my IRC logs of the experiments leading up to the final code (this was in early 2008), but I accidentally all the text :) Not that much of a loss though - for the most part Daniel's analysis is spot on.正在写一个很长的答案,其中包含导致最终代码的实验的完整运行 IRC 日志(这是在 2008 年初),但我不小心写下了所有文本:) 虽然损失不大 - 对于Daniel 的大部分分析都恰到好处。
Here's what I started with:这是我的开始:
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 *))
The differences mostly come down to the order in which the refactorings happened.差异主要归结为重构发生的顺序。
x = 1 : map (2*) x
I started with 2 : map ...
, and I kept that initial 2 right up until the very last version, where I squeezed in a (*2)
and changed the $2
at the end into $1
.x = 1 : map (2*) x
我开始用2 : map ...
,我不停,最初的2右,直到最后的版本,在这里我在挤(*2)
并改变了$2
处最后变成$1
。 The "make the map needlessly complex" step didn't happen (that early). map
function was put in before replacing liftM2 with Applicative combinators.map
函数。 That's also when all the spaces disappeared..
.
for function composition left over.<$>
apparently happened some time in the months between that and uncyclopedia.<$>
替换所有这些显然发生在那个和 uncyclopedia 之间的几个月里。 BTW, here's an updated version that no longer mentions the number 2
:顺便说一句,这是一个不再提及数字
2
的更新版本:
fix$(<$>)<$>(:)<*>((<$>((:[{- Jörð -}])<$>))(=<<)<$>(*)<$>(>>=)(+)($))$1
Both answers derive the obfuscated code snippet from the short original given out of the blue, but the question actually asks how does the long obfuscated code do its job.这两个答案都是从突然给出的简短原始代码中得出混淆代码片段的,但问题实际上是问长混淆代码如何完成其工作。
Here's how:就是这样:
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] ) =<<)
Here ( (\\ x -> [2*x]) =<<) = (>>= (\\ x -> [2*x])) = concatMap (\\ x -> [2*x]) = map (2*)
is a function, so again, <$> = .
这里
( (\\ 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..]]
are all the powers of 2 , in increasing order.都是2的幂,按升序排列。
This uses这使用
A <$> B <*> C $ x = liftA2 ABC x
and since liftA2 ABC
is applied to x
it's a function, an as a function it means liftA2 ABC x = A (B x) (C x)
. A <$> B <*> C $ x = liftA2 ABC x
并且因为liftA2 ABC
被应用到x
它是一个函数,作为一个函数它意味着liftA2 ABC x = A (B x) (C x)
。(f `op` g) = op fg = (f `op`) g = (`op` g) f
are the three laws of operator sections (f `op` g) = op fg = (f `op`) g = (`op` g) f
是算子部分的三个定律>>=
is monadic bind, and since (`op` g) f = op fg
and the types are >>=
是(`op` g) f = op fg
绑定,因为(`op` g) f = op fg
并且类型是
(>>=) :: Monad m => ma -> (a -> mb ) -> mb (\\ x -> [2*x]) :: Num t => t -> [ t] (>>= (\\ x -> [2*x])) :: Num t => [ t] -> [ t]
by type application and substitution we see that the monad in question is []
for which (>>= g) = concatMap g
.通过类型应用和替换,我们看到所讨论的 monad 是
[]
其中(>>= g) = concatMap g
。
concatMap (\\ x -> [2*x]) xs
is simplified as concatMap (\\ x -> [2*x]) xs
简化为
concat $ map (\\ x -> [2*x]) = concat $ [ [2*x] | x <- xs] = [ 2*x | x <- xs] = map (\\ x -> 2*x )
and by definition,根据定义,
(f . g) x = f (gx) fix f = let x = fx in x iterate fx = x : iterate f (fx) = x : let y = fx in y : iterate f (fy) = x : let y = fx in y : let z = fy in z : iterate f (fz) = ... = [ (f^n) x | n <- [0..]]
where在哪里
f^n = f . f . ... . f -- \\_____n_times _______/
so that以便
((2*)^n) 1 = ((2*) . (2*) . ... . (2*)) 1 = 2* ( 2* ( ... ( 2* 1 )...)) = 2^n , for n in [0..]
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.