【问题标题】:Left to right arguments type inference从左到右的参数类型推断
【发布时间】:2017-10-17 12:30:29
【问题描述】:

我有一个案例,我希望根据(例如,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 版本有效,但多参数版本似乎无法推断参数化类型,
  • 是否可以在不指定实际类型的情况下以有效的方式编写
  • 作为奖励(尽管这可能是基于意见的),您将如何编写它(我想到了关于一系列选项的某种 foldLeft...)?

【问题讨论】:

    标签: scala implicit-conversion type-inference


    【解决方案1】:

    供您考虑的一个选项:

    // 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 的咖喱版本。

    【讨论】:

    • 我喜欢将隐式反转为 Optional 类型而不是 MyObject 的想法(概括:使参数化类型可转换,从而消除另一端的任何参数化方法)。我同意你的结论:咖喱版看起来至少一样好。让我觉得可以尝试使用见证类型 (stackoverflow.com/questions/8524878/…)
    • 另见 pchiusano.blogspot.hk/2011/05/… 关于您观察到的类型推断的限制
    • 感谢您的挖掘。我不明白的是,在第一个示例实现中(没有隐式),这实际上是有效的......
    • 但是你的第一个例子给了我同样的“缺少参数类型......”错误。
    • OK... intelliJ 并没有向我突出显示它,但是尝试实际构建 JAR 会导致两个示例都出现错误。这使得 80% 的问题变得毫无意义,因为我试图理解为什么编译器没有对这两个示例进行相同的处理,而实际上它确实如此。我将更新问题以供将来参考。谢谢。
    【解决方案2】:

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

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

    如您所见,现在所有泛型所在的位置都被 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?

    【讨论】:

    • 感谢第一个建议。回想起来似乎很明显,并且更好。你的第二个提议也很好,但是有限制,而且我实际上不能修改 MyObject。您的其余答案似乎并不适用,因为它没有解释为什么当隐含开始起作用时推理停止工作......
    • @GPI 在这种情况下规则 4) 适用:4) 当推断的返回类型比您预期的更通用时,例如 Any。我的意思是你在哪里返回一个通用类型,当你想要一个特定的类型时,你不同意吗?
    • @GPI 无论如何我删除了无关紧要的部分以澄清答案,我一直很乐意提供帮助。
    • 谢谢。我没有看到返回类型是这里的罪魁祸首。有问题的是可选参数的类型,而不是任何结果类型,实际上,指定参数的类型对于编译器来说就足够了。而且我也看不出它太笼统了,我的返回类型在这里被指定为 MyObject,它是尽可能具体的。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2017-10-15
    • 1970-01-01
    • 2020-03-11
    • 1970-01-01
    • 2021-12-22
    • 1970-01-01
    相关资源
    最近更新 更多