I have a case where I wish to apply modifications to an object based on the presence of (a few, say, 5 to 10) optionals. So basically, if I were to do it imperatively, what I'm aiming for is :
var myObject = ...
if (option.isDefined) {
myObject = myObject.modify(option.get)
}
if (option2.isDefined) {
myObject = myObject.someOtherModification(option2.get)
}
(Please note : maybe my object is mutable, maybe not, that is not the point here.)
I thought it'd look nicer if I tried to implement a fluent way of writing this, such as (pseudo code...) :
myObject.optionally(option, _.modify(_))
.optionally(option2, _.someOtherModification(_))
So I started with a sample code, which intelliJ does not highlight as an error, but that actually does not build.
class MyObject(content: String) {
/** Apply a transformation if the optional is present */
def optionally[A](optional: Option[A], operation: (A, MyObject) => MyObject): MyObject =
optional.map(operation(_, this)).getOrElse(this)
/** Some possible transformation */
def resized(length : Int): MyObject = new MyObject(content.substring(0, length))
}
object Test {
val my = new MyObject("test")
val option = Option(2)
my.optionally(option, (size, value) => value.resized(size))
}
Now, in my case, the MyObject
type is of some external API, so I created an implicit conversion to help, so what it really does look like :
// Out of my control
class MyObject(content: String) {
def resized(length : Int): MyObject = new MyObject(content.substring(0, length))
}
// What I did : create a rich type over MyObject
class MyRichObject(myObject: MyObject) {
def optionally[A](optional: Option[A], operation: (A, MyObject) => MyObject): MyObject = optional.map(operation(_, myObject)).getOrElse(myObject)
}
// And an implicit conversion
object MyRichObject {
implicit def apply(myObject: MyObject): MyRichObject = new MyRichObject(myObject)
}
And then, I use it this way :
object Test {
val my = new MyObject("test")
val option = Option(2)
import MyRichObject._
my.optionally(option, (size, value) => value.resized(size))
}
And this time, it fails in IntelliJ and while compiling because the type of the Option
is unknown :
Error:(8, 26) missing parameter type
my.optionally(option, (size, value) => value.resized(size))
To make it work, I can :
- Actively specify a type of the
size
argument :my.optionally(option, (size: Int, value) => value.resized(size))
- Rewrite the
optionally
to a curried-version
None of them is really bad, but if I may ask :
- Is there a reason that a curried version works, but a multi argument version seems to fail to infer the parametrized type,
- Could it be written in a way that works without specifying the actual types
- and as a bonus (although this might be opinion based), how would you write it (some sort of
foldLeft
on a sequence of optionals come to my mind...) ?