tags:

views:

105

answers:

4

I have a trait in Scala that has a single method. Call it Computable and the single method is compute(input: Int): Int. I can't figure out whether I should

  • Leave it as a standalone trait with a single method.
  • Inherit from (Int => Int) and rename "compute" to "apply."
  • Just get rid of Computable and use (Int => Int).

A factor in favor of it being a trait is that I could usefully add some additional methods. But of course if they were all implemented in terms of the compute method then I could just break them out into a separate object.

A factor in favor of just using the function type is simplicity and the fact that the syntax for an anonymous function is more concise than that for an anonymous Computable instance. But then I've no way to distinguish objects that are actually Computable instances from other functions that map Int to Int but aren't meant to be used in the same context as Computable.

How do other people approach this type of problem? No right or wrong answers here; I'm just looking for advice.

+1  A: 

It sounds like you might want to use a structural type. They're also called implicit interfaces.

You could then refactor the methods that currently accept a Computable to accept anything that has a compute(input: Int) method.

scompt.com
Okay, so that would make any object that has a compute method usable as a Computable. But what I was more interested in knowing is, what are some considerations for or against making Computable a function type?One issue I've noted is that if Computable is a function type then classes that implement it can't be any other function type. If I had another interface, Displayable, that inherited from (String => String) then a class could not be both Computable and Displayable because it would inherit Function1 from both traits but with different type parameters.
Willis Blackburn
That's right, that's one argument against extending (or directly using) Int => Int. If you expect that that will be the typical use case, then a custom name might be better.
Sandor Murakozi
+1  A: 

One option is to define a type (you can still call it Computable), which is at the moment is Int=>Int. Use it whenever you need the computable stuff. You will get all the benefits of inheriting from Function1. Then if you realize you need some more methods you can change the type to another trait.

At first:

type Computable = Int => Int

Later on:

type Computable = ComputableTrait // with its own methods.

One disadvantage of it is that the type you defined is not really a new type, more a kind of alias. So until you change it to a trait the compiler will still accept other Int => Int functions. At least, you (the developer) can differentiate. When you change to a trait (and the difference becomes important) the compiler will find out when you need a Computable but has an Int => Int.

If you want the compiler to reject other Int => Int -s from day one, then I'd recommend to use a trait, but extend Int => Int. When you need to call it you would still have the more convenient syntax.

Another option might be to have a trait and a companion object with an apply method that accepts an Int => Int and creates a Computable out of that. Then creating new Computables would be almost as simple as writing plain anonymous functions, but you would still have the type checking (which you would loose with implicit conversion). Additionally you could mix in the trait without problems (but then the companion object's apply can't be used as it is).

Sandor Murakozi
+7  A: 

If you make it a Trait and still want to be able to use the lightweight function syntax, you could also additionally add an implicit conversion in the places where you want them:

scala> trait Computable extends (Int => Int)
defined trait Computable

scala> def computes(c: Computable) = c(5)
computes: (c: Computable)Int

scala> implicit def toComputable(f: Int => Int) = new Computable { def apply(i: Int) = f(i) }
toComputable: (f: (Int) => Int)java.lang.Object with Computable

scala> computes( (i: Int) => i * 2 )
res0: Int = 10
Mirko Stocker
I'm using a very similar approach. I have traits `Curve extends (Double => Double)` and `Surface extends ((Double, Double) => Double)`, with implicits to lift normal scala functions. If you define the conversion in the companion object of your trait `Computable`, it is automatically in the implicit scope when searching for a view from any type `T` to `Computable`.
retronym
This is in fact what I've done. But I'm wondering what the Computable trait actually "buys" me. If I always use it as (Int => Int) then why bother at all? From what I can tell it only has documentary benefit.
Willis Blackburn
If it's just for documentation, you could as well define a type alias: `type Computable = Int => Int`..
Mirko Stocker
+1  A: 

Creating a trait that extends from a function type can be useful for a couple of reasons.

  1. Your function object does something special and non-obvious (and difficult to type), and you can parameterize slight variations in a constructor. For example, suppose you were writing a trait to perform an XPath query on an XML tree. The apply function would hide several kinds of work in constructing the XPath query mechanism, but it's still worthwhile to implement the Function1 interface so that you can query starting from a whole bunch of different nodes using map or flatMap.

  2. As an extension of #1, you want to do some processing at construction time (e.g. parsing the XPath expression and compiling it to run fast), you can do once, ahead of time, in the object's constructor (whereas if you just curried Functions without subclassing, the compilation could only happen at runtime, so it would be repeated for every query.)

  3. You want to pass an encryption function (a type of Function1[String,String]) as an implicit, but not all Function1[String,String]s perform encryption. By deriving from Function1[String,String] and naming the subclass/trait EncryptionFunction, you can ensure that only functions of the right subclass will be passed implicitly. (This isn't true when declaring Type EncryptionFunction = String => String.)

I hope that was clear.

Ken Bloom