Newbie question about traits and companion objects

570 views
Skip to first unread message

Norbert Zeh

unread,
Apr 23, 2014, 5:04:34 PM4/23/14
to scala...@googlegroups.com
Hi folks,

Just started to learn Scala and like the power it promises, but I guess I tried to get too fancy right away, so I need some help on how to achieve what I'm trying to do.  Here goes:

I'd like to define a trait Readable[T] that means that type T is convertible from a string.  I would like to use such a type in a parser for trees where, in the input, the leaves are labelled with strings and in my internal representation I want to convert this to type T.  So, I would like my leaf parser to look something like this:

def leaf[T :< Readable[T]]: Parser[Node[T]] = "[^,;()]".r ^^ { l => Leaf(T(l)) }

Leaf is a constructor of the Leaf class that takes a T as an argument; Leaf[T] is a subclass of Node[T].

Now, in order to to be able to write the above, class T has to have a companion object that implements a method "apply(s: String): T".  So my thinking was that my trait Readable[T] needs to express that a class that implements trait Readable[T] has to have a companion object with the above apply method.  Either this isn't possible or I'm at least not able to figure out how to do it.

In the end, I don't care at all how the whole structure is set up.  What I want to express is that my parser can turn a valid input string into a Tree[T] as long as T is a type that can be constructed from a String.

Any suggestions?

Thanks,
Norbert

Roman Janusz

unread,
Apr 23, 2014, 7:19:29 PM4/23/14
to scala...@googlegroups.com
That's probably impossible. You can't get to the companion object having only generic type. Instead, typeclass would be awesome here, but it may be a little too much for you if you're just starting to learn Scala. Anyway, here's how it would look like:

trait Reader[T] {
  def read(s: String): T
}

// this syntax is the so-called context bound, it is actually just a syntactic sugar for taking and implicit parameter of type Reader[T]
def leaf[T: Reader]: Parser[Node[T]] = "[^,;()]".r ^^ { l => Leaf(implicitly[Reader[T]].read(l)) }

This is the generic stuff. Now, when you're defining some concrete T:

class SomeReadableClass {
  ...
}

object SomeReadableClass {
  // we put an implicit value of type Reader[SomeReadableClass] in the companion object of SomeReadableClass so that it is globally visible
  // see for example http://stackoverflow.com/questions/5598085/where-does-scala-look-for-implicits
  implicit object someReadableClassReader extends Reader[SomeReadableClass} {
    def read(s: String): SomeReadableClass = ...
  }
}

Norbert Zeh

unread,
Apr 23, 2014, 8:11:16 PM4/23/14
to scala...@googlegroups.com
Perfect, Roman.  Thanks a lot.  I'll need some time to digest this, but I'm sure I'll get it in the end ;)

Cheers,
Norbert


--
You received this message because you are subscribed to the Google Groups "scala-user" group.
To unsubscribe from this group and stop receiving emails from it, send an email to scala-user+...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Norbert Zeh

unread,
Apr 23, 2014, 8:35:26 PM4/23/14
to scala...@googlegroups.com
Cool.  Got it.  I think my main limiter here was that I had only read the online version (1st ed) of Programming in Scala, and that one does not talk about context bounds.  Anyway, thanks again for the quick and very helpful answer.  I really like Scala so far even though some parts of it mystify me still.

Cheers,
Norbert

Alexandru Nedelcu

unread,
Apr 24, 2014, 4:06:15 AM4/24/14
to Norbert Zeh, scala-user

Type-classes are perfect for this use-case. Examples from Scala’s standard library are Ordering[T], Numeric[T] and Integral[T].

This is why for example you can do List(7,3,1,2).sum or List(7,3,1,2).sorted, even though in the definition of List[T] there is no restriction on what T is. SortedSet is another example that uses the Ordering[T] type-class. In order to instantiate a SortedSet with your own types in it, you have to define an implicit Ordering for them - which is much like implementing an interface, without the restriction that your type must inherit from it directly. Numeric[T] also has a zero property that implementations must define, which is clearly not a property of individual instances of Long or whatever numeric type you want, but a property of the whole class of numbers you’re working with. This is why in Scala you can define a generic sum function for sequences of any kind, which is not possible in Java btw, because in Java “+” on primitives doesn’t implement any generic interface that you could use:

def sum[T : Numeric](seq: Seq[T]): T = {
  val num = implicitly[Numeric[T]]
  seq.foldLeft (num.zero) { case (acc, elem) => num.plus(acc, elem) }
}

Type-classes in Scala are used by means of implicit parameters. When you’re using “context bounds” such as:

def leaf[T: Reader]: Parser[Node[T]]

That’s actually syntactic sugar for something like:

def leaf[T](implicit ev: Reader[T]): Parser[Node[T]]

Calling that function for a given T means that there has to be an implicit Reader[T] defined in scope, as Roman explained. This means that you either have to define or import the definition of this implicit in the scope you want (e.g. where you’re calling the leaf function), or you can define it in the companion object of either Reader[T] or that of the T you want to work with, to be globally accessible without explicitly importing it in scope.

Speaking of Numeric[T] in the above example, Numeric[T] is actually too specific and the above could be useful for String concatenation or Set unions as well and it would have been great for Scala’s library to have a Monoid type-class defined, but thankfully we can easily implement our own:

trait Monoid[T] {
  def zero: T
  def append(x: T, y: T): T
}

object Monoid {
  // all Numeric[T] are monoids
  implicit def NumericMonoid[T : Numeric] = new Monoid[T] {
     val ev = implicitly[Numeric[T]]
     def zero = ev.zero
     def append(x: T, y: T) = ev.plus(x,y)
  } 

  // Strings are monoids too
  implicit object StringMonoid extends Monoid[String] {
    def zero = ""
    def append(x: String, y: String) = x + y
  }
}

def sum[T : Monoid](sequence: T*): T = {
  val num = implicitly[Monoid[T]]
  sequence.foldLeft (num.zero) { case (acc, elem) => num. append(acc, elem) }
}

scala> sum(1,2,3,4)
res1: Int = 10

scala> sum("foo", "bar")
res2: String = foobar

Ain’t that nice? :)

--
Alexandru Nedelcu
www.bionicspirit.com

PGP Public Key:
https://bionicspirit.com/key.aexpk
Reply all
Reply to author
Forward
0 new messages