Scala: Как определить "общие" параметры функции?
Сейчас я пытаюсь узнать Scala, имея немного опыта в Haskell. Одна вещь, которая показалась мне странной, заключается в том, что все функциональные параметры в Scala должны быть аннотированы типом - то, что Haskell не требует. Почему это? Чтобы попытаться выразить это как более конкретный пример: функция add написана следующим образом:
def add(x:Double, y:Double) = x + y
Но это работает только для удвоений (ну, ints тоже работает из-за неявного преобразования типов). Но что, если вы хотите определить свой собственный тип, который определяет свой собственный + оператор. Как бы вы написали функцию add, которая работает для любого типа, который определяет оператор +?
Ответы
Ответ 1
Haskell использует алгоритм вывода типа Hindley-Milner, тогда как 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
Ответ 2
Чтобы упростить концепцию использования неявного для себя, я написал пример, который не требует scala 2.8, но использует ту же концепцию. Я думал, что это может быть полезно для некоторых.
Сначала вы определяете общий абстрактный класс Addable:
scala> abstract class Addable[T]{
| def +(x: T, y: T): T
| }
defined class Addable
Теперь вы можете написать функцию добавления следующим образом:
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
Конечно, не так хорошо, как Haskell, который по существу сделает все это для вас. Но, где компромисс лежит.
Ответ 3
Haskell использует вывод Hindley-Milner. Такой тип вывода является мощным, но ограничивает систему типов языка. Предположительно, например, подклассы не очень хорошо работают с H-M.
Во всяком случае, система типов Scala слишком полезна для H-M, поэтому необходимо использовать более ограниченный тип вывода типа.
Ответ 4
Я думаю, что причина Scala требует аннотации типа для параметров только что определенной функции, вытекает из того, что Scala использует более локальный анализ вывода типа, чем тот, который используется в Haskell.
Если все ваши классы смешаны в признаке, скажем Addable[T]
, объявивший оператор +
, вы можете написать свою общую функцию добавления как:
def add[T <: Addable[T]](x : T, y : T) = x + y
Это ограничивает функцию добавления типами T, которые реализуют Добавляемый признак.
К сожалению, в текущих библиотеках Scala нет такой черты. Но вы можете видеть, как это можно сделать, посмотрев на похожий случай, на показатель Ordered[T]
. Эта черта объявляет операторы сравнения и смешивается с классами RichInt
, RichFloat
и т.д. Затем вы можете написать функцию сортировки, которая может принимать, например, List[T]
где [T <: Ordered[T]]
для сортировки списка элементов, которые смешиваются в упорядоченном признаке. Из-за неявных преобразований типов, таких как Float
- RichFloat
, вы можете даже использовать свою функцию сортировки в списках Int
или Float
или Double
.
Как я уже сказал, к сожалению, для оператора +
нет соответствующего признака. Таким образом, вы должны были бы написать все сами. Вы должны использовать атрибут Addable [T], создавать AddableInt
, AddableFloat
и т.д. Классы, расширяющие Int, Float и т.д. И смешивать в добавочном признаке и, наконец, добавлять неявные функции преобразования, чтобы, например, и Int в AddableInt
, так что компилятор может создать экземпляр и использовать вашу функцию добавления.
Ответ 5
Сама функция будет довольно простой:
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 = { ... }
...
}
Это может быть больше, чем вы действительно хотели знать прямо сейчас, но он показывает, как определить общие аргументы и как их реализовать.
В конечном счете, конкретные типы не требуются, но компилятор действительно должен знать, по крайней мере, ограничения типов.