Scala: Como definir parâmetros de function “genéricos”?

Estou tentando aprender Scala agora, com um pouco de experiência em Haskell. Uma coisa que se destacou como estranha para mim é que todos os parâmetros de function no Scala devem ser anotados com um tipo – algo que Haskell não exige. Por que é isso? Para tentar colocar isso como um exemplo mais concreto: uma function add é escrita assim:

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

Mas, isso só funciona para duplas (bem, o ints também funciona por causa da conversão de tipo implícita). Mas e se você quiser definir seu próprio tipo que defina seu próprio operador + . Como você escreveria uma function add que funciona para qualquer tipo que defina um operador + ?

    Haskell usa o algoritmo de inferência tipo Hindley-Milner, enquanto o Scala, para suportar o lado Orientado a Objetos, teve que renunciar a usá-lo por enquanto.

    Para escrever uma function add para todos os tipos aplicáveis ​​facilmente, você precisará usar o 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 

    Para solidificar o conceito de usar implícito para mim, escrevi um exemplo que não requer scala 2.8, mas usa o mesmo conceito. Eu pensei que poderia ser útil para alguns. Primeiro, você define uma class abstrata-genérica Addable :

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

    Agora você pode escrever a function add assim:

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

    Isso é usado como uma class de tipo no Haskell. Então, para realizar essa class genérica para um tipo específico, você escreveria (exemplos aqui para Int, Double e 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 

    Neste ponto, você pode chamar a function add com todos os três tipos:

     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 

    Certamente não tão bom quanto Haskell, que essencialmente fará tudo isso por você. Mas é aí que está o trade-off.

    Haskell usa a inferência do tipo Hindley-Milner . Esse tipo de inferência de tipos é poderoso, mas limita o sistema de tipos da linguagem. Supostamente, por exemplo, a subsorting não funciona bem com o HM.

    De qualquer forma, o sistema do tipo Scala é muito poderoso para o HM, portanto, um tipo mais limitado de inferência de tipo deve ser usado.

    Acho que o motivo pelo qual o Scala requer a anotação de tipo nos parâmetros de uma function recém-definida vem do fato de que o Scala usa uma análise de inferência de tipo mais local do que a usada em Haskell.

    Se todas as suas classs Addable[T] misturadas em um traço, digamos Addable[T] , que declarou o operador + , você poderia escrever sua function add genérica como:

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

    Isso restringe a function add aos tipos T que implementam o traço Addable.

    Infelizmente, não existe tal característica nas atuais bibliotecas Scala. Mas você pode ver como isso seria feito olhando para um caso similar, o traço Ordered[T] . Este atributo declara operadores de comparação e é misturado pelas RichInt , RichFloat , etc. Então você pode escrever uma function de sorting que pode levar, por exemplo, uma List[T] onde [T <: Ordered[T]] para ordenar uma lista de elementos que misturam a característica ordenada. Por causa de conversões de tipo implícitas, como Float para RichFloat , você pode até mesmo usar sua function de sorting nas listas Int , ou Float ou Double .

    Como eu disse, infelizmente, não há traço correspondente para o operador + . Então, você teria que escrever tudo sozinho. Você faria o atributo Addable [T], criaria AddableInt , AddableFloat , etc., classs que estenderiam Int, Float, etc. e misturariam o traço Addable, e finalmente adicionariam funções de conversão implícitas para transformar, por exemplo, Int em um AddableInt , para que o compilador possa instanciar e usar sua function add com ele.

    A function em si será bem direta:

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

    Melhor ainda, você pode apenas sobrecarregar o método +:

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

    Há uma peça que falta, no entanto, que é o parâmetro de tipo em si. Conforme escrito, o método está faltando sua class. O caso mais provável é que você esteja chamando o método + em uma instância de T, passando-o para outra instância de T. Fiz isso recentemente, definindo uma característica que dizia: “um grupo aditivo consiste em uma operação add e os meios para inverta um elemento ”

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

    Então, mais tarde, eu defino uma class Real que sabe como adicionar instâncias de si mesmo (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 = { ... } ... } 

    Isso pode ser mais do que você realmente queria saber agora, mas mostra como definir argumentos genéricos e como realizá-los.

    Por fim, os tipos específicos não são necessários, mas o compilador precisa saber pelo menos os limites de tipo.