Scala: Как определить «общие» параметры функции? - PullRequest
50 голосов
/ 10 августа 2009

Я сейчас пытаюсь выучить Scala, имея небольшой опыт работы с Haskell. Одна вещь, которая показалась мне странной, заключается в том, что все параметры функций в Scala должны быть аннотированы типом, чего не требует Haskell. Почему это? Чтобы попытаться представить это как более конкретный пример: функция добавления написана так:

def add(x:Double, y:Double) = x + y

Но это работает только для двойников (ну, интты тоже работают из-за неявного преобразования типов). Но что, если вы хотите определить свой собственный тип, который определяет свой собственный оператор + . Как бы вы написали функцию добавления, которая работает для любого типа, который определяет оператор + ?

Ответы [ 5 ]

68 голосов
/ 10 августа 2009

Haskell использует алгоритм логического вывода типа Хиндли-Милнера, в то время как Scala, чтобы поддержать объектно-ориентированную сторону вещей, должен был пока отказаться от его использования.

Чтобы легко написать функцию добавления для всех применимых типов, вам нужно использовать Scala 2.8.0:

Welcome to Scala version 2.8.0.r18189-b20090702020221 (Java HotSpot(TM) 64-Bit Server VM, Java 1.6.0_15).
Type in expressions to have them evaluated.
Type :help for more information.

scala> import Numeric._
import Numeric._

scala> def add[A](x: A, y: A)(implicit numeric: Numeric[A]): A = 
     | numeric.plus(x, y)
add: [A](x: A,y: A)(implicit numeric: Numeric[A])A

scala> add(1, 2)
res0: Int = 3

scala> add(1.1, 2.2)
res1: Double = 3.3000000000000003
18 голосов
/ 11 августа 2009

Чтобы закрепить концепцию использования неявного для себя, я написал пример, который не требует Scala 2.8, но использует ту же концепцию. Я думал, что это может быть полезно для некоторых. Сначала вы определяете обобщенно-абстрактный класс Addable :

scala> abstract class Addable[T]{
 |   def +(x: T, y: T): T
 | }
defined class Addable

Теперь вы можете написать функцию add следующим образом:

scala> def add[T](x: T, y: T)(implicit addy: Addable[T]): T = 
 | addy.+(x, y)
add: [T](T,T)(implicit Addable[T])T

Это используется как класс типов в Haskell. Затем, чтобы реализовать этот универсальный класс для определенного типа, вы должны написать (примеры здесь для Int, Double и String):

scala> implicit object IntAddable extends Addable[Int]{
 |   def +(x: Int, y: Int): Int = x + y
 | }
defined module IntAddable

scala> implicit object DoubleAddable extends Addable[Double]{
 |   def +(x: Double, y: Double): Double = x + y
 | }
defined module DoubleAddable

scala> implicit object StringAddable extends Addable[String]{
 |   def +(x: String, y: String): String = x concat y
 | }
defined module StringAddable

В этот момент вы можете вызвать функцию add всех трех типов:

scala> add(1,2)
res0: Int = 3

scala> add(1.0, 2.0)
res1: Double = 3.0

scala> add("abc", "def")
res2: java.lang.String = abcdef

Конечно, не так хорошо, как Хаскелл, который по сути сделает все это для вас. Но в этом и заключается компромисс.

3 голосов
/ 10 августа 2009

Хаскелл использует вывод типа Хиндли-Милнера . Этот тип вывода типов является мощным, но ограничивает систему типов языка. Предположительно, например, подклассы не работают хорошо с H-M.

В любом случае, система типов Scala слишком мощна для H-M, поэтому необходимо использовать более ограниченный тип вывода типов.

3 голосов
/ 10 августа 2009

Я думаю, что причина, по которой Scala требует аннотации типов для параметров вновь определенной функции, заключается в том, что Scala использует более локальный анализ вывода типов, чем тот, который используется в Haskell.

Если все ваши классы смешаны в признаке, скажем Addable[T], в котором объявлен оператор +, вы можете написать свою общую функцию добавления как:

def add[T <: Addable[T]](x : T, y : T) = x + y

Это ограничивает функцию добавления типами T, которые реализуют свойство Addable.

К сожалению, в современных библиотеках Scala такой черты нет. Но вы можете увидеть, как это будет сделано, если взглянуть на похожий случай - черту Ordered[T]. Эта черта объявляет операторы сравнения и смешивается классами RichInt, RichFloat и т. Д. Затем вы можете написать функцию сортировки, которая может взять, например, List[T], где [T <: Ordered[T]], чтобы отсортировать список элементов, которые смешиваются в упорядоченном признаке. Из-за неявных преобразований типов, таких как Float в RichFloat, вы даже можете использовать свою функцию сортировки в списках Int, или Float, или Double.

.

Как я уже сказал, к сожалению, для оператора + нет соответствующей черты. Итак, вам придется выписать все самостоятельно. Вы должны использовать черту Addable [T], создавать AddableInt, AddableFloat и т. Д., Классы, расширяющие Int, Float и т. Д., Смешиваться в черте Addable и, наконец, добавлять неявные функции преобразования, например, для поворота. и Int в AddableInt, чтобы компилятор мог создать экземпляр и использовать функцию add с ним.

1 голос
/ 10 августа 2009

Сама функция будет довольно простой:

def add(x: T, y: T): T = ...

Еще лучше, вы можете просто перегрузить метод +:

def +(x: T, y: T): T = ...

Однако отсутствует часть, которая является самим параметром типа. Как написано, метод не имеет своего класса. Наиболее вероятным случаем является то, что вы вызываете метод + для экземпляра T, передавая ему другой экземпляр T. Я сделал это недавно, определив черту, которая гласит: «аддитивная группа состоит из операции добавления плюс средства для инвертировать элемент "

trait GroupAdditive[G] extends Structure[G] {
  def +(that: G): G
  def unary_- : G
}

Затем, позже, я определяю класс Real, который знает, как добавлять экземпляры самого себя (Field extends GroupAdditive):

class Real private (s: LargeInteger, err: LargeInteger, exp: Int) extends Number[Real] with Field[Real] with Ordered[Real] {
  ...

  def +(that: Real): Real = { ... }

  ...
}

Это может быть больше, чем вы на самом деле хотели бы знать прямо сейчас, но это показывает, как определить общие аргументы и как их реализовать.

В конечном счете, конкретные типы не требуются, но компилятору нужно знать хотя бы границы типов.

...