Generalised
“Whether a language can solve the Expression Problem is a salient indicator of its capacity for expression.”
– Philip Wadler
enum DataType {
IsInt(Int),
IsString(String),
JustIs
}
int getInt(DataType d) {
switch(d) {
case IsInt(i):
return 1 + i;
case IsString(s):
return 2 + s.length();
case JustIs:
return 3;
}
}
enum DataType {
IsInt(Int),
IsString(String),
JustIs,
// new case
IsBool(Bool)
}
println(getInt(IsBool(true)));
Int getInt(DataType d) {
...
}
IsBool
is passed to getInt
getInt
function
until it is changed to handle Bool
DataTypeWithBool
, in
addition to the previous data type
println(getInt(IsBool(true)));
“We, the programmers, do solemnly swear, to never call thegetInt
function with anIsBool
value”
getInt
function until it is changed to
handle IsBool
getInt
)DataTypeWithBool
, in addition to the previous data
type
enum DataTypeWithBool {
Existing(DataType),
IsBool(Bool)
}
Apologies
but we don’t need to change that code very often anyway
Just outright, blatant irresponsibility
let’s use a programming language without types
The Expression Problem
Given our previous example:
enum DataType {
IsInt(Int),
IsString(String),
JustIs
}
We want an interface over which we can write these example functions (and more):
Bool isJustIs(DataType)
// returns the IsInt value
Maybe<Int> getInt(DataType)
// + 1 to the IsInt value if it is an int
DataType addOne(DataType)
// reverse the string if it is a string
DataType reverseString(DataType)
DataType
, but rather, each component of the data
typeI have also seen the solution, “turn all the types off, open the data type, and hold its structure in your head…
but why are there so many bugs?”
DataType
is
either:
Int
ORString
OR
enum DataType {
IsInt(Int),
IsString(String),
JustIs
}
Bool
Maybe<Int> getInt(DataType)
DataType addOne(DataType)
DataType reverseString(DataType)
enum DataType {
IsInt(Int),
IsString(String),
JustIs,
IsBool(Bool)
}
Or perhaps a more realistic data type made up of cases
enum Json {
IsNumber(Number),
IsBool(Bool),
IsString(String),
IsArray([Json]),
IsObject([String, Object]),
IsNull
}
Int
ANDString
class Person {
Int,
String
}
Person
data type, without changing our functions
class Person {
Int,
String,
Bool
}
We might have a similar set of functions over our data type
Int getInt(Person)
Person addOne(Person)
Person reverseString(Person)
We are still looking for that interface, that abstraction
interface HasInt<T> {
Int getInt(T t)
}
interface HasString<T> {
String getString(T t)
}
No. We need more than HasString
to write this function.
<T> T reverseString(T t)
1
to both Int
fields of our data type?
class Person2 {
Int,
String,
Int
}
1
to the Int
fields of the Person
case for our data type?
enum DataType2 {
IsInt(Int),
IsString(String),
IsPerson(Person2)
}
DataType
is either Int
OR
something-elsePerson
is Int
AND
something-elseDataType2
is zero-or-many
Int
Person2
is one-or-many
Int
DataType2
and
Int
is strictly weaker than that between
DataType
and Int
Person2
and
Int
is strictly weaker than that between
Person
and Int
Optic | Relationship |
---|---|
Lens | A has exactly one B and some other things |
Prism | A has exactly one B or some other things |
Traversal | A has one or many B and/or some other things |
Oliveira, Bruno C. D. S., and William R. Cook. “Extensibility for the masses: Practical extensibility with object algebras.” European Conference on Object-Oriented Programming. Berlin, Heidelberg: Springer Berlin Heidelberg, 2012.↩︎
Garrigue, Jacques. “Programming with polymorphic variants.” ML workshop. Vol. 13. No. 7. 1998.↩︎