Skip to content

You've heard of the SKI combinators... How about the πŸ™ŠπŸ™‰πŸ™Š combinators?

License

Notifications You must be signed in to change notification settings

vivshaw/three-wise-monkeys

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

10 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

πŸ™ˆπŸ™‰πŸ™Š Three Wise Monkeys πŸ™ˆπŸ™‰πŸ™Š

An exercise in obfuscated Haskell vis-a-vis a primate proverb.

What does it do?

This program simply prints the text "siddhartha" using the SKI combinator calculus. This is further obfuscated through the abuse of arbitrary Unicode identifiers, as well as the the C preprocessor.

The key to understanding how this works goes like so:

-- What the heck is this?
wisdom See no e vil is (no -> e -> vil) -> (no -> e) -> no -> vil
-- Well, take a look at the S combinator:
type S f x y = (f -> x -> y) -> (f -> x) -> f -> y
-- Yup, the first monkey is just an obfuscated S combinator
(πŸ™ˆ) :: See no e vil

-- So this one should be easier:
wisdom Hear no evil is no -> evil -> no
-- Compare to the K combinator:
type K x y = x -> y -> x
-- Yup, the second monkey is just the K combinator (a.k.a const)
(πŸ™‰) :: Hear no evil

From here, we can construct the I combinator πŸ™Š (a.k.a. id), and have the whole of the SKI combinator calculus at our disposal. Additionally, I define the B and C combinators for ease of use, as 🐡 and πŸ’ respectively. From that point the rest is not too complex. If you wish to see which chunks of code correspond to which letters, simply undo all the ASCII art made from the monkeys and linebreak each time you see a ton of closing parenthesis, then study the repeating monkey patterns leading up to them. It will become clear that there are 10 main blobs (one for each character in "siddhartha") and each repeats the pattern ((πŸ™ˆ)(🐡) equal to the character's ASCII codepoint minus 10. This rather telling pattern should make it obvious how the rest works.

Obfuscation techniqes

Unicode abuse

Haskell is generous. Haskell loves you. Haskell allows arbitrary Unicode characters in identifiers and operators, because you deserve it.

  • Type names and variable names can have any Unicode letter or number in them, with the caveat that types must start with a Capital letter, and variables must start with a lowercase letter. As it turns out, you can use things like Hanzi, hangul, or kana as well- Haskell simply treats them as lowercase letters.
  • Operator names can have any non-special unicode symbol (pretty much everything except ()|,;[]`{}_:"'), including emoji. However YMMV - some symbols or combinations thereof will make GHC wig out, especially anything 'bracket-like'.

{-# LANGUAGE CPP #-}

With this GHC extension, we can run the C preprocessor on our Haskell source before compilation. This allows a large number of unusual substitutions that don't need to follow the rules of regular Haskell syntax, but do require obeying C identifier rules. As an added bonus, the behavior may be platform-dependent based on whether your system runs clang or gcc.

the SKI combinatory calculus

anything written with combinatory logic is obfuscated by default lol

Do you have any suggestions to further beautiful, comfound, or obfuscate?

PR's welcome if there's any other "FUN" things you can make GHC do. I didn't look too far into TH, rewrite rules, or RebindableSyntax.

Credit where credit is due

Inspiration is due to Sami Hangaslammi's SKI Hello World and the technique used to construct it.

About

You've heard of the SKI combinators... How about the πŸ™ŠπŸ™‰πŸ™Š combinators?

Topics

Resources

License

Stars

Watchers

Forks