mohit
mohit

Reputation: 4999

Apply a list of parameters to a list of functions

I have a list of parameters like List(1,2,3,"abc","c") and a set of functions which validates the data present in the list like isNumberEven, isAValidString etc.

Currently, I take each value of the list and apply proper function which validates the data like isNumberEven(params(0)). This has led to big and messy code which is completely imperative in thinking.

I am expecting that it should be possible to do something like this in Scala -

List(1,2,3,"abc","c").zip(List(fuctions)).foreach{ x => x._2(x._1)}

However, this fails giving a runtime exception of type mismatch:

error: type mismatch; found : x._1.type (with underlying type Any) required: Int with String

I tried pattern matching on Function traits but it fails due to type erasure.

Any pointers will be appreciated as how can this be solved.

Upvotes: 6

Views: 4607

Answers (3)

Kigyo
Kigyo

Reputation: 5768

I just want to present a different approach without matching, although it is certainly sledgehammer-like.

First all functions are converted to functions of type Any => Boolean.

It iterates over the values in c. For each element it tries to find a function that is applicable and that results in true. If it doesn't find one, false is yielded.

def isEven(i: Int) = i % 2 == 0
def isGreaterThanTwo(i: Int) = i > 2
def hasB(s: String) = s.exists(_ == 'b')

def convert[T](func: T => Boolean) = (a: Any) => func(a.asInstanceOf[T])

val functions = List(isEven _, isGreaterThanTwo _, hasB _)
val c = List(1,2,3,"abc","c")

val result = {
  val convertedFunctions = functions.map(convert)

  c.map(elem => convertedFunctions.exists(func => Try(func(elem)) getOrElse false))
} 

with the result List(false, true, true, true, false).

The upside is that you can have as many functions as you like and it is therefore extensible. The downside is that you rely on exceptions. (which is usually not a good practice)

I first tried a solution with converting to PartialFunction and modifying the isDefined methods so it can be called on Any but then checks for a certain type. Then a lot of type-erasure happened and I couldn't make it work. Maybe that could be worth a shot.

If that is possible the code could be changed to:

def convert[T](func: T => Boolean) = new PartialFunction[Any, Boolean] {
  def isDefinedAt(x : Any) = ??? //check if x is an instance of T, type erasure problem
  def apply(x : Any) = func(x.asInstanceOf[T])
}

val result = {
  val convertedFunctions = functions.map(convert)

  c.map(elem => convertedFunctions.exists(func => 
  func.isDefinedAt(elem) && func(elem)))
}

which looks pretty nice.

Upvotes: 2

Ende Neu
Ende Neu

Reputation: 15783

Very naive and non extensible implementation, I'm not very good with types, surely there's a better way:

val c = List(1,2,3,"abc","c")

def isEven(x: Int) = if(x % 2 == 0) true else false

def isUpperCase(x: String) = if(x.head.isUpper) true else false

c.map {
  case x: Int => isEven(x)
  case x: String => isUpperCase(x)
  case _ => false
}

You could also define list of functions:

scala>   val c = List(1,2,3,"abc","c")
c: List[Any] = List(1, 2, 3, abc, c)

scala>   def isEven(x: Int) = if(x % 2 == 0) true else false
isEven: (x: Int)Boolean

scala>   def isOdd(x: Int) = !isEven(x)
isOdd: (x: Int)Boolean

scala>   def isUpperCase(x: String) = if(x.head.isUpper) true else false
isUpperCase: (x: String)Boolean

scala>   def someString(x: String) = true
someString: (x: String)Boolean

scala>   val ints = List(isEven(_), isOdd(_))
ints: List[Int => Boolean] = List(<function1>, <function1>)

scala>   val strings = List(isUpperCase(_), someString(_))
strings: List[String => Boolean] = List(<function1>, <function1>)

scala>   c.map {
     |     case x: Int => ints.map(f => f(x)).exists(f => f(x))
     |     case x: String => strings.map(f => f(x)).forall(f => f(x))
     |     case _ => false
     |   }
res2: List[Boolean] = List(true, true, true, false, false)

Upvotes: 3

fresskoma
fresskoma

Reputation: 25791

I'm not entirely certain on how you're planning on using the data afterwards, because 'foreach' would not actually return anything. But maybe this pattern-matched solution can help you achieve what you want?

scala> val f1 = (x:Int) => false
f1: Int => Boolean = <function1>

scala> val f2 = (x:String) => true
f2: String => Boolean = <function1>

scala> List(1,2,3,"abc","c").map {
    case x:String => f2(x)
    case x:Int => f1(x)
}
res3: List[Boolean] = List(false, false, false, true, true)

Upvotes: 1

Related Questions