从左到右的参数类型推断

时间:2017-10-17 12:30:30

标签: scala implicit-conversion type-inference

我有一个案例,我希望根据(少数,比如5到10)选项的存在对对象应用修改。所以基本上,如果我要强制执行,我的目标是:

var myObject = ...
if (option.isDefined) {
    myObject = myObject.modify(option.get)   
}
if (option2.isDefined) {
    myObject = myObject.someOtherModification(option2.get)
}

(请注意:也许我的目标是可变的,也许不是,这不是重点。)

我认为如果我尝试用这种方式编写流利的方式会更好看,例如(伪代码......):

myObject.optionally(option, _.modify(_))
        .optionally(option2, _.someOtherModification(_))

所以我开始使用示例代码,其中intelliJ没有突出显示为错误,但实际上并没有构建。

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))
}

现在,在我的情况下,MyObject类型是一些外部API,所以我创建了一个隐式转换来帮助,所以它实际上是这样的:

// 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)
} 

然后,我这样使用它:

object Test {
  val my = new MyObject("test")
  val option = Option(2)
  import MyRichObject._
  my.optionally(option, (size, value) => value.resized(size))
}

这一次,它在IntelliJ和编译时失败,因为Option的类型未知: Error:(8, 26) missing parameter type my.optionally(option, (size, value) => value.resized(size))

为了使它成功,我可以:

  1. 主动指定size参数的类型:my.optionally(option, (size: Int, value) => value.resized(size))
  2. optionally重写为curried-version
  3. 它们都不是真的很糟糕,但如果我可以问:

    • 是否存在curried版本有效的原因,但多参数版本似乎无法推断参数化类型,
    • 是否可以在不指定实际类型的情况下编写
    • 并且作为奖励(虽然这可能是基于意见的),你会怎么写呢(在我想到的一系列选项中会出现某种foldLeft ...)?

2 个答案:

答案 0 :(得分:2)

供您考虑的一个选项:

// Out of my control
class MyObject(content: String) {
  def resized(length : Int): MyObject = new MyObject(content.substring(0, length))
}

object MyObjectImplicits {

  implicit class OptionalUpdate[A](val optional: Option[A]) extends AnyVal {
    def update(operation: (A, MyObject) => MyObject): MyObject => MyObject =
      (obj: MyObject) => optional.map(a => operation(a, obj)).getOrElse(obj)
  }

}
object Test {
  val my = new MyObject("test")
  val option = Option(2)
  import MyObjectImplicits._
  Seq(
    option.update((size, value) => value.resized(size)),
    // more options...
  ).foldLeft(my)(_)
}

也可以像你说的那样使用optionally的咖喱版。

答案 1 :(得分:1)

考虑需要添加类型的更好方法是这样写:

object Test {
  val my = new MyObject("test")
  val option = Some(2)
  my.optionally[Int](option, (size, value) => value.resized(size))
}

另一种方式,如果你自创建对象后只管理一种类型,就是将泛型移动到类创建,但要小心,使用此选项,每个实例只能有一种类型:

class MyObject[A](content: String) {
  def optionally(optional: Option[A], operation: (A, MyObject[A]) => MyObject[A]): MyObject[A] =
    optional.map(operation(_, this)).getOrElse(this)
  def resized(length : Int): MyObject[A] = new MyObject[A](content.substring(0, length))
}

object Test {
  val my = new MyObject[Int]("test")
  val option = Some(2)
  my.optionally(option, (size, value) => value.resized(size))
}

正如你所看到的,现在所有generics所在的地方都是由Int类型占用的,因为这首先是你想要的,这里有一个很好的答案说明原因:

(我认为这里适用的部分是:)

  

4)当推断的返回类型比你想要的更通用时,例如Any。

来源:In Scala, why does a type annotation must follow for the function parameters ? Why does the compiler not infer the function parameter types?