Hacker News new | ask | show | jobs
by icedog 4260 days ago
Putting the String issue aside, I just wanted to show the beauty of pattern matching.

    for i in range(1i, 101) {
        match (i % 3, i % 5) {
            (0, 0) => println!("Fizzbuzz"),
            (0, _) => println!("Fizz"),
            (_, 0) => println!("Buzz"),
            _ => println!("{}", i),
        }
    }
-- edited: removed `.to_string()`, thanks chrismorgan
6 comments

I haven't looked too deeply into Rust yet, but was able to understand this coming from Elixir. Pattern matching makes for a beautiful solution. This is a similar solution in Elixir:

  fizzbuzz = fn(x) ->
    case {rem(x, 3) == 0, rem(x, 5) == 0} do
      {true, false} -> IO.puts "fizz"
      {false, true} -> IO.puts "buzz"
      {true, true}  -> IO.puts "fizzbuzz"
      _             -> IO.puts x
    end
  end

  Enum.each Range.new(1, num), fizzbuzz
Since functions are also pattern matched in Elixir (and Erlang!) it could also be done without using case and handled purely as functions.
Yeah, it's also fun that you can do it in a multiple function head pattern matchy way

  defmodule FizzBuzz do
    def fizzbuzz(x),          do: fizzbuzz(x, {rem(x, 3), rem(x, 5)})
    def fizzbuzz(_x, {0, 0}), do: IO.puts "fizzbuzz"
    def fizzbuzz(_x, {0, _}), do: IO.puts "fizz"
    def fizzbuzz(_x, {_, 0}), do: IO.puts "buzz"
    def fizzbuzz(x,  {_, _}), do: IO.puts x
  end

  Enum.each Range.new(1, 100), &FizzBuzz.fizzbuzz/1

or in a more ruby-esque fashion

  (1..100) |> Enum.each fn(x) ->
    cond do
      rem(x, 3) == 0 and rem(x, 5) == 0 ->
        IO.puts "fizzbuzz"
      rem(x,5) == 0 ->
        IO.puts "buzz"
      rem(x,3) == 0 ->
        IO.puts "fizz"
      true ->
        IO.puts x
    end
  end
Python doesn't have pattern matching but the code is basically the same. I guess better cases for showing off the feature are ones where the patterns aren't just True/False tuples.

    for i in range (1, 101):
    	fbsign = (i % 3 == 0, i % 5 == 0)
	if fbsign == (1, 1): print("Fizzbuzz")
	elif fbsign == (1, 0): print("Fizz")
	elif fbsign == (0, 1): print("Buzz")
	else: print(i)
One difference is that this won't do exhaustiveness checks, where the pattern match will.
Well, here's an exhaustively checking version. Not statically still of course.

    for x in range(1,101):
        print [
                          # %3 = 0
        [      x,         "Fizz"  ],
        [   "Buzz",     "FizzBuzz"] # %5 = 0
        ][x%3 == 0][x%5 == 0]
(I'll note this was hard to get than if-checks and pattern matching so I won't be replacing such logic with matrices in my Python program any time soon...)
> not statically of course

Right, but that's the point!

Some people are sayint this is extremely non idiomatic python. I think most of the problem is not following the style guides. Here's a pep8 compliant solution that is a bit more idomatic, and almost as compact.

In Python, the way to do pattern matching is with dictionaries of functions.

    fizz_buzz = {(True, True): lambda x: "Fizzbuzz",
                 (True, False): lambda x: "Fizz",
                 (False, True): lambda x: "Buzz",
                 (False, False): lambda x: x}

    for i in range(1, 30):
        fbsign = (i % 3 == 0, i % 5 == 0)
        print(fizz_buzz[fbsign](i))
I see it as over thinking simple stuff. Maybe I think about performance too much but constructing a dictionary and then defining functions to do simple signature thing is imo just over designing things. Too much abstraction for expressing a simple concept. Performance aside, you need to go find the dictionary once you see code like that while with chain of if-else statements it's right there in front of your eyes.

As to 0 and 1 thing... Python has C'ish boolean (basically an integer type). Using True and False as numbers is completely standard as PEP 285 indicates. I tend to agree that maybe here using True/False is a bit more natural but it really is type-purity nitpick in my view.

Could you point out which part of PEP8 the code in my original post violates? While I don't really like a lot of currently popular style I see a lot of values in following the PEP's and long established conventions.

That's not genuinely pattern matching, however. It only works for patterns without member binding.
That is very non-idiomatic python. Use True and False instead of 1 and 0 when comparing the (boolean) result of a comparison.
I contemplated using `match` in the article, but decided that there was enough to think about and that it was long enough already. Thanks for pointing it out, though—it certainly is a great thing!
Weird to see this mix of a very imperative for-range iterative loop with a very functional pattern match, which makes it look similar to an SML or OCaml solution to FizzBuzz. I guess this is the definition of multi-paradigm right here.

Will Rust's type checker warn you of a non-exhaustive pattern match?

Rust was first conceived by an avid Ocamler, and it was originally implemented in Ocaml too. Although the pot has been stirred quite a bit since those early days, the influence still remains, including the expression heavy programming style, pattern matching, 'let's, HM inference, and `var: T` declaration syntax. Whilst Rust is quite procedural and (you rarely use recursion), it often feels quite functional due to those things.
Small note, technically Rust never did HM, and now we _certainly_ don't. It's still inference, just not that algorithm.
Oh - I thought it was a variant on HM, extended for region inference?
I'm not _totally_ sure, but I do know when I tried to reference HM in the docs I got yelled at... and now I'm pretty sure we do http://smallcultfollowing.com/babysteps/blog/2014/07/09/an-e...
It wasn't strictly HM, as it had extensions for the subtyping that lifetimes require. It was based on HM, however.

The new bespoke scheme gives approximately the same results as HM but is drastically simpler. For all I know this could inhibit Rust's future ability to do even more powerful things with types, but AIUI this scheme has the advantage of being actually decidable given the extensions to HM that we would require in the current language.

I ain't a type theorist though, so take this as hearsay. :)

Here's the same approach in F# without the different types of String; therefore, easier to get more functional.

    let fizzbuzz num =     
       match num % 3, num % 5 with      
          | 0,0 -> "FizzBuzz"
          | 0,_ -> "Fizz"
          | _,0 -> "Buzz"
          | _,_ -> num.ToString()

    [1..100]
      |> List.map fizzbuzz
      |> List.iter (fun (s:string) -> printfn "%s" s)
And racket, just for kicks

    #lang racket
    
    (define (fizz-buzz n)
      (match (list (modulo n 3) (modulo n 5))  
          [(list 0 0) "FizzBuzz"]
          [(list 0 _) "Fizz"]
          [(list _ 0) "Buzz"]
          [_          n]))
    
    (for [(i (range 100))]
      (displayln (fizz-buzz i)))
Haskell -

  fizzbuzz x = case (x `mod` 3, x `mod` 5) of
      (0, 0) -> "FizzBuzz"
      (0, _) -> "Fizz"
      (_, 0) -> "Buzz"
       _     -> show i

  mapM_ (putStrLn . fizzbuzz) [1..100]
Pattern matching is one of the ways to get "two-mod" code where the modulus operator is used two times. For example, string concatenation and assignment operators do the same in this Python code:

    for i in range(1, 101):
        x = ""
        if i % 3 == 0:
            x += "Fizz"
        if i % 5 == 0:
            x += "Buzz"
        if x == "":
            x += str(i)
        print(x)
If anybody is randomly curious it can be fun to solve FizzBuzz in Haskell the same way that this Python code does it, but (to be more idiomatically Haskell) storing the FizzBuzz success in a Maybe (or some other variable). If you define the appropriate `~>`, and higher-precedence `|~`, and higher-precedence `|~~>`, you can write the above function as:

    fizzbuzz x = 
        mod x 3 == 0    ~> "Fizz" 
        |~ mod x 5 == 0 ~> "Buzz" 
        |~~> show x 
It's interesting because it's sort of a "follow-through guards" situation; the (|~) operator can at least be turned into a type signature of (Monoid m) => Maybe m -> Maybe m -> Maybe m.
Currying often allows for elegant point free code. Like in your last line, for F# they could also have written

    [1..100] |> List.iter (fizzbuzz >> printfn "%s")
Nice F# snippet! This just looks so simple and elegant. As another approach, the "enum" types the OP mentions map to discriminated unions in F#.

FWIW your final wildcard match can be (modestly) simplified to just

  | _ -> num.ToString()
and the last line can be distilled to just

  |> List.iter (printfn "%s")
Although I love pattern matching, I find the solution with 'if' more legible and no less functional.
Non-exhaustive pattern matching is a compilation error.
> Will Rust's type checker warn you of a non-exhaustive pattern match?

It refuses to compile entirely.

I guess beauty is in the eye of the programmer. I'd choose Python's or Ruby's FizzBuzz. It's beautiful that everyone can immediately understand those. This one, not so much. As a little experiment, I've deliberately avoided learning Rust to see if I can understand its idioms without reading any docs. I can sort of guess at what's going on here by reverse engineering what should happen with FizzBuzz, but it's not at all intuitive. For example, as an outsider, I'd expect it to be (0, 1) instead of (0, 0) since it's matching both the 0th and 1st patterns. Whereas (0, _) would be "0th pattern but not the 1st," or something, even though that really wouldn't make much sense because "0" would refer to which pattern it's matching, rather than the position of the argument determining which pattern it's matching. Etc.

If Rust is the most robust way to solve a problem, it should naturally catch on. It seems pretty promising in that regard.

EDIT: As a counter to my comment, my argument would be equally applicable to Lisp, and Lisp is beautiful. So my argument is probably mistaken.

Maybe someone has to learn a language before judging whether it's beautiful.

You can write Ruby in this fashion as well.

  def fizzbuzz x
    case [x % 3 == 0, x % 5 == 0]
      when [true, false] then puts "fizz"
      when [false, true] then puts "buzz"
      when [true, true] then puts "fizzbuzz"
      else puts x
    end
  end
The one and only time I was asked FizzBuzz in an interview, I wrote it this way, so it's not all that contrived (in my opinion, anyway!)
It must just be a matter of familiarity. Rust's pattern matching is quite similar to other languages with pattern matching.

For example in OCaml the match statement would be:

     match ( i mod 3, i mod 5) with 
      | (0,0) -> Printf.printf "Fizzbuzz"
      | (0,_) -> Printf.printf "Fizz"
      | (_,0) -> Printf.printf "buzz"
      | (_,_) -> Printf.printf "%d" i
Other than the irrelevant syntax bits like 'with', the semantics are identical, in order matching with no fall through, and _ for unnamed and unused bindings.

To me with very limited experience in it, Rust really feels like OCaml with a skin that C programmers will understand.

> As a little experiment, I've deliberately avoided learning Rust to see if I can understand its idioms without reading any docs.

:D

If you have the time, as you do this, I'd love to hear about your experience. Email me any time.

(I maintain Rust's docs, and am also starting to write some introductory curriculum. Hearing from people like you is _invaluable_.)

Oh, okay! Cool! I didn't realize it'd be valuable to anyone. I'll try to put together something for you, and I'll take it seriously so that it isn't biased one way or the other. I have some stuff coming up, but after seeing some incredibly neat stuff written in Rust, I'm planning on doing a project myself, and I'll email you with a raw braindump of my first experiences with the language, along with a list of previous languages I've learned as well as my experience level with each. Thank you for maintaining Rust's docs!
Great, thanks! (And Mozilla is owed some thanks for paying me, otherwise I wouldnt have nearly as much time to do it)
It’s whether (i % 3, i % 5) is equal to (0, 0) et al., where _ means “any value”.
To be fair, you can bind to any name. So, binding to `a` instead of `_` would work as well. You could then use that bound value in the corresponding expression.

However, I would have expected rustc to complain about unused variables in

    fn main() {
        for i in range(1i, 101) {
            match (i % 3, i % 5) {
                (0, 0) => println!("Fizzbuzz"),
                (0, a) => println!("Fizz"),
                (b, 0) => println!("Buzz"),
                c => println!("{}", i),
            }
        }
    }
but neither the playpen nor yesterdays snapshot complains. And if you want to suppress warnings about unused variables, you prefix the variable with an underscore, or just use only the underscore, which got common to mean "I don't care what value gets bound to this name.".

    fn main() { let a = 0u; }
compiles with warning: unused variable: `a`, but

    fn main() { let _a = 0u; }
compiles silently.
Thanks for noticing that! I filed https://github.com/rust-lang/rust/issues/17999
That's a very useful feature. Maybe I'll go ahead and learn Rust now. If it has a features like pattern matching, which seems about ten times more useful than the classic switch statement, then it probably has a lot of other insights worth learning.

If you were to start a hypothetical project written in Rust, what would it be? I'm looking for something to cut my teeth on.

I would suggest you port over a project that you are already familiar with. It's easier to learn a new syntax when you don't have to grapple with implementation as well. And you get to have an objective comparison of the same project implemented 2 different ways.
I agree with this, but I will say that sometimes, you end up structuring a program differently due to the language. This happens a lot in Rust.

It's still easier when you've solved the problem previously, however.

> As a little experiment, I've deliberately avoided learning Rust to see if I can understand its idioms without reading any docs.

A result of applying this approach to languages in general would be only knowing a couple of fairly similar languages. Which is bad. Really, really bad. Language influences our way of thinking in a non-trivial way, knowing only one kind of language is limiting.

Even worse, you're going to forever stay constrained to one family of languages that you didn't (probably) even chose yourself. In a current world it's ok if you were introduced to a C-like language as your first, but what if it was Pascal or Scheme?

In short: DON'T DO THIS. Learn more languages, the more FOREIGN (ie. you can't understand anything without docs) the BETTER. The 'intuitive' languages only let you express the same solution again and again, while breaking AWAY from your intuitions and learning 'non-intuitive' languages let's you see and implement DIFFERENT solutions.

It can be that _ for any is less expected than a star or a dot would be. We're used to the star in shells and the dot in regexps for decades already.

(0, *) seems more obvious in that example than (0, _) but at least we are used to ____ in the forms printed on paper too.

Just as a point of interest: in these cases, (0, ..) would also work, where .. means “any number of elements”.
`..` doesn't work with tuples (yet?).
> As a little experiment, I've deliberately avoided learning Rust to see if I can understand its idioms without reading any docs.

As an experiment of what? Whether rust code makes somewhat sense to you depends to an extent on what languages you already know (I guess ML languages would help). Same as with Ruby and Python.

I fail to see how a simple switch statement doesn't read just as easily. I mean, sure I have to know the mod 15 trick, but... not exactly hard.

    function fizzBuzz(i) { 
      switch(i % 15) { 
        case 0: return "fizbuzz";
        case 5: 
        case 10: return "buzz";
        case 3:
        case 6:
        case 9:
        case 12: return "fizz";
        default: return i.toString();
      }
    }
    [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15].map(fizzBuzz)
Even that is better in Rust (using the enum from the end):

    match i % 15 {
        0 => FizzBuzz,
        5 | 10 => Buzz,
        3 | 6 | 9 | 12 => Fizz,
        _ => Number(i),
    }
I actually agree it is better. But... my point was that the switch statement was already pretty readable. If there are gains, they feel pretty small in these examples.

And to be clear, I like pattern matching. A lot. I just don't feel this really shows it off that well.

What do you expect; it's FizzBuzz.
Only because you're expecting the cases to fall through, you're being blinded by your own expectation.

Your code is considered bad practice in many languages.