Reputation: 691
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
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?
A
extends B
then a function returning A
extends a function returning B
(that's why compiler doesn't complain about your first parameter)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