Scala - override a class method in a trait
Asked Answered
D

6

7

I'm new to Scala (came from Ruby world).

And I was curious about "traits" concept in Scala (which should be ~similar to modules in ruby, if I understand it correctly).

And here's a use case.

Suppose I have a class called User defined like this:

class User {
    def password() : String = "generating a password (default)"
}

And suppose I have a trait SecurePasswords using which I would like to "override" the password method defined in the User class.

trait SecurePasswords {
    def password() : String = "generating a secure password (non-default)"
}

And, suppose, I want it to be applicable to instances of the User class and not to the entire class itself.

val a = new User
val b = new User with SecurePasswords

a.password() # generating a password (default)
b.password() # generating a secure password (non-default)

Now this is an ideal output that I would expect, however, I get different errors like "anonymous class inherits conflicting members ... (Note: this can be resolved declaring etc etc ...)"

Can this be done in Scala or I'm asking too much / doing something really weird? Is it possible to do w/o any additional class definitions, like UserWithSecurePassword extends User

Thank you all in advance!

P.S In case you are wondering "why?", just assume that system would contain a lot of entities that require password (and, potentially, secure password), so the trait could be used in a lot of places.

Denti answered 7/11, 2014 at 22:49 Comment(0)
P
12

The conflict between the two method definitions is because you didn't make them the "same method". All you did was make them coincidentally have the same name, arguments, and return type.

To make them really the same method, so that one can override the other, define them in the same place:

trait HasPassword {
  def password(): String
}

class User extends HasPassword {
  override def password(): String = "generating a password (default)"
}

trait SecurePassword extends HasPassword {
  override def password(): String = "generating a password (non-default)"
}

new User with SecurePassword

By being defined in the trait HasPassword, and then overridden (instead of doppleganged or duck-typed) in User and SecurePassword, Scala understands that this is truly the same method being redefined. Thus, when you mix in SecurePassword, it can override the password() method in User.

Paganini answered 7/11, 2014 at 23:16 Comment(0)
T
5

In addition to my previous answer - a completely different way to get what you want is to pass the password function into the User class, rather than using traits:

  class User(pw: ()=>String=default) {
    def password = pw
  }

  val default = () => "generating a password (default)"
  val secure = () => "generating a secure password (non-default)"

  val a = new User()
  val b = new User(secure)

  a.password() // generating a password (default)
  b.password() // generating a secure password (non-default)

As shown, you can use a default argument to avoid having to specify the password function in the default case.

Terr answered 7/11, 2014 at 23:13 Comment(1)
After looking at this a bit closer, I think this solution is simpler and more elegant than the one using traits.Shimmer
S
1

I'm not sure what the use case would be for this. Why not just have the User, in Ruby terms, 'mixin', the SecurePasswords trait and override password within it's class definition?

Coming from Ruby, this might be more difficult to get, but Scala is a compiled language and it's generally not a good idea to change class definitions dynamically/on-the-fly like this. Think of the type system in Scala as a way of testing your code. The more you defer code interpretation to runtime, the less testable/safe your code becomes. This is one of the strengths of Scala/Java/Haskell/... (insert compiled typed language) - the type system can catch a lot of errors at compile time. Use this to your advantage, don't fight against it.

I would look into the use of implicit parameters and how they can relate/be used with traits.

Also, without the broader context of what you are trying to accomplish in your code with this pattern, it's hard to know, but this link might prove useful to you if you are trying to implement some sort of adapter pattern.

http://danielwestheide.com/blog/2013/02/06/the-neophytes-guide-to-scala-part-12-type-classes.html

Shimmer answered 7/11, 2014 at 23:5 Comment(0)
T
1

You can provide different password behaviour on a per-instance basis as follows - but you need to provide an explicit trait in each case (default or secure):

  abstract class User {
    def password(): String
  }

  trait SecurePasswords {
    def password(): String = "generating a secure password (non-default)"
  }

  trait DefaultPasswords {
    def password(): String = "generating a password (default)"
  }

  val a = new User with DefaultPasswords
  val b = new User with SecurePasswords

  a.password() // generating a password (default)
  b.password() // generating a secure password (non-default)

Update: However, I think Dan Getz's answer is probably closer to what you originally asked for

Terr answered 7/11, 2014 at 23:10 Comment(0)
N
1

I ran into a similar challenge, with the slight difference being I didn't have control of the User class - i.e. it is pulled in from a third-party library. A "self-type" approach worked just fine:

class User {
  def password() : String = "generating a password (default)"
}

trait SecurePasswords { this: User =>
  override def password() : String = "generating a secure password (non-default)"
}

val a = new User
val b = new User with SecurePasswords

a.password() // generating a password (default)
b.password() // generating a secure password (non-default)
Nork answered 8/6, 2023 at 13:33 Comment(0)
O
0

Note: regarding the error message, there is a pending issue 128 "no ambiguity error when inheriting conflicting member from java default method"

It should be resolved in scala 2.12.x by commit 3a3688f64

SD-128 fix override checks for default methods

The check for inheriting two conflicting members was wrong for default methods, leading to a missing error message.

We were also not issuing "needs `override' modifier" when overriding a default method.

Odo answered 23/8, 2016 at 16:3 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.