Deaing with the removal of type projections


#1

Hello,

While looking at the Scala days presentation videos, someone mentioned that in the future generic type projections will be removed (seems inference is not complete/sound). In a code base I am working on I have something like this:

  trait Base {
    type A
    type B
 }

  class ExtBase(a: ExtBase#A, b: ExtBase#B) extends Base {
    type A = Int
    type B = Double
  }

The idea is that in ExtBase I only change types A and B if/when needed and follow the compilation errors to correct the code. So I have two questions:

  1. Is what I am doing incorrect, ill advised or simply not idiomatic Scala
  2. When projection are removed how can one refer to the types A and B in the parameters (aside from simply using the types directly)?

TIA,


#2

I don’t think that what you are doing will become impossible. Only projections on type parameters or abstract types will be removed. For instance a type projection like this will no longer be possible, because X is a type parameter:

trait A { type B }
class Foo[X <: A](b: X#B)

However your code currently doesn’t compile with dotty, but I think that’s a bug, and not the intended behavior.


#3

I have huge consideration towards the same problem.

The first usage that comes to mind is type lambdas, but they would become redundant after adopting the feature directly in the dotty.

Other usage that currently I have a little clues how to transcribe to dotty is TList (type-level list of types). Example of how it is defined:

sealed trait TListI[Self <: TListI[Self]] { this : Self =>
  type Merge[X <: TList] <: TList
}
sealed trait TListN extends TListI[TListN] {
  override type Merge[X <: TList] = X
}
sealed trait TListC[H, T <: TList] extends TListI[TListC[H,T]] {
  override type Merge[X <: TList] = TListC[H, T#Merge[X]]
}

type TNil = TListN
type TList = X forSome {type X <: TListI[X]}
type |[H, T <: TList] = TListC[H, T]
type ||[L <: TList, R <: TList] = L#Merge[R]

Here type projection is essential for type level computation. And we have zero objects to depend on and dotty is focused on dependent objects (as I conclude from the name) not on types which has no representing objects.


#4

I suspect you’ll have to encode them with implicits instead. Since there are no runtime objects you might be able to do it with phantom types; that will probably erase all the runtime overhead that you might otherwise get with implicits.


#5

Thanks @Jasper-M for the feedback. I will stick to this assuming compilation failure is due to a bug.


#6

It should be fixed now: https://github.com/lampepfl/dotty/pull/2963


#7

I suspect you’ll have to encode them with implicits instead.

I could encode anything with implicits. I could reimplement OOP in pure implicits. But it is makes me develop certain language features that should be already presented in core.

Implicits are always cumbersome and takes more code to express than normal type-level solutions. And they lead to pretty cryptic error messages. And implicits works counter-intuitive sometimes due to lack of backtrack feature, require from programmer to understand clearly order of expansion. I personally not a very good programmer, so I forced to supply every implicit code with tests to check if they would expand as I imagined.

Implicits propagate through your code like plague. I’d like to avoid them as much as I can.

Finally I’m not sure if implicits would not introduce larger problems than type projections. If some feature is non-secure in core, there are chances that manual encoding would give the same or larger problems, which would be only hidden deeper inside your custom code which had never be verified formally.


#8

Already seen the the pull request (had subscribed to the issue). Thanks.


#9

Well I think that if you want to get any good use out of your typelevel list, beyond the most basic usecase of checking that one tlist is exactly equal to another tlist, you would need implicits anyway.


#10

Checking if some type belongs to a TList, if a TList is completely in super/sub type realtion to the type, if one Tlist is a sublist of another Tlist could be easily implemented with type level computations. I doubt if there is need for more complex stuff.


#11

I would be genuinely interested to see how you would do all that without implicits.

For instance:

trait Foo[Flags <: TList]
def onlyForInt[Flags <: TList](foo: Foo[Flags]) = ???

How do you ensure (by adapting the method signature of onlyForInt or the implementation of TList) that onlyForInt can only be called if Flags contains Int?

Anyway, I’m just the messenger here :slight_smile: Abstract type projections seem to be unsound, so in Dotty the (probably only) alternative will be implicits.


#12

Here is my problem:
I have main method in it written a method1.
From other main method i am trying to call method1.
They were in objects.
In both objects i am using spark context.
Its throwing error for multiple spark context.
How could i resolve this?
I tried using “multiple spark context set true”