Threads for kubanczyk

    1. 6

      I think this article would be significantly improved if the author summarized each factor before their discussion. I’ve read the original thing before but I surely didn’t memorize it, ya know?

      1. 6

        Every image has a small caption. It contains what seems to be the original factor, such as “One codebase tracked in revision control, many deploys”.

        1. 3

          Wow I completely missed those and I read the whole dang thing.

    2. 1

      These are very good!

      I’m about to make a presentation about SOLID in my company, can I use your drawings?


      For the Interface Segregation Principle, I don’t think the example is a good idea, because the “universal charger” is exactly what is being worked on (but with a single port).

      I don’t have a suggestion though, sorry

      1. 2

        Hey, sure, feel free to use those drawings! Just mention the source of it.

        About the Segregation Principle, my idea was to illustrate the scenario when we may want to avoid the Banana Gorilla Problem with interfaces (when you need a banana, but instead you get a gorilla, holding the banana and the entire jungle).

        Let’s say I need to charge my laptop, I’m asking for the charger, but instead of just a laptop charger, I’m getting the “universal” charger (in a bad sense here). As a result, I’m using only one output wire, but I also have 5 other different wires just hanging around with no use (for old MacBooks, for a PC laptop, for an iPhone, for the power bank, etc.)

        Do you think it makes sense?

      2. 1

        Yeah, that specific example is just wrong: the method is Plug(where), there is no other method there.

        I was thinking about a portable speaker. Some people expect to Carry() things, other people want things to Blast(). But it’s not that easy to illustrate that I want to Carry() also my groceries, even if they cannot possibly Blast(). It would be a comic strip, xkcd style.

    3. 2

      Very much in favour of this.

      I’m also in favour of a further change - making the loop variable alias when using a range loop, so that the underlying array element can be mutated.

      I know one can use the index variable for this. However:

      1. at the moment, any mutating method will have its effects silently discarded (it mutates the temporary copy)
      2. I’m not aware of a downside to this (it would be great if someone could suggest some)

      Motivating use case:

      1. you have a method on an object which returns some expensive-to-compute property
      2. you loop over a collection of these objects calling this method and using the value, perhaps more than once
      3. later, you modify the property-getting function to update and use a private cache of the property in the object to avoid re-computation result: the cache is not used, since the internal cache is only updated in the loop variable copy, never the element of the underlying array.

      Again - this is all fixable when you notice it, but the “silently discard the mutated copy” behaviour is poor (and has bitten me in the past). The problem manifests as “my performance improvement didn’t work”.

      1. 3

        The GitHub thread at its core is about scoping language change so that users would agree for it to arrive quickly via a minor version (1.x), despite the fact the change is mildly breaking.

        Your own proposal is very much breaking, because there is a ton of code that modifies the range-iterated var today (when there is any computation within a loop it is a convenient local variable). So it would need either a different keyword than “range”, or it would be a v2 discussion.

      2. 1

        making the loop variable alias when using a range loop, so that the underlying array element can be mutated.

        So what would be the type of the loop variable — T, or *T?

        If the loop variable were a struct type, what would happen in each of these bodies?

        for _, v := range map[int]T{...} {
            v.elem = "abc"
            f(v) // func f(v T)  { v.elem = "def" }
            g(v) // func g(v *T) { v.elem = "ghi" }
        }
        
        1. 1

          That’s a good point. I think my answers are:

          So what would be the type of the loop variable — T, or *T?

          T - we’re aliasing the underlying elt.

          And you’re right. Explicit invocation of a function with an arg of type T would cause a copy (as it does outside a loop).

          However, for methods golang chooses to implicitly call methods with a *T receiver on values of type T. So:

          for _, v := range map[int]T{...} {
              // set all 'elem' to "abc", since we are aliasing. Same as outside loop.
              v.elem = "abc" 
              // Same as f(v) outside loop, no effect (due to copy in the func arg, not loop copy)
              f(v) // func f(v T)  { v.elem = "def" }
              // Same as g(v) outside loop, mutate v
              g(v) // func g(v *T) { v.elem = "ghi" }
              // Same as v.h() outside loop, no effect (due to copy in the receiver, not loop copy)
              v.h() // func (v T) h()  { v.elem = "jkl" }
              // Same as v.i() outside loop, mutate v
              v.i() // func (v *T) i()  { v.elem = "mno" }
          }
          

          or am I missing something due to the map in your example? I’m sorry - I don’t see why that is different from a loop over a slice (since we are only considering the map values, not keys).

          The model I am thinking of is “on each iteration of the loop for i, v := range xs the value v is equivalent to xs[i]. i.e. using i is only useful for cases where you want the numeric value of the index, not to mutate the underlying array.

          1. 2

            Map values aren’t addressable in Go, if you want to call a pointer-receiver method on one you have to copy the value out, call the method, and assign the result back. There are reasons for that, and I don’t think you can get away with breaking/sidestepping that rule.

          2. 1

            am I missing something due to the map in your example? I’m sorry - I don’t see why that is different from a loop over a slice (since we are only considering the map values, not keys).

            No, map or slice, shouldn’t matter.

            on each iteration of the loop for i, v := range xs the value v is equivalent to xs[i]

            So the original motivation (or at least one of them) for the current behavior was so that each range clause would only need to allocate a single value for the second parameter, and re-use it for each iteration. I’m not positive, but I believe this approach would require a new allocation per iteration.

      3. 1

        I prefer this behavior too / find it intuitive. It’s what I go with in my little Go->C++ transpiler – https://github.com/nikki93/gx. The generated C++ does for (auto &elem : collection).