web-dev-qa-db-ja.com

Scala:「汎用」関数パラメーターを定義する方法は?

Haskellで少し経験を積んで、今Scalaを学ぼうとしています。私にとって奇妙な点の1つは、Scala mustのすべての関数パラメーターに型の注釈を付けることです。Haskellには必要ありません。どうしてこれなの?それをより具体的な例として試してみましょう:add関数は次のように書かれています:

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

ただし、これはdoubleに対してのみ機能します(暗黙の型変換のために、intも機能します)。しかし、独自の+演算子を定義する独自の型を定義する場合はどうでしょう。 +演算子を定義する型に対して機能するadd関数をどのように作成しますか?

50
airportyh

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
68
Walter Chang

implicitを使用するという概念を固めるために、scala 2.8を必要としないが、同じ概念を使用する例を作成しました。最初に、generic-abstractクラスを定義します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の型クラスのように使用されます。次に、realize特定の型のこの汎用クラスに、次のように記述します(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

この時点で、3つのタイプすべてで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ほどニースではありません。これは基本的にすべてをあなたのために行います。しかし、そこにトレードオフがあります。

18
airportyh

理由は、Scalaは、新しく定義された関数のパラメーターに型注釈が必要だということです。これは、Scala Haskellで使用されます。

すべてのクラスがAddable[T]演算子を宣言した+などの特性に混在している場合、次のように汎用のadd関数を記述できます。

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

これにより、add関数は、Addableトレイトを実装するタイプTに制限されます。

残念ながら、現在のScalaライブラリにはこのような特性はありません。しかし、同様の場合、Ordered[T]特性を見ると、どのように実行されるかを見ることができます。演算子は、RichIntRichFloatなどのクラスによって混合されます。その後、たとえば、List[T]を取得できるソート関数を作成できます。ここで、[T <: Ordered[T]]順序付けられた特性に混在する要素のリストを並べ替えるFloatからRichFloatへの暗黙的な型変換のため、Intのリストで並べ替え関数を使用することもできます。 、またはFloatまたはDouble

私が言ったように、残念ながら、+演算子に対応する特性はありません。したがって、すべてを自分で書き出す必要があります。 Addable [T]トレイトを実行し、AddableIntAddableFloatなど、Int、Floatなどを拡張するクラスを作成し、Addableトレイトで混合し、最後に暗黙的な変換関数を追加します。たとえば、IntをAddableIntに変換します。これにより、コンパイラはインスタンスを作成して、追加関数を使用できます。

3
fxt

Haskellは Hindley-Milner 型推論を使用します。この種の型推論は強力ですが、言語の型システムを制限します。たとえば、サブクラス化はH-Mではうまく機能しないと思われます。

とにかく、Scala型システムはH-Mには強力すぎるため、より限定的な種類の型推論を使用する必要があります。

3

関数自体は非常に簡単です。

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はGroupAdditiveを拡張します):

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

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

  ...
}

それはあなたが本当に知りたいと思っていた以上のものかもしれませんが、それは一般的な引数を定義する方法とそれらを実現する方法の両方を示しています。

最終的には、特定の型は必要ありませんが、コンパイラーは少なくとも型の境界を知る必要があります。

1
mtnygard