【问题标题】:Generalizing scala code into a function将scala代码泛化为函数
【发布时间】:2012-01-05 05:06:04
【问题描述】:

所以我最近不小心为一个 Scala 问题写了一个 Haskell 答案。由于对 Haskell 相当熟悉,我很容易找到解决方案:

myMaxBy :: (a -> a -> Ordering) -> [a] -> [a]
myMaxBy _ [] = undefined
myMaxBy f (x:xs) = foldr step [x] xs
  where step y acc@(z:_) = case f y z of
          GT -> [y]
          EQ -> y:acc
          LT -> acc

然后有人提醒我这是一个 Scala 问题。我开始将我的代码翻译成 Scala,在经历了很多痛苦之后,我决定了:

(List(xs.head) /: xs.tail) { (acc, y) =>
  y compare acc.head match {
    1  => List(y)
    0  => y :: acc
    -1 => acc
  }
}

但我终其一生都无法让 Scala 类型系统屈服于我的意愿,并将其概括为一个函数,其中 xscompare 是输入(理想情况下,首先使用比较器进行咖喱输入)。虽然这肯定是由于我对 Scala 不熟悉,但我也略微责怪 Scala 的复杂(尽管非常强大)类型系统。你能帮我看看如何把它变成一个 generalized 函数,其类型签名类似于 Haskell 等价物吗? (阅读:as general as。)如果比myMaxBy(myCompare)(someList)更复杂,也请演示用法。

【问题讨论】:

    标签: function generics scala haskell types


    【解决方案1】:

    您错过了模式匹配中的 case 关键字,这些关键字非常重要!

    您所需要的只是集合参数类型能够使用compare 方法。在 Scala 中有两种比较事物的系统:扩展 Ordered,或使用 Ordering。两者之间存在隐式转换,因此您选择哪个并不重要;第一个可能更容易理解。

    首先,使用Ordered

      def myMaxBy[A <% Ordered[A]](xs: List[A]) = {
        (List(xs.head) /: xs.tail) { (acc, y) =>
          y compare acc.head match {
            case 1  => List(y)
            case 0  => y :: acc
            case -1 => acc
          }
        }  
      }
    

    这里我们使用&lt;% 给我们的泛型类型A 一个视图绑定,这意味着“可以被视为”。这比使用上限&lt;: 更通用,并且对于本身不是Ordered 但具有到Ordered 类的隐式转换的类很有用,例如IntRichInt

    或者,如果您希望能够灵活地更改排序标准,您可以这样写:

      def myMaxBy[A](xs: List[A])(implicit ord: Ordering[A]) = {
        (List(xs.head) /: xs.tail) { (acc, y) =>
          ord.compare(y, acc.head) match {
            case 1  => List(y)
            case 0  => y :: acc
            case -1 => acc
          }
        }
      }
    

    调用时,如果范围内有隐式Ordering[A],则可以省略第二个参数。第二种方式还有一个优点,即您可以在任意类上定义Ordering,无论它们是否已经支持它。

    您可以使用例如myMaxBy(List(1,2,3,4,3,4)) 来调用这两者。第二个,如果你想逆序排列:myMaxBy(List(1,2,3,4,3,4))(Ordering.Int.reverse)

    在此上下文中您可能会看到的另一件事是上下文边界。例如。 [A: Ordering]。这意味着与[A](implicit ord: Ordering[A]) 相同,更简洁,只是你没有处理Ordering,所以必须使用implicitly 来召唤它。所以在这里最好像上面一样明确地说明它。

    【讨论】:

    • 感谢case的提醒,我相应地修正了我的其他答案!我在我的代码中使用case 对其进行了测试,但在复制我的代码时以某种方式忽略了它。
    • 你可以把import Ordering.Implicits._放在文件的最前面,这样就可以避免implicitly到处传唤了。
    • @missingfaktor 我不确定导入会有什么帮助,因为我们得到的 Ordering.Ops 没有 compare 方法
    • 我看不出在这里使用模式匹配的充分理由。 (因此也不适用于compare。)因此建议。
    【解决方案2】:

    您需要让 A 类型扩展 Ordered 或更好,限制它以便有可用的 Ordering 类型类。第一个看起来像:

    def sort[A <: Ordered[A]](xs: List[A]) =
      (List(xs.head) /: xs.tail) { (acc, y) => y compare acc.head match { … } }
    

    正如 Luigi 指出的,更好的是使用视图绑定

    def sort[A <% Ordered[A]](xs: List[A]) =
      (List(xs.head) /: xs.tail) { (acc, y) => y compare acc.head match { … } }
    

    最好使用 Ordering 类型类:

    def sort[A](xs: List[A])(implicit ord: Ordering[A]) =
      (List(xs.head) /: xs.tail) { (acc, y) => ord compare (y, acc.head) match { … } }
    

    您也可以使用上下文绑定:

    def sort[A: Ordering](xs: List[A]) = {
      val ord = implicitly[Ordering[A]]
      (List(xs.head) /: xs.tail) { (acc, y) => ord compare (y, acc.head) match { … } }
    }
    

    虽然这在语法上稍微干净一些,但它不允许您传入显式的备用排序(例如不区分大小写的字符串排序)。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2012-05-05
      • 2016-10-30
      • 2011-01-13
      • 1970-01-01
      • 2017-07-26
      • 2021-02-21
      • 2021-12-06
      相关资源
      最近更新 更多