SE(たぶん)の雑感記

一応SEやっている筆者の思ったことを書き連ねます。会計学もやってたので、両方を生かした記事を書きたいと考えています。 でもテーマが定まってない感がすごい。

(小話)他言語と比べたScalaの楽しいところ

最近、Scalaを仕事で使っているのは、以前の記事で述べたとおりです。

それとはちょっと離れますが、Scala学習の一環として社内でプログラミングの問題を解こうという催し?が行われています。私は解くのは義務ではないのですが、解けないと示しがつかないのと、Scalaに慣れるために問題を解いています。

ちょっとScalaに慣れてきたので、こういうところ楽しいよ!という部分を、他言語と比べて書いてみます。

式の考え方

Scalaの式は、値を返すものを指します。

ifforも、Scalaでは式となります。他の言語でいうif文やfor文も式です。

正確に言うと、Scalaのforは糖衣構文であり、適切な構文に展開されるようです。

他言語では文と扱われるものが、式になるものがある

forだろうがifだろうが、式として扱われるのは前述のとおりですが、C#では書けないこんな記述もScalaなら通ります。

//コンパイルエラーになる
public string Get(string a)
{
    var result = if (a == "a") { "correct"} else { "incorrect"}
    return result;
}

もっとも、この程度なら以下の書き方で代替できます。(三項条件演算子

public string Get(string a)
{
    return a == "a" ? "correct" : "incorrect";
}

Scalaだと、これで動きます。

def get(a: String): String = {
  if (a == "a") "correct" else "incorrect"
}

// もしくは
def get(a: String): String = {
  a match {
    case "a" => "correct"
    case _ => "incorrect"
  }
}

条件が増えた場合、C#だとswitch文を使います。すると、三項条件演算子みたいな簡潔な書き方はできなくなります。しかし、Scalaならmatchも式として扱われるので、ほとんど変わらない書き方ができます。

def get(a: String): String = {
  a match {
    case "a" => "correct"
    case "b" => "next"
    case "c" => "last"
    case _ => "incorrect"
  }  
}

C#でも、switch式追加されようとしているようです。

ufcpp.net

今使うと、設定入れないとコンパイルエラーです。構文から見ても、Scala等の関数型言語と同じものを実現しようとしていますね…C#すごいや。

f:id:hiroronn:20190506190408p:plain
vs2019で試したもの

型チェックが優秀

上記のようなmatch式は、右辺の型が揃っていないとコンパイルエラーとなります。

考え方としては、

値aを受け取って、文字列を返す式

です。C#Pythonなら、ラムダ式が想像つきやすいでしょうか。

型名の省略が可能(型推論が優秀)

型が推測できる場合、型名を省略できます。

ただし、メソッド引数の型は省略できませんし、publicのメソッドの戻り値は明示するほうが良いです。

省略できる = 書かなくてよい、ではないです。

例えば、上で書いたget関数ですが、以下のように書けます。

def get(a: String) = {
  a match {
    case "a" => "correct"
    case "b" => "next"
    case "c" => "last"
    case _ => "incorrect"
  }
}

戻り値はStringであると明らかなので、省略できます。IntelliJ IDEAで見ると表示上は型が補われます*1が、明示したほうが良いでしょう。

f:id:hiroronn:20190507075431p:plain

メソッドのreturnが省略できる

ここでは、自分でdefで定義する構文のことを、メソッドと呼びます。

今まで特に触れずに書いていましたが、戻り値を返すreturnは省略できます。正確には

メソッドの最後に記述された値を戻り値とする

という動作になっています。ここで、式が値を返すという仕様がさらに輝きます。

なお、通常のreturnも使えます。プログラミングの問題解く際、それ以上計算が不要な場合に計算を打ち切る場合等に使う場合が多いです。

この仕様、コードを書く上ではとても楽ですが、returnの書き忘れでコンパイルエラーとならないので、C#Python*2に慣れていると時々間違います。こういうことが起こらないよう、細かくメソッドを区切るのがScalaらしい書き方なのかもしれません。

Scalaらしい書き方とは…難しいですね。

決まり文句が少なく、書きやすい

静的言語に対するPythonの特徴と似ていますが、Scalaは他言語に比べると比較的決まり文句が少ない印象です。

これはいくつかの理由があります。列挙すると、

  • returnが省略できる(前述)
  • 戻り値が省略できる(前述)
  • 変数宣言時の型宣言を省略できる
  • 行末のセミコロンは基本的に不要
  • とりあえず{}で囲えば式になる(乱暴)
  • 不変のクラスが欲しかったらとりあえずcase classにする

という感じです。

(一つだけ)しんどいところ

単に慣れていないだけ、という部分もあるでしょうが。

型推論コンパイラが優秀な反動もありますが、IDEが無いと開発はしんどい気がしています。

式を連鎖させることができるのですが、最終的な型が何なのか、IDEが無いと分からなくなります。特にfor<-という部分。これも関数なので、なんなのか本当に分からなくなります。

for(i <- Range(1, 5)) println(i + 2)

TraveersalLike.foreachの呼び出しと解釈されます。

for(i <- Range(1, 5)) yield i + 2

TraversalLike.mapの呼び出しと解釈されます。

forOption等、Scalaを使う上で重要なクラスでも使うので、ループのための構文であるという固定観念から抜け出さないと理解しがたいです。

ちなみに、実際に何の呼び出しになるかは、こんな感じでIDEのヒントで見れます。

f:id:hiroronn:20190507084813p:plain
IntelliJでのヒント

forでいろんなことができる、というのは楽でもあり、理解しがたいという意味でしんどいです。

おわりに

Scalaに慣れてきた気がする、という今日この頃です。できているのか自分では疑問です。

この前久々にC#やっていて、テストコード書くときに

public IEnumerable<~> Create() {
    return for(int i = 1;i < 10; i++) {…}
}

みたいなのを無意識に書いていた*3のに自分で笑ってしまったので、きっと慣れてきたのだと思います。

いろんな言語やると、言語の違いや思想が分かってよいですね。

*1:IntelliJの設定によって表示非表示が切り替えられる

*2:Pythonはreturnを書き忘れても動く。ただ、return書かないと戻り値が返せない

*3:Enumerable.Range(1, 10).Select()みたいな感じで書き直した