web-dev-qa-db-ja.com

Scalaの 'type'キーワードの機能を理解する

私はScalaを初めて使用しますが、typeキーワードについて多くを見つけることができませんでした。次の表現の意味を理解しようとしています。

type FunctorType = (LocalDate, HolidayCalendar, Int, Boolean) => LocalDate

FunctorTypeは何らかのエイリアスですが、それは何を意味しますか?

137
Core_Dumped

はい、type aliasFunctorTypeは単なる短縮形です

(LocalDate, HolidayCalendar, Int, Boolean) => LocalDate

タイプエイリアスは、残りのコードを単純にするためによく使用されます。

def doSomeThing(f: FunctorType)

コンパイラーは次のように解釈します

def doSomeThing(f: (LocalDate, HolidayCalendar, Int, Boolean) => LocalDate)

これにより、たとえば、他のタイプで定義されたタプルまたは関数である多くのカスタムタイプを定義することを回避できます。

また、[ Scala でのプログラミング]の この章 で説明されているように、typeの興味深い使用例もいくつかあります。

136
Roland Ewald

実際、Scalaのtypeキーワードは、複雑な型をより短い名前にエイリアスするだけではありません。 typeメンバを導入します。

ご存知のように、クラスにはフィールドメンバーとメソッドメンバーを含めることができます。まあ、Scalaでは、クラスに型メンバーを含めることもできます。

あなたの特定の場合、typeは実際、より簡潔なコードを書くことができるエイリアスを導入しています。型システムは、型チェックの実行時にエイリアスを実際の型に置き換えるだけです。

しかし、あなたはこのようなものを持つこともできます

trait Base {
  type T

  def method: T
}

class Implementation extends Base {
  type T = Int

  def method: T = 42
}

クラスの他のメンバーと同様に、型メンバーも抽象にすることができ(その値を実際に指定しないでください)、実装でオーバーライドできます。

型メンバーは、ジェネリックで実装できるものの多くが抽象型メンバーに変換できるため、ジェネリックのデュアルと見なすことができます。

はい、そうです、それらはエイリアシングに使用できますが、これだけに限定しないでください。それらはScalaの型システムの強力な機能だからです。

詳細については、この優れた回答をご覧ください。

スカラ:抽象型とジェネリック

184
Marius Danila

エイリアスとして「type」を使用する方法を確認するための単なる例:

type Action = () => Unit

上記の定義では、Actionを、空のパラメーターリストを取得してUnitを返すプロシージャ(メソッド)の種類のエイリアスとして定義しています。

5

Roland Ewald からの回答が気に入ったのは、彼がタイプエイリアスの非常に単純なユースケースで説明し、詳細については非常に素晴らしいチュートリアルを紹介したからです。ただし、この投稿ではtype membersという名前の別のユースケースが導入されているため、その最も実用的なユースケースについて言及したいと思います。 much:(この部分は here :から取得されます)

要約タイプ:

type T

上記のTは、使用されるこのタイプはまだ不明であり、具体的なサブクラスに応じて定義されることを示しています。プログラミングの概念を常に理解するための最良の方法は、例を提供することです。次のシナリオがあるとします。

Without Type Abstraction

ここでは、クラスCowとTigerのeatメソッドは、パラメーターAnimalのクラスが異なるため、クラスAnimalのeatメソッドをオーバーライドしないため、コンパイルエラーが発生します。クラスCowのGrass、クラスTigerのMeat、クラスAnimalのFoodはスーパークラスであり、すべてのサブクラスは準拠する必要があります。

型の抽象化に戻り、次の図と単に型の抽象化を追加するだけで、サブクラス自体に応じて入力の型を定義できます。

With Abstract Type

次のコードを見てください:

  val cow1: Cow = new Cow
  val cow2: Cow = new Cow

  cow1 eat new cow1.SuitableFood
  cow2 eat new cow1.SuitableFood

  val tiger: Tiger = new Tiger
  cow1 eat new tiger.SuitableFood // Compiler error

コンパイラは満足しており、設計を改善しています。私たちは牛に牛を飼うことができます。SuitableFoodとコンパイラは、タイガーに適した食物を牛に与えることで私たちを防ぎます。しかし、牛1の種類の適切な食品と牛2の水田鍋の種類を区別したい場合はどうでしょう。別の言葉では、(もちろんオブジェクトを介して)型に到達するためのパスが基本的に重要な場合、いくつかのシナリオで非常に便利です。 scalaの高度な機能のおかげで、次のことが可能になります。

パス依存型:Scalaオブジェクトはメンバーとして型を持つことができます。タイプの意味は、アクセスに使用するパスによって異なります。パスは、オブジェクトへの参照(クラスのインスタンス)によって決定されます。このシナリオを実装するには、Cow内でGrassクラスを定義する必要があります。つまり、Cowは外部クラスであり、Grassは内部クラスです。構造は次のようになります。

  class Cow extends Animal {
    class Grass extends Food
    type SuitableFood = Grass
    override def eat(food: this.SuitableFood): Unit = {}
  }

  class Tiger extends Animal {
    class Meat extends Food
    type SuitableFood = Meat
    override def eat(food: this.SuitableFood): Unit = {}
  }

このコードをコンパイルしようとした場合:

  1. val cow1: Cow = new Cow
  2. val cow2: Cow = new Cow

  3. cow1 eat new cow1.SuitableFood
  4. cow2 eat new cow1.SuitableFood // compilation error

4行目では、GrassがCowの内部クラスであるためエラーが表示されます。したがって、Grassのインスタンスを作成するには、牛オブジェクトが必要であり、この牛オブジェクトがパスを決定します。したがって、2つの牛オブジェクトは2つの異なるパスを生成します。このシナリオでは、cow2はそのために特別に作成された食物のみを食べたいです。そう:

cow2 eat new cow2.SuitableFood

今では誰もが幸せです:-)

4
Mehran