News for hackers. Stuff that matters.
Re:Overthinking Fizzbuzz with Monoids by alanbernstein on Wednesday September 13, 2017 @07:35PM (#15243744)
Yea, 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.
Re:Overthinking Fizzbuzz with Monoids by KirinDave on Wednesday September 13, 2017 @07:44PM (#15243793)
Well, 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.

Re:Overthinking Fizzbuzz with Monoids by rbehrends on Thursday September 14, 2017 @05:53AM (#15246360)
The thing is, you're not only complicating your code by introducing monoids, you're unnecessarily restricting yourself. The only thing you need is higher-order functions to be sufficiently general:

  let every m s i =
    if i mod m = 0 then Some s else None

  let fallback i l =
    if l = [] then [ string_of_int i ] else l

  let join l = String.concat "" l

  let fizzbuzz gens i =
    List.filter_map (fun f -> f i) gens |> fallback i |> join

  let main () =
    let fizzbuzzstd = fizzbuzz [ every 5 "Fizz"; every 3 "Buzz" ] in
    for i = 1 to 100 do
      print_endline (fizzbuzzstd i)
    done
  
I've pulled out `join` and `fallback` as separate functions to show that you can also parameterize the code over these functions. And you can use other patterns instead of "every n-th entry" by using generators other than `every`.

This approach can make joining O(n) as opposed to O(n^2) (naive concatenation) or O(n*log(n)) (ropes) worst case and is more general. For example, you can also generate numbered lists for each line ("1. Fizz 2. Buzz"). Yeah, that's silly for FizzBuzz, but we're sort of assuming that this is a placeholder for a real problem that we may want to generalize or tune for performance.

And if it's actually a monoid and we know nothing more about it than that, we can just go with `List.fold_left` as the join function. Works for all monoids, too, without mentioning the word.

Re:Overthinking Fizzbuzz with Monoids by KirinDave on Thursday September 14, 2017 @12:14PM (#15249440)
First of all, thanks for your post. I don't think your code is wrong or bad in any way.

> The thing is, you're not only complicating your code by introducing monoids, you're unnecessarily restricting yourself.

Strictly speaking? I agree. All we need for this problem is semigroups. We never explicitly need a "monoid." But I'm not sure offering the "mempty" case is really over-complicating the code, it just bloats the rhetoric a wee bit.

But I think what you're referring to is the conceptual overhead of Monoids? I'll get to that in a sec.

> The only thing you need is higher-order functions to be sufficiently general:

    > code follows
For "sufficiently" general code, it certainly seems to be less general than a piece of code relying on monoid or semigroup operators. You've hardwired yourself to strings and arrays.

F# actually is doing a natural transformation for you from (Maybe~>Maybe String) implicitly in there, so you're really relying on the fact that F#'s got some monoid logic baked in.

> And you can use other patterns instead of "every n-th entry" by using generators other than `every`.

Right, the essence of fizzbuzz is in the list of partial applications of every onto a list of prime factors and strings. Everything else is repetitive joining logic. Which is why appealing to a pattern that captures nearly all that logic seems like a good idea, no?

> This approach can make joining O(n) as opposed to O(n^2) (naive concatenation) or O(n*log(n)) (ropes) worst case and is more general.

I do not see how it's more general unless you demand non-associativity which—while fair to do in an interview question—is quite unheard of in the programming world. In terms of runtime performance, rope concatenation is constant and a full retrieval is O(n + log n), not O(n log n), so I'm not sure what you're getting here. But it's worth noting this code (like a lot of F# code) tends to pin onto exactly one data structure and can't simply imply the data structure from calling types.

> Works for all monoids, too, without mentioning the word.

There's an implicit value judgement here: that not naming this pattern is better. You mentioned it above with the "you complicate your code by introducing monoids." I'm curious why the default mode is that I need to defend naming a pattern that obviously saves code here, and captures more use cases. I don't mean this to be a contentious discussion, I just want to know why you think it's inherently bad.

Re:Overthinking Fizzbuzz with Monoids by Veedrac on Wednesday September 13, 2017 @09:55PM (#15244442)
Less? Even ignoring the glut of imports, it's significantly longer than the straightforward and more extensible attempt where you just chain a few `if`s. (The article did strawman that attempt, but it was transparent enough about it.)
Re:Overthinking Fizzbuzz with Monoids by KirinDave on Wednesday September 13, 2017 @11:07PM (#15244721)
Which if my examples is it longer than? It's been awhile since I wrote this so...
Re:Overthinking Fizzbuzz with Monoids by Veedrac on Wednesday September 13, 2017 @11:38PM (#15244829)
None of the examples you specifically gave. As I said, it strawman'd the easy method.
Re:Overthinking Fizzbuzz with Monoids by KirinDave on Wednesday September 13, 2017 @11:41PM (#15244840)
I don't know what the "easy" method you're referring to is. I did include an else-if chain and did my best to not inflate it.

Even though it was 5 years ago I need to revisit this blog to update the pronouns anyways, so please do correct me.

Re:Overthinking Fizzbuzz with Monoids by Veedrac on Wednesday September 13, 2017 @11:46PM (#15244855)

    loop i = 0..100 {
        out = ""
        if i % 3 == 0 { out += "Fizz" }
        if i % 5 == 0 { out += "Fizz" }
        if i % 7 == 0 { out += "Fizz" }
        if out == "" { out = str(i) }
        display out
    }
Re:Overthinking Fizzbuzz with Monoids by KirinDave on Wednesday September 13, 2017 @11:53PM (#15244881)
Is this really much shorter than my Haskell example? If we count the import decls it seems to be a bit of a wash.
Re:Overthinking Fizzbuzz with Monoids by alanbernstein on Thursday September 14, 2017 @11:28AM (#15248991)
It's obvious and readable, and therefore maintainable, which is one of the most important parts of software engineering.

Again, as an example for exploring the concepts, this is a great article, as long as the overengineering isn't taken as a good thing in itself.

Re:Overthinking Fizzbuzz with Monoids by Veedrac on Wednesday September 13, 2017 @11:59PM (#15244905)
It's about 0.7x the length of the final Haskell version, ignoring indentation and imports, but I don't really care about golfing. My point was simply that your statement that you "write less of [your] own code" was not justified.
Re:Overthinking Fizzbuzz with Monoids by KirinDave on Thursday September 14, 2017 @12:17AM (#15244967)
> It's about 0.7x the length of the final Haskell version,

The final haskell version with the explicit "show" does 2 things your example here does not: offer parameterized final case, and use a rope data structure for the strings.

But in a very real sense your code is a special case of the haskell code c_wraith offered. You're using a builtin monoid (strings) and explicit control flow to do it, so your code can't be reused in other contexts.

If I tactically asked you for a feature that made the output = '' in some cases, you'd have to refactor pretty significantly. The haskell version not only doesn't have that weakness, but it can even be used to do more abstract things like count the number of times Bar _would_ be printed if we wanted to do that.

> but I don't really care about golfing

Okay, but for someone that's indifferent you are sorta gut punching c_wraith's code by penalizing haskell for explicit modules and imports for libraries, writing code in a style where I can explain it line-by-line, and comparing a simplified version to my most complicated version.

> My point was simply that your statement that you "write less of [your] own code" was not justified.

It's a debate I'm willing to have, but I think my blog post defends this point. That code lacks any sense of abstraction over very common patterns, and it ultimately ends up appealing to a builtin monoid and its operations to accomplish the same thing.

It's fizzbuzz, so it's hardly a crisis. But the point of this entire discussion here is about how even in these small cases we can see big abstractions appear and be relevant. I'd rather add 4 import lines and lean on code tested till the cows come home than pretend I can't ever have a bad day writing bespoke logic.

Edit: it occurs to me that implicitly in this assumption is the notion that we must defend using simple, wide-reaching abstractions. Can you give a good reason why something as trivial as "monoids" isn't exactly the sort of thing we should all be using, as opposed to bespoke logic that on a good day conforms to this pattern? Why _wouldn't_ we?

Re:Overthinking Fizzbuzz with Monoids by Veedrac on Thursday September 14, 2017 @12:28AM (#15245011)
> offer parameterized final case

The raw code is more parameterizable than the list, since it is customized with the extremely powerful language "pseudocode". It does so with no loss of concision and is no less abstracted.

> use a rope data structure for the strings

It's pseudocode. Change the first line of the loop to `out = rope()` if you care.

> you are sorta gut punching c_wraith's code by penalizing haskell for explicit modules and imports for libraries

Given I have explicitly excluded this every single time I mention it, I don't know why you are saying this.

Re:Overthinking Fizzbuzz with Monoids by KirinDave on Thursday September 14, 2017 @12:35AM (#15245042)
> The raw code is more parameterizable than the list, since it is customized with the extremely powerful language "pseudocode". It does so with no loss of concision and is no less abstracted.

I can't tell if you're joking or not here. But let's pretend you're not.

> Given I have explicitly excluded this every single time I mention it, I don't know why you are saying this.

Sorry, I thought you were comparing WITH imports to the proper version. In that case the apples-to-apples version comparison is 0.625x (5/8 or 4/7) between your pseudocode and real world code with abstractions.

I'm... pretty happy with that! Even the version that uses ropes, can be reused in many contexts, and relies on external and highly tested code is only 1 line longer in all cases.

Thank you, I hadn't really considered psuedocode as a valid competitor but now I realize it makes my blog post look a lot better.

Re:Overthinking Fizzbuzz with Monoids by Veedrac on Thursday September 14, 2017 @07:51AM (#15246978)
> I can't tell if you're joking or not here.

100% serious. I don't see how you can seriously consider the version that is way more complex and much less flexible "better".

> the apples-to-apples version comparison is 0.625x (5/8 or 4/7) between your pseudocode and real world code with abstractions

Which code samples are you actually comparing? Because no matter how I try to slice it, the pseudocode I gave is shorter.