TeeKai
TeeKai

Reputation: 691

Function as a parameter

I have a few functions with a same coding pattern:

def updateFooValidationStatus(fooId: Long, user: String, valid: Boolean): Option[Foo] = {

  val fooMaybe = fooDao.getFooById(activityId)
  fooMaybe match {
    case Some(foo) => {
      fooDao.update(foo.copy(updatedBy = Some(user),
        validationStatus = if (valid) Some(DataEntryValidationStatus.Valid) else Some(DataEntryValidationStatus.Invalid))
    )
   }
   case None =>
    throw new DomainException(s"Foo with ID: '$fooId' doesn't exist")
  }
}

To make my code less repeated, I write a new private function as

private def updateDomainObjectValidationStatus(f1: Long => Option[DomainObject],
               parameter1: Long,
               f2: DomainObject => Option[DomainObject],
               paramter2: String,
               valid: Boolean ): Option[DomainObject] ={

  val somethingMaybe = f1(parameter1)
  somethingMaybe match {
    case Some(something) =>
      f2(
        something.copyMe(updatedBy = Some(paramter2),
        validationStatus = if(valid) Some(DataEntryValidationStatus.Valid) else Some(DataEntryValidationStatus.Invalid))
      )
    case None =>
      throw new DomainException(s"Object with ID: '$parameter1' doesn't exist")
  }
}

where

trait DomainObject { ... }

case class Foo( ... ) extends DomainObject { ... }

With the above changes, I, however, can't call updateDomainObjectValidationStatus inside of updateFooValidationStatus due to an error on one parameter

type mismatch, expected (DomainObject) => Option[DomainObject], actual (Foo) => Option[Foo]

Interestingly, it doesn't complain the first parameter

(Long) => Option[DomainObject]

which takes

(Long) => Option[Foo]

What will be a code design in the Scala idiomatically fashion to make the above code works?

Upvotes: 0

Views: 39

Answers (1)

Tzach Zohar
Tzach Zohar

Reputation: 37852

What happens here is: updateDomainObjectValidationStatus can't accept a value with type (Foo) => Option[Foo] as an argument of type (DomainObject) => Option[DomainObject], because functions are contravariant in their argument type(s) and covariant in their return type. What does that mean?

  • If A extends B then a function returning A extends a function returning B (that's why compiler doesn't complain about your first parameter)
  • However, if A extends B, then a function with argument of type B extends a function with argument of type A - meaning, the other way around!

Why does that make sense?

Think about it: if A extends B, we can say "A is a B". Now, "a function on A" can work on any A, but not necessarily on any B, right? If your function has an Int argument, you can't pass a value with AnyVal type... The opposite does work - a function on AnyVal can definitely be called with an Int.

To fix this - in this case, looks like the best approach would be to give updateDomainObjectValidationStatus a type that extends DomainObject:

private def updateDomainObjectValidationStatus[T <: DomainObject](
   f1: Long => Option[T],
   parameter1: Long,
   f2: T => Option[T],
   paramter2: String,
   valid: Boolean): Option[T] = { ... } 

When you call it with a first parameter of type (Long) => Option[Foo], the type T is inferred to be Foo, and then compiler expects (Foo) => Option[Foo] for the third, as expected. You even get a nice bonus - the return type would then be more specific: Option[Foo] instead of Option[DomainObject].

Upvotes: 4

Related Questions