Trait parameter overriding question (Scala 3)

How do (Scala 3) trait parameters interact with parameters of classes that extend the trait?

I thought I wanted to write something like this:

trait ExampleTrait(val param: Int)
case class Class(override val param: Int) extends ExampleTrait(param)
val x = (Class(1): ExampleTrait).param

However, the compiler rejects the class line, saying:

... error overriding value param in trait ExampleTrait of type Int;
  value param of type Int cannot override val parameter value param in trait ExampleTrait

I’m not clear on why that “value param of type Int cannot override val parameter value param in trait ExampleTrait”.

I tried several variations (e.g., no var on trait parameter, no override on class parameter, no val on class parameter), but nothing I tried that still had the trait parameter and had a class parameter with the same name worked. (Using different parameter names worked, but they’re logically the same thing, so I want to keep the names identical.)

Of course, I can do it without trait parameters, as in:

trait ExampleTrait {
  def param: Int
}
case class Class(override val param: Int) extends ExampleTrait
val x = (Class(1): ExampleTrait).param

However, I thought maybe that would be doable using trait parameters.

(No, it’s not essential that I use a trait parameter. However, I’m also trying to learn Scala 3, so I’d like to understand what’s going on and what the rules are, and whether trying use a trait parameter makes sense for my use case.)

Thanks.

(I’m on 3.3.0.)

Why you need val? Just experimenting for understanding?
Because this works:

scala> trait ExampleTrait(param: Int)
// defined trait ExampleTrait

scala> case class Class(param: Int) extends ExampleTrait(param)
// defined case class Class

Let’s try the val:

scala> :r
Resetting REPL state.

scala> trait ExampleTrait(val param: Int)
// defined trait ExampleTrait

scala> case class Class(val param: Int) extends ExampleTrait(param)
-- [E164] Declaration Error: -----------------------------------------------------------------------------------------
1 |case class Class(val param: Int) extends ExampleTrait(param)
  |                     ^
  |                     error overriding value param in trait ExampleTrait of type Int;
  |                       value param of type Int needs `override` modifier
1 error found

scala> case class Class(override val param: Int) extends ExampleTrait(param)
there was 1 deprecation warning; re-run with -deprecation for details
1 warning found
// defined case class Class

This also works! What’s the deprecation warning? Let’s look:

-- Deprecation Warning: /home/spam/Templates/Scala.scala:2:30 --------------------------------------------------------
2 |case class Class(override val param: Int) extends ExampleTrait(param)
  |                              ^
  |   overriding val parameter value param in trait ExampleTrait is deprecated, will be illegal in a future version
1 warning found

See here: Scastie - An interactive playground for Scala.

You can read up on the new trait params of Scala 3 here:
https://docs.scala-lang.org/scala3/reference/other-new-features/trait-parameters.html

But that doesn’t actually work, in the sense of making param accessible from the trait. That is, in that case, the following doesn’t work:

val x = (Class(1): ExampleTrait).param

Just experimenting for understanding?

Yes, partly. (While working on some code, seeing where/how new Scala 3 features are, or aren’t, applicable and how they work.)

Yes, I’ve seen that, and looked again, but neither it nor the linked-to SIP seems to say anything about the overriding relationship between the trait and its parameter(s) and an implementation subclass.

(I sure wish Scala 3 had a detailed, organized, consistent language specification as Scala has the spec. at Scala Language Specification | Scala 2.13. )-: )

Oh, but sometimes this can be the intuitive thing to do. For example, this does not compile:

trait Define :
  type Test

trait Trait(define: Define) :
  type Test = define.Test

where the error is:

non-private type Test in trait Trait refers to private value define
in its type signature  = Trait.this.define.Test

whereas this does:

trait Define :
  type Test

trait Trait(val define: Define) :
  type Test = define.Test

Of course you can circumvent it with an auxiliary variable:

trait Define :
  type Test

trait Trait(define: Define) :
  val aux = define
  type Test = aux.Test

but that is not very ‘DRY’. BTW, it is not clear to me why the first example does not compile, i asked it before on this forum, but nobody seemed to know the answer.

exposing private path-dependent (afaiu) type as public doesn’t make sense (i.e. scala compiler wouldn’t be able to do anything with the type from outside), so maybe change the code to:

trait Define :
  type Test

trait Trait(define: Define) :
  private type Test = define.Test

?

if you don’t place a val before a class or trait parameter then it’s private by default.

here you make a public field val aux so it makes sense to expose public path-dependent types based on that public val.

The recent history is this PR, which links to the ticket and also the previous effort to warn about the pattern.

That started warning in 3.2.2 and still errors only under -source:future.

2 Likes

Yes, making the type private, of course ‘resolves the error’. But my point was that there may be a legitimate reason for having a val. If making the type public does not make sense from the compilers perspective i immediately believe you on that for i do not know enough about the compilers internals to disagree, but imho the implicit private marker on the type is surprising, for this only applies to types, not to fields.

From a design perspective there may be good reasons to export the type whereas keeping the rest of the trait parameter private (there can be more than a type definition in there). Now i have to use an intermediate value to achieve this goal.

Lastly, it feels strange to me that making something public what is private is forbidden by default. We do this all the time. The other way around would not make sense to me indeed. And, it is still possible by a workaround, so why this obstacle?

Jus for the record, this is the way i go around the limitation:

package test 

trait Define :
  type Test

trait Trait(private[test] val define: Define) :
  type Test = define.Test

object Main :
  def main(args: Array[String]): Unit = ()

Here, the trait parameter define is still perfectly shielded for external use, whereas the type is not. No need for an auxiliary value.

But we are getting off topic here. In general i do agree with the fact that a public value in the trait definition does not serve many purposes, other maybe than orthogonality of the language. That however does not justify a possible unsoundness of course.

when main constructor parameter has neither val nor var applied to it then it’s implicitly private and IIRC can be even completely erased if it’s not needed after class initialization. other types of class members (including types) are implicitly public.

no, it’s not possible. your workaround is making a non-private field and exposing a path-dependent type based on that non-private field (that’s the crucial part). private[test] is also not fully private as it’s visible outside of the class, so there’s a way to meaningfully use the exposed type, so there’s no need to throw a compilation error.

here’s a simpler example showing the same problem:

class Exposer(param: Int): // no var or val, so `param` is implicitly private
  type ExposedType = param.type // compilation error here
  def expose: ExposedType = param

val exposer = Exposer(5)
val number: exposer.ExposedType = exposer.expose
println(number)

yep, probably our posts should be moved to your original thread.

1 Like

I guess, if there are things missing from the doc, it would be good to open an issue on that page at GitHub - lampepfl/dotty: The Scala 3 compiler, also known as Dotty.

1 Like

Some extra context in the deprecation message would have been nice.