Re: [scala-language] The cake’s problem, dotty des ign and the approach to modularity.
Shelby <[email protected]>
| Newsgroups | gmane.comp.lang.scala |
|---|---|
| Message-ID | <[email protected]> |
I believe this post is illuminating. I hope to get some feedback.
I believe I show herein the fundamental importance of objects (as in
"OOP"), that subclassing (but not subtyping) is fundamentally an
anti-pattern, and that the new DOT calculus is essential.
For the goal of completely solving the Expression Problem, I believe the
requirement for a "global vtable" which I pondered upthread, is implicitly
fulfilled by the injection of inversion-of-control I had proposed.
Objects are passed around as the vtable, which I believe is a form of the
extensible modularity advantage Martin was referring to in the thread
"Usefulness of OOP":
https://groups.google.com/d/msg/scala-language/PIhmEeMg-Ms/3AE0udamW5EJ
http://www.scala-lang.org/old/node/1637
Here is my example which afaics (?) could not be achieved with subclassing
(i.e. virtual inheritance with a vtable bound to each instance) without the
overhead of reconstructing a copy of the List with "pimp my library"
instances:
trait Drawable[T] {
def draw(o: T): Unit
}
class Point(x: Int, y: Int)
class Line(a: Point, b: Point)
implicit object LineDrawable extends Drawable[Line] {
def draw(line: Line)...
}
// OT: below I'm pseudo-coding a hypothetical `List[T]` design that can't
throw an exception when accessing head— which last time I checked some
years ago Scala's standard library design unfortunately does.
def draw[T](list: List[T])(implicit drawable: Drawable[T]) { list match {
case Head(head) => drawable.draw(head) case Nil => } }
draw(List(Line(Point(0,0), Point(1,1))))
trait DrawableMorphed[T] {
def drawMorphed(o: T): Unit
}
implicit class MorphedDrawable[T](drawable: Drawable[T]) extends
DrawableMorphed[T] {
def drawMorphed(o: T) { drawable.draw(o) } // OT: code for morphing of
input `o` is not shown, given it is irrelevant noise to what is being
elucidated
}
def draw2[T](list: List[T])(implicit drawable: DrawableMorphed[T]) { list
match { case Head(head) => drawable.drawMorphed(head) case Nil => } }
def draw3[T](list: List[T])(implicit drawable: Drawable[T]) { draw2(list) }
The subclassing anti-pattern rears its ugly head again though as implicit
subsumption as in the following example:
class Box(a: Point, b: Point, slope: Point) // OT: to insure the
construction can't throw an exception, use only the diagonal corners and
slope of one of the edges
implicit object BoxDrawable extends Drawable[Box] {
def draw(box: Box)...
}
draw(List(Line(Point(0,0), Point(1,1)), Box(Point(0,0), Point(1,1),
Point(0,1)))) // Error: no implicit found for Drawable[Any]
I assume the new DOT calculus will instead implicitly subsume `T` to `Line ∨
Box` instead of `Any`?
Perhaps the Dotty compiler could automatically generate the implicit object
`Drawable[Line ∨ Box]`. Thus we retain subtyping (i.e. `Line` and `Box` are
subtypes of `Line ∨ Box`) while eliminating subclassing (i.e. there is no
nominal type which is the supertype of `Line ∨ Box` or at least `Any`
should only occur with a cast since I've shown it discards extensible
static typing). I am not clear if this is modeled by the fine grained
methods structural typing in DOT that Adiaan Moors alluded to in the
context of a prior discussion about a union type in one of the Scala
discussion group threads (can't seem to locate it quickly at the moment);
or if could be modeled in the compiler employing a monad, e.g. see the
`sequence` conversion of `List[Thing[T]]` to `Thing[List[T]]` where the
disjunction would be a list of types in the disjunction such as `DisjunctionList[Drawable[T]]`
converted to the necessary implicit `Drawable[DisjunctionList[T]]`:
http://www.codecommit.com/blog/ruby/monads-are-not-metaphors
Another benefit of deprecating subsumption via subclassing in favor of
subtyped disjunction, is distinct invariantly parametrized types can be
added to the same List:
class TaggedLine[Tag](a: Point, b: Point, tag: TAG)...
draw(List(TaggedLine(Point(0,0), Point(1,1),
1), List(TaggedLine(Point(0,0), Point(1,1), "1"))) // Error: can not
subsume to List[TaggedLine[Any]] because TAG is invariant
I assume the new DOT calculus will instead implicitly subsume `TAG` to `Int ∨
String` instead of `Any`?
Somewhat OT, I am pondering how will DOT deal with the following?
trait Invertible[T <: Invertible[T, A, B], A, B] {
def to(a: A): B
def from(b: B): A
}
object AB extends Invertible[AB, A, B] {
def to(a: A): B...
def from(b: B): A...
}
object CD extends Invertible[CD, C, D] {
def to(c: C): D...
def from(d: D): C...
}
def to[T, A, B](invertible: Invertible[T, A, B], a: A): B = invertible.to(a)
f(AB ∧ CD, new A)
f(AB ∧ CD, new C)
So the Dotty compiler has to automatically supply:
object `AB ∧ CD` extends Invertible[`AB ∧ CD`, A ∨ C, B ∨ D] {
def to(a: A): B...
def to(a: C): D...
def from(b: B): A...
def from(d: D): C...
}
On Sunday, July 5, 2015 at 11:50:07 PM UTC+8, Shelby wrote:
>
> Coming back to this ... and sorry no time to construct a blog too rushed
> ...
>
> To summarize ideas against premature specialization (not claiming these
> are original ideas):
>
> 1. Don't use subclassing, i.e. don't implement any method in a supertype
> (e.g. `trait`) that isn't `final`, thus all methods are final (closed) in
> their existential type[1].
>
> 2. Subtyping should only be injected locally at the call site with
> inversion-of-control (c.f. my ideas upthread for potential new forms of
> compiler assistance), or globally due to the Liskov Substitution Principle
> resulting from type constructors of kind >= 0 (a.k.a. type parametrization)
> and first-class functions (a.k.a. functional programming, i.e. functions
> can evaluate and return functions).
>
> 3. Functional programming is differentiated from imperative programming in
> that the semantics are expressed more unified (atomically, i.e.
> higher-level) — of which premature specialization employing discarded
> semantics[2] (which includes unrolling function recursion replaced with
> loops and mutable variables), mutable variables[3], or referential
> opaqueness are egregious cases. Category theory can raise this up another
> level.
>
> Several resources[1][4] seems to confirm the criticism that Scala while
> being high-level, introduces more complexity than Haskell.
>
> Most of the aforementioned premature specializations Haskell makes
> difficult to introduce (which the Scala programmer could choose to avoid
> but isn't discouraged from doing so), except apparently Haskell can't do
> the implicit global subtyping due to LSP (in #2)—such as adding elements of
> different types to a list—because refinement types are impossible due to
> bottom populating all types, i.e. Haskell can't do the new injunction and
> disjunction types of the DOT calculus (Dotty)![5]
>
...
> Except apparently the lack of subtyping (refinement types) in Haskell is a
> major weakness (even after we remove subclassing, which is an anti-pattern)
> which means DOT (Dotty) could potentially take a big leap ahead? Is this
> paucity the price paid for Haskell's revered brevity?
>
>
> [1]
> https://existentialtype.wordpress.com/2011/03/16/what-is-a-functional-language/
>
> "5. It should have a rich type structure that permits introduction of new
> abstract types and which supports modular program development. By giving
> short shrift to expressions and evaluation, imperative language have an
> impoverished notion of type—and not support for modularity. Worse,
> object-oriented programming, a species of imperative programming, is
> fundamentally antimodular because of the absurd emphasis on inheritance and
> the reliance on class-based organizations in which functionality
> metastasizes throughout a program."
>
>
> http://tonymorris.github.io/blog/posts/what-kind-of-things-are-easy-in-haskell-and-hard-in-scala-and-vice-versa/index.html
>
> "Scala also has the ability to namespace a function by giving special
> status to one of its arguments (some people call this OO, then don’t, in
> the very next breath – I never get it). What I mean is, you may have two
> functions with the same name, which are disambiguated at the call site by
> the type of the argument to the left of the dot. I am deliberately not
> calling this by any special name, but rather focussing on its utility –
> Haskell can do this with qualified imports – not quite so nice. I am
> usually, though not always, particularly unimaginative when it comes to
> inventing function names – allowing me to reuse one without a penalty is
> very handy indeed. Note here I do not mean overloading – I think the Scala
> community has worked out that overloading is just not worth it – do not do
> it, ever."
>
> [2] https://existentialtype.wordpress.com/2011/03/15/boolean-blindness/
>
> "I must associate a provenance with that bit in order to give it meaning.
> “This bit being true means that e and e’ are equal, whereas this other bit
> being false means that some other two expressions are not equal.” Keeping
> track of this information (or attempting to recover it using any number of
> program analysis techniques) is notoriously difficult. The only thing you
> can do with a bit is to branch on it, and pretty soon you’re lost in a
> thicket of if-the-else’s, and you lose track of what’s what. Evolve the
> program a little, and you’re soon out to sea, and find yourself in need of
> sat solvers to figure out what the hell is going on."
>
> "It’s just that Boolean thinking has infected the software world to such
> an extent that I feel that I have to fight back. Just the idea of
> comparison to “null” to obtain a Boolean is absurd, even if you think you
> need a “null” pointer (which you don’t, in a properly designed language)."
>
> [3]
> https://existentialtype.wordpress.com/2011/03/16/what-is-a-functional-language/
>
> "2. It should support both computation by evaluation and computation by
> execution. Evaluation is a smooth generalization of high school- and
> university-level mathematics, and is defined in terms of standard
> mathematical concepts such as tuples, functions, numbers, and so forth.
> Variables are given meaning by substitution, and evaluation consists of
> simplifying expressions. Execution is the action of a program on another
> agent or data structure conceived of as existing independently of the
> program itself. The data lives “over there” and the program “over here”
> diddles that data. Assignables (my word for what in imperative languges
> are wrongly called variables) are given meaning by get and put operations
> (fetch and store), not by substitution. Execution consists of performing
> these operations."
>
> [4] http://hammerprinciple.com/therighttool
> http://joshbassett.info/2013/hello-haskell-goodbye-scala/
> http://blog.samibadawi.com/2013/02/scala-vs-haskell-vs-python.html
> http://jnordenberg.blogspot.com/2012/05/my-take-on-haskell-vs-scala.html
>
> http://blog.srinivasan.biz/software/if-you-have-to-learn-just-one-programming-language
>
> http://tonymorris.github.io/blog/posts/what-kind-of-things-are-easy-in-haskell-and-hard-in-scala-and-vice-versa/index.html
>
> "These tools exist and are as useful as they are, because of certain
> fundamental properties of Haskell itself. Here I mean, the hoogle function
> is only as useful as it is because Haskell tags IO effects in the type
> delineating values with types IO t and t, so hoogling for say, [a] -> Int
> eliminates a lot of candidate functions that would have this type in other
> environments. In Scala, without the delineation between an Int that has
> been computed with its arguments, and an Int that has been computed with
> the entire universe, a hoogle-equivalent would not be as useful"
>
> [5]
> https://www.reddit.com/r/haskell/comments/3aqj67/in_what_ways_does_haskell_support_intersection/
>
...
--
You received this message because you are subscribed to the Google Groups "scala-language" group.
To unsubscribe from this group and stop receiving emails from it, send an email to [email protected].
For more options, visit https://groups.google.com/d/optout.