How will it work with overloaded methods?
IIUC Such way to exclude ones is not very convenient for overloaded methods.
How will it work with overloaded methods?
IIUC Such way to exclude ones is not very convenient for overloaded methods.
Hey - I’d love to see some functionality along these lines.
I think there are two possible semantics here and I think we should be clear which of the two we are targeting. The first is a very light-weight semantic of simply pulling identifiers into scope automatically when you pull other identifiers in. The second is a heavyweight semantic of binding new identifiers as aliasses for underlying identifiers.
class Copier {
private val scanUnit = new Scanner
export scanUnit.scan
}
So the two potential semantics I can see here are:
val c: Copier
in scope, I have import c.scan._
also in scope, with any needed magic to navigate visibility restrictionsdef foo = this.scanUnit.foo
, adjusted as needed for signatureThe distinction really, really matters. In the former case, we don’t make any new values. There are no new implied instances, for example. It lets you lift members out of objects to package level for facades, or out of instances to support call-sight delegation, for example.
In the latter case, it becomes possible to use the mechanic to implement API-level delegation. Classes get new, concrete members proxying in the delegate members. However, this comes with the potential cost of aliasing, for example, resulting in clashing implied instances pointing to the same value, or accidentally holding things in memory through unexpected references.
Overloaded methods will be excluded in bulk. So, yes, if you want to replace one of them you have to replace all of them. I believe that’s still an acceptable price to pay.
Holding things in memory is not an issue because all aliases are defs. Clashing implicits: I am not yet sure whether this would be an issue in practice. If it is an issue one could tweak the resolution rules to
declare two implicits the same if they forward to the same value.
Thank you it is clear.
It seems I am in other camp
I just do not understand which gain will we receive for that price?
I can see name clashing. I think it is the same question as variable shadowing.
For example let us try to imagine which gain we will receive if variable shadowing is not enabled. Will it be better?
I can see troubles but I cannot see much gain comparable to that troubles .
I think such name clashes just increase coupling in the case when we use
export someVal._
I do not think that
export someVal.{method =>_, _ }
is a good statement.
Let us imagine that instead of override def someMethod()
we would use something like ‘class A extends B.{method =>_, _ }’.
I think it is something very ugly.
Good question. The difference is two (or three?) degrees of implicitness vs one. For variable shadowing, a variable you write could shadow a variable further out. For export hiding, some member that you think is accessed by a wildcard export is in fact not aliased since there is some preexisting definition in some baseclass with the same name and signature. There’s not a single syntactic instance that could tell you this is even happening. I believe this is not comparable with variable showing, and is clearly a lot more problematic.
IIUC thare are 2 points
I think if the first point is forbidden nobody will really suffer.
The second point can be solved very easy with some annotation:
trait Session{
def call1(): Unit
def call2(): Unit
def call3(): Unit
}
trait SomeAspect{
def call1(): Unit = {}
}
class SessionProxy(val session:Session) extends SomeAspect with Session {
export session._
@overimport
override def call1(): Unit = {
super.call1()
session.call1()
}
}
I cannot see any advantages in export someVal.{someMethod=>}
when I want to exclude method. But disadvantages is obvious at least for overrided method.
Just to note in PreparedStatement
it seems that more than half methods are overrided.
You could allow inline export
syntax to opt into the inline semantics.
Is it possible to allow exporting members in self-type?
type ID = Any { self: Long =>
export self.{+, -}
}
I think it would be useful when creating opacity types Proposal for Opaque Type Aliases
That’s not legal synax, but let’s assume instead this:
trait ID { self: Long =>
export self.{+, -}
}
You can write that, but you won’t be able to tie the knot afterwards. +
and -
would be final in ID
. At some point you have to mix Long
and ID
in one class. At that point you would get an “cannot override final member” error.
It’s not legal syntax, it’s syntax he proposed in the other thread
If we allow self-types for type aliases, there would not be the mix type problem, since the typer knows that ID =:= Long
at the context inside ID
.
If my library uses export
from another library, e.g., shapeless
, at top level, does it mean that the user of my library will get all of the shapeless
namespace?
I’m unsure what the rules for implicit values are.
trait Foo
object Import {
implicit val foo : Foo = new Foo{}
}
object Export {
implicit val foo : Foo = new Foo{}
}
import Import._
export Export._
implcitly[Foo]
Do we have a name collision? Do we have an ambiguity? Does the order between import
and export
matter?
Just for comparison:
https://kotlinlang.org/docs/reference/delegation.html
interface Base {
fun printMessage()
fun printMessageLine()
}
class BaseImpl(val x: Int) : Base {
override fun printMessage() { print(x) }
override fun printMessageLine() { println(x) }
}
class Derived(b: Base) : Base by b {
override fun printMessage() { print("abc") }
}
I think it is just a simple.
It’s indeed just as simple in this case. But what if the exported object is not an argument but a locally defined member? I don’t see how this would work with by
, so it seems that this construct does not support aggregation in general.
I just wanted to say that overriding is more simple in kotlin.
The kotlin’s documentaion says:
The Delegation pattern has proven to be a good alternative to implementation inheritance
And we have such relatively complicated rule for overriding.
But such overriding is more complicated in general.
Let’s look, how this can be applied to the problem of the composability of the import
statements (where you want to have a way to ‘shrink’ few import statements into one, like importing db access API with a specific context, as in motivation example in https://docs.google.com/document/d/1dlT6NgB9610jqLscCJW2LRB7TapDh3q4d2S3YA_q5zs/edit?usp=sharing )
The simplest possible analog version in dotty will-be:
DAO:
package db
class DAO[K,T] given PersistencyOptions[T], IdOptions[K,T] {
def find(id: k): Future[Option[T]]
def save(value:T): Future[Unit]
}
MyProjectDBImplicits:
package myproject.dboptions
implied def myPersistenceOptions[T] given Generic[T]
Also exists module for default db.stdoptions
package db.stdoptions
implied def defaultPersistenceOptions[T] given Generic[T]
So, I want to have one clause, i.e. I want:
object MyCompanyDAOS {
export db._
implied export myproject.dboptions._
}
But for using this, developers should write:
import MyCompanyDAOS._
implied import MyCompanyDAOS._
(i.e. still two imports, and developers also have the possibility to shoot yourself in the foot, by writing
import MyCompanyDAOS._
implied import db.stdoptions._
)
So, the original problem still exists, but now not because of imports uncomposability, but because we require separate imports for ‘normal’ and ‘implicit’ cases.
The other part (ie. shrink set of different implicit-s into one) this PRE-SIP cover well. So, we can say that 1/2 of the original problem is solved
A question which remains open for me: is it possible to restrict use of required implicit values, in such way, that
import MyCompanyDAOS._
implied import db.stdoptions._
should generate compile-time error ?
Having old-style ‘full’ imports, with ‘explicit’ and ‘implicit’ as options, as proposed in the Import implied covering import will fix this issue.
Instead of omitting existing names from wildcard exports you could make the exported methods non-final to enable delegation. This is what we typically do anyway for big interfaces. See https://github.com/slick/slick/blob/master/slick-testkit/src/main/scala/com/typesafe/slick/testkit/util/DelegateConnection.scala for an example.
You don’t implement forwarders for missing methods in all possible Connection
impementations that delegate the other methods to another object. Instead you implement a DelegateConnection
which delegates all calls once and then subclass it to override individual methods.
This is not as convenient for ad-hoc delegation because you need an additional delegate class but it pays off as soon as you need more than one implementation.
I believe that would make export
a powerful (and rather dangerous!) booster for inheritance based architectures. But that’s not its purpose.
It’s purpose is to provide something simpler where inheritance is not necessary. I believe final
is an essential ingredient for that. It prevents you from playing overriding tricks with export aliases, and I believe that’s a good thing.