Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is there a way to know through inheritance (or other way) when a class defines the .map function in Scala?

Tags:

scala

My problem is phrased in the code below. I'm trying to get some input that has the .map function in it. I know that if I call .map to it, it will return an Int to me.

  // In my case, they are different representations of Ints
  // By that I mean that in the end it all boils down to Int
  val list: Seq[Int] = Seq(1,2,3,4)
  val optInt: Option[Int] = Some(1)
  // I can use a .map with a Seq, check!
  list.map {
    value => println(value)
  }
  // I can use it with an Option, check!
  optInt.map {
    value => println(value)
  }
  // Well, you're asking yourself why do I have to do it,
  // Why don't I use foreach to solve my problem. Check!
  list.foreach(println)
  optInt.foreach(println)

  // The problem is that I don't know what I'm going to get as input
  // The only thing I know is that it's "mappable" (it has the .map function)
  // And that if I were to apply .map it would return Ints to me
  // Like this:
  def printValues(genericInputThatHasMap: ???) {
    genericInputThatHasMap.map {
      value => println(value)
    }
  }

  // The point is, what do I have to do to have this functionality?
  // I'm researching right now, but I still haven't found anything.
  // That's why I'm asking it here =(

  // this works:
  def printValues(genericInputThatHasMap: Seq[Int]) {
    genericInputThatHasMap.map {
      value => println(value)
    }
  }

Thanks in advance! Cheers!

like image 443
wleao Avatar asked Dec 15 '12 15:12

wleao


2 Answers

First for a quick note about map and foreach. If you're only interested in performing an operation with a side effect (e.g., printing to standard output or a file, etc.) on each item in your collection, use foreach. If you're interested in creating a new collection by transforming each element in your old one, use map. When you write xs.map(println), you will in fact print all the elements of the collection, but you'll also get back a (completely useless) collection of units, and will also potentially confuse future readers of your code—including yourself—who expect foreach to be used in a situation like this.

Now on to your problem. You've run into what is in my opinion one of the ugliest warts of the Scala standard library—the fact that methods named map and foreach (and flatMap) get magical treatment at the language level that has nothing to do with a specific type that defines them. For example, I can write this:

case class Foo(n: Int) {
  def foreach(f: Int => Unit) {
    (0 until n) foreach f
  }
}

And use it in a for loop like this, simply because I've named my method foreach:

for (i <- Foo(10)) println(i)

You can use structural types to do something similar in your own code:

def printValues(xs: { def foreach(f: (Int) => Unit): Unit }) {
  xs foreach println
}

Here any xs with an appropriately typed foreach method—for example an Option[Int] or a List[Int]—will compile and work as expected.

Structural types get a lot messier when you're trying to work with map or flatMap though, and are unsatisfying in other ways—they impose some ugly overhead due to their use of runtime reflection, for example. They actually have to be explicitly enabled in Scala 2.10 to avoid warnings for these reasons.

As senia's answer points out, the Scalaz library provides a much more coherent approach to the problem through the use of type classes like Monad. You wouldn't want to use Monad, though, in a case like this: it's a much more powerful abstraction than you need. You'd use Each to provide foreach, and Functor for map. For example, in Scalaz 7:

import scalaz._, Scalaz._

def printValues[F[_]: Each](xs: F[Int]) = xs foreach println

Or:

def incremented[F[_]: Functor](xs: F[Int]) = xs map (_ + 1)

To summarize, you can do what you want in a standard, idiomatic, but arguably ugly way with structural types, or you can use Scalaz to get a cleaner solution, but at the cost of a new dependency.

like image 68
Travis Brown Avatar answered Nov 15 '22 07:11

Travis Brown


My thoughts on the two approaches.

Structural Types

You can use a structural type for foreach, but for map it doesn't appear you can construct one to work across multiple types. For example:

import collection.generic.CanBuildFrom

object StructuralMap extends App {
  type HasMapAndForeach[A] = {
//    def map[B, That](f: (A) ⇒ B)(implicit bf: CanBuildFrom[List[A], B, That]): That
    def foreach[B](f: (A) ⇒ B): Unit
  }

  def printValues(xs: HasMapAndForeach[Any]) {
    xs.foreach(println _)
  }

//  def mapValues(xs: HasMapAndForeach[Any]) {
//    xs.map(_.toString).foreach(println _)
//  }

  def forComp1(xs: HasMapAndForeach[Any]) {
    for (i <- Seq(1,2,3)) println(i)
  }

  printValues(List(1,2,3))

  printValues(Some(1))

  printValues(Seq(1,2,3))

//  mapValues(List(1,2,3))
}

scala> StructuralMap.main(new Array[String](0))
1
2
3
4
5
6
7
8
9
10

See the map method commented out above, it has List hardcoded as a type parameter in the CanBuildFrom implicit. There might be a way to pick up the type generically - I will leave that as a question to the Scala type gurus out there. I tried substituting HasMapAndForeach and this.type for List but neither of those worked.

The usual performance caveats about structural types apply.

Scalaz

Since structural types is a dead end if you want to support map then let's look at the scalaz approach from Travis and see how it works. Here are his methods:

def printValues[F[_]: Each](xs: F[Int]) = xs foreach println

def incremented[F[_]: Functor](xs: F[Int]) = xs map (_ + 1)

(In the below correct me if I am wrong, I am using this as a scalaz learning experience)

The typeclasses Each and Functor are used to restrict the types of F to ones where implicits are available for Each[F] or Functor[F], respectively. For example, in the call

printValues(List(1,2,3))

the compiler will look for an implicit that satisfies Each[List]. The Each trait is

trait Each[-E[_]] {
  def each[A](e: E[A], f: A => Unit): Unit
}

In the Each object there is an implicit for Each[TraversableOnce] (List is a subtype of TraversableOnce and the trait is contravariant):

object Each {
  implicit def TraversableOnceEach[A]: Each[TraversableOnce] = new Each[TraversableOnce] {
    def each[A](e: TraversableOnce[A], f: A => Unit) = e foreach  f
  }
}

Note that the "context bound" syntax

def printValues[F[_]: Each](xs: F[Int])

is shorthand for

def printValues(xs: F[Int])(implicit ev: Each[F])

Both of these denote that F is a member of the Each typeclass. The implicit that satisfies the typeclass is passed as the ev parameter to the printValues method.

Inside the printValues or incremented methods the compiler doesn't know that xs has a map or foreach method because the type parameter F doesn't have any upper or lower bounds. As far as it can tell F is AnyRef and satisfies the context bound (is part of the typeclass). What is in scope that does have foreach or map? MA from scalaz has both foreach and map methods:

trait MA[M[_], A] {
  def foreach(f: A => Unit)(implicit e: Each[M]): Unit = e.each(value, f)

  def map[B](f: A => B)(implicit t: Functor[M]): M[B] = t.fmap(value, f)
}

Note that the foreach and map methods on MA are constrained by the Each or Functor typeclass. These are the same constraints from the original methods so the constraints are satisfied and an implicit conversion to MA[F, Int] takes place via the maImplicit method:

trait MAsLow extends MABLow {
  implicit def maImplicit[M[_], A](a: M[A]): MA[M, A] = new MA[M, A] {
    val value = a
  }
}

The type F in the original method becomes type M in MA.

The implicit parameter that was passed into the original call is then passed as the implicit parameter into foreach or map. In the case of foreach, each is called on its implicit parameter e. In the example from above the implicit ev was type Each[TraversableOnce] because the original parameter was a List, so e is the same type. foreach calls each on e which delegates to foreach on TraversableOnce.

So the order of calls for printValues(List(1,2,3)) is:

new Each[TraversableOnce] -> printValues -> new MA -> MA.foreach -> Each.each -> TraversableOnce.foreach

As they say, there is no problem that can't be solved with an extra level of indirection :)

like image 20
sourcedelica Avatar answered Nov 15 '22 07:11

sourcedelica