Why your first Rust FizzBuzz implementation may not work
chrismorgan.info
chrismorgan.info
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 chrismorganIf 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.
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.
It's still easier when you've solved the problem previously, however.
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.: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_.)
(0, *) seems more obvious in that example than (0, _) but at least we are used to ____ in the forms printed on paper too.
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!)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.
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 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.
Will Rust's type checker warn you of a non-exhaustive pattern match?
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)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") #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))) fizzbuzz x = case (x `mod` 3, x `mod` 5) of
(0, 0) -> "FizzBuzz"
(0, _) -> "Fizz"
(_, 0) -> "Buzz"
_ -> show i
mapM_ (putStrLn . fizzbuzz) [1..100] [1..100] |> List.iter (fizzbuzz >> printfn "%s") 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.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. :)
I'm curious (and a bit skeptical) of your claim that the scheme is "drastically simpler" than HM. HM is a beautifully simple design, which can be expressed (abstractly) in just a couple of lines.
http://smallcultfollowing.com/babysteps/blog/2014/07/09/an-e...
"This scheme simplifies the code of the type inferencer dramatically and (I think) helps to meet our intutions (as I will explain). It is however somewhat less flexible than the existing inference scheme, though all of rustc and all the libraries compile without any changes."
It refuses to compile entirely.
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. 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 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) 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...)Right, but that's the point!
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))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.
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) match i % 15 {
0 => FizzBuzz,
5 | 10 => Buzz,
3 | 6 | 9 | 12 => Fizz,
_ => Number(i),
}And to be clear, I like pattern matching. A lot. I just don't feel this really shows it off that well.
Your code is considered bad practice in many languages.
A slice has storage that is borrowed from somewhere else, but it itself does not have its own storage.
When you type `"foo"`, you are creating "static" storage (in the binary) and the slice is borrowed from that fixed-position location in memory.
Mostly, your functions produce Strings and consume slices.
That functions produce Strings and consume slices is a good way of expressing it.
I expect that to be fixed before 1.0.
I have personally found this to be pretty clarifying, because as much a we may like to abstract over it, the location of storage often worms its way into the programming model even in HLLs.
trait WhatIsThis {
fn what_is_this(self);
}
impl WhatIsThis for String {
fn what_is_this(self) {
println!("'{:s}' is a string!", self);
}
}
impl WhatIsThis for &'static str {
fn what_is_this(self) {
println!("'{:s}' is a string slice!", self);
}
}
fn print_me<T>(me: T) where T: WhatIsThis {
me.what_is_this();
}
fn main() {
print_me("Blah blah blah");
print_me("Yada yada yada".to_string());
}It's already possible to have a function take "either a String or slice" generically:
def print_me<T>(me: T) where T: Str {
println!("I am '{:s}'", me.as_slice());
}
The overall ergonomics of this (or at least, the well-documented idioms) will certainly improve in the coming months.This is not obviously true to me, Rust provides more tools for building abstractions, even just "handle errors less manually"-abstractions, so they're possibly not that different (this certainly applies to the 'shorter' section).
I guess it may be true for the things Go is suited/designed for; time and experience will tell.
I'm pretty happy with the speed at which I write Rust code, but I can definitely churn out Go code more quickly. (Probably on the order of how quickly I can write Python, although refactoring Go code is much faster.) I'm not sure exactly why, but my guess is that there are fewer abstractions to deal with (and fewer opportunities to make abstractions). I have written several medium Go applications (near or above 10 KLOC, which ideally, I would never hit in a dynamic language), and I'm pretty happy with how the code turned out in all but one of them. (But that one is a window manager.) I haven't yet written a similarly sized Rust application, though.
I do write Rust code more quickly that I write Haskell code though. :-)
The reality is that people have very little time to try out new languages, and have to rely on anecdotes about the long-term cognitive costs of things like this.
My personal experience is that many of the seemingly more-onerous things about Rust end up falling away once the rhythm of programming sets in.
This is likely similar to how the error-handling approach of Go looks onerous at first, but seems to be something that doesn't slow people down too much in practice.
Also, while working with headerless languages may be easier, calling working with headers 'very painful' is hyperbole.
On OSes for which this is true you'd be forced to compile python yourself as well, because it's also just a dependency (of pip, for one!) written in a compiled language.
Edit: My point is: usually it's as easy as 'yum install', but on the rare occasion you will need to compile, I admit. However, that could happen with pip too; don't tell me its repos are always completely up to date. And in those cases it won't be quite as simple as 'pip install'.
There are IDEs that have their proprietary project formats that are incompatible.
Having header files makes refactoring by hand much more difficult than necessary, yet refactoring tools for C++ are mostly not possible.
Finally, https://gist.github.com/shurcooL/86949a392dcdac1f94cf.
Now, of course, no one would program that way, but I think it does help visualize what really happens.
The obvious cost from delaying the printing is that you have to branch a second time, later in the code, to consume the value. I wonder how feasible it would be to introduce some kind of compiler transform that could invert the control flow, essentially pasting the surrounding code into the inner branches, to make this abstraction cost-free.
This is a bit disingenuous. I understand what he's aiming at, and he also mentions StringBuilder later on, but saying that a GC necessatites immutable strings is simply not true.
As a counterexample: PHP has mutable strings and uses copy-on-write in situations where it "feels" that conflicts could occur. (Granted PHPs rules on how it handles its variables is a bit arbitrary and magical, and PHP didn't have a GC till version 5.3 .. but the argument still stands.)
for i in range(1, 101):
print('FizzBuzz' if i % 15 == 0 else 'Buzz' if i % 5 == 0 else 'Fizz' if i % 3 == 0 else i)
python doesn't have expressions? oh dear me.Moreover, the code you've presented here certainly isn't idiomatic, which counts for something.
the formatting can of course be improved:
for i in range(1, 101):
print('FizzBuzz' if i % 15 == 0 else
'Buzz' if i % 5 == 0 else
'Fizz' if i % 3 == 0 else
i)
there's also a suspicious "return" at the end of the second code segment which mysteriously appeared some time after the first one; looks like the author was trying a little too hard to differentiate python and rust. let result = if i % 15 == 0 {
"FizzBuzz"
} else if i % 5 == 0 {
"Buzz"
} else if i % 3 == 0 {
"Fizz"
} else {
i
};
in rust? either this is good, readable code or this is poorly written, unintelligible code. you cannot make the argument that sometimes it is readable and sometimes not based on the presence of braces.The vast majority of conditionals in languages I know follow the {cond} {true} {false} order: IIf({cond}, {true}, {false}) in VB, SQL, and spreadsheets; if({cond}) {true} else {false} and ternary {cond}?{true}:{false} in C and C-derived languages; (if {cond} {true} {false}) in the Lisp family; if {cond} then {true} else {false} in ALGOL/Pascal, etc. There's probably a reason for this order, as seeing a condition in the middle of an expression feels surprising and unexpected.
http://blog.rust-lang.org/2014/09/15/Rust-1.0.html#release-p...
So they could come in one of the post 1.0 releases and that could possibly be soon.
const char* outs[] = { "%d\n", "Fizz\n", "Buzz\n", "FizzBuzz\n" };
for (int i = 1; i < 101; i++)
printf(outs[((((i%5)==0)<<1)+((i%3)==0))], i); If the format is exhausted while arguments remain, the excess
arguments are evaluated (as always) but are otherwise ignored.
but that's just a consequence of stdarg, which does not require all supplied arguments to be consumed.You know it's going to be a FAQ....
The reason type inference was such a big one was because if you use it right, annoying situations like "Two types of strings? What is this?" go the hell away. You have three types, static built in and binary strings, and a third that only makes the gaurentee that the datatype can do all the things a string aught to be able to do, and from there the compiler works out the practical implementations.
This article has done a great job in killing my enthusiasm for the language.
I guess it's implementation of type inference only goes as far as golangs, in that const keyword.
Maybe I was being a bit naive in what I was expecting, hell maybe what I'm expecting isn't reasonably possible. bleh.
Really, this is showing one of the more tricky parts of Rust, potentially to balance the claims of excessive bullishness for Rust that I have heard levelled at me! Don’t let it dim your enthusiasm too far; Rust is still very much worth while trying out in practice.
E.g.
fn main() {
let mut v;
if true {
v = vec![];
v.push("foo");
}
}
is a valid Rust program: the compiler can infer that `v` must have type `Vec<&str>` based on how it is used. I don't think it's possible to syntactically write a Go program at all similar to this (a variable has to be either initialised or have a type), and the C++ equivalent (using `auto v;`) is rejected, as `auto` variables need an initialiser. template<typename...Args>
inline auto make_vector(Args&&...args) {
using T = typename std::common_type<Args...>::type;
return std::vector<T>{{std::forward<Args>(args)...}};
}
...
auto v = make_vector("asd", "dsa", std::string("asdsa"));
It will obviously not deduce types after the vector is declared, but it's as close as one gets to type deduction based on the vector's content.There is one instance in C++ where information does flow backwards in a sense: disambiguating template overloads. For example,
using fn_type = std::vector<int>(&)(int&&,int&&,int&&);
auto v = static_cast<fn_type>(make_vector)(1, 2, 3);
In this case, the static_cast information flows "back" to the type deduction of `make_vector` to deduce what Args&& is. This is not very useful, just a curiosity.Consider doing something similar in Haskell, setting a variable to either be a string or Text:
GHCi, version 7.4.1: http://www.haskell.org/ghc/ :? for help
Prelude> import qualified Data.Text as T
Prelude T> let x = (if True then "foo" else T.empty)
<interactive>:3:32:
Couldn't match expected type `[Char]' with actual type `T.Text'
In the expression: T.empty
In the expression: (if True then "foo" else T.empty)
In an equation for `x': x = (if True then "foo" else T.empty)
Sure, Haskell can sometimes auto-infer very complex types, and has more extensive type inference than Rust does. But it's not magic, and will not do everything for you.What you're asking for is not type inference, but something else. Perhaps what you really want is weak typing (automatic type conversions), or message sending (can not statically dispatch).
As for the main function and `if __name__ == '__main__': main()`, that part is actually generally recommended.