10 ms·
Overthinking Fizzbuzz with Monoids
- nicolashahn 9y agoI took a course that focused on Haskell in college. I enjoyed it. I love reading articles about how neat Haskell is. I've still never been convinced that I should use Haskell over a more mundane language for any actual project.
- coolsunglasses 9y agoI do pretty ordinary stuff and it makes my work a lot nicer. An open source example is the Elasticsearch client I wrote called Bloodhound: https://github.com/bitemyapp/bloodhound https://github.com/bitemyapp/bloodhound It makes writing and maintaining applications that use ES a lot more maintainable. Makes the Query API a lot more discoverable too.
- hood_syntax 9y agoDepends on what the project is. If parsing is the biggest part of the project then I would say Haskell is top-class in that respect. It has proven success stories. However, due to the lack of control over GC it has latency issues with certain kinds of work and it's not a great choice for writing demanding games. For me, one of the big holes in the Haskell story is a great, idiomatic GUI toolkit. That makes it a little more difficult to use for standard desktop applications, but not impossible (after all, there are binding to common frameworks like GTK etc).
- nicolashahn 9y agoHave to agree with you on all counts. I forgot about parsing, I generally don't have to do that. The final project my group attempted was a simple game/simulator in Haskell and it was by all counts a pain to work around the statelessness. When we were trying to create an interface for the game we also noticed the lack of good GUI libraries.
- jfoutz 9y agoI'd extend parsing to any part of a compiler. Or a whole compiler. It's really nice for managing that complexity.
- paulajohnson 9y agoOn the GUI toolkit issue, I'd recommend combining gtk2hs (which is a thin wrapper around GTK) with Reactive Banana. GTK signals are isomorphic with Banana events and GTK attributes are isomorphic with Banana behaviors. So you can connect up a button press signal from GTK to an Event, transform that event with data from some other behavior, and hence generate a new behavior which is connected to the GTK attribute of the text displayed in a text box. Your application logic is then expressed as the plumbing that pipes data from event sources to event and behavior sinks. This works well because the event plumbing is composable in ways that callbacks and state variables of native GTK aren't.
- hood_syntax 9y agoThanks for the advice! I'll look into that
- framebit 9y agoNo discussion of overthinking FizzBuzz is complete without a shoutout to the glorious EnterpriseFizzBuzz: https://github.com/EnterpriseQualityCoding/FizzBuzzEnterpriseEdition https://github.com/EnterpriseQualityCoding/FizzBuzzEnterpris...
- LanceH 9y agoMy effort (I got lucky): https://play.golang.org/p/Q7Ek05QjOP https://play.golang.org/p/Q7Ek05QjOP
- KirinDave 9y agoLet the nerd snipe Olympics, BEGIN!
- roblabla 9y agoMy favorite fizzbuzz-related story will aways be http://joelgrus.com/2016/05/23/fizz-buzz-in-tensorflow/ http://joelgrus.com/2016/05/23/fizz-buzz-in-tensorflow/ Similar to this story, a ridiculously overengineered fizzbuzz using machine learning...
- pja 9y agoNo where near compact (or possibly obtuse?) enough for a Haskell version, surely? Here's one I found elsewhere on the net sometime ago: (~>) :: (Integral a) => a -> String -> a -> Maybe String (n ~> str) i = str <$ guard (i `mod` n == 0) fizzbuzz = 3~>"Fizz" <> 5~>"Buzz" main = mapM_ putStrLn $ catMaybes $ fizzbuzz <$> [1..100] At least it generalises nicely to arbitrary fizzbuzz numbers! (edit: as noted below this version as written fails to print the non fizzbuzz digits. An exercise for the reader :) )
- KirinDave 9y agoTBF, I wrote this article 5 years ago. Monad Comprehensions were in fashion at the time. Now every dang person wants to write an arrow with a tilde in it and exclaim, 'Naturality!' ;) Gotta love the use of guard in this. Maybe I should revisit. I suspect there is also a clever way to do this applicatively, but haven't really noodled with it to see if it lines up. Any chance to use applicativeDo!
- wz1000 9y agofizzbuzzbazz = mconcat [ 3 ~> "Fizz" , 5 ~> "Buzz" , 7 ~> "Bazz" ] Also, I believe you should have something like main = mapM_ putStrLn $ map (fromMaybe . show <*> fizzbuzz) [1..100] to print out the number when it isn't matched by any of the rules.
- 101km 9y agofrom functools import partial def rem(i, n, message): return message if not (i % n) else '' [(''.join([ partial(rem, message='fizz', n=3)(i), partial(rem, message='buzz', n=5)(i) ]) or i) for i in range(1,101)] vs {-# LANGUAGE MonadComprehensions #-} module Main where import Data.Monoid (mappend) import Data.Maybe (fromMaybe, listToMaybe, maybe) import System.Environment (getArgs) fizzbuzz i = fromMaybe (show i) $ mappend ["fizz" | i `rem` 3 == 0] ["buzz" | i `rem` 5 == 0] -- mapM_ is our iterator, putStrLn writes to console. main = mapM_ putStrLn [ fizzbuzz i | i <- [1..100] ]
- KirinDave 9y agoAs the author of the article, I feel like you've missed the point of this article if you're writing a "vs" post. The important point here is that monoids make this easier and less full of unique logic. The code you've posted here is clever and thoughtful, but still commits to the same "we don't abstract over monoids" problem I identified. You saved 4 lines over other versions and probably lost clarity in your quest for that. It's worth noting that you are leveraging a built monoid: strings. '' = mzero and join is your + operation.
- 101km 9y agoSorry, it is very early in the morning so I'm still waking up. I didn't mean anything by the groggy 'vs' other than a rosetta syntax comparison to Python. With that being said, I don't think my snippet lost clarity and is functionally (har har) equivalent to the Haskell. I believe where Haskell would shine is if the mappend operated on a more complex type than a regular string, but honestly I've only started reading learn you a Haskell two days ago so maybe I'm missing something deeper? Edit: I guess you could say Python strings are monoids and maybe works on ''.
- KirinDave 9y agoFair enough. Sorry, last time this was posted here we had a lot of those.
- 9y ago
- paultopia 9y agoPython can produce extensible solutions! It can! (Edit from sillier comment.) def fizzbuzz(n): outstring = "" outputs = {5: "buzz", 3: "fizz", 7: "bar"} for x in sorted(outputs.keys()): if n % x == 0: outstring += outputs[x] if outstring: return outstring return n Then all you gotta do to add more prime numbers is stick them in the dict (or factorize composite numbers you want to put in). Which is more idiomatic in python than endless if elif elif elif elif.
- deleted 9y ago[deleted]
- alanbernstein 9y agoYea, I'm all for overthinking things, but as a solution to fizzbuzz, this article is silly. You don't need any complicated control flow or logic, or functional primitives, for an extensible, readable fizzbuzz.
- KirinDave 9y agoWell, of course you do not "need" solid engineering to express the solution to a trivial problem. The point is not what you need to solve fizzbuzz, it's to give people a taste of what it feels like to do so. And the result is: "oh, it feels normal, but I write less of my own code and reuse existing control flow code." Which is a lot of the point. It's also to point out simple abstractions like Monoid can find uses even in simple problems.
- kbenson 9y agoThere's some interesting Perl 6 implementations here[1] (and in the discussion of that), which shows some interesting capabilities of multiple dispatch, and farther down using subtypes along with multiple dispatch. I particularly like this terse one: sub fizzbuzz($n, $_ = [$n % 3, $n % 5]) { when [0, 0] { "fizzbuzz" } when [0, *] { "fizz" } when [*, 0] { "buzz" } default { $n } } That's fairly tight, while remaining somewhat readable. There's a version that adds a line or two by moving $_ into the body of the sub as well in case that's more readable to some. 1: https://gist.github.com/masak/b9371625ad85cfe0faba https://gist.github.com/masak/b9371625ad85cfe0faba
- laylomo 9y agoNice, reminds me of the equivalent OCaml implementation: let fizzbuzz n = match n mod 3, n mod 5 with | 0, 0 -> "fizzbuzz" | 0, _ -> "fizz" | _, 0 -> "buzz" | _ -> string_of_int n
- cdevs 9y agoJust pass in a associative array with numbers 3 and 5 for keys and what to say if mod that key value == 0, extendable, done, clock out.
- KirinDave 9y agoAssociative arrays are monoids. Just saying.
- Zelizz 9y agoWe can go a little shorter with C :) main(i){for(;i<101;puts(printf(i%3?"%d\r":"fizz",i)&&i++%5?"":"buzz"));}
- arthurcolle 9y agoThug life
- KirinDave 9y agoThe nightmare is real.
- feelin_googley 9y agoYou're hired.
- AlexCoventry 9y agoHow does the divisible-by-five case suppress the numeral output, when `i%3` is nonzero?
- vidarh 9y agoThe "\r" in the format string for the number is carriage return. It ensures that as long as you print to terminal rather than redirect to file etc., the number gets overwritten if anything else gets printed on the same line.
- AlexCoventry 9y agoAh, thank you.
- vidarh 9y agoIgnoring that this only works if "\r" works, here's a shorter Ruby version without even trying hard: (1..100).each{|i|e=[["fizz"][i%3],["buzz"][i%5]].join;puts(e[0]?e:i)} (array lookup with out-of-range offset returns nil, Array#join produces empty strings for nil elements, and accessing element 0 in a zero length string returns nil.
- Koshkin 9y agotl;dr: concatenation of strings corresponds to concatenation of "optional strings"; monoid homomorphism!
- ibotty 9y agoMonoid is a concept that is older than category theory. And way simpler. I don't get why people always say category theory when they mean basic algebra. Maybe they don't know better (lack of math background, expository texts that go straight to "category theory")? Anyone an idea on why that's happening?
- anon335dtzbvc 9y agoI guess adding the discovery year of monoids to a blog post is not associative.
- icen 9y agoIntroduction to category theory via haskell, and not learning the right areas of abstract algebra? It's plausible to even do a degree in mathematics and never encounter the name 'monoid' - you'll obviously encounter the structure, but my abstract algebra classes just skipped ahead to groups, and then to rings and modules, instead of spending any time on the simpler structures. Other times, like when studying formal languages, because regular languages are a monoid, the structure is introduced, via noting that you have an empty language and a method to combine any two languages, but not given the name monoid. So you come across haskell, and it gives a name to things like monoids, monads, functors, and so on, and says that monads and functors come from category theory, and it seems like a valid inference to say that monoids also come from category theory.
- tome 9y agoHaskellers tend to think category theory is far more useful than it is, and non-Haskellers the complete opposite. On a forum of predominantly non-Haskellers I'm happy for them to get a bit of a overdose of the terminology.
- rowyourboat 9y agoWhy is FizzBuzz a separate branch? To produce the desired output, all you have to do is print Fizz and Buzz in the correct order.
- Deestan 9y agoOr you can underthink it. It's side-effect free, takes no arguments, and is terminating, so it can be optimized perfectly for speed: #!/bin/cat 1 2 fizz 4 buzz 6 … 98 fizz buzz
- philh 9y agoThat prints #!/bin/cat at the beginning, though. If I can return to overthinking it, you could use a silly tool[1] I wrote ages ago and do something like (untested) #! /usr/bin/cmd tail -n +2 1 2 fizz ... [1] http://reasonableapproximation.net/2010/07/27/silly-things-to-do-with-shebang-lines.html http://reasonableapproximation.net/2010/07/27/silly-things-t...
- bitsofpancake 9y agoTranslation to Python: for i in range(100): print((("fizz" if i % 3 == 0 else "") + ("buzz" if i % 5 == 0 else "")) or str(i)) The "+" makes it a monoid.
- machiaweliczny 9y agoThis praised Ruby code in article is crap. It abuses side effects(unnecessary 3 invokes of printing) and needs to resort to using helper (@printed = "Fizz"). I would never let something like this through code review. Like you can't simply first generate data and then print it...
- KirinDave 9y agoFor a high pressure situation like an interview, I think it's quite good code to produce. Post fact we could separate concerns.
- deleted 9y ago[deleted]
- ldd 9y agoJust for the fun of it, I tried going the complicated way in Javascript: ``` var createRange = n => Array.from(Array(n), (_, i) => i + 1); var rem = (i, n, message) => (i % n ? '' : message); var accumulator = (a, b) => a + b; var insideReducer = (p, n) => accumulator(p,rem.apply(null,n)); var outsideReducer = (p, n, i) => n ? p + n: p + (i + 1); var messageFilter = (n, filterArray) => createRange(n) .map(i => filterArray.map((el)=>[i,...el]) .reduce(insideReducer,"") ) .reduce(outsideReducer,"") var fizzbuzz = n => messageFilter(n, [[3, 'fizz'],[5, 'buzz']]) ```