
Moving Beyond Types (2017) - pmoriarty
https://hypothesis.works/articles/types-and-properties/
======
tmcb
Static typing is not at all bad; usual static typing mechanisms are, due to
the fact that most of them force you into designing your types before you even
have a working prototype.

(If you should first understand your problem before you write some code or if
you should use code to help you understand the problem and then write more
code is up to debate, of course. Typing is an invaluable tool of thought to
help you understand your problem, yes, but it is just one more tool in the
toolbox.)

The sad thing I noticed though is that, having hacked a mostly untyped Python
code base during the last few days, making sure that all typing annotations
you add to the code base are sound is a big PITA, to put it bluntly, and I
would pretty much prefer to work with a statically typed language in this
particular case. If your typing discipline is uncoupled from the ability to
run code, people simply remove that obstacle from their way. It starts to be
treated pretty much like tests and documentation: indispensable in theory,
relegated to second plan in practice.

So my impression is that gradual typing almost got it right, but it may do a
great disservice to overall code quality as well. I am now inclined to think
that some kind of barbell strategy on typing will render better results: use
both a completely untyped dynamic language such as Tcl, where everything is a
string, and a static, strongly typed programming language (ideally a proof
assistant with dependent types). You prototype with the former and move into
production after translating your solution to the latter. If you ever need to
push untyped code into production, it will be obvious to everyone involved
and, since it is so decoupled, it cannot affect the quality of the statically
typed codebase by any chance.

~~~
sbergot
> Static typing is not at all bad; usual static typing mechanisms are, due to
> the fact that most of them force you into designing your types before you
> even have a working prototype.

> Typing is an invaluable tool of thought to help you understand your problem,
> yes, but it is just one more tool in the toolbox

For me those two sentences contradict each others. Types force you to draw an
outline of your solution. For any non trivial problem this will be a time
saver even in the prototype phase.

~~~
tmcb
I can't see how they do, sorry. In fact, they complement each other. The usual
static typing mechanisms I mentioned force you to approach a problem with
their specific mindset, very much like math problems that add unreasonable
constraints to their statements in order to check if you grasped a specific
concept really well. In a real world scenario you should be able to reason
your way out of the problem without such artificial constraints. So it should
not be a restriction to anybody if they can accurately explain a concept or
implement a program without using tools such as types.

------
mathetic
So I use QuickCheck often and I'm a true believer, but the point this post is
making is absolutely justified. Tying the test generation to the type is often
requires some creative ways of writing `Arbitrary` instances (the
specification of how test cases should be generated).

However, I don't think what the author is suggesting in its place better. The
advantage of tying the test case generation to the type is that it's
compositional. Lots of sub Arbitrary instances come together to generate the
test case and in the long term I find this to be more maintainable. Whereas
the author's approach discourages compositionally.

The real problem is that the types in vanilla Haskell are too loose, so there
lots of inhabitants of a type that we are not interested in. The author
mentions in passing, dependent-types might be a solution and that is exactly
right. What is needed is to make the irrelevant terms illegal altogether. This
is pretty achievable and is already somewhat used. For example, the program
synthesis (proof search) mechanism of Idris 2[0] uses linear[1] dependent
types[2] to heavily constrain which terms are possible candidates for a given
type. The specificity of the type reduces the search space, hence increases
the effectiveness of the proof search.

[0] [https://github.com/edwinb/Idris2](https://github.com/edwinb/Idris2)

[1]
[https://en.wikipedia.org/wiki/Substructural_type_system#Line...](https://en.wikipedia.org/wiki/Substructural_type_system#Linear_type_systems)

[2]
[https://en.wikipedia.org/wiki/Dependent_type](https://en.wikipedia.org/wiki/Dependent_type)

------
patrickmn
Readers may also be interested in LiquidHaskell:

[https://ucsd-progsys.github.io/liquidhaskell-blog/](https://ucsd-
progsys.github.io/liquidhaskell-blog/)

[https://ucsd-progsys.github.io/liquidhaskell-
tutorial/01-int...](https://ucsd-progsys.github.io/liquidhaskell-
tutorial/01-intro.html)

The Python hypothesis package strikes me more like a dynamic version of
Haskell/LiquidHaskell than Haskell/hedgehog.

------
pwm
I find it rather disrespectful to summarise a 20 years old, very successful
and at the time truly revolutionary tech like QuickCheck with a simple “this
is a bad idea”. If you stand on the shoulder of giants at least pay some
respect.

------
vfaronov
The title of this HN post is completely out of context, and the discussion
here is unrelated to the article. The author is not at all arguing against
types or Haskell, nor is he bashing QuickCheck (he’s developed a QuickCheck-
like framework for Python). He’s merely complaining about some details of
QuickCheck’s design.

~~~
thsealienbstrds
I agree with your observation, except for the part where he not bashing on
QuickCheck (he is, but you have to click the first link in the article in
order to find it).

> One of the big differences between Hypothesis and Haskell QuickCheck is how
> shrinking is handled.

> Specifically, the way shrinking is handled in Haskell QuickCheck is bad and
> the way it works in Hypothesis (and also in test.check and EQC) is good. If
> you’re implementing a property based testing system, you should use the good
> way. If you’re using a property based testing system and it doesn’t use the
> good way, you need to know about this failure mode.

> The big difference is whether shrinking is integrated into generation.

> Integrating shrinking into generation has two large benefits...

> The first is mostly important from a convenience point of view...

> But the second is <i>really</i> important, because the lack of it makes your
> test failures potentially extremely confusing.

------
mrkeen
> In Haskell, traditionally we would fix this with a newtype declaration which
> wraps the type. We could find a newtype NonEmptyList and a newtype
> FiniteFloat and then say that we actually wanted a NonEmptyList[FiniteFloat]
> there.

We sure would! What's the issue?

------
kccqzy
> In Haskell, traditionally we would fix this with a newtype declaration which
> wraps the type. We could find a newtype NonEmptyList and a newtype
> FiniteFloat and then say that we actually wanted a NonEmptyList[FiniteFloat]
> there. […] But why should we bother? Especially if we’re only using these in
> one test, we’re not actually interested in these types at all, and it just
> adds a whole bunch of syntactic noise when you could just pass the data
> generators directly.

Did you know it's one function call to change your NonEmptyList FiniteFloat
into [Float]? It's called coerce. There's very little extra syntactic baggage.

And you know what, carrying data generators around is a bad idea. It forces
you to think about _how_ you want the random data to be generated, and not
_what_ the random data should look like.

------
pjc50
In order to understand "shrinking", an unfamiliar term to me, I read the
linked blog post on test case reduction:
[https://blog.regehr.org/archives/1284](https://blog.regehr.org/archives/1284)

------
chewxy
The primary assumption of whatever's in the first page is that types are sets
of values right? Integrated shrinking assumes the rules that form the set
while type based shrinking assumes subsetting. Am I right in reading this?

If so.. why are we still using the notion that types = sets of values?

------
cryptica
Static typing (in any language) is just like training wheels on a bike. It's
great to learn with them but eventually, as your skill increases, the training
wheels become a problem and then they must come off.

Most things in the real world don't have a fixed set of attributes, attributes
can change over time and as software developers we need to model that. There
is a reason why the largest package managers (with the most packages
published) are for dynamically typed languages. It's simply more flexible;
these packages can work with anything; no need to import type definitions that
don't fit within your type system. They are unopinionated, they return only
simple objects and therefore they are highly composable.

~~~
continuational
The number of packages is correlated with number of users. Popular untyped
languages tend to be designed for beginners, and thus have more users.

~~~
cryptica
They're more popular with beginners because they're more expressive and
simpler to reason about.

I find naming types to be a major pain. In complex projects, you often end up
with multiple types that are almost the same but have a few attributes
different. In reality these are the same type, it's just not supposed to have
a single fixed schema.

For example, how would you model a tadpole which develops into a frog with a
statically typed language? Do you need two different types for this? What
about all the awkward intermediate states between tadpole and frog? How do you
model those? Do you really need to create multiple different instances in the
code to represent that one entity?

~~~
seanparsons
In a dynamic language it's entirely likely you still have those types, but
they're just not defined anywhere. You the developer have to validate them in
your head.

~~~
cryptica
In dynamically typed languages, you never check or think about the types,
instead you only think about the features. Does this object have a run()
method? Does it have a color property? It doesn't matter what type it is...
Maybe it's a new kind of type that you could not have imagined when you first
wrote the code, but if it supports the necessary method, it's going to work
and you don't need to change or recompile your code to account for it.

~~~
cjfd
Asking the question 'what are the features of this thing' is a potentially
very complicated question in a dynamic language. Each and every line of code
could have added or removed a feature. Dynamic language make for very messy
and unreliable computing.

~~~
gcthomas
But the choice between static and dynamic typing (or duck typing add described
here) is not cost neutral.

Dynamically typed languages can be much quicker and cheaper to write, making
for more rapid business product cycles. The lack of type safety can largely be
made up by adding to the already necessary unit tests.

Isn't that how the YouTube startup out-manoeuvred Google's much larger dev
team working with static types?

~~~
AnimalMuppet
> Dynamically typed languages can be much quicker and cheaper to write...

Code isn't just written, though. It's read, modified, and maintained. Sure,
you can write it faster. But are you building that program for this week, or
for the next two decades?

(Yes, I know, you can be in a situation where writing it faster _now_ is life-
or-death for the company. Most of us won't spend most of our careers in that
situation, though.)

