Лирическое отступление
Когда я писал предыдущий пост, я не стал создавать функцию maximum
в общем виде, а рассмотрел лишь ее частный случай для List[Int]
.
Впоследствии я подумал: а почему бы собственно и нет? Тем более что, покопавшись на StackOverflow, я нашел сразу два способа, как это сделать.
В чем проблема
Проблема в том, что мы не можем просто написать общую (generic) функцию:
def maximum[A](l:List[A]):A = {
l match { case Nil => throw new Exception("Nil")
case x::Nil => x
case x::xs if x > maximum(xs) => x
case x::xs => maximum(xs)
}
}
Компилятор выдаст ошибку:
error: value > is not a member of type parameter A
Наш тип A не имеет метода > !
Очевидно, что наш общий (generic) тип нужно привести к чему-то, что имеет механизм сравнения.
В скале есть 2 трэйта scala.Ordered
и scala.math.Ordering
, обладающие необходимыми нам методами.
View Bound
Хотя данный способ считается устаревшим (deprecated), он довольно нагляден и понятен:
def maximum[A <% Ordered[A]](l:List[A]):A = {
l match { case Nil => throw new Exception("Nil")
case x::Nil => x
case x::xs if x > maximum(xs) => x
case x::xs => maximum(xs)
}
}
println(maximum[Char]('f'::'a'::'h'::Nil))
В нашем примере Char
не является Ordered
, поэтому происходит неявное (implicit) преобразование. Мы как бы заставляем компилятор принимать Char
за Ordered
.
Context Bounds
При помощи этого подхода можно получить в Scala что-то похожее на type classes из Haskell.
def maximum[A](l: List[A])(implicit ord: Ordering[A]): A =
l match {
case Nil => throw new Exception("maximum of empty list")
case x :: Nil => x
case x :: xs => ord.max(x, maximum(xs))
}
println(maximum[Char]('f'::'a'::'h'::Nil))
Наша функция принимает неявный (implicit) параметр ord
имеющий тип Ordering[A].
Все рабтает. Но возникает вопрос, откуда функция берет этот параметр?
Дело в том, что объект-компаньон Ordering содержит большой набор преобразований для различных типов.