Threads for contivero

  1. 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.

    1. 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 :]

          1. 2

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

            1. 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!

          1. 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!