3

I'm in the process of trying to build a for comprehension in Scala, but am running into some issues when I try to use a more complex filter.

I'm aware of basic for comprehension filtering:

for (x <- 1 until 20 if x>3) yield {
  x
}

However, I want to build a for comprehension which has a much more complex filtering statement. For example, here's an illustrative version of what I'm trying to do:

for (
    element <- elementList
    val otherElement = databaseCall.getMatching(element.id)
    if element.name==otherElement.name
) yield {
  element
}

Basically, if you want to do a filter with some more complex requirements, this gets awkward, because the for comprehensions don't allow declaring vals in the filter statement, and you'd otherwise have to fit that all one one line.

An alternative would be to not use the filter mechanism at all, and just yield either Some(element) or None, and end up with an Option[elementType] list instead. However, I don't want to have Optional types in this case.

Imperatively, I'd simply create a mutable list, and only append to the list when my condition is met, but I want to see how to do this in more of a declarative manner (not all of the way declarative yet, but I'm still learning!).

Any suggestions of good declarative ways to do this would be hugely helpful.

Shookit
  • 1,162
  • 2
  • 13
  • 29
  • I don't understand what you mean by "for comprehensions don't allow declaring vals in the filter statement". My tests in the REPL show that it does, though the `val` is now unnecessary and deprecated. – wingedsubmariner May 07 '14 at 20:14
  • You can actually assign variable inside for comprehension, look at [this](http://stackoverflow.com/questions/7413185/println-in-scala-for-comprehension) – Ende Neu May 07 '14 at 20:15

2 Answers2

4

Is using the filter method of List not the simplest solution in this case?:

elementList.filter(el => databaseCall.getMatching(el.id).name == el.name)
  • Ugh, sorry to have bugged you guys with this, hopefully it will be helpful to someone. This is what I was looking for; I had it in my head that the for comp was just syntactic sugar around filter and map, so I never even considered going back to that. I'm still stuck in an imperative mindset, unfortunately. – Shookit May 07 '14 at 21:12
1

Looks like you're doing something which is a map with side effects. These side effects can lead to exceptions, which in turn can lead to a result list you'd probably like to avoid. So use a closure around what you're attempting to do in the scope of what you want to do:

 def outerScope(elementList: List[YourType]): List[YourType] =
   def dbAction(element: YourType): Option[YourType] = try{
     val other = databaseCall getMatching element.id //assuming can't return null
     if(other.name == element.name) Some(element) else None
   }
   catch{
     case ex: Exception => None //don't forget to log it
   }

   for{
     elem <- elementList
     value <- dbAction(elem)
   } yield value
 }

As you've figured out, making the for comprehension contain and do everything sometimes leads to rather verbose or confusing statement. For comprehensions should be readable and intuitive in use. Separate out the conditions so that you can compose over the results you desire, even if it means all the logic isn't going to be in the comprehension.

Side note: If the DB query can return a null then a better method is to just wrap the call in an Option.

wheaties
  • 35,646
  • 15
  • 94
  • 131