Fizzbuzz, Interviews, And Overthinking
dave.fayr.am
dave.fayr.am
cases = (
(3, 'Fizz'),
(5, 'Buzz'),
(7, 'Bazz'),
(11, 'Boo'),
(13, 'Blip'),
)
for i in range(1, 101):
out = []
for c in cases:
if i % c[0] == 0:
out.append(c[1])
if out:
print ''.join(out)
else:
print i
Edit: not to detract from the post's point, I think it's valid. Monoids are cool and all but simple counting arguments can take you a long, long, long, way when case analysis fails you. (function fizzbuzz(n, cases) {
var i = 0;
while (i++ < n) {
console.log(cases.reduce(function (prev, c) {
return i % c[0] ? prev : (prev || '') + c[1];
}, null) || i);
}
}(100, [[3, 'Fizz'], [5, 'Buzz'], [7, 'Bazz'], [11, 'Boo'], [13, 'Blip']]));
Excuse the terseness, I didn't want to make this post too lengthy, but it's still quite readable, IMO. Array.reduce was designed to solve problems like this.The Ruby solution author quotes as ideal and impressive seems way overblown to me. And I don't buy that ,,it would probably be dismissed as “overly complex” by younger programmers'' because it is exactly what I would consider perfect approach... few years ago. Since then I learned the value of simplicity, and abstractions with adequate flexibility.
That Ruby code exhibits neither.
Secondly, I feel like you (and aristus in his/her code snippet above) missed the secondary point of my post. Please consider that monoids and Maybe are capturing a higher level pattern in a way that we can compose. To write a classical for loop and toss in conditionals and whatnot is to descend to exactly the same depths as the Ruby code you say is not "simple".
Basically, you say one is "simple" and the other is not is you picking up on what you're comfortable with in Python. Both are complex in the same way; the Python just makes a marginally better choice in how to represent the rules (as data).
If you'd like to see how I'd take that piece of code and golf it to include that feature, I'm happy to oblige. I originally was going to post that, but cut it because the post was already overlong and the point is to explore the unusual patterns fp is capturing.
Here is my bullshit golfing derivative, though:
{-# LANGUAGE MonadComprehensions #-}
module Main where
import Control.Applicative
import Data.Monoid
import Data.Maybe
import System.Environment
factors = [(3, "fizz"), (5, "buzz"), (7, "bazz")]
rules = [\i -> [res | i `rem` fac == 0] | (fac,res) <- factors]
fizzbuzz d i = fromMaybe (d i) $ mconcat (rules <*> pure i)
main = do
upTo <- fmap (maybe 100 read . listToMaybe) getArgs
mapM_ putStrLn [ fizzbuzz show i | i <- [1..upTo] ]Maybe is a way to represent conditionals that is amenable to higher order patterns. if-then-else conditionals are simply that, whereas Maybe is amenable to Monad, Monoid, and Applicative functor laws. So when we pull out mconcat or <> or mappend, we're actually talking about a higher order pattern.
As I mentioned in the article, we could take that same fizzbuzz code and modify it in many ways under the monoid rules. For example, a different harness in the main function could use Map String Integer and completely change the behavior with the same fizzbuzz function.
fizzbuzz d i = mconcat (rules <*> pure i) <|> pure (d i)
This is written in terms of an Applicative Functor and a Monoid.And then can just do some haskell things. I've done less golfing here and more type signatures to make the code more approachable.
{-# LANGUAGE MonadComprehensions, OverlappingInstances, FlexibleInstances#-}
module Main where
import Control.Applicative
import Data.Monoid
import Control.Monad
import Data.Maybe
import Data.List
import qualified Data.HashMap.Strict as M
import System.Environment
-- Let's make it clear what we're working with.
type Counter = M.HashMap String Integer
-- We want an instance slightly different from the default.
instance Monoid Counter where
mempty = M.empty
mappend = M.unionWith (+)
factors = [(3, "fizz"), (5, "buzz"), (7, "bazz")]
-- Our rule function is slightly different.
-- Not unexpected, since our logic has changed. But we could generalize
-- this further!
rules :: [(Integer -> Maybe Counter)]
rules = [\i -> [M.singleton res 1 | i `rem` fac == 0] | (fac,res) <- factors]
-- Fizzbuzz remains unchanged.
fizzbuzz d i = mconcat (rules <*> pure i) <|> pure (d i)
main = do
upTo <- fmap (maybe 100 read . listToMaybe) getArgs
let results = foldl' mappend mempty [ fromJust $ fizzbuzz (const mempty) i | i <- [1..upTo] ]
putStrLn $ show results
And then a typical session: ~/P/h/fb-toys > time ./fbg3 10000000
fromList [("bazz",1428571),("fizz",3333333),("buzz",2000000)]
3.58 real 3.54 user 0.03 sys
Which is a pretty expensive way to avoid doing algebra, but the point is that we're talking about very high level patterns here for fizzbuzz. Fizzbuzz is probably a bad name here, it's more like mergeOptionalPatterns. I'm willing to bet if I dug a round a bit in parser combinator libraries I could find something that does nearly exactly this.I confess I had to play with it a bit to get it to play nice with large inputs.
Good job.
for i in range(110):
pr = ""
pr += "Fizz" * (i%3 == 0)
pr += "Buzz" * (i%5 == 0)
pr += "Bazz" * (i%7 == 0)
print (pr if pr else i)
I iterated over range(110) to show that it handles the "FizzBuzzBazz" case correctly.EDIT: Or we could use lambdas as OP's Ruby code did; this might be more maintainable...
cases = [
lambda n: "Fizz" * (n%3 == 0),
lambda n: "Buzz" * (n%5 == 0),
lambda n: "Bazz" * (n%7 == 0) ]
for i in range(110):
pr = ""
for case in cases:
pr += case(i)
print (pr if pr else i)
That's nine nonblank lines. cases = [(3, "Fizz"), (5, "Buzz"), (7, "Bazz")]
for i in range(110):
pr = ''.join([v[1] * (i % v[0] == 0) for v in cases])
print pr or i pip install fizzbuzz
And then: import fizzbuzz
print fizzbuzz.fizzbuzz()
Or for the second solution: print fizzbuzz.fizzbuzzbazz()print '\n'.join((lambda x:(''.join(x) if x else str(i)))([w+'zz' for (n,w) in ((3,'Fi'),(5,'Bu'),(7,'Ba')) if i%n==0]) for i in range(1,100))
for i in xrange(1, 101):
s = ""
if not i % 3:
s += "Fizz"
if not i % 5:
s += "Buzz"
if not i % 7:
s += "Bazz"
print s or i
Which can be shortened to: for i in xrange(1, 101):
s = ""
s += "Fizz" if i % 3 == 0 else ""
s += "Buzz" if i % 5 == 0 else ""
s += "Bazz" if i % 7 == 0 else ""
print s or i
Finishing the article inspired: print "\n".join(
"".join(filter(None, ("Fizz" if i % 3 == 0 else None,
"Buzz" if i % 5 == 0 else None,
"Bazz" if i % 7 == 0 else None)))
or str(i) for i in xrange(1, 101))I think in talking about programming, we are often hindered by the fact that it is much more complicated than our brains can handle. Our only choices are to write novel length treatments of real programs or short story length posts about a toy program, or just the tiniest piece of a real program.
Which is all to say, as sick of hearing about Fizzbuzz as I am, I'm glad there are silly little examples like it that we all know. Even though it was ostensibly about interviewing, that was a much clearer introduction to monoids than most. I think it was largely because it was in reference to Fizzbuzz: something very concrete with which we're all familiar.
Too many introductions to Haskell's abstractions are too abstract. Good on the author for finding away around that.
What sort of surprised me as I shopped my copy around and showed people the python-add-one-factor example is that even programmers I consider experts didn't realize how quickly the conditional cascade blows up. It's easy to miss. And I looked around for people to mention this in blog posts, but almost no one does. So I feel like there's a bit of life left in the old fizzbuzz yet.
(And yes, before anyone pops in, these are guaranteed two-player games. Of all the rules of the games in question which have changed over time, that is the one rock-solid constant which will not change in this application.)
The pressure of judgement is not the same as the work pressure of a stressful scenario. It's why public speaking and job interviews are among the most feared activities people go through, but people consider meetings boring and mundane.
The point is more that for a person with a given skill level, interviews can be stressful and make them perform below that skill level. I try to structure my interviews such that people get more comfortable and I don't ram their head into a metaphorical wall. I get all the same information, but without all those hard feelings.
A few questions for you. How old are you? Have you hired developers before? Truthfully, have you ever had to reverse a string under pressure at your current job.
The general reason why such a question is asked is because interviewers don't have an infinite amount of time and there's no point in considering any candidate who can't do this. It's a way to weed out ridiculously bad candidates so that you can focus more on good candidates.
More often than not the people that spout that kind of nonsense are twenty-something junior developers that read a bit of HN and have decided that they know better than everyone else about what makes a good programmer.
Why would you test if someone can reverse a string if you're hiring someone to maintain and build on some shitty web app?
Doing a string reverse is so trivial that it's not about needing that skill, but rather what not being able to do that speaks about the candidate's general aptitude and skill level.
If that question can't be answered then your opinion on someones skill level is irrelevant. Also, if you've got time, answer the questions in my first post:
"How old are you? Have you hired developers before? Truthfully, have you ever had to reverse a string under pressure at your current job."
The only way this question wouldn't be useful is if you had a good filter such that almost everyone who gets to that point would answer that question correctly. But apparently, this is not the case. There are people who fail such tests (despite apparently good paper qualifications and ability to talk BS). It's fairly important that you not let through such imposters.
And yes I've hired people and all of them have had to answer much more complex algorithmic questions to get the job and all of them turned out to be competent. And "have you ever had to" questions are ingenuous - interview settings are never going to be replicated exactly in a work setting, unless your job is to go around to be interviewed for different jobs. Good interview questions don't simulate work setting - they extract most critical work-relevant information about the candidate without wasting time.
So...I'm hiring someone that can reverse a string because this demonstrates that they can program real things that real people use?
"And yes I've hired people and all of them have had to answer much more complex algorithmic questions to get the job and all of them turned out to be competent."
Out of interest, what did this job entail? I assume that you quizzed them on their knowledge of basic algorithms because they are required to write them, right?
The difference between reversing a string and pulling data from a database are, literally, completely different tasks. The killer is that the latter is something that a candidate will do in a job.
You're more than welcome to ask those questions, and you're probably likely to weed out the lowest common denominator types. In my experience, they're a waste of time and the best possible interview you can give to a developer is a frank face-to-face discussion about programming, and then getting them to work on a test project for two hours from your own code base. If they can do that much they can program.
Why can't you do both? Seriously? Pulling data from a database is orders of magnitude harder to get right than reversing a string.
And this is where you misunderstand the purpose of these types of questions. Programming is a very dynamic field where you're likely be required to solve novel problems daily (not novel in the grand scheme of programming, but novel to you). You need the mental flexibility and fluidity to solve problems you haven't entirely encountered before. Testing specifically what you do at the job is the wrong approach. You need to test the required mental faculties to solve novel problems. Programming is not pulling data from a database, writing glue code, and shoving it to the screen. It's solving new problems every day.
Being able to recognise the correct answer in the sea of dross that is search engine results pages is better.
Being able to take the correct answer, and adapt it to what you need, and plugging it into what you already have, is best.
Do you really want to employ people who say they have X years of experience doing something but who cannot do a very simple task in the choice of their language? That's not a stupid trick gimmick question - there's no "gotcha" there. Perhaps we're getting caught up on the specific "reverse a string". Substitute that for something relevant to maintaining and building on a shitty web app.
If so are these people who have held down a programming job before?
reversed_string = string[::-1]
Is it just using the equivalent, or a new implementation?To put it another way - when I code it's rarely different from writing to me; code is simply an expression of thought. But when you ask me to do something that seems a bit artificial (reverse a string) it's like asking me to write a haiku - I can do it, but give me a moment while I count out my syllables.
for i in xrange(1,101): print (('' if i%3 else 'Fizz')+('' if i%5 else 'Buzz')) or i
or the even more general: mapping={3:'Fizz',5:'Buzz'}
for i in xrange(1,101):
print ''.join(['' if i%x else mapping[x] for x in mapping]) or i
We can even do this in C though to write something as extensible as the second would require writing more helper functions than I can justify for this brief comment: #include<string.h>
#include<stdio.h>
int main(){
int i;
char s[4];
char n[3];
for (i=1;i<101;i++){
sprintf(n,"%d",i)
s=strcat((i%3)?"":"Fuzz",(i%5)?"":"Buzz")
printf("%s",(strlen(s))?s:n)
}
return 0;
} ('' or 10) == 10 # True
That's like making the universe crazier one electron at a time.But yes, it's just about having the right abstractions. What's nice about the Haskell solution is that the right abstractions make the code much nicer (to my eye).
About as far as I am willing to go is nil punning.
It's extremely useful in a lot of contexts. The basic logic is that an empty string is an empty container, and empty containers are false, so you can test for them more easily.
#include <stdio.h>
#include <stdbool.h>
#define when(mod, msg) do { if((i mod) == 0) { fputs(#msg, stdout); *hit = true; } } while(0)
#define through ; i <=
#define engine(range) \
void rules(int, bool *);\
int main(void) { for(int i = range; ++i) { bool hit = false; rules(i, &hit); if(!hit) printf("%d", i); putchar('\n'); } } \
void rules(int i, bool *hit)
engine(1 through 100) {
when(% 3, Fizz);
when(% 5, Buzz);
}
Super extensible!https://github.com/rcs/fizzbuzz/blob/master/bitwise.c
Choice excerpts:
char fmts[] = "FizzBuzz%u";
and // Default start is 8
unsigned int start =
// Shift down 1 if div5
(8 >> DIV5(mask))
// Shift down another 4 if div3 */
>> ( DIV3(mask) << 2);Or, since github's being broken right now:
#include <iostream>
#include <arpa/inet.h>
template <int multiples_of, uint32_t noise>
class Noisemaker {
private:
union {
uint32_t noise_as_int;
char noise_as_cstr[5];
};
public:
Noisemaker() {
noise_as_int = htonl(noise);
noise_as_cstr[4] = '\0';
}
bool operator()(std::ostream &out, int i) {
if(i % multiples_of == 0) {
out << noise_as_cstr;
return true;
}
return false;
}
};
template <typename Noise1, typename Noise2>
class NoisemakerPair {
private:
Noise1 noise1;
Noise2 noise2;
public:
bool operator()(std::ostream &out, int i) {
bool matched = false;
matched |= noise1(out, i);
matched |= noise2(out, i);
return matched;
}
};
int main(int argc, char *argv[]) {
NoisemakerPair<Noisemaker<3, 'Fizz'>, Noisemaker<5, 'Buzz'> > fizzbuzz;
for(int i=1;i<=100;++i) {
if(!fizzbuzz(std::cout, i))
std::cout << i;
std::cout << std::endl;
}
}
You can nest NoisemakerPairs to add more Noisemakers. But good luck with strings longer than 4 characters… for some absurd reason, string literals can't be template parameters. Go figure.Actually, this spec is ambiguous, too. For multiples of five, do we print the number AND "Buzz" or just "Buzz" ? Similarly for multiples of 15.
I thought the OP was a joke, BTW. It had to be, doesn't it? If somebody thought of this problem and solution the way the job candidate did, why on earth would one want to hire somebody who loves complexity for its own sake?
To me, the following is the clearest way to express this (in C anyway). I suppose it says a lot about me! lol
#include <stdio.h> #define FALSE (0) #define TRUE (!FALSE)
int main(){
for(int i=1; i <= 100; i++){
int printed_something_already=FALSE;
if(i%3 == 0){
printf("Fizz"); printed_something_already=TRUE;
}
if(i%5 == 0){
printf("Buzz"); printed_something_already=TRUE;
}
if(!printed_something_already) printf("%d",i);
printf("\n");
}
return 0;
}Well balls to you too, I guess? Or not. We're all just trying to have a good time here. And from the look of this thread, people are having fun.
I think golfing fizzbuzz into an unreadable mess sorta misses the point, but most people here are software engineers because they love to code and build stuff. So let's enjoy the moment.
> why on earth would one want to hire somebody who loves complexity for its own sake?
You remind me of: http://wondermark.com/333/
It's not complexity for its own stake. I daresay the functional patterns tend to be simpler. Certainly moreso than things like Visitor or AbstractFactory.
> To me, the following is the clearest way to express this (in C anyway). I suppose it says a lot about me!
Your version is almost identical to the Ruby version I listed. And it's an appropriate solution for C.
Yes, if we're all trying to have a good time, we'd shoot heroin and be done with it. ;-) IMHO, we are group sharing to learn better ways to build startups, small businesses, and to better ourselves, co-workers, and companies with best practices - and to do it with fun and a twinkle in our eyes.
I did look at that wordmark comic -- I believe I ask a credible question: If complexity buys you nothing, then why make something complex? And, believe me, if you are thinking "Visitor" or "AbstractFactory" then I have already lost you. ;-)
Thank you for your comment about my C version. I wanted to make sure it was extensible and quite obvious -- because I write code to be read by humans as my 2nd goal (my first is that it works & meets the spec!). Knuth's Literate Programming taught me that. The other thing about that C solution is that it's simple and many people can understand it. One does not need to know the difference between a monad and a gonad to "get it".
My overall comment: Complexity: when required; otherwise, choose simple. Believe me, I have learned this lesson over and over again during my 45 years of programming.
I think maybe the disconnect here is that I work on products with a much larger scale than you may be accustomed to. My idea of complexity begins to blossom at scales past many boxes. For me, 25 interconnected machines is barely enough to register as a "big system". So perhaps I am just desensitized?
Abstraction is generally trying to find ways to mitigate complexity, not embrace it. At some point we need to have sympathy for the machines we run on to have efficient code, but at the same time you and I need to find good perspectives to write code at.
A more concrete example of this is my advice that people building distributed systems take a cue from Boundary and have systems rendezvous in Zookeeper rather than just using something like hardwired ports and /etc/hosts. You may think that "sounds complex" but it's actually a very simple solutin from our perspective as users of Zookeeper. It ends up being pretty nice the next time a giant storm takes out half of us-east-1 and you're frantically re-provisioning boxes to keep your service up.
Monoids are that kind of choice at the programming level.
> Complexity: when required; otherwise, choose simple. Believe me, I have learned this lesson over and over again during my 45 years of programming.
I think c_wraith's monoid example made the program simpler, cleaner, and less bug prone. If you think monoids are a "complex" idea, then my blog post has failed profoundly and I apologize for wasting your time.
And that's a pity, because it makes abstractions harder to explain in a proper context.
You need an example where the complexity at least rears its ugly head before the abstraction starts to shine.
For many people, especially those not familiar with it at the time of the reading, the simple example using a manage huge complexity abstraction reeks of "complexity for its own sake" or "killing flies with a cannon" or "premature optimization" and it's natural because the example is not showing the strengths of the abstraction, rather its weaknesses.
I've received 5 emails and about 10 tweets from people saying, "I think I understand monoids better because your tutorial sat at the right level of abstraction and overhead for me to get."
So, I think I succeeded for 15 people. Which I am pretty damn happy about. Because whenever I write one of these I pretty much assume everyone will misread, misunderstand, or ignore 3/4 of my post. It's just my experience.
People who call the monoid abstraction "confusing" and "complexity for its own sake"... I just have to assume these people can't handle the discomfort of learning new concepts anymore and write them off as unreachable. It's not like I owe them anything. Because I'm pretty sure it's actually not confusing at all, it's just a different perspective.
The real irony is that for awhile I was deep in the OO and Ruby culture and said a lot of these same things. You can find them online if you know where to look. So I understand where these people are coming from and know that without their cooperation, I cannot explain anything at all.
My comment was not a jab at your article, but a comment on how hard it is to find the proper abstraction level.
And that I fully understand people who'd say it is making things more complex for its own sake, and I'm not sure it necessarily is because they don't want to learn.
The Haskell solution was really cool:
[max(show x)(concat[n|(f,n)<-[(3,"Fizz"),(5,"Buzz")],mod x f==0])|x<-[1..100]]
This is much simpler and it looks easier to extend as well.
(1..100).map{|i|(f=[["Fizz"][i%3],["Buzz"][i%5]].join).empty? ? i:f}
[1 to 100]map(->[s for s,n of{Fizz:3,Buzz:5}|it%n<1]*''||it)
l=range(101)
l[3::3]=100/3*['Fizz']
l[5::5]=100/5*['Buzz']
l[15::15]=100/15*['FizzBuzz']
print l[1:]
This one is 86 characters by using constants: l=range(101)
l[::3]=34*['Fizz']
l[::5]=21*['Buzz']
l[::15]=7*['FizzBuzz']
print l[1:]function fizzbuzz (n) { return new Array(n + 1).join().split(',').map(function (j, i) { return (i % 3 ? '' : ' fizz') + (i % 5 ? '' : ' buzz') || ' ' + i; }).slice(1).join().slice(1); }
185 characters
Edit: If I move from a general solution to only 1-100 and remove spaces and semicolons:
new Array(101).join().split(',').map(function(j, i){return (i%3?'':' fizz')+(i%5?'':' buzz')||' '+i}).slice(1).join().slice(1)
Down to 126, but a lot of that is related to precision with the whitespace and handling a JS quirk with map on undefined values; without the extra joins/split/slices, it goes down to 83 characters (but also doesn't work).
i=0;while(i++<100)console.log(((i%3?'':'Fizz')+(i%5?'':'Buzz'))||i)
Pardon the global.
for i in range(100):print ''.join([s*(i%m==0)for m,s in[(3,"Fizz"),(5,"Buzz")]])or i " ".join(i%3/2*"Fizz"+i%5/4*"Buzz"or str(i+1) for i in range(100)) # 97 characters
for my $i (1..100) { say join("", map { {3=>'fizz',5=>'buzz'}->{$_} unless $i % $_ } 3,5) || $i }
# 81, if those pesky spaces are removed
for my$i(1..100){say join("",map{{3=>'fizz',5=>'buzz'}->{$_}unless$i%$_}3,5)||$i} (1..100).each do |i|
o = ""
o.concat("Fizz") if i % 3 == 0
o.concat("Buzz") if i % 5 == 0
o.concat("Bazz") if i % 7 == 0
o.concat(i.to_s) if o.empty?
puts o
endAnyway, FizzBuzz and the like are the kinds of questions you would probably ask to a new graduate because frankly, there is nothing else to ask from them. They have no related experience for the most part.
For experienced ones, the way our company (http://aelogica.com) identifies good candidates is via a day of pair-programming. Technical knowledge is just part of the package, and any kind of problem solving will not address that.
Nonsense.. you call a simple loop with a couple conditions difficult?
And why introduce monad comprehensions if you're just introducing monoids?
Because it's a bit easier to write the tail end of the logic in generic terms. You can go talk to c_wraith on Freenode#haskell if you want.
> You can just use concat; if the result is the empty string then print the number. If you want it to work for any monoid, then use mconcat, and test for equality to mempty.
You can find other discussions about why flexibility is important.
> And why introduce monad comprehensions if you're just introducing monoids?
Because it's a really nice syntax? Compared to explicitly using guard, anyways.
a=b=c=d=(e=1..100).each do |num|
print num, ?\r,
("Fizz" unless (a = !a) .. (a = !a)),
("Buzz" unless (b = !b) ... !((c = !c) .. (c = !c))),
("Bazz" unless ((d = !d) .. (d = !d)) ... (e = !e)),
?\n
end subs = {3 => "Fizz", 5 => "Buzz", 7 => "Bazz"}
(1..100).map{|n| subs.keys.inject(nil) {|a,k| n % k == 0 ? (a||"")+subs[k] : a} || n.to_s}
Kind of boggles the mind sometimes to realize the crappy code people will write without thinking things through. divisors = {Fizz:3, Buzz:5, Bazz:7}
for i in [1..100]
alert (name for name,divisor of divisors when i % divisor == 0).join("") || ifor(var i=1;i<=100;i++) console.info(((!(i % 3) ? "Fizz" : "") + (!(i % 5) ? "Buzz" : "") + (!(i % 7) ? "Bazz" : "")) || i);
The proper structure is if(3){if(5)}elif(5){}else{},unless anyone has a better proposition. While of course it is possible to define abstractions that handle fizzbuzzing anything for any number, it's clear that the monoid way is a bad overweight bloated approach.
Second the example written is code bloat à la java, writing tons of stuff for no reason.
Third Ruby is a beta prototype language that doesn't have any production ready implementation.
Lastly functional and procedural programming enable you to work at the highest level of abstraction you can think of, whereas OO tends to lock you at a specific abstraction level, which is pretty low and not adapted to most cases.
I have to admit it's impressive how such a simple test can show so many failures in people's deep understanding of programming.
> While of course it is possible to define abstractions that handle fizzbuzzing anything for any number, it's clear that the monoid way is a bad overweight bloated approach.
Why? It is not computationally expensive, and it avoids excess modulo operations. All the dispatching work gets figured out at compile time and you end up with pretty much the same sorts of string concatenation costs you get in every other implementation.
Do you mean to suggest that it is conceptually expensive?
> Lastly functional and procedural programming enable you to work at the highest level of abstraction you can think of, whereas OO tends to lock you at a specific abstraction level, which is pretty low and not adapted to most cases.
I actually agree with this, although I sort of question procedural's place on the totem. I suppose stuff like Forth suggests you're right.
Additionally, it does _not_ avoid excess mod operations, _and_ the implementation is _broken_ because it won't print fizzbuzz for the 15.
Lastly, using lambda's and whatnot's just because you can is yet another form of inefficiency and completely obfuscates what the machine will do.
I mean to suggest that 1) it should be a much shorter read 2) it's inefficient, and wrong 3) one does not simply lambda everything.
If we're talking about the Haskell version here (I can't tell?), you're wrong on all counts.
• It is about as short as any reasonable impl I've seen of FizzBuzzBazz. Not long at all.
• And it does print out correctly for 15.
• And it does NOT do excess operations. It does exactly one modulo per factor.
Do you not understand how it works? I detail it pretty closely in the post; please look again.
The second ruby version _is_ long, but does not perform excess operations (unless you're complaining about Ruby's implementation of lambdas, which is out of scope). As I said both in my article and in this discussion; coding during an interview is tricky and that's not a bad effort.
Fianlly, the naive if-chain extensions are deliberately wrong to extend, but my initial research and experience is that most programmers don't realize that until they try it once. But then, they're lifted right from Rosetta Code so they're hardly unrepresentative of what people consider an "unacceptable" version of Fizzbuzz.
In this week's installment, the variation where it is claimed that common production languages are inadequate for a problem of this complexity, and the tool stack should be shifted to languages supporting monads... er monoids? Sigh.
It's like a super long joke where the punch line comes way too late. But instead of a punch line it's monoids and instead of being funny it's not.
P.S., hello edits!
It's probably a good thing I don't interview people for programming positions...
The purpose of an interview isn't to be abstractly "fair", it's to find the best candidates for the job. You choose what "best" is (which is to say, please don't put words in my mouth about "only interviewing for the exact skillset" or whatever... you choose what is best, whatever that is). My preferred approach is to give the problem, then let the candidate write in whatever language they choose. If they flail in their putatively favorite language with which they've putatively been working for 4 years... well... I've certainly learned some very important things in those few moments.