1

I need to refactor my whole system and I would like suggestions on how to deal with a problem with types.

I have this situation: I'm using PMML (basically an XML with a data schema specification) and HBase (a columnar storage that keeps no data type schema). I need to match the two while manipulating the values in my system. So I need to have an abstraction to handle all the possible data types that could be part of my system. Right now I have Int, Double and String.

For every type I need to be able to define a parser (for HBase), a writer and a set of operations defined as high order functions ((T,T)=>T).

Right now I tried to do that with a trait MyValue[T] that was extended by MyValueInt extends MyValue[Int] or MyValueDouble extends MyValue[Double]. I could define a list of operations like List[(MyValue,MyValue)=>MyValue] but I had to match it everytime with every case and created a lot of other problems with the type system.

What I need is a good abstraction to model the fact that these values can be only Int, Double and Strings, to create lists of these values and to be able to treat these values with a generic logic until I need to extract the real value and apply it to an operation.

Chobeat
  • 3,445
  • 6
  • 41
  • 59
  • 1
    Have a look into [type classes](http://stackoverflow.com/questions/5408861/what-are-type-classes-in-scala-useful-for) – Régis Jean-Gilles Sep 29 '15 at 13:10
  • Can you be more specific about the trouble you ran into? From what you've stated so far, it's not obvious that you were actually on the wrong track. – Seth Tisue Sep 29 '15 at 13:28
  • The example is simplified because this types are spread in all the system with many logic over it. Anyway, for example, I define an UpdateAction[Int] that is a function from (MyValueInt,MyValueInt)=>MyValueInt and another UpdateAction[Double] that does the same for Double. If I put them in the same list and i try to map and match on the different MyValue* cases, it doesn't compile. If I need to match on stuff like (T,UpdateAction[T], MyValue[T]) it's even worst. – Chobeat Sep 29 '15 at 13:45
  • Sounds interesting. I'd suggest editing your question to be about that — with actual code and enough detail so that we can offer help. – Seth Tisue Sep 29 '15 at 14:21

1 Answers1

2

Type classes look like a solution for your problem.

// Define the behaviors you want your types to support
trait Parser[T] {
  def parse(s: String): T
}

trait Writer[T] {
  def write(value: T): Unit
}

trait Operations[T] {
  def add(a: T, b: T): T
  def multiply(a: T, b: T): T
}

// Implement the behaviors for each of the types you need
object IntStuff {
  implicit object IntParser extends Parser[Int] {
    override def parse(s: String): Int = Integer.valueOf(s)
  }
  implicit object IntWriter extends Writer[Int] {
    override def write(value: Int): Unit = println(value)
  }
  implicit object IntOperations extends Operations[Int] {
    override def add(a: Int, b: Int): Int = a + b
    override def multiply(a: Int, b: Int): Int = a * b
  }
}

// Define an algorithm on the type, put a context bound on it.
// This would make sure that the operations you need are supported for type `T`.

def writeSum[T: Parser : Writer : Operations](xs: String*): Unit = {
  // Bring the implementation from implicit scope
  val parser = implicitly[Parser[T]]

  // Use the implementation
  val values = xs.map(parser.parse)
  if (values.nonEmpty) {
    val writer = implicitly[Writer[T]]
    val operations = implicitly[Operations[T]]

    val sum = values.reduce(operations.add)
    writer.write(sum)
  }
}

// Bring the implementation to the implicit scope
import IntStuff._

// Enjoy :)    
writeSum[Int]("1", "2", "3")
Ihor Kaharlichenko
  • 5,944
  • 1
  • 26
  • 32
  • Ok, I've been trying this road in the last hour. It came out a bit different from your but this is definitely what I need. I still have a doubt but before opening another question I will ask you. In your solution, how can I create for example a list of these operations? Imagine I read an input of the form ("10","0.001","banana"). So I will have to parse the first as an Int, the second as a double and keep the third as a string. I know these types from a schema that I already have but how should express this schema? The same could be true for writing or doing operations. – Chobeat Sep 29 '15 at 15:03
  • The general case would be to have a list of high order functions and every function is bound to a different type, i.e. IntStuff, DoubleStuff, StringStuff. So for example I could zip my input (a list of strings) to a list of Parser and then apply all the parsers (or operations or writings) when I need them. – Chobeat Sep 29 '15 at 15:05
  • Hm. Looks like type classes might not be what you want. They are useful when you want to generalize some algorithm over a single type. See, `writeSum` is generalized over some `T` that has a `Parser`, a `Writer` and an `Operations` implemented for it. But the `T` stays the same over all the `writeSum` method. From what I understood you want different `T`s in your single method. Am I right? – Ihor Kaharlichenko Sep 29 '15 at 15:11
  • Yeah. Another, more meaningful, part of my use case is that I have a list of operations of the form List[(T,T)=>T]. These can become partially applied functions but it doesn't really change anything. This, anyway should be a single list. Right now I went for a tuple of lists, something like (List[UpdateAction[Int]], List[UpdateAction[Double]], List[UpdateAction[String]]). Clearly this is a pain to pass around and manipulate because in the end I just need to apply these functions one by one. – Chobeat Sep 29 '15 at 15:17
  • 1
    It would help if you provided an [mvce](http://stackoverflow.com/help/mcve) with the problem you would like to solve. Just update your question with some example code. Also, this might be a good candidate for code review rather then stackoverflow. – Ihor Kaharlichenko Sep 29 '15 at 15:19
  • @IhorKaharlichenko w/r/t CodeReview, the OP would have to supply their code in the question, else it will get closed **very** fast. – Kaz Sep 29 '15 at 15:27