前回の記事ではChisel Bootcampのモジュール1を全部まとめるつもりで読み進めていった。 、、、が分量多すぎて挫折したのでまさかモジュール1:第3回目。。
今日で終わらせる!!
Module 1: Scala入門の続き
ということで今回はクラスの話からになるのだが、まずはその前にScalaってオブジェクト指向言語だから!!というトピックが挟まっているので、そこから見ていく。
Scalaはオブジェクト指向言語
というまんまの題名でスタートするこの項目。特に文法的なことをに触れるわけでもないので、またしても引用させてもらい適当に訳してものを載せておくことにする。
Scalaはオブジェクト指向言語で、これを理解することはScalaとChiselのメリットを最大限享受する上でとても大切なことである。以下のような複数の事実からもそれは疑いようのないことだ。
- 変数はオブジェクト
val
で表現される定数もまたオブジェクト- 文字でさえもオブジェクト
- 同様に関数でさえもオブジェクト。これについてはまた後ほど。
- クラスのインスタンスもオブジェクト
- クラスの定義をする際に、プログラマーは以下のように明示する:
- (
val
,var
)のようなデータをクラスに紐付ける- クラスのインスタンスはメソットや関数と呼ばれる処理を使用できる
- 他のクラスによってクラスを拡張できる
- クラスはトレイトからも継承可能。トレイトとは一つ以上のスーパークラスを限定的に継承することを許可された軽量なクラスである。
- (シングルトン)オブジェクトはScalano特別なクラスである。
- これらは上記のようなオブジェクトではない。私達がこれをインスタンスと呼ぶことを頭に留めておいてほしい。
クラスの例
クラスの簡単な例としてラップカウンターが紹介されている。
// WrapCounter counts up to a max value based on a bit size class WrapCounter(counterBits: Int) { val max: Long = (1 << counterBits) - 1 var counter = 0L def inc(): Long = { counter = counter + 1 if (counter > max) { counter = 0 } counter } println(s"counter created with max value $max") }
- 実行結果
defined class WrapCounter
特にインスタンスを作って何か実行したわけでもないので、実行結果としてもWrapCounter
が定義された、というメッセージが出るだけとなる。
ただ、Chselのブートキャンプでありハードウェアを設計するための勉強なので、処理がそっちよりになってきている。ここの説明はとっても細かく書いてあるのだが、やってることは大したこと無いので自分的に必要そうなとこだけピックアップ。
println(s"counter created with max value $max"
はインスタンスの生成時に表示される- このケースの文字列の表示は補間文字列と呼ばれる
println
の文字列の前に付いている"s"が補間文字列- 補間文字列は実行時に処理される
$max
はクラス内のパラメータmax
に置き換えられる。- もし
$
の部分にコードブロックが続いていてもその部分を処理してくれる- 例えば
println(s"doubled max is ${max + max}")
だとmax * 2
値が表示される - このコードブロックの戻り値は
${...}
内の値になる。 - もし戻り値が
String
型にならない場合は、変換処理が実行される;Scalaの全てのクラス/型はString
型に変換するための暗黙の変換が定義されている。 - この例の場合だと
Int
型→String
型の変換が行われた後文字列への埋め込みが行われている
- 例えば
- このようなクラスインスタンスを生成する度に文字列を出力するような処理はデバッグ目的でもない限り、やめたほうが良い。さもないと標準出力が文字列の出力で溢れかえってしまう。
クラスのインスタンスの生成
クラスの生成はScalaのキーワードnew
によって実行可能
val x = new WrapCounter(2)
- 出力結果
counter created with max value 3 x: WrapCounter = ammonite.$sess.cmd12$Helper$WrapCounter@319017a7
上記の"counter created with max value 3"が前項で触れた補間文字列が使用されている文字列出力になる。
ここの説明では以下に引用したように、new
なしのインスタンス生成についての言及があった。
この先、次のように
new
を使用せずにインスタンスを生成しているコードを見ることがある。これは特別の注意を払うことに十分メリットがある。これはコンパニオンオブジェクトを使用する際に必要になるものであるが、詳細については後ほど取り扱うことにする
実際に上記のWrapCounterを使用した処理は以下のようになる。
x.inc() // カウンタをインクリメント // クラスのメンバ変数"x"は"private"無しに宣言されているのでクラス外部からも参照可能 if(x.counter == x.max) { println("counter is about to wrap") } x inc() // Scalaでは"."がなくても処理可能;これはDSLをより自然に見せるために役に立つ
- 実行結果
説明中に”2回実行してみて”と書いてあるので2回実行して結果を見てみる
// 1回目 res2_0: Long = 1L res2_2: Long = 2L // 2回め counter is about to wrap res3_0: Long = 3L res3_2: Long = 0L
見てのとおりだが、インスタンス生成時にビット幅を2bitで定義しているので、inc
関数を4回呼ぶとカウンタが0に戻っている。
コードブロック
説明中には何回もコードブロックと記載してきているが、説明としてはここで初登場。
説明として大事なのは以下
{}
で括られたブロックがコードブロック- 中に含まれるコードが1行の場合は
{}
は省略可能 - コードブロックの最終行の値がそのコードブロックの戻り値となり、場合によっては無視しても良い。
- 特に値を返却しないようなコードの場合にはNULL風な特別のオブジェクトである
Unit
が返却される - コードブロックはScalaおn全体で使われる。
パラメタライズされたコードブロック
コードブロックはパラメータを受け取ることが出来る。以下の例ではc
とs
がパラメータとなる。
def add1(c: Int): Int = c + 1 class RepeatString(s: String) { val repeatedString = s + s }
- 実行結果
defined function add1
defined class RepeatString
上記のサンプルの後に以下のように”重要"とかかれたブロックが存在しているので、ここはしっかり見ておく
重要: 上記の他にもコードブロックをパラメタライズする方法がある。以下がその例だ。
val intList = List(1, 2, 3) val stringList = intList.map { i => i.toString }
- 実行結果
上記のようにListに含まれるmap
を使うことでパラメタライズが出来るようだ。以下がこのコードの実行結果。
intList: List[Int] = List(1, 2, 3) stringList: List[String] = List("1", "2", "3")
なるほど。Int
型のリストintList
の中身に対して、toString
関数を適用した結果がstringList
に格納されている。感覚的にはperlのmap
サブルーチンやpythonのリスト内包表記に近い感じがする。
説明を見てみよう。
上記の例ではコードブロックが
List
クラスのmap
関数に渡されている。このmap
関数はコードブロックを一つのパラメータとして必要とする。渡されたコードブロックはリストのメンバー一個に対して一回呼ばれ、上記の例ではtoString
関数によってString
型に変換された値がコードブロックの戻り値として返却される。Scalaはこの文法が多用される傾向にある。この種のコードブロックは無名関数と呼ばれる。この無名関数の詳細については後のモジュールで取り扱う。
なるほど、無名関数。Perl、pythonでは普通に使ってるけどScalaにもあるのね。
その後の説明では、Scalaのスタイルガイドの紹介もしてくれているので、時間を見つけて読んでみよう。
ここでのゴールはあなたが異なる表記に出くわした際に、違う書き方もあるんだ、、と気づいてもらうことにある。Scalaを使っていると、これはより快適で親切なことのように思える。著者は特定のスタイルに引き寄せがちではあるが、その時々の個々の文脈上でより自然に見える表現があることもある。ワンライナーは確かに簡潔になる傾向にはある。しかし複雑なブロックはたいていの場合、より詳細で理解しやすい見た目をしている。コラボレーションをより簡潔に行うために、Scalaのスタイルガイドにざっと目を通してベストプラクティスを見つけることを検討してほしい。
名前付きパラメータとデフォルト値
次の関数の定義を考えてみよう。
という書き出しから始まる。その定義とは以下のものだ。
def myMethod(count: Int, wrap: Boolean, wrapValue: Int = 24): Unit = { ... }
このような関数呼び出す際には、以下のように名前付きのパラメータに値を渡すコードをよく見かけることになる
とのこと。
myMethod(count = 10, wrap = false, wrapValue = 23)
pythonのキーワード引数やね。なので以下のようにパラメータの順番が入れ替わることも可能
myMethod(wrapValue = 23, wrap = false, count = 10)
Bootcampの説明的には大事なのは以下のことになりそう。
- あんまり共通で使用されない関数の特にBool型の引数に対しては名前付きで渡しておくと読みやすくてGood
- 関数が同じ型の長いリストを持つ場合には、引数名を明示して渡すとエラーを減らせる
- クラス定義のパラメータにも名前付き引数を使おう
pythonでよく使ってるので、とっても頷ける説明です。 #とはいえ、あんまりに長くするとそれはそれで読みにくくなるんですが。。変数名の命名がうまくなりたい。。。
この項のもうひとつのトピック”デフォルト値”だが、これについてはその名前そのままで、変更の必要なければ設定しなくても大丈夫!という機能。上記の例ではwrapValue: Int = 24
がデフォルト値ありなので以下のように呼び出し時には設定しなくてもOK
myMethod(wrap = false, count = 10)
ということで、いざ始めて見ると思いの外分量のあったモジュール1だが、これでおしまい。以前にやった勉強の復讐にもなったし、新しいこともいくつか見れたので飛ばさずにやってみてよかったかな、という印象。
次回はモジュール2に入っていよいよChiselで最初のモジュールを書いてみることにする。