Reputation: 8281
In scala, there are multiple ways to declare a trait with only one method
trait OneMethod extends (A => B)
trait OneMethod {
def myMethod(a: A) : B
}
What are the pro and con of each solution ?
Upvotes: 3
Views: 997
Reputation: 2401
A nice example of one-method trait with could be used in two ways as suggested by @lambdista is StringLike
in John A De Goes's functional design intro:
trait StringLike[-A] extends (A => String):
final def apply(a: A): String = makeString(a)
extension (a: A) def makeString: String
Here we declare a trait that seemingly have 2 methods - apply
and makeString
. However, apply
has "default implementation" and is not preventing it from being "functional interface" in terms of Java.
makeString
is declared using extension
and in fact becomes another declaration:
def makeString(a: A): String
And that's the way it's being invoked in apply
.
In order to implement this makeString
method, we could either declare it explicitly:
class CharStringLike extends StringLike[Char]:
def makeString(a: Char): String = a.toString
or use Scala's shorthand way of implementing functional interfaces using simple function:
val charStringLike: StringLike[Char] =
(a: Char) => a.toString
or even shorter:
val charStringLike: StringLike[Char] = _.toString
Upvotes: 0
Reputation: 1905
You can have the pro of both solutions using the following approach:
trait Transformer[A, B] extends (A => B) {
override def apply(a: A): B = transform(a)
def transform(a: A): B
}
object FooTransformer extends Transformer[String, String] {
override def transform(a: String): String = a + ", world"
}
val transformer = FooTransformer
// "classic" usage
val foo = "hello"
// "hello, world"
val transformedFoo = transformer.transform(foo)
// functional usage
val fooList = List("foo", "bar", "baz")
// List("foo, world", "bar, world", "baz, world")
val transformedFooList = fooList map transformer
This gives you flexibility and descriptiveness because you can use it both as a function and in a classic fashion.
Upvotes: 1
Reputation: 52681
By extending (A => B)
, you are saying that OneMethod
is a function, and can be used directly as such:
trait TraitA extends (Int => String)
class ClassA extends TraitA { def apply(i: Int) = i.toString }
val a = new ClassA
(1 to 5).map(a) // IndexedSeq[String] = Vector(1, 2, 3, 4, 5)
If you don't extend (A => B)
, you can't do that; instead, you'd have to tell it explicitly what the method name is:
trait TraitB { def myMethod(i: Int): String }
class ClassB extends TraitB { def myMethod(i: Int) = i.toString }
val b = new ClassB
(1 to 5).map(b) // error, required: Int => ?
(1 to 5).map(b.myMethod) // IndexedSeq[String] = Vector(1, 2, 3, 4, 5)
So: extending (A => B)
makes your class a bit more flexible in its use, with less verbosity. On the other hand, if you want a more descriptive name than apply
, you can do version B.
Also worth noting: neither version restricts the trait to having only one method; you can add extra methods to either.
Upvotes: 6