【问题标题】:Trait, FunctionN, or trait-inheriting-FunctionN in Scala?Scala 中的特征、函数或特征继承函数?
【发布时间】:2010-06-18 10:13:45
【问题描述】:

我在 Scala 中有一个特性,它只有一个方法。称它为 Computable,唯一的方法是 compute(input: Int): Int。我不知道我是否应该这样做

  • 使用单一方法将其作为独立特征。
  • 从 (Int => Int) 继承并将“compute”重命名为“apply”。
  • 只需摆脱 Computable 并使用 (Int => Int)。

支持它成为特征的一个因素是我可以有用地添加一些额外的方法。但是当然,如​​果它们都是根据计算方法实现的,那么我可以将它们分解成一个单独的对象。

支持只使用函数类型的一个因素是简单,并且匿名函数的语法比匿名 Computable 实例的语法更简洁。但是我无法区分实际上是 Computable 实例的对象与将 Int 映射到 Int 但不打算在与 Computable 相同的上下文中使用的其他函数。

其他人如何处理这类问题?这里没有正确或错误的答案;我只是在寻求建议。

【问题讨论】:

    标签: scala


    【解决方案1】:

    如果您将其设为 Trait 并且仍希望能够使用轻量级函数语法,您还可以在需要的位置另外添加隐式转换:

    scala> trait Computable extends (Int => Int)
    defined trait Computable
    
    scala> def computes(c: Computable) = c(5)
    computes: (c: Computable)Int
    
    scala> implicit def toComputable(f: Int => Int) = new Computable { def apply(i: Int) = f(i) }
    toComputable: (f: (Int) => Int)java.lang.Object with Computable
    
    scala> computes( (i: Int) => i * 2 )
    res0: Int = 10
    

    【讨论】:

    • 我正在使用一种非常相似的方法。我有特征Curve extends (Double => Double)Surface extends ((Double, Double) => Double),隐含提升正常的scala 函数。如果在 trait Computable 的伴生对象中定义转换,则在搜索从 TComputable 的任何类型的视图时,它会自动位于隐式范围内。
    • 这实际上是我所做的。但我想知道 Computable 特性实际上“买”了我什么。如果我总是将它用作 (Int => Int) 那么为什么还要麻烦呢?据我所知,它只具有文件价值。
    • 如果只是为了文档,你也可以定义一个类型别名:type Computable = Int => Int..
    【解决方案2】:

    创建从函数类型扩展的特征可能很有用,原因有几个。

    1. 您的函数对象做了一些特殊且不明显(并且难以键入)的事情,您可以在构造函数中参数化细微的变化。例如,假设您正在编写一个特征来对 XML 树执行 XPath 查询。 apply 函数会隐藏多种构建 XPath 查询机制的工作,但仍然值得实现Function1 接口,以便您可以使用mapflatMap 从一大堆不同的节点开始查询。

    2. 作为 #1 的扩展,您想在构造时进行一些处理(例如,解析 XPath 表达式并编译它以快速运行),您可以提前在对象的构造函数中执行一次(而如果你只是在没有子类化的情况下对Functions 进行了柯里化,则编译只能在运行时进行,因此对于每个查询都会重复。)

    3. 您希望将加密函数(Function1[String,String] 的类型)作为隐式传递,但并非所有 Function1[String,String]s 都执行加密。通过从Function1[String,String] 派生并将子类/特征命名为EncryptionFunction,您可以确保仅隐式传递正确子类的函数。 (声明 Type EncryptionFunction = String => String 时,情况并非如此。)

    我希望这很清楚。

    【讨论】:

      【解决方案3】:

      听起来您可能想使用structural type。它们也被称为隐式接口。

      然后,您可以重构当前接受 Computable 的方法以接受任何具有 compute(input: Int) 方法的方法。

      【讨论】:

      • 好的,这样任何具有计算方法的对象都可以用作 Computable。但我更感兴趣的是,支持或反对使 Computable 成为函数类型的一些考虑因素是什么?我注意到的一个问题是,如果 Computable 是一种函数类型,那么实现它的类就不能是任何其他函数类型。如果我有另一个接口,Displayable,它继承自 (String => String),那么一个类不能同时是 Computable 和 Displayable,因为它会从两个特征继承 Function1,但类型参数不同。
      • 没错,这是反对扩展(或直接使用)Int => Int 的一个论据。如果您希望这将是典型的用例,那么自定义名称可能会更好。
      【解决方案4】:

      一个选择是定义一个类型(你仍然可以称它为 Computable),此时它是 Int=>Int。每当您需要可计算的东西时使用它。您将获得从 Function1 继承的所有好处。然后,如果您意识到需要更多方法,则可以将类型更改为另一个特征。

      起初:

      type Computable = Int => Int
      

      稍后:

      type Computable = ComputableTrait // with its own methods.
      

      它的一个缺点是你定义的类型并不是真正的新类型,更像是一种别名。因此,除非您将其更改为特征,否则编译器仍将接受其他 Int => Int 函数。至少,您(开发人员)可以区分。当您更改为特征(并且差异变得很重要)时,编译器会发现您何时需要 Computable 但具有 Int => Int。

      如果您希望编译器从第一天开始拒绝其他 Int => Int -s,那么我建议使用 trait,但扩展 Int => Int。当您需要调用它时,您仍然可以使用更方便的语法。

      另一种选择可能是拥有一个特征和一个伴随对象,以及一个接受 Int => Int 并从中创建一个 Computable 的 apply 方法。 然后创建新的 Computables 几乎就像编写普通的匿名函数一样简单,但是您仍然需要进行类型检查(通过隐式转换您可以放松)。此外,您可以毫无问题地混入特征(但不能按原样使用伴生对象的应用)。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2020-11-11
        • 1970-01-01
        • 2018-06-02
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多