Suugaku
Suugaku

Reputation: 2747

Scala generics: specializing a method

In Haskell I can define a generic type for a tree as:

type Tree t = Leaf t | Node (Tree t) (Tree t)

If I want to define a function for a specific parameterization of Tree, I can simply do:

-- Signature of a function that takes a tree of bool
foo :: Tree Bool -> Int

-- Takes a tree of numbers
bar :: (Num n) => Tree n -> Bool

We can define a similar tree type in Scala with:

abstract class Tree[T]()
case class Leaf[T](t: T) extends Tree[T]
case class Node[T](left: Tree[T], right: Tree[T]) extends Tree[T]

But how can I define a method for Tree that only applies to certain types? Do I need to use inheritance or there's a way to say:

abstract class Tree[T]() {
  // Method only for Tree[String]:
  def foo[String] = ...
}

Upvotes: 1

Views: 190

Answers (3)

muhuk
muhuk

Reputation: 16085

In Haskell types don't have instance methods like Scala does.

foo in your example should be defined (preferably) in Tree's companion object.

sealed abstract class Tree[T]()
case class Leaf[T](t: T) extends Tree[T]
case class Node[T](left: Tree[T], right: Tree[T]) extends Tree[T]

object Tree {
  // Method only for Tree[String]:
  def foo(tree: Tree[String]) = ...
}

PS: IMO a sealed class or trait is more appropriate here. (Scala's sealed abstract vs abstract class)

PS II: I am just typing Gregor Raýman's comment as an answer.

Upvotes: 2

Alexey Romanov
Alexey Romanov

Reputation: 170713

The obvious way (and equivalent to Haskell) is to define a method which takes Tree[String] as an argument, as in muhuk's answer. If you want it to look like a method on Tree[String], you can use implicit class:

implicit class Foo(val tree: Tree[String]) {
  def foo = ...
}

val tree: Tree[String] = ...

tree.foo // Foo needs to be in scope here

I recommend avoiding Akos Krivachy's answer in most circumstances.

Upvotes: 0

Akos Krivachy
Akos Krivachy

Reputation: 4966

This may not be the answer you are looking for, as I haven't done much Haskell, but it's a possibility: You can define a trait that can only be mixed into specific instances of a tree:

trait StringFooFunctionality {
   this: Tree[String] => // Selftype, can only be mixed in to classes that are Tree[String]
   def foo = "Yay" // String is the datatype of Tree here
}

You would use this like this:

val sNode = new Node(Leaf("a"), Leaf("b")) with StringFooFunctionality
sNode.foo
// Yay

The downside is that it explicitly needs to be mixed in on object creation.

Other possibility is to create a new trait called StringTree:

trait StringTree extends Tree[String] {
   def foo = ...
}

But you would have to define the other String datatypes:

case class StringLeaf(t: String) extends StringTree
case class StringNode(left: StringTree, right: StringTree) extends StringTree

And when you encounter a Tree[T] you can pattern match on it to see if it's StringTree.

Upvotes: 1

Related Questions