ハードウェアの気になるあれこれ

技術的に興味のあることを調べて書いてくブログ。主にハードウェアがネタ。

Scalaの勉強 - 制御構文(2) - match式

先日に引き続きScalaの制御構文の章を。

制御構文の続き

match式

Javaswitchのように複数の分岐を表現する制御式。

構文は以下の通り。

<対象式> match {
  (case <パターン> (if <ガード>)? '=>'
    (<式> (;|<改行>))*
  )+
}

switch相当の処理だが、<パターン>にかける内容が幅広いとのこと。

まずはJavaC言語switch相当の処理から見ていく。

scala> val taro = "Taro"
taro: String = Taro

scala> taro match {
     |     case "Taro"   => "Male"
     |     case "Jiro"   => "Male"
     |     case "Hanako" => "Female"
     | }
res1: String = Male

val taroの中身がString型の"Taro"で、それ自体をパターンに入れるので、結果C言語switch相当の動きになる。

パターンには文字列以外にも数値なども使用可能で、 case _とするとdefault項の扱いになる。

scala> val one = 1
one: Int = 1

scala> one match {
     |     case 1 => "one"
     |     case 2 => "two"
     |     case _ => "other"
     | }
res2: String = one

パターンをまとめる

以下のように|を使って複数のケースをまとめることが出来る。

scala> "abc" match {
     |     case "abc" | "def" =>
     |         println("first")
     |         println("second")
     | }
first
second

パターンマッチによる値の取り出し

Listのようなコレクションの要素の一部にマッチさせることも可能。

scala> val lst = List("A", "B", "C")
lst: List[String] = List(A, B, C)

scala> lst match {
     |     case List("A", b, c) =>
     |         println("b = " + b)
     |         println("c = " + c)
     |     case _ =>
     |         println("nothing")
     | }
b = B
c = C

上記の例では、パターンに入れた1stが要素数が3で先頭のデータが"A"となるリストだと、case List("A", b, c)にマッチするため、bcにListの2番め、3番めの要素が入り、printlnが実行される。

最初に書いた構造文にあったとおり、caseには<ガード>が設定可能((case <パターン> (if <ガード>)? '=>')なため、特定の条件のデータにはマッチしないといった柔軟な条件設定が可能になる。

ということなので試してみよう。先ほどと同様に、3つの要素のList("A", "B", "C")<ガード>を設定することで弾いてみる。

scala> val lst = List("A", "B", "C")
lst: List[String] = List(A, B, C)

scala> lst match {
     |     case List("A", b, c) if b != "B" =>
     |         println("b = " + b)
     |         println("c = " + c)
       |   case _ =>
     |         println("nothing")
     | }
nothing

上記のとおり、最初のcaseで先頭のデータが"A"の3つの要素からなるListにはマッチするが、その後のガード部分でb != "B"が設定されているため、最終的にはcase _にマッチし出力が"nothing"となった。

この機能は強力だな。。。

さらにパターンのネストも可能。

scala> val lst = List(List("A"), List("B", "C"))
lst: List[List[String]] = List(List(A), List(B, C))

scala> lst match {
     |     case List(a@List("A"), x) =>
     |         println(a)
     |         println(x)
     |     case _ => println("nothing")
     | }
List(A)
List(B, C)

上記のようにListを要素に持つList1stを作成し、先頭のListの要素が"A"という条件を設定してmatch式にかけると、最初のcaseにマッチし、axがそれぞれprintlnで出力された。

なお、上記のcase List(a@List("A"), x)a@List("A")@はasパターンと呼ばれるもので、マッチした場合に、そのデータを@の前の変数で使用可能にするとのこと。

中置パターン

上記で書いた以下のパターンは書き換えが可能。

scala> val lst = List("A", "B", "C")
lst: List[String] = List(A, B, C)

scala> lst match {
     |     case List("A", b, c) =>
     |         println("b = " + b)
     |         println("c = " + c)
     |     case _ =>
     |         println("nothing")
     | }
b = B
c = C

書き換えると以下のようになる。

scala> val lst = List("A", "B", "C")
lst: List[String] = List(A, B, C)

scala> lst match {
     |     case "A" :: b :: c :: _ =>
     |         println("b = " + b)
     |         println("c = " + c)
     |     case _ =>
     |         println("nothing")
     | }
b = B
c = C

書き換え後に出てきている"A" :: b :: c :: _を中置パターンと呼ぶ。

リファレンスでの説明はこれかな↓。

\4. The pattern x :: y :: xs matches lists of length ≥ 2, binding x to the list’s first element, y to the list’s econd element, and xs to the remainder.

素数が2以上のリストにマッチし、xは最初の要素に、yは2番めの要素に、xsは残りの要素に紐づく。これも試してみよう。

なおここからは普通にソースコード書いてscalaコマンドで試していく。

object Chuuti {
  def main(args: Array[String]): Unit = {
    val lst = List("A", "B", "C", "D")

    lst match {
      case "A" :: b :: xs =>
        println("b = " + b)
        println("remain = " + xs)

      case _ =>
        println("nothing")
    }
  }
}

上記をscalaコマンドを使って実行すると以下のようになる。

$ scala sample_00.scala
b = B
remain = List(C, D)

確かに、remainには定義したListのうち3番目以降のデータのみが格納されている。

今はまだあんまりイメージがわかないが資料によると、この使い方はScalaプログラミングにおいては頻出するものらしい。

型によるパターンマッチ

パターンには値のみではなく、特定の型を指定することも出来る。方法は以下

  • 名前:マッチする型

さっそく試してみる。

import java.util.Locale

object TypePatternMatch {
  def main(args: Array[String]): Unit = {
    val obj: AnyRef = "String Literal"

    obj match {
      case v:java.lang.Integer =>
        println("Integer")
      case v:String =>
        println(v.toUpperCase(Locale.ENGLISH))
    }
  }
}

STRING LITERAL

確かに、case v:Stringにマッチして、文字列"String Literal"が大文字に変換されたものが表示された。

なお、上記のAnyRefJavaObject型に相当する方で、あらゆる参照型の値を格納することが出来るとのこと。Cのvoid*perlのリファレンスみたいなもんか?

JVMの制約による型のパターンマッチの落とし穴

Scalaを実行するJVMの制約により正しくパターンマッチが行われないケースがあるので注意が必要なようだ。

具体的には以下のように型変数を使った場合が該当する。

object JVMWarn {
  def main(args: Array[String]): Unit = {
    val obj: Any = List("A")

    obj match {
      case v: List[Int]    => println("List[Int]")
      case v: List[String] => println("List[String]")
    }
  }
}

warning: non-variable type argument Int in type pattern List[Int] (the underlying of List[Int]) is unchecked since it is eliminated by erasure
      case v: List[Int]    => println("List[Int]")
              ^
warning: non-variable type argument String in type pattern List[String] (the underlying of List[String]) is unchecked since it is eliminated by erasure
      case v: List[String] => println("List[String]")
              ^
warning: unreachable code
      case v: List[String] => println("List[String]")
                                     ^
three warnings found
List[Int]

Scalaコンパイラの「型消去」により、List[Int]Int部分が消されるのでチェックが出来ない作りらしい。そのため上記match式のcaseは同様のcaseとして扱われることになる。

同様のcaseが複数あった場合、パターンマッチが上から順番に実行される関係で2番目のパターンマッチは到達しないコードになる。

型変数を含む型をパターンマッチする場合にはワイルドカードパターンを行うこといいとのこと。

obj match {
  case v: List[_] => println("List[_]")
}

練習問題

scala new scala.util.Random(new java.security.SecureRandom()).alphanumeric.take(5).toList

以上のコードを利用して、 最初と最後の文字が同じ英数字であるランダムな5文字の文字列を1000回出力してください。 new scala.util.Random(new java.security.SecureRandom()).alphanumeric.take(5).toList という値は、呼びだす度にランダムな5個の文字(Char型)のリストを与えます。なお、以上のコードで生成されたリストの一部分を利用するだけでよく、最初と最後の文字が同じ英数字であるリストになるまで試行を続ける必要はありません。これは、List(a, b, d, e, f)が得られた場合に、List(a, b, d, e, a)のようにしても良いということです。

解答

object MatchPractice {
  def main(args: Array[String]): Unit = {
    for (i <- 1 to 1000) {
      val str = new scala.util.Random(new java.security.SecureRandom()).alphanumeric.take(5).toList
      str match {
        case c :: _ =>
          val new_str = str.slice(0, 4) :+ str(0)
          println("count = " + i + " :: " + str + " -> " + new_str)
        case _ =>
          println("nothing to do")
      }
    }
  }
}

Listの中身をどうやって書き換えるんだ??と思って調べたけどmutableらしいのでとりあえず新しい変数作る方向で書いてみた。

出力は以下。

count = 1 :: List(G, Y, f, x, 5) -> List(G, Y, f, x, G)
count = 2 :: List(A, M, B, Q, C) -> List(A, M, B, Q, A)
count = 3 :: List(V, n, K, B, w) -> List(V, n, K, B, V)
count = 4 :: List(j, h, y, N, R) -> List(j, h, y, N, j)
~中略~
count = 998 :: List(7, Z, 5, A, u) -> List(7, Z, 5, A, 7)
count = 999 :: List(4, W, l, e, q) -> List(4, W, l, e, 4)
count = 1000 :: List(0, 5, 9, V, 4) -> List(0, 5, 9, V, 0)

とりあえずmatch式はめちゃくちゃ強力な構文でこれから頻出しそうなのでもう少しいろいろいじってみたい。

RISC-Vの実装の1つ - SCR1の解析 - riscv-testsの実行

前回のSCR1の記事ではriscv-toolsのビルドを行い、spikeを使ってRISC-Vのバイナリの実行が可能なことを確認した。

tech-diningyo.hatenablog.com

今回はビルドしたRISC-V用のtoolchainを使ってriscv-testsをSCR1向けにビルドしVivadoのシミュレータでシミュレーションを実行してみたい。

SCR1シミュレーション向けのビルド環境について

SCR1の環境では以下の3つのシミュレータ向けの環境が準備されている。

  • vcs
  • modelsim
  • ncsim

上記の中でなんとか実行できそうなのはIntel FPGA向けに機能限定版が提供されているmodelsimくらいか。とはいえ、以下の10,000ラインの制限があるので厳しそう。

SCR1のgithub環境の最上位ディレクトリに登録されているMakefile(以下)にはSCR1のシミュレーション実行に必要なビルド処理が一式含まれている。

scr1
└── Makefile

例えば以下のようなものだ。

  • SCR1自体のコンフィグレーション(SCR1の外部I/Fの選択やサポートする命令セットなど)
  • シミュレーションで実行するテストの選択とビルド(実際のビルドの制御はサブディレクトリのMakefileが行う)
  • シミュレーションの実行(vcs/modelsim/ncsim)

そのためmakeの実行時の引数を変更することで、SCR1のコンフィグレーション、シミュレーション用バイナリのビルド〜指定したシミュレーションの実行までを一気に実行することが可能だ。

SCR1+Vivadoシミュレータでのriscv-testsの実行

基本的な手順はSCR1のgithubリポジトリの手順に従うがVivadoのシミュレータで実行するので、そのあたりだけ適宜変更していく。

github.com

riscv-testsの準備

前項に記載したように、トップのMakefile中にシミュレーション用バイナリのビルドが含まれておりSCR1のリポジトリの手順に従うことでシミュレーション用のバイナリを生成できるので、まずはバイナリを生成していく。

riscv-toolsの設定

riscv-toolsのビルド時にすでに設定していれば不要だが、ビルド後にインストールしたRISC-Vのコンパイラへのパス設定を行っておく

$ cd <RISC-Vのコンパイラのパス>
$ export PATH=$PATH:$PWD

riscv-testsの準備

ロスコンパイラのパス設定が終わったら、riscv-testsを準備する。環境変数設定でriscv-testsの場所を指定する作りになっているので、クローンする場所は自由に変更可能。

一応、SCR1の手順では使用するriscv-testsのリビジョンの指定があるのでそれに従っておく。

$ git clone https://github.com/riscv/riscv-tests
$ cd riscv-tests
$ git checkout a9433c4daa287fbe101025f2a079261a10149225

チェックアウトが終わったら、環境変数RSICV_TESTを設定して、SCR1環境がriscv-testsの場所を認識できるようにする。

$ export RSICV_TEST=<riscv-testsのディレクトリ>

ここまでを行うとSCR1環境のMakefileを使ってシミュレーション用のバイナリを生成することが可能になる。

riscv-testsのビルド

手順に従うと以下のコマンドを実行すると、シミュレーションまでの一連の処理が実行される。

make run_<SIMULATOR> BUS=<AHB, AXI> RVE=<0, 1> RVM=<0, 1> IPIC=<0, 1>

例えば以下のように実行する。

make run_vcs BUS=AHB RVE=1 RVM=1 IPIC=1

上記のコマンドでは

  • SCR1の外部I/FはAXI
  • サポートする命令セットはRV21EM
  • IPICを使用する構成
  • VCSでシミュレーションを実行

となる。

ところで以前の記事でriscv-toolsをビルドした際には32bit-CPU環境のクロスコンパイラを生成している。SCR1のMakfileではデフォルトの設定がriscv64-unknown-elf-になっておりそのまま実行するとエラーになってしまう。そのため、実行前にコマンドの部分だけを修正しておく。

修正する場所はMakefileのL.34のCROSS_PREFIXを修正する

#export CROSS_PREFIX ?= riscv64-unknown-elf-
export CROSS_PREFIX ?= riscv32-unknown-elf-

makeコマンド実行時にビルドされるテストはコマンド実行時のSCR1のコンフィグレーションによって決定される作りとなっており、今回目的とするriscv-testsをビルドするためにはRVM=1を指定する必要がある。

ということで今回は以下のコマンドにてビルドを実行する。

$ make run_vcs BUS=AXI RVM=1

riscv-testsのビルドが通ると以下のように選択したシミュレータによるシミュレーションを実行しようとしてエラーが発生する(以下はVCSを選択した場合)。

/bin/sh: 2: vcs: not found
Makefile:22: ターゲット 'build_vcs' のレシピで失敗しました
make[1]: *** [build_vcs] エラー 127
make[1]: ディレクトリ '/home/dnn-admin/workspace/hw/study/1000_scr1/scr1/src' から出ます
Makefile:95: ターゲット 'run_vcs' のレシピで失敗しました
make: *** [run_vcs] エラー 2

ビルド時の全ログはこちら

バイナリのビルドが終わるとbuildというディレクトリに各種riscv-testsのテストごとに以下の3つのデータが生成される。

通常のフローだと上記のバイナリ生成の後にシミュレータにxxx.hexをデータとして入力することでriscv-testsが実行されるが、今回はシミュレータが違うのでそこをVivado向けに変更していく

Vivado向けの設定

以下のようにシミュレーション実行時にはいくつかの設定を$value$plusargsを使ってシミュレーション環境に渡している。

# scr1_top_tb_ahb.sv - L.105 - L.108
$value$plusargs("imem_pattern=%h", imem_req_ack_stall);
$value$plusargs("dmem_pattern=%h", dmem_req_ack_stall);
$value$plusargs("test_info=%s", s_info);
$value$plusargs("test_results=%s", s_results);

これらの設定の意味は以下の通り。

パラメータ 説明
imem_pattern imem(命令フェッチ用のI/F)からのメモリアクセス発行時のメモリの遅延設定。FFFFFFFF - no delay, 00000000 - random delay, 00000001 - max delay
dmem_pattern dmem(データフェッチ用のI/F)からのメモリアクセス発行時のメモリの遅延設定。設定値はimem_patternと同様。
test_info シミュレーション実行対象となるRISC-Vバイナリのリスト
test_results シミュレーションの実行結果が出力されるファイルのパス

というわけでVivadoシミュレータの設定を行い、上記の設定をシミュレータに渡すように設定を行う必要がある。が、やり方を知らないので調べるところから。

ロジックシミュレータのガイドによると、xsimのコマンド引数に-testplusarg <arg>を渡せば設定が可能なようだ。

引数 説明
-testplusarg <arg> plusargsが$test$plusargsおよび$value$plusargsシステム関数で使用されるように指定します

"PROJECT MANAGER"の"Settings"を開き”Simulation”の項目を開くと画面右側から"Simulation"のタブが選択可能になる↓。

f:id:diningyo-kpuku-jougeki:20180905123717p:plain

この"Simulation"タブの一番下にxim.simulate.xsim.more_optionsという項目があるので、ここに上記4項目の設定を追加する。

-testplusarg imem_pattern=FFFFFFFF -testplusarg imem_pattern=FFFFFFFF-testplusarg test_info=<test_infoのパス> -testplusarg test_results=<出力ログのパス>

なお、上記のtest_infoはmakeコマンド実行時に生成されるシミュレーションを実行するバイナリファイルのリストになっており、RISC-Vのバイナリと同様にbuildディレクトリの下に生成されている。

riscv-testsの実行

前項の設定を行い、シミュレーションを実行してみる。

、、、と、エラーが出て止まった。。

FATAL_ERROR: Vivado Simulator kernel has discovered an exceptional condition from which it cannot recover. Process will terminate. For technical support on this issue, please open a WebCase with this project attached at http://www.xilinx.com/support.

Vivadoを立ち上げたコンソールの方にもスタックトレースが。。

*** Error in `xsim.dir/scr1_top_tb_axi_behav/xsimk': double free or corruption (!prev): 0x0000000001c7ed50 ***

とりあえずメモリ周りで何かが起きた模様。

f:id:diningyo-kpuku-jougeki:20180905123743p:plain

Vivado上のソースを見るとSCR1に接続しているメモリモデルのメモリ初期化の処理で止まっているようなので、とりあえずコメントアウトして再実行。

シミュレーションは実行されたが、メモリからデータが出てこない。

f:id:diningyo-kpuku-jougeki:20180905123821p:plain

そして以下の警告が。

WARNING: File h referenced on /home/dnn-admin/workspace/hw/study/fpga-scr1/scr1/src/tb/scr1_memory_tb_axi.sv at line 148 cannot be opened for reading. Please ensure that this file is available in the current working directory.

scr1/scr1/src/tb/scr1_memory_tb_axi.sv at line 148ここはメモリにRISC-Vのバイナリを読み込む$readmemhの実行部分なのでファイルが開けずにRISC-Vのバイナリがメモリに読み込まれてないことになり、シミュレーションの挙動とも一致する。

always @(negedge rst_n) begin
    //memory = '{SIZE{'0}};
    if(stuff_file.len()>0) $readmemh(stuff_file,memory); # L.148
end

上記のstuff_fileテストベンチのトップ階層で"test_info"ファイルの情報を元に設定されている。

最終的にはtest_infoの情報から実行できるようにしたいが、とりあえずシミュレーションが動くかどうかを確認をするために、上記stuff_fileの部分を直接buildディレクトリ以下にある適当なxxx.hexファイルを示すように書き換える(ここではaddi.hexを使用)。

always @(negedge rst_n) begin
    //memory = '{SIZE{'0}};
    $readmemh("./scr1/build/addi.hex", memory); // 修正後
    //if(stuff_file.len()>0) $readmemh(stuff_file,memory);
end

この状態で実行してみると、、

f:id:diningyo-kpuku-jougeki:20180905123848p:plain

動いた!!

メモリから読み出されている値も"addi.dump"の値と一致している。

00000200 <_start>:
 200:  f1402573     csrr     a0,  mhartid
 204:  e101         bnez     a0,  204         <_start+0x4>

ということで、なんとかビルドしたRISC-Vのバイナリをシミュレーション環境に読み込ませて、SCR1が動く環境が出来た。

ここまでに記載したようにとりあえず...の対策をいくつか施して動かした部分もあるし、実はこのシミュレーションも正常に終わってなかったりしているので、引き続きもう少しシミュレーション環境の調査を行っていくつもりだが、今日はここまで。

Scalaの勉強 - 制御構文(1)

引き続きScalaのお勉強を。
記法の章は各種文法をどう記載するかを書いてあるだけなので飛ばして、今日はScalaの制御構文の章を。

制御構文

「構文」、「式」、「文」とは

Scalaの文法についてを把握する前に、「構文」、「式」、「文」の用語の解説があったのでこれについてまとめておく。

  • 構文(Syntax) : プログラム言語内においてプログラムが構造を持つためのルール。前回出てきたUserクラスの記述全体がこれに当たる。
  • 式(Expression) : プログラムを構成する部分のうち評価が成功すると値になるもの。例えば1 + 1など。
  • 文(Statement) : 式とは逆に評価しても値にならないもの。Scalaの変数定義val i = 1は文

ブロック式

Scalaでは{}で複数の式を囲むと、それ全体が式となる。

読み進めているドワンゴScala資料ではこの{}で括られた式を便宜上「ブロック式」と呼称する。

{ <式1>(;|<改行>) <式2>(;|<改行>) ... }

Scalaの言語仕様としてはただBlocksと書いてある(6.11 Blocks)。

BlockExpr  ::=  ‘{’ CaseClauses ‘}’
             |  ‘{’ Block ‘}’
Block      ::=  BlockStat {semi BlockStat} [ResultExpr]

下記の例では、ブロック式中の最後の式1 + 2がブロック式の値となる。

scala> { println("A"); println("B"); 1 + 2; }
A
B
res0: Int = 3

上記ブロック式の構文に従うとメソッドの呼び出しprintlnも式になるがそれでいいのか気になったので別に試してみた。

scala> { println("A"); println("B");}
A
B

とりあえず、上記のようにprintlnだけでブロック式を終わらせると1 + 2のようにres0: Int = 3という結果が表示されない。

探してみると以下の記事で丁寧に解説してくださっていた。

qiita.com

printlnなどの一見、値を返さなそうなメソッドがあります。しかしScalaではメソッド呼び出しは、式として判別されるため、意味のある結果を返さないUnit型として判断されます。

とのこと。このようなものの結果型を調べるためには:tを式の前につけるといいらしい。

scala> :t println("B");
Unit

なるほど、確かにUnitが返ってきた。

if式

「if文」ではなく「if式」なのね。形式はJavaと一緒(らしい)。

C言語とかとも一緒。

if '('<条件式>')' <then式> (else <else式>)?
  • 条件式Boolean
  • else項は省略可能
scala> var age = 18
age: Int = 18

scala> if(age < 18) {
     |    "18歳未満です"
     |  } else {
     |    "18歳以上です"
     |  }
res1: String = 18歳以上です

上記で書いたようにif式なので値が返却される。else項を省略した場合は先程紹介したUnitが補われた形で扱われる。このUnitJavaではvoidに相当するもので返すべき値が無いときに使用され()を値として持つ。

以下、ScalaのStandard LibraryのUnitの項目からの引用。

Unit is a subtype of scala.AnyVal. There is only one value of type Unit, (), and it is not represented by any object in the underlying runtime system. A method with return type Unit is analogous to a Java method which is declared void.

練習問題

var age: Int = 5という年齢を定義する変数とvar isSchoolStarted: Boolean = falseという就学を開始しているかどうかという変数を利用して、 1歳から6歳までの就学以前の子どもの場合に“幼児です”と出力し、それ以外の場合は“幼児ではありません”と出力するコードを書いてみましょう。

解答

scala> var age: Int = 5
age: Int = 5

scala> var isSchoolStarted: Boolean = false
isSchoolStarted: Boolean = false

scala> if((1 <= age) && (age <= 6) && (!isSchoolStarted)) {
     |   println("幼児です")
     | } else {
     |   println("幼児ではありません")
     | }
幼児です

while式

while式もJavaC言語と一緒。

while '(' <条件式> ')' 本体式

if式と一緒でwhileも式になる。返却すべき値がないのでUnit型の()が返却される。

scala> while (i <= 10) {
     |     println("i = " + i)
     |     i = i + 1
     | }
i = 1
i = 2
i = 3
i = 4
i = 5
i = 6
i = 7
i = 8
i = 9
i = 10

do while式もある。

scala> do {
     |     println("i = " + i)
     |     i = i + 1
     | } while (i < 10)
i = 1
i = 2
i = 3
i = 4
i = 5
i = 6
i = 7
i = 8
i = 9

Scalaにはbreak文やcontinue文は無いらしい。ただ後ほど扱う高階関数を使えばあまり必要にならないとのこと。

練習問題

do whileを利用して、0から数え上げて9まで出力して10になったらループを終了するメソッドloopFrom0To9を書いてみましょう。loopFrom0To9は次のような形になります。???の部分を埋めてください。

def loopFrom0To9(): Unit = {
  var i = ???
  do {
    ???
  } while(???)
}

解答

def loopFtom0To9(): Unit = {
    var i = 0
    do {
        println("i = " + i)
        i = i + 1
    } while (i < 10)
}

for式

構文は以下の通り。

for '(' (<ジェネレータ>;)+ ')' '<本体>' 

ジェネレータを1つ以上使うことが出来る。またジェネレータは以下の通り。

<ジェネレータ> = x <- <式>

言語仕様によると、Guard条件も含めれるみたい。

Expr1          ::=  ‘for’ (‘(’ Enumerators ‘)’ | ‘{’ Enumerators ‘}’)
                       {nl} [‘yield’] Expr
Enumerators    ::=  Generator {semi Generator}
Generator      ::=  Pattern1 ‘<-’ Expr {[semi] Guard | semi Pattern1 ‘=’ Expr}
Guard          ::=  ‘if’ PostfixExpr

さっそく例を。

scala> for (x <- 1 to 5; y <- 1 until 5) {
     |      println("x = " + x + " y = " + y)
     | }
x = 1 y = 1
x = 1 y = 2
x = 1 y = 3
x = 1 y = 4
x = 2 y = 1
x = 2 y = 2
x = 2 y = 3
x = 2 y = 4
x = 3 y = 1
x = 3 y = 2
x = 3 y = 3
x = 3 y = 4
x = 4 y = 1
x = 4 y = 2
x = 4 y = 3
x = 4 y = 4
x = 5 y = 1
x = 5 y = 2
x = 5 y = 3
x = 5 y = 4

for式は他の言語とはだいぶ違って独特。ジェネレータを複数持てるのでループ構造がシンプルになるのはいい感じ。pythonzip関数が一番近いかな。ちなみにtountilの違いは以下のように示す範囲の違いにあるようだ。

表現 範囲
1 to 10 1から9まで
1 until 10 1から10まで

以下のようにList(コレクションの一つ)を一つずつ辿って処理することも出来る。

scala> for(e <- List("A", "B", "C", "D", "E")) println(e)
A
B
C
D
E

perlpythonでよく使う形のループ処理も可能ということか。これだけでも結構強力な処理が可能だな。

上記のコレクションを加工することにも使えるという例もあった。

scala> for(e <- List("A", "B", "C", "D", "E")) yield {
     |   "Pre" + e
     | }
res0: List[String] = List(PreA, PreB, PreC, PreD, PreE)

yieldを使うことで、”コレクションの要素を加工して返す”という処理にすることが出来て、この動きをfor-comprehensionと呼ぶことがある。

練習問題

1から1000までの3つの整数a, b, cについて、三辺からなる三角形が直角三角形になるような a, b, cの組み合わせを全て出力してください。直角三角形の条件にはピタゴラスの定理を利用してください。 ピタゴラスの定理とは三平方の定理とも呼ばれ、a ^ 2 == b ^ 2 + c ^ 2を満たす、a, b, c の長さの三辺を持つ三角形は、直角三角形になるというものです。

解答

for (a <- 1 until 1000; b <- 1 until 1000; c <- 1 until 1000) {
    if ((a * a) == (b * b) + (c *c)) {
        println("(a, b, c) = (" + a + ", " + b + ", " + c + ")")
    }
}

愚直にif式使って書いたら、解答はGuard条件を使ったもっとスマートなものだった。

ひとつ気になってScalaのべき乗の書き方を調べてみたが、python**のように演算子を使って書くものはなくMathライブラリを使うみたい。

この章は残りはmatch式のみだが結構な分量がありそうなので今日はここまでにして残りは次回。

Scalaの勉強 - sbtを使ったコンパイルと実行

引き続きScalaの学習をドワンゴScalaの研修資料を見ながらやっていく。
今日は"sbtでプログラムをコンパイル・実行する"。

sbtプロジェクトの作成

適当な名前でプロジェクト用のディレクトリを作って、その中にScalaのプログラムのファイルとビルド用のファイルbuild.sbtを置けばいい。
資料ではsandboxというディレクトリになっているのでそれに従って進める。

sandbox
├── HelloWorld.scala
└── build.sbt

記法も含めて慣れていくために、写経する。

object HelloWorld {
    def main(args: Array[String]): Unit = {
        println("Hello, World!!")
    }
}
  • build.sbt
scalaVersion := "2.12.6"

scalacOptions ++= Seq("-deprecation", "-feature", "-unchecked", "-Xlint")

sbtプロジェクトの実行

上記の2つのファイルを作成したあと、sandboxディレクトリに移動し

$ sbt

と実行すると、勝手にbuild.sbtの解析が始まる。 最初、上記のscalacOptionsをscalaOptionsと勘違いしており、以下のように”そんなオプションは無い!”と怒られてしまった。

[info] Updated file /home/dnn-admin/workspace/hw/study/2000_chisel/sandbox/project/build.properties: set sbt.version to 1.2.1
[info] Loading settings for project global-plugins from idea.sbt ...
[info] Loading global plugins from /home/dnn-admin/.sbt/1.0/plugins
[info] Loading project definition from /home/dnn-admin/workspace/hw/study/2000_chisel/sandbox/project
[info] Updating ProjectRef(uri("file:/home/dnn-admin/workspace/hw/study/2000_chisel/sandbox/project/"), "sandbox-build")...
[info] Done updating.
/home/dnn-admin/workspace/hw/study/2000_chisel/sandbox/build.sbt:3: error: not found: value scalaOptions
scalaOptions ++= Seq("-deprecation", "-feature", "-unchecked", "-Xlint")
^
[error] Type error in expression
Project loading failed: (r)etry, (q)uit, (l)ast, or (i)gnore? q

build.sbtに誤りがなければ以下のようにsbtのシェルが起動する。

[info] Loading settings for project global-plugins from idea.sbt ...
[info] Loading global plugins from /home/dnn-admin/.sbt/1.0/plugins
[info] Loading project definition from /home/dnn-admin/workspace/hw/study/2000_chisel/sandbox/project
[info] Loading settings for project sandbox from build.sbt ...
[info] Set current project to sandbox (in build file:/home/dnn-admin/workspace/hw/study/2000_chisel/sandbox/)
[info] sbt server started at local:///home/dnn-admin/.sbt/1.0/server/3f8de47fa7c5e96122b9/sock
sbt:sandbox> 

sbtシェルでrunを実行するとHellowWorld.scalaが実行される。

sbt:sandbox> run
[info] Updating ...
[info] Done updating.
[info] Compiling 1 Scala source to /home/dnn-admin/workspace/hw/study/2000_chisel/sandbox/target/scala-2.12/classes ...
[info] Done compiling.
[info] Packaging /home/dnn-admin/workspace/hw/study/2000_chisel/sandbox/target/scala-2.12/sandbox_2.12-0.1.0-SNAPSHOT.jar ...
[info] Done packaging.
[info] Running HelloWorld 
Hello, World!!
[success] Total time: 2 s, completed 2018/09/03 18:21:25
sbt:sandbox> 

runコマンドでは、その環境下におけるScalaプログラムからmainメソッドを持っているオブジェクトを探して実行をしてくれるとのこと。
C言語とかの統合開発環境で勝手にMakefile作ってくれるのに似てる感じだけど、調べてみるといろいろ多機能だわ、これは。。
以下のsbtのリファレンスにによく使われるコマンドが載っている。

sbt Reference Manual — 実行

こちらの例題でみる sbt の方には自動ビルドやテストの継続実行などのフローが一通り体験できるチュートリアルがあるので後ほどトライしてみることにして資料に戻ろう。 引き続き、User.scalaを作って実行していく。

class User(val name: String, val age: Int)

object User {
    def printUser(user: User) = println(user.name + " " + user.age)
}

後々出てくるんだろうけど、これがScalaのクラスの定義になるのか。
Userクラスを宣言して、nameageがメンバ変数(と呼ぶのか?)になって、printUserというメソッドを持つという感じか。
このUserクラスを定義したUser.scalaがあるディレクトリでsbt consoleを起動すると、以下のように作成したクラスが使用できる。

scala> val u = new User("dnn-developer", 13)
u: User = User@4a3557e0

scala> User.printUser(u)
dnn-developer 13

ちなみにディレクトリを変更すると、以下のように”そんなクラスは無い”とエラーになる。
この辺はCのインクルードパスとかpythonのモジュールのインポートとかと一緒で、カレントディレクトリは探索パスに加えられるということみたい。

scala> val u = new User("dnn-developer", 13)
<console>:11: error: not found: type User
       val u = new User("dnn-developer", 13)

scalaコマンドでの実行

sbtを使わない場合は直接scalaコマンドにファイルを入力することでも実行可能。

$ scala ./HelloWorld.scala
Hello, World!!

scalacコマンドに流すとコンパイルされてバイナリコード(HelloWorld$.classというテキストじゃないファイル)が生成されてるように見える。

$ scalac ./HelloWorld.scala

見えるんだけど、これ何で動くの??
このあたりの関係がまだ良く理解できてない。 一通り文法学んでから、広げて調べていくことにしよう。

Scalaの勉強 - 基本的な文法(2)

前回、ドワンゴScala研修資料のScalaの基本を読み進めていた。
そのままなぞっているだけだが意外に長くなってたので今回はその続き。

tech-diningyo.hatenablog.com

変数の基本

ということで変数の基本から。

変数val

Scalaの変数valの宣言は以下。

scala> val x = 3 * 2
x: Int = 6

C言語みたいに型の指定は必要なく型推定が働く。
このあたりはperlとかpythonチック。
上で定義したxString型として扱うとエラーになるらしい。
ということでやってみる。

scala> val x = 3 * 2
x: Int = 6

scala> x = "string"
<console>:12: error: reassignment to val
       x = "string"

確かにエラーが返ってきた。
ん??違うか。エラーメッセージは再代入不可って言ってる気がする。
と思って読み進めたらvalは再代入不可と書いてあった。
Cとかのconstの扱いか。Javaではfinalな変数というらしい。
ということで改めて再代入が可能なもうひとつの変数宣言varで試してみる。

変数var

先ほどのvalの代わりにvarにするだけ。

scala> var x = 1
x: Int = 1
  • Int型をString型へ
scala> var x = 1
x: Int = 1

scala> x = "String"
<console>:12: error: type mismatch;
 found   : String("String")
 required: Int
       x = "String"
           ^
  • Int型をLong型へ
    アップキャスト的な動きはどうだろうと思い試してみる。
scala> var x = 1
x: Int = 1

scala> x = 1L
<console>:12: error: type mismatch;
 found   : Long(1L)
 required: Int
       x = 1L
           ^
  • Int型をDouble型へ
    結果は同じだ取ろうけど一応、Int型をDouble型へ。
scala> var x = 1
x: Int = 1

scala> x = 1.0
<console>:12: error: type mismatch;
 found   : Double(1.0)
 required: Int
       x = 1.0
           ^
  • Long型をInt型へ
    逆はどうだろう。
scala> var x = 1L
x: Long = 1

scala> x = 1
x: Long = 1

なるほど、これは包含されてるからOKなのか。
計算するときに複数の型が混ざるのはどうなんだろう。

scala> x = x + 1.0
<console>:12: error: type mismatch;
 found   : Int
 required: ?{def +(x$1: ? >: Double(1.0)): ?}
Note that implicit conversions are not applicable because they are ambiguous:
 both method int2long in object Int of type (x: Int)Long
 and method int2float in object Int of type (x: Int)Float
 are possible conversion functions from Int to ?{def +(x$1: ? >: Double(1.0)): ?}
       x = x + 1.0
           ^
<console>:12: error: overloaded method value + with alternatives:
  (x: Int)Int <and>
  (x: Char)Int <and>
  (x: Short)Int <and>
  (x: Byte)Int
 cannot be applied to (Double)
       x = x + 1.0
             ^

なんかいっぱい出た。

前回の記事で出てきたasInstanceOfは??

scala> x = 1
x: Int = 1

scala> x = x.asInstanceOf[Double] + 1.0
<console>:12: error: type mismatch;
 found   : Double
 required: Int
       x = x.asInstanceOf[Double] + 1.0
                                  ^

なんか怒られた。
ひとまず、結構厳し目だということがわかってればいいので、この辺の挙動はおいおい調べることにしよう。

変数の型の指定

変数名の後ろの: <型名>を付ければ、型指定が可能とのこと。

scala> var x : Double = 1
x: Double = 1.0

確かにDouble型になった。

練習問題

    1. ¥3,950,000を年利率2.3%の単利で8か月間借り入れた場合の利息はいくらか(円未満切り捨て)

解答

scala> var x : Double = 3950000 * 0.023 / 12 * 8
x: Double = 60566.666666666664

scala> x.asInstanceOf[Int]
res1: Int = 60566

このasInstanceOfは通るのか。ということで¥60,566が答え。

    1. 定価¥1,980,000の商品を値引きして販売したところ、原価1.6%にあたる¥26,400の損失となった。割引額は定価の何パーセントであったか

解答

scala> var x = 26400 / 0.016
x: Double = 1650000.0

scala> x = x - 26400
x: Double = 1623600.0

scala> x = 1980000 - x
x: Double = 356400.0

scala> x = x / 1980000
x: Double = 0.18

答えは0.18で18%。

これでScalaの基本は終了。

Scalaの勉強 - 基本的な文法(1)

Scalaのお勉強ってことで、引き続きドワンゴの研修資料を読んで勉強していく。
今日はScalaの基本の章を。
基本的には章の構成のとおりに進めていく感じです。

REPLでScalaを触ってみる

Hellow, World!

前回の記事でIntellij IDEA上でのHello World!は実行したが、今回はsbt console上で実行してみる

scala> println("Hello, World!)
Hello, World

printlnを外してみると、、

scala> "Hello, World!!"
res0: String = Hello, World!!

なるほど、式の評価の結果がString型になっている、と。

練習問題

ここからは練習問題。以下のリテラルをsbt consoleで出力してみる。

  • 0xff
scala> 0xff
res1: Int = 255

これは普通にInt型。

  • 1e308
scala> 1e308
res2: Double = 1.0E308

これも普通にDouble

  • 9223372036854775807L
  • 9223372036854775808L
scala> 9223372036854775807L
res3: Long = 9223372036854775807
scala> 9223372036854775808L
<console>:1: error: integer number too large
       9223372036854775808L
       ^

なるほど、LをつけたのでLong型になる。そして最大値。
なので+1すると、範囲を超えてエラーになる、、、と。エラーになるのはちょっと新鮮。

  • 9223372036854775807
scala> 9223372036854775807
<console>:1: error: integer number too large
       9223372036854775807
       ^

こちらはL無しなのでInt型として扱われるが、最大値を超えているのでエラーになる。

  • 922337203685477580.7
scala> 922337203685477580.7
res4: Double = 9.2233720368547763E17

大きい数字、かつ小数の値なのでDouble型。

  • 1.00000000000000000001 == 1
scala> 1.00000000000000000001 == 1
res5: Boolean = true

==で比較ができる、かつ精度の関係でtrue判定になる。

  • "\u3042"
  • "\ud842\udf9f"
scala> "\u3042"
res6: String = あ
scala> "\ud842\udf9f"
res7: String = 𠮟

2byte文字も扱える。
と、ここまでのことから明示的な型の指定はなく推測がされるということもついでにわかった。

簡単な計算

引き続き、REPLで試していく。まずは足し算から。

scala> 1 + 2
res8: Int = 3

整数同士の足し算なので、結果もInt型が返ってくる。 他のプログラム言語と同様に+, -, *, /, %Int型で使用可能↓

Int型

scala> 2 * 2
res11: Int = 4

scala> 4 / 2
res12: Int = 2

scala> 4 % 3
res13: Int = 1

Double型

同じことは浮動小数点型でも可能。

scala> 1 + 2
res16: Int = 3

scala> 2.2 * 2
res17: Double = 4.4

scala> 4.5 / 2
res18: Double = 2.25

scala> 4.0 % 2.0
res19: Double = 0.0

scala> 4.0 % 3.0
res20: Double = 1.0

scala> 4.0 % 2.1
res21: Double = 1.9

scala> (4.5).asInstanceOf[Int]
res22: Int = 4

scala> (4.0).asInstanceOf[Int]
res23: Int = 4

Double型の変数にメソッドがついててその中の一つasInstanceOfを使ってキャスト??

qiita.com

Double型の、、ではなくAny型のメソッドとのこと。それで調べ直した。

www.scala-lang.org

練習問題

再度、ここからは練習問題。
この辺の感覚は他のプログラミング言語と一緒。

  • 2147483647 + 1
scala> 2147483647 + 1
res0: Int = -2147483648

Int型とInt型なので結果もInt

  • 9223372036854775807L + 1
scala> 9223372036854775807L + 1
res1: Long = -9223372036854775808

Long型とInt型なのでLongにアップキャスト(Scala的にこういうのかは知らないが。。)。

  • 1e308 + 1
scala> 1e308 + 1
res2: Double = 1.0E308

Double型とInt型でDouble型。

  • 1 + 0.0000000000000000001
scala> 1 + 0.0000000000000000001
res3: Double = 1.0

Double型とDouble型の加算だけど丸め誤差の関係で1.0になる

  • 1 - 1
scala> 1 - 1
res4: Int = 0

Int型同士の減算なのでInt

  • 1 - 0.1
scala> 1 - 0.1
res5: Double = 0.9

Double型とInt型でDouble型。

  • 0.1 - 1
scala> 0.1 - 1
res6: Double = -0.9

Double型とInt型でDouble型。

  • 0.1 - 0.1
scala> 0.1 - 0.1
res7: Double = 0.0

Double型同士なのでDouble型の0になる。

  • 0.0000000000000000001 - 1
scala> 0.0000000000000000001 - 1
res8: Double = -1.0
  • 0.1 * 0.1
scala> 0.1 * 0.1
res9: Double = 0.010000000000000002

Double型とDouble型の減算+誤差で1.0になる

  • 20 * 0.1
scala> 20 * 0.1
res10: Double = 2.0

Double型同士の乗算でDouble

  • 1 / 3
scala> 1 / 3
res11: Int = 0

Int型同士の除算で小数になるので結果が0

  • 1.0 / 3
scala> 1.0 / 3
res12: Double = 0.3333333333333333

こっちは1.0Double型を明示したので結果がDouble型になる。

  • 1 / 3.0
scala> 1 / 3.0
res13: Double = 0.3333333333333333

こっちも同様3.0Double型を明示したので結果がDouble型になる。

  • 3.0 / 3.0
scala> 3.0 / 3.0
res14: Double = 1.0

Double型同士の除算

  • 1.0 / 10 * 1 / 10
scala> 1.0 / 10 * 1 / 10
res15: Double = 0.01

1.0 / 10の結果がDouble型なので結果も正常に計算できてDouble型の値が得られる。

  • 1 / 10 * 1 / 10.0
scala> 1 / 10 * 1 / 10.0
res16: Double = 0.0

こちらは1 / 10の結果がInt型で0になり、その結果と後続の式が計算されるので結果が0になる。

ということで、今日はここまで。

Scalaの勉強 - 統合開発環境IntelliJ IDEAのインストール

前回の記事では、Scalaの環境を整えるためにsbtをインストールしてsbt consoleコマンドでsbt consoleが立ち上がることを確認した。

tech-diningyo.hatenablog.com

今回も引き続き、ドワンゴが公開してくれているScalaの研修資料を参考に統合開発環境をインストールしていく。

IntelliJ IDEA

本格的にScalaの勉強をスタートする前にIDE環境も整えておく。
jetbrain社の開発しているJavaIDEIntelliJScalaの開発も可能とのことなので、これをインストールする。
ありがたいことに、無償版のCommunity EditionでOK!!

www.jetbrains.com

インストール

インストールはdownloadのページからtarのデータをダウンロードして任意のディレクトリに展開するだけでOK

$ tar xf ideaIC-2018.2.2-no-jdk.tar.gz

起動

解凍して出来たディレクトリの下のbinに入り、idea.shを実行するとIntelliJ IDEAが起動する。

$ cd idea-IC-182.4129.33/bin
$ ./idea.sh

1. 設定の引き継ぎ

起動すると、以前の設定を引き継ぐかどうかの画面が出る。以前に使っていた場合はその時のディレクトリを示せばいい。
今回は新規インストールなので、"Do not import things"を選択。

f:id:diningyo-kpuku-jougeki:20180826150948p:plain

2. プライバシーポリシーへの同意

お次はPrivacy Policy。最後までスクロールすると”Accept"が選べるようになるので、最後まで送ってAccept。

f:id:diningyo-kpuku-jougeki:20180826151023p:plain

3. 統計情報の送信への同意

その次には、Data Sharingの質問があるので、お好みの選択を。

f:id:diningyo-kpuku-jougeki:20180826151207p:plain

4. IntelliJ IDEAの外観設定

この次からはIntelliJ IDEAの設定になる。まず最初はGUIの外観の選択。お好みの画面を選択。

f:id:diningyo-kpuku-jougeki:20180826151259p:plain

5. デスクトップショートカットの作成

次はデスクトップにショートカットを作るかどうか。チェックボックスにチェックを入れておけばデスクトップにショートカットが作成される。

f:id:diningyo-kpuku-jougeki:20180826151324p:plain

6. ターミナル用起動スクリプト

すでにパスの通った場所に作れば、そのコマンドでターミナルから起動可能になる。

f:id:diningyo-kpuku-jougeki:20180826152916p:plain

7. 各種ツールの設定

不要なものがあればDisableにしておくといい。

f:id:diningyo-kpuku-jougeki:20180826152957p:plain

8. プラグインの設定

あとからでも設定可能だが、プラグインをダウンロードして入れるかどうかが問われる。
ScalaのためIntellij IDEAを入れるのでScalaをインストールする。

f:id:diningyo-kpuku-jougeki:20180826153459p:plain

ここまでやると起動が行われるが、最初の./idea.shを管理者権限で実行しておらず5./6.で各種ショートカットを作成する場合にはパスワードが求めれられる。

f:id:diningyo-kpuku-jougeki:20180826153704p:plain

9. プロジェクト作成

以下のような画面が出て操作が可能になるので"Create New Project"を選択
#この後の"サンプルプロジェクトの実行"でドワンゴのサンプルプロジェクトを使った動作確認を行うので、そちらを実行したほうがいいかも。

f:id:diningyo-kpuku-jougeki:20180826153752p:plain

Scalaのインストールが成功していれば"New Project"画面の左の一覧から"Scala"を選択できて、sbtを使ったプロジェクトが作成可能となる。

f:id:diningyo-kpuku-jougeki:20180826154358p:plain

適当なプロジェクト名を入力し、場所を選んで"Finish"を押せばプロジェクト作成完了

f:id:diningyo-kpuku-jougeki:20180826154310p:plain

サンプルプロジェクトの実行

ドワンゴが公開してくれているサンプルプロジェクトを使って動作を確認していく。

1. サンプルプロジェクトのインポート

適当なディレクトリにgithubのサンプルプロジェクトのデータをクローンしてくる。

$ git clone https://github.com/dwango/scala-sandbox 

今度は上記のgithubデータを使うためプロジェクトをインポートする。

f:id:diningyo-kpuku-jougeki:20180826155457p:plain

インポートの際に、クローンしたデータの中に含まれるbuild.sbtを選択すると、以下のような画面が開くのでそのままOKを押す。

f:id:diningyo-kpuku-jougeki:20180826181054p:plain

しばらく待っていると、ビルド画面の同期が終わり以下のような画面になる。

f:id:diningyo-kpuku-jougeki:20180826181422p:plain

2. バージョンの修正

ここまでの手順でインストールされたScalaは2.12.6だが、このサンプルプロジェクトはScala2.12.4で作られておりこのままだと実行が出来ないのでbuild.sbtで指定されるバージョンを修正する。 build.sbtを開き、scalaVersionのバージョンを2.12.6にする

//scalaVersion := "2.12.4"
scalaVersion := "2.12.6"

3. 実行

src->main->scala->"Hello World"を右クリックで開き、Run "Hello World"を選ぶと実行が出来て、Run画面に"Hello, World!"が表示される。

f:id:diningyo-kpuku-jougeki:20180826182604p:plain

ということで、IntelliJ IDEAの環境構築はこれで完了。