Yann Moisan
Yann Moisan

Reputation: 8281

How to declare a one-method trait

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

Answers (3)

Arseniy Zhizhelev
Arseniy Zhizhelev

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

lambdista
lambdista

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

dhg
dhg

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

Related Questions