tag:blogger.com,1999:blog-791618634354641106.post5948110628325697988..comments2020-12-11T06:30:16.284+00:00Comments on Why $\lambda$‽: Private type members in Scala aren't Anonymoushttp://www.blogger.com/profile/16246192515095112722noreply@blogger.comBlogger3125tag:blogger.com,1999:blog-791618634354641106.post-51520491847333028452015-11-07T17:05:55.701+00:002015-11-07T17:05:55.701+00:00Thanks, Paolo, that's very helpful. Your firs...Thanks, Paolo, that's very helpful. Your first (D1) suggestion to use traits is exactly what I told the students on Tuesday :)<br /><br />I had, naturally (and perhaps naively!), been assuming that "undefined" type members behave like existentially-quantified types (or type members of signatures in ML) - where I was surprised was the fact that "private type" was allowed, but didn't seem to have the anticipated (or any) effect. From your explanation, it sounds like the right way to think of this is: private just constrains whether we can refer to D.T from other scopes, not whether the abbreviation D.T=Int is visible to other scopes. If we explicitly say that D.T abbreviates ty, then the meaning of the program is invariant under replacement of D.T with ty anywhere (except that some references to D.T might be disallowed because of the private keyword).<br /><br />I couldn't find anything about how "private" interacts with "type" in the language specification either.Anonymoushttps://www.blogger.com/profile/16246192515095112722noreply@blogger.comtag:blogger.com,1999:blog-791618634354641106.post-16045743003347126112015-11-07T01:16:04.406+00:002015-11-07T01:16:04.406+00:00Some further general observations.
Generally, the...Some further general observations.<br /><br />Generally, the whole Scala type system is best understood as a huge superset of the<br />ML module system, with first-class modules (objects),<br />mixin composition, and more, and every bit as complex.<br />Many features of ML modules can be encoded, and that was<br />one of the design goals. Look up the paper "Scalable Component Abstractions"<br />for details on how to achieve that; for a less researchy introduction,<br />look up the "cake pattern", which is the same thing (though most won't mention that).<br /><br />In my experience with Scala, after reading the spec, I recommend a trip to the issue tracker and/or<br />asking on the scala mailing lists (scala-language, scala-internals). That's often a good next step after reading the spec. Among other reasons:<br />- the spec hasn't kept up with compiler changes<br />- it was always incomplete (type inference isn't actually specified, whatever the spec seems to suggest).<br />- the correct behavior itself is still under research. In this case, the bugtracker shows two Scala compiler hackers figuring out what the behavior should be. Generally, access modifiers on type members show up in no formal model of Scala.<br /><br />A cooler example of behavior under research: you mention type members in objects. In fact, they can be left abstract — the compiler used to give an error in some cases but not in others. When I pointed out the inconsistency, that restriction which was there in some cases turned out to be pointless, and was fixed (https://github.com/scala/scala/pull/4024). In fact, undefined type members model existential types, and that's one crucial insight of the new variants of Scala's type system.Anonymoushttps://www.blogger.com/profile/04485097839438234853noreply@blogger.comtag:blogger.com,1999:blog-791618634354641106.post-79815577874054682752015-11-07T01:14:14.801+00:002015-11-07T01:14:14.801+00:00There's a bug here, but only in the displayed ...There's a bug here, but only in the displayed types, not in the "actual behavior".<br />(Bugtracker entry: https://issues.scala-lang.org/browse/SI-8812).<br />Since the bug description is extremely terse, you might want to read on.<br /><br />Regarding information hiding, there are other ways to hide T's definition.<br /><br />Because of private, `D.T` will not work outside of `D`, just like `D.c`:<br /><br />```<br />scala> object D {<br /> | private type T = Int<br /> | val c:T = 42<br /> | def f(x: T): T = x + 1<br /> | }<br />defined object D<br /><br />scala> val v: D.T = 42<br />:11: error: type T in object D cannot be accessed in object D<br /> val v: D.T = 42<br /> ^<br />```<br /><br />However, `T = Int` is a type alias, so it can be freely inlined inside D. Hence your object is equivalent to:<br />```<br />object D {<br /> private type T = Int<br /> val c:Int = 42<br /> def f(x: Int): Int = x + 1<br />}<br />```<br />So all your usage example should actually typecheck, as they do. Arguably, a correct implementation would (internally) just normalize the types of the members. Under that model, it's clear that private type members can't achieve abstraction. However, type synonyms should usually not be expanded for readability.<br />It's certainly bad that `D.f`'s type is shown as `D.T => D.T` (that's what the bugtracker entry is about), and the internal logic of the compiler is... not well defined.<br /><br />More in general, `expression.T` is dispatched *statically* based on the static type `U` of `expression`, if that type `U` defines `T` with equality (something which is not so intuitive — I only got that after reading Odersky et al.'s νObj paper at ECOOP 2003).<br />So if `D.T` weren't private, it could be freely inlined everywhere in the program.<br /><br />For information hiding — you need to use abstract types, similarly<br />to what you'd need to do in ML (signatures become traits,<br />structures become classes/objects).<br /><br />To achieve abstraction in your example, you could try this (compiles, otherwise untested):<br />```<br />trait D {<br /> type T<br /> val c: T<br /> def f(x: T): T<br />}<br /><br />// One instance. D1.T is still Int, though, but that doesn't affect<br />// clients accepting D.<br />object D1 extends D {<br /> type T = Int<br /> val c: T = 42<br /> def f(x: T): T = x + 1<br />}<br /><br />// If you want to hide your implementation more<br /><br />object DCompanion {<br /> val anInstance: D = new D {<br /> type T = Double<br /> val c: T = 42.0<br /> def f(x: T): T = x + 1.0<br /> }<br /> private object AnotherInstance extends D {<br /> type T = Double<br /> val c: T = 43.0<br /> def f(x: T): T = x + 2.0<br /> }<br /> val anotherInstance: D = AnotherInstance<br />}<br /><br />class Client {<br /> def f(d: D) = {<br /> // Here d.T is fully abstract.<br /> //1 : D //would give a compile error.<br /> }<br />}<br />```<br />Anonymoushttps://www.blogger.com/profile/04485097839438234853noreply@blogger.com