Skip to content

Instantly share code, notes, and snippets.

@crdrost
Last active August 29, 2015 14:07
Show Gist options
  • Save crdrost/e48aadcc685f0ca65576 to your computer and use it in GitHub Desktop.
Save crdrost/e48aadcc685f0ca65576 to your computer and use it in GitHub Desktop.
Custom guards for FizzBuzz in Haskell
Custom guards in Haskell
========================
In 2007, people changed their programming interview questions from complicated IQ-style questions to the new "FizzBuzz" style: give them a task which should be absurdly easy if you have ever programmed anything, but with one or two essential complications to prove that they have thought through the idea. The canonical example is:
* Write a program which prints the numbers from 1 to 100 in order, except instead of any number divisible by 3 it prints "Fizz" and instead of any number divisible by 5 it prints "Buzz". (When a number is divisible by both, it should print "FizzBuzz".)
This query tests for familiarity with the environment (e.g. in JavaScript you'll have to "print" to the DOM) and basic mathematics (what does "divisible" mean?) and logic (since it's not immediately in an if / else if / else format), but we assume that if you can do these things then you can do anything.
Some of the cooler answers use fall-through:
```php
<?php // Not a part of the LHS code!
for ($i = 1; $i <= 100; $i++) {
if ($i % 3 == 0) echo "Fizz";
if ($i % 5 == 0) echo "Buzz";
if ($i % 3 != 0 && $i % 5 != 0) echo "$i";
echo "\n";
}
```
The reason that this is "cooler" is because you can extend it directly to the "FizzBuzzBaz" case (if the number is divisible by 7, print "Baz") with just one more line of code, plus modifying one of the existing lines. (What we'd *really* want however is a check to say "if none of these conditions has matched.)
In Haskell you can get a quick victory for FizzBuzz by pattern-matching on `(mod i 3, mod i 5)` and handling the `(0, 0) -> "FizzBuzz"` case specially, but it lacks that cool fall-through aspect.
To do this in Haskell, I'll define a **custom guards** syntax.
Custom Guards defined
---------------------
We're going to need a model of the IO stream out, as given by `echo` above, and the simplest such model is a String, but any Monoid can hypothetically do.
> import Data.Monoid
If you're upset, for example, with my identification of `""` with a fixed-point of the group ("what if I want to explicitly represent the empty string?"), this gives you `instance (Monoid m) => Monoid (Maybe m)`, for which the `Nothing` token will be an explicit null value distinct from `Just ""`.
Our guards look like `|~ test ~> value` where the value must be both equatable and monoidal; if the test fails we just use `mempty`:
> (~>) :: (Eq m, Monoid m) => Bool -> m -> m
> test ~> x = if test then x else mempty
> infixr 1 ~>
We then have two logical-OR operations, both yielding `mempty` if and only if both arguments are `mempty`. The first is concatenative and the second clobbers:
> (|~) :: (Eq m, Monoid m) => m -> m -> m
> (|~) = (<>)
> infixl 0 |~
>
> (|~/~>) :: (Eq m, Monoid m) => m -> m -> m
> a |~/~> b = if a == mempty then b else a
> infixl 0 |~/~>
Interestingly, the `Monad` instance for `Maybe` defines a Kleisli operator which acts like a logical-AND (so `x -> Maybe y` and `y -> Maybe z` get chained together by a composition which yields `Nothing` if either `Maybe` yields `Nothing`); this kind of looks like a reverse of that. Anyway, you could also define things like:
a |~* b = if a == mempty || b == mempty then mempty else a <> b
a |~*~> b = if a == mempty || b == mempty then mempty else b
but they are not a part of this example.
FizzBuzz with our custom guards
-------------------------------
You can optionally omit the leading `"" |~` and the line breaks here
> main = mapM_ putStrLn [""
> |~ mod i 3 == 0 ~> "Fizz"
> |~ mod i 5 == 0 ~> "Buzz"
> |~/~> show i
> | i <- [1..100]]
The `mapM_ putStrLn` does the actual IO here. Unlike PHP, this has only two calls to `mod` and tests for the empty string on the final result; it is essentially equivalent to the Python code:
```python
for i in range(1, 101): # Not a part of the LHS code!
x = ""
if i % 3 == 0:
x += "Fizz"
if i % 5 == 0:
x += "Buzz"
if x == "":
x = str(i)
print i
```
As promised, the above Haskell code can be generalized to the FizzBuzzBaz case with an extra `|~ mod i 7 == 0 ~> "Baz"` line; no other changes are needed (unless you want to push the range past 105 so you see `FizzBuzzBaz` once).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment