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
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?
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
Thanks! That’s a neat trick. The number acting as a function was one of the main things confusing me.
I would love to see this book completed. I think it’d be a great service to the Haskell community.
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.
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 :]
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!
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!
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!
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.
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.
I just started that book last night! So far I’m digging it.
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.
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.