Threads for contivero

    1. 10

      Likewise, never break up a function just because it feels too long. If doing so decreases duplication, sure. But if the function is merely hard to understand and work with, consider delimiting sections inside; each with a comment, and a dedicated scope so local variables don’t spill to the next sections. Often that is enough to make it amenable again.

      A lot of interesting things in the article but this one I disagree with. Creating a predicate function or a filter function and calling them is often much more readable than splattering that code all inside the same function, separated by comments or spaces, even if those helper functions will never be used by another. I prefer a 5 line function that calls on another 3 than a 30 line function I have to read in its entirety to see which part I care about.

      You often only want to get the general idea of a function, or want to debug a particular part of it.

      This is a minor gripe I have with how many people write Lisp code in particular: it’s very hard to read functions that have 8 indentation levels and you have to take it all at once. Making functions is cheap, visually separates code, explicitly names the domain concepts and helps document abstractions, there’s no reason not to do it.

      Obviously if you need too much of the binding context to make sense of a helper function then it’s more wasteful and less readable, but in my experience that is rare: most helper functions act on just a one or two of the variables of a larger function.

      I do concede that this view is biased in my use of dynamic languages. In static languages there’s a lot of overhead to functions.

      1. 6

        It’s not an absolute rule. It gets stronger with languages like C and Java than it is in Lisp and Haskell because of the greater syntactic overhead. And my definition of “one-liner” mentioned below is more about simple expressions (or a simple statement) than it is about composing 5 functions (j . i . h . g . f) together like you would in Haskell.

        Moreover, languages like Lisp and Haskell let you define functions locally, very close to their point of use. I do like for instance to define helpers at the beginning of the main function in those languages. As long as the defining code is close to the point of actual use, we’re mostly fine. In C and C++ however, helper functions are typically defined outside the main functions, making them harder to find. I inline more aggressively there.

      2. 2

        I came to say this. Even if the method is used only in one place, it’s a way of (1) grouping a bunch of lines and giving them a name that summarizes what they are doing, and (2) folding / collapsing them (akin to how an IDE collapses multiline comments or documentation for instance) so that I don’t need to read all the low-level details of a method, but rather can get a quick overview and decide where I need to focus. If it’s worth to inline for performance reasons, I’ll let the (AOT or JIT) compiler decide, which most likely can make a more informed decision than me.

    2. 3

      This is in line with what Vladimir Khorikov advices in his book “Unit Testing Principles, Practices, and Patterns” (and blog). Mocks tend to produce brittle tests (i.e. tests that break due to refactorings, changes to implementation details that don’t change the behavior of a piece of code), so the recommendation is to only mock what he calls “Shared, unmanaged out-of-process dependencies”, mainly because these tend not to change much (they have a strong requirement of backwards compatibility), making it less likely that mocks will need to be updated. By doing this, I normally end up mostly with integration tests.

      1. 2

        By that author, this is one of the bests posts ever: https://enterprisecraftsmanship.com/posts/growing-object-oriented-software-guided-by-tests-without-mocks/

        In it, he takes a real design posed by the authors of the Growing Object-Oriented Software Guided by Tests book (pretty much the book that really proposed mocking as a primary testing technique) , and proposes what he feels is a design which eliminates most of the mocks. The result is much simpler (subjective of course).

        Now, GooS is an amazing book. I’m not knocking the book, since they have many amazing points in it, and there is a wealth of knowledge to be taken from it. But what I love about this response post is that it’s not theoretical. We need non-trivial projects like this to compare multiple different designs for the same realistic project.

        Some of this just falls down to design sensibility too, which is relatively subjective. Not everyone clicks with the immutable architecture, but for me it’s fantastic and aligns with my sensibilities.

      2. 1

        I just started that book last night! So far I’m digging it.

        1. 1

          Highly recommended! I was trying to find some answers as to what constitutes good tests and when is and isn’t appropriate to mock, and found one of the author’s blog posts tackling a bit the second, so eventually I gave the book a try. I really liked how he establishes some principles early on which are easy to agree on, and then from those all the rest follows.

          1. 1

            That’s exactly what I’m looking for! After years of reading bits and pieces and just general development work I felt like it was time to read a more complete account of how to test well. Glad to get another positive review.

    3. 9

      In Haskell:

      data Bytes =
          B
        | KB
        | MB
      
      instance (b ~ Double) => Num (Bytes -> b) where
        fromInteger i B =
          fromInteger i
        fromInteger i KB =
          fromInteger $ i * 1024
        fromInteger i MB =
          fromInteger $ i * 1024 * 1024
      
      instance (b ~ Double) => Fractional (Bytes -> b) where
        fromRational r B =
          fromRational r
        fromRational r KB =
          fromRational $ r * 1024
        fromRational r MB =
          fromRational $ r * 1024 * 1024
      
      Prelude> 100 B
      100.0
      Prelude> 100 KB
      102400.0
      Prelude> 100 MB
      1.048576e8
      
      1. 3

        I had no idea that was possible. Would you mind explaining a bit more in detail how it works? Do you need any language extensions?

        1. 2

          Do you need any language extensions?

          You need flexible instances and ~, usually from TypeFamilies. These are things I have turned on by default in all of my application projects.

          Would you mind explaining a bit more in detail how it works?

          Warning: This explanation won’t work great if you (generic you, I know contivero knows Haskell) know literally zero Haskell. If that’s the case, uhhhh, buy my book? http://haskellbook.com

          The Num and Fractional type classes are the basis for integral and fractional literals in Haskell. Every numeric literal in Haskell is polymorphic until the instance is resolved to a concrete type. 1 :: Num a => a and 1.0 :: Fractional a => a, where :: is type ascription and can be read as “has type.”

          The (b ~ Double) isn’t strictly necessary if you don’t care about type inference. You could elide it and then the examples would look like:

          Prelude> 100 B :: Double
          

          The essence of it is in the weird shape of the instance type: Num (Bytes -> b). We’re making an instance for numerical literals that is function-typed. We’ve said the inputs must be values from our Bytes type. This makes it so that in the expression: 100 B, the 100 gets resolved to a function which multiples the underlying number by the magnitude expressed in the data constructor passed to it as an argument, be it B, KB, or whatever else.

          Now, hypothetically, we might know that we always want Bytes -> Double. If we don’t care about type inference, then our instance can be pretty simple:

          instance Num (Bytes -> Double) where
          

          And we’re done, but that didn’t satisfy me. I wanted to see if I could get fancy syntax. If you try it unqualified, you get:

          Prelude> 100 KB
          
          <interactive>:7:1: error:
              • No instance for (Num (Bytes -> ())) arising from a use of ‘it’
                  (maybe you haven't applied a function to enough arguments?)
              • In the first argument of ‘print’, namely ‘it’
                In a stmt of an interactive GHCi command: print it
          

          GHCi will default the return type to () when the instance head is Bytes -> Double. So the stages of evolution are:

          Bytes -> Double
          Bytes -> b
          (b ~ Double) => Bytes -> b
          

          The weird part is, (b ~ Double) => Bytes -> b and Bytes -> Double are the same type. b ~ Double just means, “b is Double”. However, that part of the type-checker runs at a different time than instance resolution.

          What we really want is “instance local functional dependencies” and that’s exactly what this trick accomplishes. See more about functional dependencies here: https://wiki.haskell.org/Functional_dependencies

          What the type (b ~ Double) => Bytes -> b lets us do is “capture” uses of function-typed numerical literals whose inputs are Bytes and that have a return type whose type is unknown, then we supply a concrete type for the return type with the type equality b ~ Double after the instance is already resolved.

          You can’t overlap them:

          Duplicate instance declarations:
                instance (b ~ Double) => Num (Bytes -> b)
                  -- Defined at /home/callen/work/units/src/Lib.hs:40:10
                instance (b ~ Integer) => Num (Bytes -> b)
                  -- Defined at /home/callen/work/units/src/Lib.hs:49:10
          

          But if the input types are different, totally kosher:

          data Bytes =
              B
            | KB
            | MB
          
          instance (b ~ Double) => Num (Bytes -> b) where
            fromInteger i B =
              fromInteger i
            fromInteger i KB =
              fromInteger $ i * 1024
            fromInteger i MB =
              fromInteger $ i * 1024 * 1024
          
          data BytesI =
              Bi
            | KBi
            | MBi
          
          instance (b ~ Integer) => Num (BytesI -> b) where
            fromInteger i Bi =
              i
            fromInteger i KBi =
              i * 1024
            fromInteger i MBi =
              i * 1024 * 1024
          
          Prelude> 100 B
          100.0
          Prelude> 100 Bi
          100
          

          An older application of this trick where I originally explored it: https://github.com/bitemyapp/buttress/blob/master/src/Buttress/Time.hs#L58

          1. 2

            Thanks! That’s a neat trick. The number acting as a function was one of the main things confusing me.

    4. 4

      I would love to see this book completed. I think it’d be a great service to the Haskell community.

      1. 6

        Having spent the last 2 years starting a (Haskell) company, my time for open source is not abundant. If things go well I’ll certainly pick it up again though.

        1. 3

          I’m with him on this, I’ve just read the first 4 or 5 chapters, but I’m planning to eventually sit down and follow it through, implementing everything. If there is anything we can do (as a community I mean) to help with it, say so! At the very least I can help reporting typos :]

        2. 2

          That’s completely understandable. I hope the company’s doing well.

        3. 2

          No rush, we can wait :)

          I actually binge-read/reproduced write you a haskell for the last three days.

          I really enjoyed it, thanks for writing it!

          1. 2

            Wonderful, makes me happy to hear people get some value out of the first manuscript.

            1. 1

              Yep, its quite cool, I learnt a lot. Just being selfish in saying it would be great to have it completed. But I also understand you starting a business and having commitments. So no worries, thanks for all the fish so far!

    5. 3

      I’ve been working on a CSS minifier for quite some time. In many cases it achieves better results than the minifiers normally used, although it doesn’t provide much compatibility options for older browsers. I was currently playing with the idea of using GADTs to refactor a module, which I managed to, but now I’m having issues when mixing them with type classes in another module. I’m hoping type families might help, although that’s beyond my knowledge at the moment. Anyway, I’d love any feedback on the project, so if you have some free time please try it out!