より良いエンジニアを目指して

1日1つ。良くなる!上手くなる!

Scastieとドワンゴ研修テキストでScalaを学ぶ〜なぜプログラミング言語は増えていくのか

ドワンゴによるScalaの研修テキストで学んでみることにしました。

きっかけとか

tenshoku.mynavi.jp

— 川上:仕事がつまらないから、仕事に対してモチベーションを発揮できないから、せめて新しい言語を使って刺激を受けたい、っていうのが本音なんですよ。僕のビジネスのスタンスだってそうなんですよ。成功すると分かっていても、同じやりかたのビジネスなんて退屈でテンションを維持できないからやりたくないもん。

とか。若い人の方が新しいプログラミング言語を取得したくないという調査結果もあったりします。

japan.zdnet.com

— 川上:複数の言語を使っていった方が、プログラムがコンピュータ上でどう動作しているかのイメージを持ちやすい。エンジニアの長期的な体力・筋力をつけるために、複数の言語を使うのはメリットがあると判断しました。ただ、複数の言語を使わないと時代に取り残されるとか云々の話自体は、たわごとだと思ってますw

とか

— 川上:僕はやっぱり性能を追求するんだったら、C++で書けよと思ってるんですよね。『ドワンゴ』って、もともと通信ゲームサーバーを作っていて、全部C++で書いていたんです。スクリプト言語は楽してプログラミングするために使うもので、だからPHPで十分だった。でも、『ドワンゴ』も今はC++を書ける人は少数派になってしまったから、PHPだけだと面白くないし、脳が腐るとか怒り出すエンジニアが出てくるのもしょうがない。だからScalaの利用も促進しています。でも、本当に性能を考えるならC++を使うべきだと思います。

KotlinやScala、Goとか言語が登場している、そんな今のトレンドを上手く説明した発言で、なるほどなと。

Scastie

scastie.scala-lang.org

研修テキストでは環境構築を紹介しているのですが、Scastieを使うことにします。

ローカルにほとんど使われずに残っているGoフォルダのようにしたくないので、学ぶ段階ではこういうサービスからのスタートが効率的だと思いました。

  • Paizaでも良いが、研修資料がREPL前提なのでこちらの方が近い感覚で言語を動かしながら学べる
  • GitHubのアカウントでログイン可能
  • Command+Enterでコードを実行

f:id:rimever:20190209183824p:plain

動かして気づいた点

varとval

変数宣言にはvarとvalの二つがありますが、valで代入した変数は再代入できないようです。

Swiftのletなどの立ち位置。

var x = 3 * 3
x = 3 * 4

val y = 3 * 3
y = 3 * 4 // reassignment to val(再代入するにはvalにする)

for文

for (x <- 1 to 5; y <- 1 until 5) {
  println("x = " + x + " y = " + y)
}

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

for (x <- 1 to 5; if x == 3) println("x = " + x)
  • 1行で二つ分のfor文を実現できる。「for (x <- 1 to 5; y <- 1 until 5) {」
  • for文で、本来のカウンタを回すだけでなく、配列をループさせられる。
  • ポイントは<-という逆アローでループさせる値を指定する。
  • 1 to 5とか 1 until 5と数値範囲指定は直感的。最後の値は含むかで混乱することがある。
  • さらにfor文の中にifを含めることができる。

match文

いわゆるselect〜case文。

配列に対して行えるのが特徴でしょう。

var a = List("A","B","C")
a match {
  case List(first,second,third) =>
    println("first = " + first)
    println("second = " + second)
    println("third = " + third)
  case _ => println("default")
}

以下のようにも書き換えることも出来るようです。

lst match {
  case first :: second :: third :: _ =>
    println(first + "," + second + "," + third)
  case _ => println("default")
}

でも、どういう時に必要になるんだろう?

メソッド

defで宣言します。 = {} と=をつけるのがポイント。

Scalaに関するよくある誤解(1) - 最後の処理の値が返り値になるのでreturnは不要 - kmizuの日記

また、Scalaは値を返すreturn を省略するのが基本です。return は処理の途中を抜ける際に使うようです。

def add(x:Int,y:Int):Int = {
  // return x + y returnは省略が基本
  x + y
}

クラス・メソッド

シンタックスシュガーを嫌うGoとは対照的に、かなり様々なシンタックスシュガーが用意されています。

class Adder {
  def add(x : Int)(y :Int): Int = x + y
}
val adder = new Adder()
adder.add(2)(3)

val fun = adder.add(2) _
fun(3)

初見殺しですね。

コンパニオンオブジェクト

case class Point(private val x:Int, private val y:Int)
Point(1,2).equals(Point(1,2))

/// コンパニオンオブジェクト
object Point {
  def print(): Unit = {
    val p = new Point(1,2)
    println(p.x + "," + p.y)
  }
}

Point.print()

クラスと同じ名前で宣言されたシングルトンオブジェクトをコンパニオンオブジェクトを呼ぶようです。

トレイル

これはScala独特かと思います。

trait Part {
  def run(): Unit
}

trait Engine extends Part {
  override def run() {
    println("エンジン")
  }
}

trait Tire extends Part {
  override def run() {
    println("タイヤ")
  }
}

trait Wing extends Part {
  override def run() {
    println("翼")
  }
}


class Car extends Engine with Tire
class Airplane extends Engine with Wing

(new Car).run() // タイヤ
(new Airplane).run() // 翼
 
  • コンストラクタのないクラス。
  • 抽象クラスのように実体化(new)できない。
  • メソッドもフィールドも宣言できる
  • extendsの継承対象にもできるし、withとしてmix-inの対象にできる(クラスはmix-inの対象にできない)
  • インターフェースかというとそれも違う。withで追加できるのも一つだけ

配列は非変

言葉からして、ややこしい話なのですが、

class A 
class B extends A

val a:A = new B() // これは通る

val array:Array[A] = new Array[B](1) // 配列にすると、非変なのでコンパイルエラー

という現象があります。ここまでScalaという言語はJavaをよりパワフルに使いたいがために拡張したものという印象でした。

特にmatch文の過剰というほどの拡張ぶりには驚かされてました。

が、ここではScalaの厳密性を感じます。

ちなみにC#の場合は通ります。

public class Hello{
    public static void Main(){
        A a = new A();
        A[] array = new B[1];
        System.Console.WriteLine("Hello C#");
    }
    class A {}
    class B: A{}
}

さらに反変というのもあるのですが、このあたりは必要に応じて使いこなすことになるかなと。

関数とメソッドは違う

Scalaでは厳密には別物です。日本語になると特に混乱させられますね。

  • 関数…Function
  • メソッド…Method。defで宣言されたものだけがメソッド。
def clickMethod(event: (Int,Int) => Unit) {
  event(100,200)
}

val clickFunction = (x:Int,y:Int) => println("(" + x + "," + y +")")

clickMethod(clickFunction)

C#がデリゲートとかActionとか色々変遷を経て得たのに比べるとわかりやすい方です。

foldRightとfoldLeft

Scala関数型言語の側面をもつので、らしいと言えばらしいのですが、一見ではわかりにくいです。

下記のようなFunctionを宣言して、私は理解を深めました。

val output = new Function2[Int,Int,Int] {
  def apply(x:Int,y:Int):Int = {
    println("left = " + x + ", right = " + y)
    return x+y    
  }
  
}
output(100,200)

println("sumLeft")
def sumLeft[T](list:List[Int]):Int = list.foldLeft(0)((a,b) => output(a,b))
sumLeft(List(1,2,3,4,5))
println("sumRight")
def sumRight[T](list:List[Int]):Int = list.foldRight(0)((a,b) => output(a,b))
sumRight(List(1,2,3,4,5))

map,filter,find,takeWhile,count

C#でいうLINQみたいなもの。メソッドチェーンも当然できます。

(1 to 5).filter(x => x % 2 == 0).count(x => x % 2 == 0)

OptionとEither

ScalaではOptionという型にNone(Null)もしくは値を一つだけ入れておくことができます。

Some(3).map(_ * 3)
val n: Option[Int] = None
// getOrElseで値があれば、値を取得、Noneだったら、-1を取得
n.map(_ * 3).getOrElse(-1)

EitherはLeftもしくはRightの値を持つというもの。   これを用いて、Leftにエラー、Rightに正しく取得できた値というように収めるというもの。

やってみて

研修資料はよく出来てます。練習問題は、かなり気合が入ってます。

年利を月利にするときどうするんだろう?とか思いましたし、探索木はガチなアルゴリズム問題です。

1日でこなしたと言いたいところですが、丸2日以上かかりました。3連休の間は、ほとんどこれにかかりきりでした。

ZOZOTOWNScala

WEB+DB PRESS Vol.108 (2018年12月22日発売) でZOZOTOWN特集がされており、ZOZOTOWNでもScalaを採用するようです。

パフォーマンスとJavaのライブラリが使えるという点で決めたようですが、詳しいことが気になる方は読んでみてください。

Scalaについての自分の印象

Goは無駄を削ぎ落としたのに対して、Scalaは豊富な機能を持って柔軟に使えるようになっており対照的です。

個人的にはGoより好きです。

気がかりなのはKotlinにせよ、Javaベースであり、今後、こうした言語はJava有償化によりお金を支払う必要があるのかという点。そこがはっきりしません。

お金を集めるのが上手いOracleなので、大丈夫だったとしても今後、影響が来そうな気もするのです。