🕶️

Scalaにおける=>の使われ方をまとめてみた

2022/06/24に公開約3,800字

初めましての方は初めまして、そうでない方も初めまして。
株式会社プラハに入社して半年が経った漢です。
今年の夏も暑くなりそうですが、クーラーの効いた部屋で食べるアイスは別格ですね。

話は変わりますが、最近個人的にScala×Akkaにかなり興味を持っていて、色々と勉強しています。
ただなにぶん基本的な構文とかを知らずに詰まることが多いので、Scalaの言語仕様をまずは知らなければ!と思って勉強していたら、=>の使われ方が多すぎて混乱してしまいました。
ということで、今日は自分にとっての備忘も兼ねて、=>の使われ方をまとめてみました!

ちなみに今回のサンプルコードは以下のサイトにコピペしてもらうと実際に動作を試すことができますので、もし興味がある方は色々試してみてください。

https://scastie.scala-lang.org/

パターンマッチ(match式)

Scalaにおける必須項目とも言えるパターンマッチです。

具体的には、ケースにマッチした時の処理を指定する際に使います。
これは割と直感的な感じがしますね。

sealed trait Animal

object Animal {
  case object Dog  extends Animal
  case object Cat extends Animal
}

def echo(animal: Animal) = {
  animal match {
    case Animal.Dog => println("わん!")
    case Animal.Cat => println("にゃー!")
  }
}

echo(Animal.Dog)
echo(Animal.Cat)

出力結果は以下のようになります。

わん!
にゃー!

関数の型定義

これはJSなどでも同じような書き方をするので、違和感なく読めるかなと思います。
ここでは、hogeメソッドの引数fの型はStringを受け取ってStringを返す関数になります。
fは、受け取った文字列を[]で囲います。

def hoge(f: String => String) = {
  println(f("hoge"))
}
  
val f = (s: String) => s"[${s}]"

hoge(f)

出力

[hoge]

無名関数

これはJSなどでも同じような書き方をするので、違和感なく読めるかなと思います。
以下のように、引数と実際の処理の間に書きます。

val add = (x: Int, y: Int) => x + y

ちなみにScalaの関数は、Function0〜Function22というトレイトを継承したオブジェクトです。
そのため、以下のような書き方もできます。

val add = new Function2[Int, Int, Int]{
  def apply(x: Int, y: Int): Int = x + y
}

別名インポート

これはその名の通り、インポートしたパッケージに別名を付けるために使います。

import users.{UserPreferences => UPrefs} 

名前渡しパラメータ

名前渡しパラメータとは、ざっくり言うと、パラメータに渡した式の評価タイミングを遅らせる際に使用します。

サンプルコードを例に説明すると、実際にhogeの中でbodyが使われるタイミングで初めて評価(実行)されます。
名前渡しパラメータとして定義するのは簡単で、=>を引数の型の前に付けるだけです。

def hoge(isNeed: Boolean, body: => Unit) = {
  println("hoge start.")
  if (isNeed) {
    body
  }
  println("hoge end.")
}

val fuga = () => {
  println("fuga")
}

hoge(true, fuga())
println("----------------")
hoge(false, fuga())

今回のケースだと、以下のように出力されます。

hoge start.
fuga
hoge end.
----------------
hoge start.
hoge end.

これは、2回目のhogeメソッドの呼び出しでは、isNeedがfalseになっていて、bodyが使われていないため、評価されることがないからです。

ちなみにもしbodyの定義を値渡しパラメータにした場合は、以下のように出力されます。

fuga
hoge start.
hoge end.
----------------
fuga
hoge start.
hoge end.

これはhogeメソッドを呼ぶ前に、fuga関数が評価されるため、最初にfugaと言う文字列が出力されています。

自分型

自分型とは、クラス定義する段階では継承していなくても、インスタンスが生成される時にはあるトレイトを継承していることを宣言する方法です。
これを使うことで、簡単に継承するトレイトの実装を切り替えることができます。


trait Animal {
  def echo: String
}

trait Dog extends Animal {
  override def echo = "わん!"
}

trait Cat extends Animal {
  override def echo = "にゃー!"
}

class Hoge {
  self: Animal =>
}

val hogeDog = new Hoge() with Dog;
println(hogeDog.echo)


val hogeCat = new Hoge() with Cat;
println(hogeCat.echo)

出力結果は以下のようになります。

わん!
にゃー!

応用編(?)

最後にちょっとしたクイズです。
こちらのサンプルコードですが、どんな順でログが出力されるでしょうか?

def func1(num: Int): (=> Int) => Int = { r =>
  {
    println("func1 start")
    val result = r * num
    println("func1 end")
    result
  }
}

val getFour = () => {
  println("four")
  4
}

println(func1(2)(getFour())) // 8

正解は以下のように出力されます。

func1 start
four
func1 end
8

なんとなく難しそうに見えますが、Intの引数を受け取ったら、Intの引数を受け取ってIntの結果を返す関数を返す、という動きをします。そして、2つ目の引数が名前渡しになっているので、実際にrが使われるところで評価されると言う流れになります
(自分なりにこう解釈しましたが、もし違ってたらマサカリお待ちしています)

まとめ

色々な記事を調べましたが、とりあえずこれで一通りは網羅できたかなと思います。
それにしても、Scalaの=>使われ方、多すぎるような気もします。
他にもScalaはシンタックスシュガーが豊富だったり、implicitで定義することで暗黙的にパラメータを渡せたりと、割と特徴があるなーと感じることが多いですね。
とはいえ、個人的にはAkkaに凄く魅力を感じているので、仕事で使えるレベルくらいにはScala×Akkaの勉強をしていきたいなと思っています!

ちなみにプラハではアウトプットガチャという制度があり、このように記事を投稿することで応募する権利を得られます。
絶対に最大金額を当てて持って帰りたいと思います!

https://note.com/prahainc/n/naa32d54c48e4

参考

https://docs.scala-lang.org/ja/tour/tour-of-scala.html
https://scala-text.github.io/scala_text/
https://qiita.com/mizunowanko/items/e699f45ef918476ad81e
https://zenn.dev/wooootack/scraps/1b238de1c5cae8

Discussion

ログインするとコメントできます