Notes (‘epistemic status’): This post is conjecture and hypothesis; the purpose of this post is for me to work through ideas. No authority is claimed.
I am dissatisfied with my throughput as a product engineer 1.
While I hope and believe that I am at least an above-average developer, by throughput 2, in my tool sets of choice, I do not believe my throughput has increased
significantly in the last year. Additionally, I am troubled by a nagging feeling that there must be a better way to develop software.
As a (chiefly) Ruby programmer who has never used a strongly and statically typed language professionally, I have been exploring type systems, type theory, and
category theory in order to determine if a type system may enable significant throughput increases.
In theory, the benefits of a type system are (a) reducing programming errors, and revealing errors sooner, using the type system, and (b) for statically typed
languages, enabling more-powerful program analysis.
In practice, the benefits ability of a type system to reduce programming errors is restricted by the power 3 of the type system.
The benefits of more-powerful program analysis are reduced by poor tools, and made unnecessary by powerful language features 4.
What both of these amount to is: a type system can make a programmer more effective in managing complexity. Ideally, they help the programmer manage inherent complexity, the
complexity that is required by the problem.
The cost of a type system is incidental complexity. When programmers complain about the boilerplate that languages like Java and C++ force for defining interfaces,
they are complaining about having to manage complexity not inherent to the problem they are solving.
The “loss of flexibility” complaint is, in my mind, a symptom of extra incidental complexity; more complex systems are harder to change, regardless of whether the complexity is
incidental or inherent.
The question, then, is: does a type system provide more benefit in managing inherent complexity than it costs in incidental complexity?
When phrased this way, the problem is more clearly a matter of choice: a programmer’s method of managing complexity is personal to them.
I use the term ‘product engineer’ to mean ‘a software engineer who has and takes responsibility for designing product features as well’.↩
“Throughput” in the sense used by Eliyahu M. Goldratt in his books; I choose it to avoid the existing connotations and preconceptions of ‘productivity’.↩
No precise definition of ‘power’ or ‘powerful’, but a. A type system’s power increases with its ability to correctly and automatically infer types. b. A type system’s power increases with the quantity of restrictions that can be placed on a type. That is, Liquid Haskell’s refinement types are more powerful than regular Haskell types; Idris’ dependent types are more powerful than both, in terms of specificity. The two senses of power can be made to trade off against each other; whether Liquid Haskell’s type system is more powerful than Idris’ is a matter of debate, as I do not know how precisely weight Idris’ dependent types against Liquid Haskell’s much better automatic type inference.↩
“Powerful” language can be taken to be something like “expressive”; think of the trade off between Java with great IDE refactoring tools, and a ‘more powerful’ language like Ruby, where you rarely need to use those tools in the first place. The ability to manipulate the text of the language source easily is among the most powerful features. A good library for manipulating the AST of the language can substitute. This is why I consider lisp to be one of the most-powerful languages.↩
I recently had an issue with very slight display lag on my Ubuntu 17.10 desktop, which had a very interesting fix.
For context, I have an i5-4XXX series desktop with a nVidia GTX 970, and two 1080p Samsung BX24440 ‘SyncMaster’ monitors connected via DVI.
A few nights ago, I turned off my computer to move it so that I could switch to a new desk setup. By doing so, I allowed my OS to apply a few kernel updates that require a reboot to be fully applied.
After rebooting, my machine had a noticeable display lag, on everything from mouse movement to hardware-accelerated video playback on YouTube.
My first thought was that by updating my kernel I had inadvertently applied some update that did not agree with my system. In my experience, this is very, very rare on modern Linux distributions, especially Ubuntu, but with the recent Spectre / Meltdown patches, I figured it was still possible.
So, I began rolling back each package that had been updated, one by one. An hour or so later, and after an hour of cursing myself for not just installing NixOS already, I figured I had hit a dead end.
So I dove deeper. Two things were apparent: During the period of lag, Xorg was using about 30% of a core, which is very high for Xorg.
Inspecting the Xorg log in /var/log/Xorg.0.log revealed something curious. Hundreds of messages blocks like this;
As far as I can tell, when I moved my computer, the DVI connection between my GPU and my monitor became ever so slightly loose, in such a way that the GPU was detecting the monitor to become disconnected and reconnected about once per second. Even though there was no perceptible monitor flicker or “disconnected” messages, the process of disconnecting and reconnecting appears to have been consuming enough resources to delay frame rendering slightly every time it happened.
So, lesson for you - if you have display lag on a Linux system that you just can’t debug, maybe you just need to tighten your monitor connections.
This post is a set of notes on Bad Engineering Properties of Object-Oriented Languages” by Luca Cardelli, available at the time of writing from Cardelli’s site.
This post is mostly intended for my own notes. You may not find it useful.
First, Cardelli opens by defining 6 areas in which one can informally evaluate a language’s effectiveness for software engineering.
He then comments on some areas in which “advancements in procedural programming” have created positive changes in those six areas. Of particular note;
“In ML, accurate type information eliminates the need for nil-checking on pointer dereferencing”.
“… expereinced programmers adopt a coding style that causes some logical errors to show up as typechecking errors”
Cardelli then lists ways in which Object Oriented languages fail or score poorly on the metrics in question:
The first two points he lists, that object-oriented style is intrinsically less efficient and slower to compile, seem to be somewhat less relevant today than at the time of writing.
Cardelli argues that OO scores a “big win” in economy of small-scale development. Note that “The type systems of most object-oriented languages are not expressive enough.”
According to Cardelli, OO languages “have extremely poor modularity properties with respect to class extension and modification”. Note that another “large scale development problem” is the “confusion between classes and object types”, along with “the fact that subtype polymorphism is not good enough for expressing container classes”.
Cardelli then states where work is happening and what still needs to be done:
“Subtyping and subclassing must be separated. Similarly, classes and interfaces must be separated”
This post comprises of my notes while reading Luca Cardelli’s 1985 Paper, Semantics of Multiple Inheritance.
At the time of writing, a copy of this paper was available for free from ScienceDirect.
These notes are semi-structured, and are intended primarily for my own reflection.
Section 1 -
Cardelli draws a distinction between organizing data in a way “derived from standard branches of mathematics” versus in a way “dervi[ed] from biology and taxonomy”, i.e. Object Oriented programming.
“The notions of inheritance and object-oriented programming first appeared in Simula 67 (Dahl, 1966)”
In OO, “functions and procedures are considered as local actions of objects, as opposed to global operations acting over objects”.
At the time of writing, “the definition of what makes a language object-oriented is still controversial”, but “An examination… suggests that inheritance is the only notion critically associated with [OOP]”
Here’s a statement that excites me:
The aim of this paper is to present a clean semantics of multiple inheritance and to show that, in the context of strongly typed, statically scoped languages, a sound typechecking algorithm exists.
Multiple inheritance is also interpreted in a broad sense: instead of being limited to objects, it is extended in a natural way to union types and to higher-order functional types.
This constitutes a semantic basis for the unification of functional and object-oriented programming.
Section 2 - Objects as Records
The paper lays out that there are “two … interpretations of objects”. One is “objects as records”; “objects as essentially records with possibly functional components”. Record is only defined in section 3, a “finite association of values to labels”.
The second interpretation “derives from lisp”; an object “if a function which receives a message and dispatches on the message to select the appropriate method”.
“Records can be represented as functions from labels (messages) to values”, however, to say that “objects are functions” is misleading, because we must qualify that “objects are functions over messages”.
Section 3 - Records
The bit at the end of page 5 (in the PDF I looked at, i.e. of the paper, not labeled as page 5) about how typed functions can invert subtype direction is quite interesting.
To get intuitive subtype inclusion, we must define types as “set[s] of all records which have at least the required number of fields of the correct types”. That is, if we define car as having 3 fields, and vehicle as having 2, then for car to be a vehicle we have to use the “at least” rule.
Multiple inheritance in the sense of “and” - as in a thing is a “vehicle and machine” means that the object in question has the required fields of the union of the two types.
The trivial type, whose only defined constant is the constant ‘unity’, is called the ‘unit’ type.
An enumeration is a “variant type where all fields have unit type”variant
The rest of this section seems to just lay out that variant types have subtype operators basically the way one would hope and expect.
Section 5 - Inheritance Idioms
The first comment in this section is interesting - it shows that inheritance, as defined so far in the paper, is an implication of definitions, and need not be declared explicitly.
“Typechecking provides compile-time protection against obvious bugs”
There is a paragraph: “The subtype relation only holds on types, and there is no similar relation on objects. Thus we cannot model directly the subobject relation used by, for example, Omega (Attardi, 1981), where we could define the class of gasoline cars as the cars with fuel equal to “gasoline.” This sounds like the author is talking about “dependent types” as used on the wikipedia page for dependent types.
The next paragraph after that introduces a solution by defining a set of fuel types and then defining “gasoline_car” as a subtype of “car”. My gut is that this sort of compile-time-versus-runtime type-checking (and type-definition?) distinction is related, at some level, to the “code versus data” issue that Lisp (partially?) addresses through homoiconicity.
Interesting note, in that “constructor” is “a vague term indicating that, in an implementation, computation can be temporarily suspended thereby avoiding some looping situations”. This makes me wonder; how does object initialization (construction) work in Ruby?
Possibly to be continued
[Note 2018-05-06] The paper has another 7 sections or so, but I put down and temporarily abandoned process back in December of 2017. I decided to publish these notes anyway, again, mostly for my own reflection and reference.