Robert Harper, a Carnegie Mellon professor, writes about programming language theory, type systems, modules and parallelism.
HN commenters cite it as the classic argument that dynamic languages are static languages, and enjoy his opinionated posts on ML versus Haskell.
"Modularity" is nearly defined by the ability to forget certain details of an interface so as to leave freedom to change the underlying details while maintaining the behavior at the interface (Reynold's Abstraction Principle). In almost the same breath one would declare that types are a primary mechanism to achieve this. [0]
Type structure is a syntactic discipline for
enforcing levels of abstraction.
Modularity is more or less required in order to take a stab at defining or talking about composability, but is "good" modularity necessary (or sufficient) for achieving composability?
I don't have a bulletproof argument here, but essentially I'd like to say that greater modularity allows you to forget inessential details and subsequently increase the surface area with which you can compose your modules.
So finally, we come to generics as a powerful tool for using types to erase inessential details. Generics essentially operate via providing a new logical structure to types, the ability to introduce and operate sensibly over type variables. So I would argue that a type language without generics is much like a programming language without variables [1]. You are perhaps not completely crippled, but pretty close.
So, my argument is something like "without generics your type language is crippled so as to make control of modularity very weak which will prevent you (syntactically) from ensuring composability".
The "(syntactically)" bit is important since, as we all know,…
All of the proposed solutions are deficient:
> Pattern 1: Boolean Params
This solution suffers from what we typeful programmers call “Boolean blindness”: https://existentialtype.wordpress.com/2011/03/15/boolean-blindness/ . When you compute a bit, what you are actually interested in is the meaning of the bit, which isn't included in the bit itself. For example, if the bit is set, then “x” is less or equal than “y”; if it's unset, then “x” is greater than “y”.
> Pattern 2: Switch to Polymorphism
Dynamic dispatch (what the author wrongly calls “polymorphism”) can be used for open-ended case analysis. But it suffers from two drawbacks: (0) Unless you have multimethods, case-analyzing two or more values at the same time is a bitch! (1) Even if you do have multimethods, the open-ended nature of this whole business makes static exhaustiveness checking (making sure that no case is missing) impossible.
> Patte[r]n 3: NullObject/Optional over null passing
Null objects are still falling back to pattern 2, and optionals are severely underpowered in languages without pattern matching and compile-time exhaustiveness checks. For instance, one of my favorite patterns is implementing operations (say, “merge”) on non-empty collections, then extending them to possibly empty ones:
(* pe = possibly empty *)
datatype 'a pe = Empty | Cons of 'a
(* extend a merge operation on non-empty collections
* to possibly empty ones *)
fun pointed _ (xs, Empty) = xs
|…
Sounds like you may enjoy Algorithmic Information Theory.
In particular, Solomonoff Induction is the most information-efficient method of inducing computable rules from observations https://en.wikipedia.org/wiki/Solomonoff%27s_theory_of_inductive_inference
It uses Bayes rule to update hypotheses based on observations; and its initial hypothesis is the "universal prior", which assigns probabilities to observations based on how likely they are to be output by a randomly-generated computer program (more specifically, an infinite sequence of uniformly-random bits, interpreted as a self-delimiting, Turing-complete language). The universal prior is uncomputable, but has been proven to dominate all computable probability distributions. Whilst computable approximations of the universal prior, like https://en.wikipedia.org/wiki/Speed_prior , aren't as information-efficient, they are more time-efficient, and approach the universal prior when given more time to run (they're identical in the limit of using infinite time).
This is relevant for Occam's Razor, since the universal prior itself is dominated by the shortest program that outputs an observation, i.e. the best prediction we can make about what any computable system will do next, is to look for the simplest description of (AKA shortest program outputting) everything we've seen it do so far, and make a prediction based on what that program does next. When we say the shortest program "dominates", this means that even if every…
There's this pretty abstract idea of boolean blindness, which basically boils down to the idea that a boolean by itself must always be associated with an interpretation and said interpretation is external to the boolean. Afterall a boolean by itself is just a single bit of data, 0 or 1, how to interpret that bit is entirely independent of the bit itself.
This means anytime you operate on a boolean, you must somehow recover its interpretation from somewhere, and the argument is that such recovery is error prone and fragile.
The preferred way, presumably, is then to encode the interpretation directly into the type, so for example you use an enum along with pattern matching, or you write a type that wraps a boolean with some value (like a Maybe/Optional type). In effect anytime you have a boolean, you also carry with it its interpretation side-by-side so you don't need to go figuring out how to recover it.
This article goes into it in more depth:
https://existentialtype.wordpress.com/2011/03/15/boolean-blindness/
I personally think it's insightful and useful, but there are also downsides to it as well, for example when you need to operate on multiple booleans, it becomes unergonomic and bloated to deal with pattern matching over multiple enums or having to write out a lot of redundant code instead of operating on boolean operators.
Basically consider these two options:
Option 1: boolean blindness.
bool isEven(int value);
...
if(isEven(x))
{
…
Here are a bunch of blogs from my RSS reader that I think are worth reading. Some of them haven't updated in a while, but the archives may be worth a look anyway.
* Blogs with a math focus:
http://blog.sigfpe.com
http://bit-player.org
http://mathr.co.uk/blog/
https://www.johndcook.com/blog/
* Blogs with a programming languages focus:
https://existentialtype.wordpress.com
https://bluishcoder.co.nz
http://blog.ezyang.com
http://akkartik.name
http://lambda-the-ultimate.org
http://mrale.ph
* Blogs with a UI design focus:
http://www.brandonwalkin.com/blog/
http://ignorethecode.net/blog/
http://interuserface.net
* Blogs with a graphics / 3D rendering focus:
http://c0de517e.blogspot.com
http://diaryofagraphicsprogrammer.blogspot.com
https://directtovideo.wordpress.com
http://graphicrants.blogspot.com
http://www.reedbeta.com
http://www.realtimerendering.com/blog/
https://blog.demofox.org
http://eelpi.gotdns.org/blog.wiki.html
* Other interesting / more general programming blogs (many of these have a "systems" focus):
http://danluu.com
http://ewontfix.com
http://chneukirchen.org/blog/
http://neverworkintheory.org
http://apenwarr.ca/log/
https://www.snellman.net/blog/
https://www.pvk.ca
http://psy-lob-saw.blogspot.com
https://randomascii.wordpress.com
http://fastcompression.blogspot.com
https://fgiesen.wordpress.com
http://blog.plover.com
http://chneukirchen.org/trivium/
http://rachelbythebay.com/w/
I agree with your sentiment and resonate with some of my opinions on languages (been reading about TT and abstract math in general for a while as a hobby)
my opinions so far (subject to change as i read more):
dynamic typing = just one type [0]. yes, good to test ideas quick, to develop quick gigs that i don't care about and tiny snippets/scripts, i use js for that.
gradual typing = guaranteed mess evolving large code bases, I guess the gradual typing thing in practice never happen and when it happens your IDE becomes red and your compiler errors explode, i don't recall where i read about that but it made sense to me, i used groovy to take advantage of the optional typing but i was the only dev in that project.
sound type system + parametric polymorphism = the best thing around, although i prefer to have explicit type annotations.
no formal math object behind a language = the language does not exist, just , absurd rules ,idioms and syntactic mess that you have to remember and tied to specific monkey patched implementations to compensate incoherent foundations, sadly unavoidable, as most, if not all popular languages do not exist,.
Yes, I've been reading Harper a lot.
[0] https://existentialtype.wordpress.com/2011/03/19/dynamic-languages-are-static-languages
edit: formatting