Scala学習ノート(3):対象と容器向け

13830 ワード

オブジェクト向けプログラミング
これは、Scalaのオブジェクト向けプログラミングの基本概念を一例で説明します.ここでは、MongoDB(私の別のブログを参照)のために簡単なAPIを作成する必要があります.MongoDBはJava APIを公式に提供していますが、Scalaを使用して簡単なパッケージを作成します.
Class
まず、MongoClientというクラスをデータベース接続を確立するエントリとして構築し、MongoClientクラスを定義し、主構造関数の2つのパラメータ(val host:String,val port:Int)を指定します.
class MongoClient(val host: String, val port: Int)

コンストラクション関数パラメータのフォーマットは[val/var]param:typeであり、valパラメータは読み取り専用(getメソッドのみに相当)であり、varパラメータは書き込み可能(getメソッドとsetメソッドあり)であり、接頭辞がなければプライベート(getメソッドとsetメソッドなし)である.
コンストラクタ
デフォルトのコンストラクション関数を含む複数のコンストラクション関数をクラスで定義できます.たとえば、以下はMongoClientの完全な定義です.
class MongoClient(val host: String, val port: Int) {
  
  require(host != null, "You have to provide a host name")
  private val underlying = new Mongo(host, port)

  def this() = this("127.0.0.1", 27017)
  def version = underlying.getVersion()
  def dropDB(name: String) = underlying.dropDatabase(name)
  def createDB(name: String) = DB(underlying.getDB(name))
  def db(name: String) = DB(underlying.getDB(name))
}

def this()はデフォルトの構造関数を定義します.
Case class
Case classは特殊なClassであり、コンパイラはテンプレートコード(equals,hashCode,toString,applyなど)を自動的に生成します.
case class Person(firstName:String, lastName:String)

Object
Scalaには静的変数と静的メソッドは存在しません.すべての変数とメソッドはクラスまたはobjectに定義する必要があります.objectとは単一のオブジェクトです.
objectは、実行可能なクラスを定義するために使用できます.
object HelloWrold extends App {
    println("Hello World")
}

objectは定義可能
objectのもう一つの一般的な使い方は、クラスの伴随オブジェクトとして、いくつかの「静的」なファクトリ関数を定義することです.たとえば、次のプログラムでは、まずDBクラスを定義し、同じDBと呼ばれる付随オブジェクトを定義します.付随オブジェクトは付随クラスのプライベートメンバーにアクセスできます.
class DB private (val underlying: MongoDB){ //  privateDB         
  def collectionNames = for(name <- new JSetWrapper(underlying.getCollectionNames())) yield name
  
  private def collection(name: String) = underlying.getCollection(name)
  
  def readOnlyCollection(name: String) = new DBCollection(collection(name))
  def administrableCollection(name: String) = new DBCollection(collection(name)) with Administrable
  def updatableCollection(name: String) = new DBCollection(collection(name)) with Updatable 
}

object DB {
  def apply(underlying: MongoDB) = new DB(underlying)
}

パッケージと導入
Scalaのパッケージも特殊なオブジェクトです.パッケージ内で他のクラスとオブジェクトの定義を見ることができます.パッケージ内で関数または変数を定義する場合は、パッケージオブジェクトを使用します.
//package.scala
package object bar {
  val minimumAge = 18
  def verifyAge = {}
}

//other.scala
package bar
class BarTender {
  def serveDrinks = { verifyAge; ... }
}

パッケージの定義は、ネストされた(C#でよく見られる)、またはフラットパネル(Javaでよく見られる)です.リファレンスパッケージの一般的な書き方は、次のとおりです.
import com.mongodb.DBObject
import com.mongodb._  //_ Java  ×(  )     
import com.mongodb.{DBCollection => MongoDBCollection} //         

Scalaの継承モデル
scala.Anyは任意のクラスの親です.Scala.AnyRefはJavaのObjectクラス、すなわち参照クラスの親クラスに相当する.AnyValは任意の値クラスの親です.
図中の実線は継承関係を表し、破線はview関係を表し、viewはタイプ間に暗黙的な変換があることを意味する.
クラスレベルの一番下にはScala.NullとScala.Nothing,Scala.Nullタイプにはインスタンス化オブジェクトnull,Scalaが1つしかありません.Nothingはインスタンス化できません.
特質
Traitは特質に訳すことができて、あなたは特質が1種の強化版のインタフェースだと考えることができて、しかし私はそれを1種の部分の実現のクラスと理解するのがもっと適切だと思います.どうしてそう言うの?特質構造プログラムを使用するため、大きなクラスを複数の特質に分散させることができ、機械を部品に分解してから自由に組み立てることに相当します.
とくせい
Interface (Java)
Abstract class (Java + Scala)
Trait
インスタンス化
No
No
No
構築パラメータ
No
Yes
No
メソッド定義
Yes
Yes
Yes
メソッド実装
No
Yes
Yes
メンバー変数
No
Yes
Yes
マルチコンビネーション(継承)
Yes
No
Yes
例えば次の例ではmongodb java APIの機能をいくつかの異なる特質に分散しているので,特質はReadOnly特質から継承されている.
trait ReadOnly {
  val underlying : MongoDBCollection
  
  def name = underlying getName
  def fullName = underlying getFullName
  def find(doc: DBObject) = underlying find doc
  def findOne(doc: DBObject) = underlying findOne doc
  def findONe = underlying findOne
  def getCount(doc: DBObject) = underlying getCount doc
  
  def find(query: Query): DBCursor = {
    def applyOptions(cursor:DBCursor, option: QueryOption): DBCursor = {
    option match {
      case Skip(skip, next) => applyOptions(cursor.skip(skip), next)
      case Sort(sorting, next)=> applyOptions(cursor.sort(sorting), next)
      case Limit(limit, next) => applyOptions(cursor.limit(limit), next)
      case NoOption => cursor
    }
    }
    applyOptions(find(query.q), query.option)
  }
}

trait Administrable extends ReadOnly {
  
  def drop: Unit = underlying drop
  def dropIndexes : Unit = underlying dropIndexes
  
}

trait Updatable extends ReadOnly {
  def -=(doc: DBObject): Unit = underlying remove doc
  def +=(doc: DBObject): Unit = underlying save doc
}

trait LocaleAware extends ReadOnly {
  override def findOne(doc: DBObject) = {
    doc.put("locale", java.util.Locale.getDefault.getLanguage)
    super.findOne(doc)
  }
  override def find(doc: DBObject) = {
    doc.put("locale", java.util.Locale.getDefault.getLanguage)
    super.find(doc)
  }
}

class DBCollection(override val underlying: MongoDBCollection) extends ReadOnly

//  
new DBCollection(mongoDBCollection) with Updatable with Administrable

Scala容器
Scala中の容器は2種類に分けられる:immutableとmutable、それぞれパッケージscalaに属する.collection.immutableとscala.collection.mutable.Javaプログラマーは、StringとStringBufferの違いを理解することができます.すなわち、immutableコンテナの削除操作は新しいimmutableコンテナを返し、mutableコンテナの削除操作はin placeです(副作用が発生します).
すべてのimmutableとmutableコンテナはscalaから継承する.collectionで定義された抽象クラスです.そのクラス構造は次のとおりです.
IndexedSeqの実装はVector,LinearSeqの実装はlist である.
暗黙変換を使用することにより、JavaのStringタイプとArrayタイプは、IndexedSeqインタフェースをScalaで「継承」する.
一般的なcollection操作
mapは関数f(T=>T)を用いてcollectionの要素を新しいcollection にマッピングする.
flatMap関数はf(T=>List[T])をマッピングするが、collectionを平たくする.
filter
foldLeft
foldRight
コンカレントコンテナ
Scala 2.9は同時コンテナを導入し、多くのコンテナ操作が安全に同時実行できるようにした.例えば、通常の容器類の後に加える.par接尾辞は同時コンテナに変換できます.
myCollection.par.foldLeft(0)((a,b) => a+b)

Par容器の内部実装は,MergeSortのような分割結合アルゴリズムに基づいている.