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.
If you are developing AWS Lambda Functions, you will likely use AWS SAM Local
for running your functions in a local development environment.
AWS SAM Local is a great tool. However, many users, including myself, run into very
slow response times when first using AWS SAM Local, as documented in issue 134.
Simlpe functions may be taking more than 6 or even 10 seconds to evaluate.
This is likely for one of two reasons:
You are using a language with compiled-and-compressed code packages like Java’s JAR files.
You have not configured your AWS credentials.
For (A), AWS SAM Local unpacks your compressed code package on every request.
That process takes a few seconds, period. There are a few workarounds,
which include manually unzipping your .jar and pointing at the unzipped files
in your template.yaml’s CodeUri parameter.
However, if you are not using the C# or Java environments and are still experiencing slow requests,
you are in (B), and you can solve this problem by configuring your AWS Credentials.
The documentation
indicates that you can provide credentials in one of two ways:
Specify AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY environment variables
Configuring a profile in ~/.aws/credentials for Linux or MacOs
Nota Bene: When AWS SAM Local’s README refers to ~/.aws/credentials, there is an implication
that you are using the AWS CLI v2, not the older v1, which stores information in ~/.aws/profile.
There are two gotchas with this documentation:
While both solutions will speed up execution, specifying the environment variables is noticeably faster.
I recommend you skip the AWS CLI configuration and just specify the environment variables.
If you put your credentials in ~/.aws/credentials in a profile, you need to specify --profile <profile_name
when you invoke sam for AWS SAM Local.
Note that for basic AWS SAM local operation, you do not need to specify valid AWS credentials - any old
value, even the empty string, will do!
Recently, I tried to get a development environment set up for developing
a single page web application ClojureScript - but with a backend written
to run on AWS Lambda functions, also in ClojureScript.
ClojureScript seems to use the Google Closure Compiler for compiling
JavaScript. The Google Closure Compiler uses its own module and
import/export/require system that is different from Node’s module system.
Because AWS Lambda expects your JS to export the handler functions for the
Lambda function using the Node module system, I ended up with a real problem:
How do you require Google Closure-compiled ClojureScript into a node module?
The answer ended up being quite simple. Given that Closure is creating a compiled
file with name functions.js, which in turn is exporting a namespace functions.core,
you can create a new main.js file in the same directory as the compiled functions.js
file with the following content:
This took me longer than I care to admit to figure out, though that’s probably
mostly a function of how little I deal with JavaScript module systems. Since
thanks go to Matthew Stump’s post Writing NodeJS Modules in ClojureScript,
which had the trick as part of it.