Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to write a polymorphic input function in Scala?

I'm trying to write some convenience functions in Scala for reading in arrays of values.

I started with a function that converts a string like "1 1 2 3 5 8" to an Array[Int]:

def readInts(in: String) = in.split(" ").map(_.toInt)

This works fine, except that if I want to read not just Ints but Longs or BigInts or Doubles, I need to define a function for each one, which seems wasteful (especially if I generalize to reading in matrices or other compound data)

I'd like to be able to write a single polymorphic function as follows:

def readArray[A](in: String) = in.split(" ").map(_.to[A])

As far as I understand, this is impossible because the String class doesn't have a polymorphic 'to' method. Alright; I'll try to define it as a helper method instead:

def to[A](in: String) = ???

It seems like I need to define the method conditionally on the type parameter - if A is Int, then call in.toInt; if A is Double, call in.toDouble; if A is Tuple2[Int,Int], call a helper method toTupleOfInts(in). As far as I know, this is also impossible.

In the other functional language I know, Haskell, this problem is handled by the 'Read' typeclass, which defines a polymorphic function 'read' that converts from a String to the desired data type.

What is an idiomatic way to do this (i.e. write polymorphic input functions) in Scala?

like image 921
Ivan Vendrov Avatar asked Jan 16 '23 10:01

Ivan Vendrov


1 Answers

You can do something very close to Haskell typeclasses. However, it cannot be derived automatically (at least yet, maybe macro will allow that in some future version)

First, define a trait, equivalent to the typeclass.

trait Read[A] {
  def read(in: String): A
}

Then make some instance implicitly available, preferably in the companion object

object Read {

  implicit object ReadInt extends Read[Int] {
    def read(in: String): Int = in.toInt
  }

  implicit object ReadDouble ....

  implicit def readArray[A](implicit readItem: Read[A]) : Read[Array[A]]
    = new Read[Array[A]] {
      def read(in: String) = in.split(" ").map(readItem.read _)
    }

   implicit def readTuple[A,B](implicit readA: Read[A], readB: Read[B]) ...

}

Finally, define a method that makes the Read easily accessible

def read[A](in: String[A])(implicit reader: Read[A]) = reader.read(in)

You may call read on any type for which there is a Read instance in implicit scope.

like image 53
Didier Dupont Avatar answered Jan 18 '23 23:01

Didier Dupont