k0pernikus
k0pernikus

Reputation: 66430

How to map sequence only if a condition applies with scala using an immutable approach?

Given a sequence of Price objects, I want to map it to applyPromo function if a condition, i.e. promo == "FOO" applies, otherwise return the sequence as is.

This is my applyPromo:

val pricePromo = price => price.copy(amount = price.amount - someDiscount)

In a mutable way I probably would write it like this:

var prices: Seq[Price] = Seq(price1, price2, ...)
  .map(doStuff)
  .map(doSomeOtherStuff)
if (promo == "FOO") {
  prices = prices.map(applyPromo)
}
prices

I was wondering if I could do something similar like this while keeping the immutable approach of scala. Instead of creating a temp var, I prefer to keep the chain.

Pseudo-code:

val prices = Seq(price1, price2, ...)

prices
  .map(dosStuff)
  .map(doOtherStuff)
  .mapIf(promo == "FOO", applyPromo)

I don't want to check the condition within the map function in this case, as it applies for all elements:

prices.map(price => { 
  if (promo == "FOO") {
    applyDiscount(price) 
  } else
    price
  } 
)

Upvotes: 0

Views: 1199

Answers (4)

prayagupadhyay
prayagupadhyay

Reputation: 31192

oxbow_lakes has an interesting answer

Easy way solve to me is wrapping Seq in a Option context.

scala> case class Price(amount: Double)
defined class Price

when condition matches,

scala> val promo = "FOO"
promo: String = FOO

scala> Some(Seq(Price(1), Price(2), Price(3))).collect{
            case prices if promo == "FOO" => prices.map { p => p.copy(p.amount - 1 )} 
            case prices => prices} 
res6: Option[Seq[Price]] = Some(List(Price(0.0), Price(1.0), Price(2.0)))

when condition does not match

scala> val promo = "NOT-FOO"
promo: String = NOT-FOO

scala> Some(Seq(Price(1), Price(2), Price(3))).collect{
            case prices if promo == "FOO" => prices.map { p => p.copy(p.amount - 1 )} 
            case prices => prices} 
res7: Option[Seq[Price]] = Some(List(Price(1.0), Price(2.0), Price(3.0)))

Upvotes: 1

insan-e
insan-e

Reputation: 3922

I'd do it like this:

val maybePromo: (Price => Price) = 
  if(promo == "FOO") applyPromo else identity _

prices.map(maybePromo)

Or you can inline it within map itself:

prices.map(if(promo == "FOO") applyPromo else identity)

Upvotes: 2

oxbow_lakes
oxbow_lakes

Reputation: 134260

In scalaz, a function A => A is called an endomorphism and is a Monoid whose associative binary operation is function composition and whose identity is the identity function. This is useful because there is a bunch of syntax available where monoids are concerned. For example, scalaz adds the ?? operation to boolean along these lines:

def ??[A: Monoid](a: A) = if (self) a else Monoid[A].zero 

Thus:

prices
  .map(doStuff)
  .map(doSomeOtherStuff)
  .map(((promo === "FOO") ?? deductDiscount).run)

Where:

val deductDiscount: Endo[Price] = Endo(px => px.copy(amount = px.amount - someDiscount))

The above all requires

import scalaz._
import Scalaz._

Notes

  1. === is typesafe equals syntax
  2. ?? is boolean syntax

Upvotes: 1

Gábor Bakos
Gábor Bakos

Reputation: 9100

You just need to use else to make it functional (and you can create an implicit class to add the mapIf method if you prefer):

val prices: Seq[Price] = Seq(price1, price2,...).map(doStuff).map(doSomeOtherStuff)
/* val resultPrices = */ if (promo == "FOO") {
  prices.map(price => {
    price.copy(amount = price.amount - someDiscount)
  })
} else prices

Something like this:

implicit class ConditionalMap[T](seq: Seq[T]) extends AnyVal {
  def mapIf[Q](cond: =>Boolean, f: T => Q): Seq[Q] = if (cond) seq.map(f) else seq
}

You can also map(x => x) in the else case:

val discountFunction = if (promo == "FOO") (price: Price) =>
    price.copy(amount = price.amount - someDiscount) else (x: Price) => x
val prices: Seq[Price] = Seq(price1, price2,...).
  map(doStuff).
  map(doSomeOtherStuff).
  map(discountFunction)

Upvotes: 2

Related Questions