Deriving Dependently-Typed OOP from First Principles
arxiv.org
arxiv.org
> the essence of object-oriented programming is programming against interfaces, which correspond to the type theoretic concept of codata and copattern matching
They then use the classic codata example of a Stream, with a head and a tail. The Stream they declare looks a lot more like the version of that concept in functional languages than the version of it in an OO one, but clearly it exists in both paradigms.
https://www.cs.cmu.edu/~15150/resources/libraries/stream.pdf https://hackage.haskell.org/package/Stream-0.4.7.2/docs/Data... https://docs.oracle.com/en/java/javase/21/docs/api/java.base...
The Java stream interface also has a lot of methods that can technically be implemented in terms of other methods on the interface, but are present so that implementers can provide more efficient implementations depending on the capabilities of their model of streams. It makes sense that the present research would only consider the bare essentials of streams, and not things that could be layered on top at the cost of some optimization opportunities.
As a Java programmer, the essence of an OOP stream seems to be captured by the following interface:
interface Stream<T> {
T next();
}
If we make mutation explicit, this evolves into: interface Stream<T> {
Pair<T, Stream<T>> next();
}
And then we can split this into two methods, providing each of the two components of the original `Pair`: interface Stream<T> {
T head();
Stream<T> tail();
}
This is exactly what the paper shows on page 2, up to syntactic differences (like explicit type parameters to the methods).https://polarity-lang.github.io/oopsla24/#ChurchEncodingCoda...
Teaser: the first OO language was Church's lambda calculus.
At the end of the day, all languages that are turing complete are the same language and the only differences lie in the kind of front end we provide. Unfortunately, we basically still have a single frontend, called C and every subsequent programming language has essentially just taken the C frontend and restricted or expanded it in small ways. We still ultimately work in terms of records, contiguous arrays and pointers. You can think of basically any more "advanced" construct in terms of pointers and it will make perfect sense nearly every time.
(a | b | c) -> T
to (a -> T, b -> T, c -> T) -> T
where (a | b | c) is a sum type saying "it can be either a of type a, type b, or type c", and (a -> T, b-> T, c -> T) is a stand-in for a record of functions (i.e. the v-table of an instance of a visitor class).[0]: https://www.haskellforall.com/2021/01/the-visitor-pattern-is...
((a | b | c) -> T) -> T
This is just a mistake I made paraphrasing link 1.http://logji.blogspot.com/2012/02/correcting-visitor-pattern...
Which makes me wonder what the next steps for a proof assistant based on this is. Will the de-/refunctionalization play an active role in the proof assistant as well, thus solving it as described in section 4.1?