Magnolia Tech

いつもコードのことばかり考えている人のために。

programming in scala fifth edition

Scala3対応の第5版。日本ではScala 2.13対応の第4版が出たばかりですが、原著はScala3対応となりました。

以前と構成も変わった箇所も多いですが、すべてのサンプルコードがScala3のquite style(要はpythonっぽいインデントベースの記法)になっていたり、implicit系の機能が全てScala3のgivenやusing、extensionといったキーワードに置き換わってところが特徴です。

新しい機能には比較的「これはScala3からの機能です」と書かれていますが、説明なしに解説が始まっている機能も有り、「絶対にScala3のソースしか読まない!書かない!」という方には良いのですが、Scala2ベースのソースが消えてなくなる訳でもないので、とまどうかもしれないですね。


というわけで、しばらくは、Scala2に入門する方が多いと思うので、今からScalaへ入門する方は第4版の翻訳版の方をお勧めします。

言葉にすること、言葉にする前と、言葉にした後のその先のこと

そもそも解くべき課題が設定できた時点で勝ったも同然、みたいな話も有るけど、みんながみんなそんな役回りでも無いし、「課題は無限に湧いてくる」状態であることも有るのでそこは議論しないとして...

  1. 考えられる
  2. 考えたことを言葉にできる
  3. その考えに至った背景や構造を整理できる
  4. 考えたことが妥当、正当であることが説明できる

この順に難易度が高いな、と思っているけど、果たしてどうすれば次のステップに進めるようになるのか、と考えている。

と、onkさんの見事な言語化エントリを読んで思ったのでした。

onk.hatenablog.jp

Scala3学習メモ: Scalaとnull, Scala2とScala3と

Rustの公式ドキュメントにnullに関する興味深い記事("Null References: The Billion Dollar Mistake"(Null参照: 10億ドルの間違い))が引用されています。

Enumを定義する - The Rust Programming Language

nullは、インタフェースにおける双方の合意として取り得る値とするか、しないかがいつもあいまいになってしまう点が問題。ある変数の型を見てもnullを取り得るかは明示されていないにも関わらず、人によって「nullを考慮すべき」「nullは取り得ない」の二つの状態が混在してしまう。たとえ、同じ人が双方のコードを書いていたとしても。

ScalaにおけるNull型のクラス階層における位置付けと、nullの代入

https://docs.scala-lang.org/resources/images/tour/unified-types-diagram.svg

https://docs.scala-lang.org/tour/unified-types.html

Scalaにおける型の階層ではNull型はAnyRefのサブタイプになっています。そして、AnyValのサブタイプではありません。

scala> class Person(name: String, age: Int)
// defined class Person

scala> val person: Person = null
val person: Person = null

scala> val num: Int = null
1 |val num: Int = null
  |               ^^^^
  |               Found:    Null
  |               Required: Int

つまり、AnyRefを継承しているクラス型の変数にはNull型の唯一の値であるnullを代入できますが、AnyValを継承しているInt型の変数にはnullの代入はできない、ということを示します。

Option型によるnullへの対応

しかし、nullはやっかいです。参照すればすぐにNull pointer exceptionが発生します。意図してインタフェースとしてnullを取り得る設計としたとしても、受け取る側がnullに対応するコードを十分に書けないこともあるし、意図してnullは取り得ない設計としたとしても渡す側がうっかりnullを渡すことが有ります。

前者は、requireなどで引数のnullチェックを行うルールにすべきですが、残念ながら強制力がありません。

強制力を持たせるためにはインタフェースの境界を、Option型でラップすることです。

scala> case class Person(name: String, age: Int)
// defined case class Person

scala> def printPerson(person: Option[Person]): Unit =
     |   person match {
     |     case Some(p) => println(s"name:${p.name}, age: ${p.age}")
     |     case None    => println("no person")
     |   }
     |
def printPerson(person: Option[Person]): Unit

scala> val p1 = null
val p1: Null = null

scala> printPerson(Option(p1))
no person

scala> val p2 = new Person("Arthur", 42)
val p2: Person = Person(Arthur,42)

scala> printPerson(Option(p2))
name:Arthur, age: 42

Option型でラップされていることで、Some、Noneに対応するコードを必ず書くための強制力が働き、nullが有る世界と、nullが無い世界を分離されました。

(Optionのapplyにnullを渡すとNoneが生成され、null以外を渡すと、その値をラップしたSomeが生成されます)

世界は平和になりました...

いや、そうか?では全ての外部からもたらされる変数をいちいち全部Option型でラップするのか?意図しないnullの混入を防ぐためにはtoo muchではないか?

EXPLICIT NULLS

nullにおける問題は、その変数がnullを取り得るか、明示されていないことにある...インタフェースの設計として明示に書いてあればいいけど、ドキュメントに頼るのも限界があるし、内部のインタフェースでいちいち文書化もしていられないだろう。

そもそもAnyRefがnullを許容しているからいけない...ということで、Scala3からAnyRefのサブタイプへのnull代入を不可とするモードが追加になりました。

https://docs.scala-lang.org/resources/images/scala3/explicit-nulls/explicit-nulls-type-hierarchy.png

Explicit Nulls | Scala 3 Language Reference | Scala Documentation

クラス階層が変更になっています。

ただし、デフォルトで有効になるのではなく、コンパイラオプションとして-Yexplicit-nullsを設定したときだけ有効になります。

$ scala -Yexplicit-nulls
scala> case class Person(name: String, age: Int)
// defined case class Person

scala> val person: Person = null
1 |val person: Person = null
  |                     ^^^^
  |                     Found:    Null
  |                     Required: Person

scala> val num: Int = null
1 |val num: Int = null
  |               ^^^^
  |               Found:    Null
  |               Required: Int

もうnullを代入することはできません。

どうしてもNullを代入したい時は、明示的にNull型を許容することを指定します。

scala> val person: Null = null
val person: Null = null

しかし、これではNull型に固定されてしまうため、あまり役に立ちません。

Scala3から導入されたUnion型が使えます。

scala> val person: Person | Null = null
val person: Person | Null = null

こうして、「うっかり」nullが混入し、Null Pointer Exceptionが発生すること限りなく抑制することができ、かつ記述も簡潔になりました。

世界は少し平和になりました。

Scala3学習メモ: 新しい制御記法

Scala3では、“quiet” syntaxと呼ばれるPythonっぽい新しい記法が導入されていて、括弧が大幅に削減できるようになっています。

おそらく一番大きな変化点ですね。Scala3ベースの解説書である『Programming in Scala Fifth Edition』もすべてのコード例がこの記法に置き換わっているので、今後は統一されていくのでしょう。

ifの条件式に括弧が不要になり、代わりに条件式の終わりを示すthenというキーワードを置きます

$ cat test.scala
@main def example =
  val x = 1
  if x == 1 then
    println("one")
$ scala test.scala
one

(はてなブログのカラーシンタックスは既にthenをキーワードとしてハイライトするようになっているようですね)

whileの条件式に括弧が不要になり、代わりに条件式の終わりを示すdoというキーワードを置きます

$ cat test.scala
@main def example =
  var x = 1
  while x != 0 do
    x -= 1
    println("one")
$ scala test.scala
one

forやtryでも同様です。

クラスやメソッド定義でも括弧が不要になりました

$ cat test.scala
@main def example =
  Hello.hello()

object Hello:
  def hello() =
    println("hello")
$ scala test.scala
hello

C言語風のシンタックスから大きく変えてきたのが驚きですが、従来の記法もまだ使えます。

また、従来の形式のソースファイルを、scalac [ファイル名] -rewrite -indentというオプションを与えると、コンパイルと同時に元のソースコードを新しい形式に書き換えてくれます(コメント等はちゃんと残ります)。

$ cat oldstyle.scala
package example

object Test {
  var answer = 42
  def hello(): Unit = {
    if (answer == 42) {
      println("hello")
    }
  }
}

$ scalac oldstyle.scala -rewrite -indent
[patched file oldstyle.scala]

$ cat oldstyle.scala
package example

object Test:
  var answer = 42
  def hello(): Unit =
    if (answer == 42)
      println("hello")

元に戻したいときは、 -rewrite -no-indentで書き換えられます。また、特に何のオプションも与えなければ、一つのソースコードの中で従来の記法と新しい記法が混在していても問題ありませんが、-old-syntaxや、-new-syntaxというオプションをセットすることで、記法を強制できます(異なる記法はコンパイルエラー)。

なお、インデントだけだと可読性が悪い(インデントの階層がネストしている、コードの行が長い)場合には、オプションとしてendというキーワードを差し込めます。rubyのendと違うのは、後ろに閉じたい内容を書かないといけないところです。ifを閉じるなら、end ifだし、fooというメソッドの定義を閉じたい時には、end fooとなります。従来のbraceであれば一発で対応関係を確認する方法がエディタに用意されていましたが、これでは独自のプラグインを書かないと分からないですね

package example

object Test:
  var answer = 42
  def hello(): Unit =
    if (answer == 42)
      println("hello")
      val x = 1
    end if
  end hello

公式ドキュメントには-rewrite -new-syntax-rewrite -old-syntaxで書き換えられる、という記載があるのですが、どうもできないようです(記述誤りか?)

なお、当然ですが、インデントの法則が完全に変わってしまうので、例えばvim-scalaのようなScala3に対応していないプラグインではインデントが崩れてしまいます。

僕らはいつまでUSB Type-Cケーブルを選ぶのに迷うのだろう…まだ迷っている人のための覚え書き - 2021年、夏

自分用の買い物メモ、Thunderbolt4ケーブルが続々とリリースされてきたので、2021年夏用にアップデート。

いつもの「全部同じじゃないですか」案件。


USB Type-Cケーブルの選び方は難しい…あらゆる規格をサポートするけど、あらゆる規格を”同時に”サポートするわけではないので、主にケーブル長や用途などで上手く選ばないと、使えなかったり、無駄に高いケーブルを選ぶことになってしまう

そんなことを起こさないためのメモ

あれこれ迷わないための”全部入り”

  • 低速から高速まで色々な周辺機器の接続に使う(USB2.0, USB3.2, USB4, Thunderbolt3, 4)
  • ディスプレイ接続に使う(DisplayPort)
  • 給電に使う(最大100W)

などなどを考えると、Thunderbolt4のケーブルを選ぶと全部対応している。長さは2.0mまで選べる。

Thunderboltのマークと規格を示す数字がコネクタに刻印(「⚡︎ 4」)されているものを選んでおくと後で「これ何の規格のケーブルだっけ?」とならないので便利。USB規格のケーブルは何も規格の種類を示す刻印がなかったり、最大転送速度を示す刻印しか無く、タグをつけておかないと確実に分からなくなる。

価格は0.8mで3000円程度、2.0mだと倍の6000円くらい。

Thunderbolt3は長さによってパッシブケーブル、アクティブケーブルといったケーブルの種類の違いが有ったけど、Thunderbolt4はその違いは無い。また規格上USB PDによる100Wまでの給電対応は必須ではなかった(でもたいてい対応していた)けど、Thunderbolt4は規格上必ず100W対応している...つまり、迷うことなく、分かりやすい。

Thunderbolt4ケーブル、全部入りなので迷うことがない分便利だが、値段は単なるケーブルとして見るとちょっと高いので、用途を絞れば選択肢が広がる。

Type-Cケーブルを使う主な用途と言えば、USB PDによる給電か、給電+ディスプレ接続か、そして本来のデータ転送となるが、Thunderbolt4の規格が示す40GB/sの転送速度が必要になる場面はあまり無いので用途に合わせて選んだ方が良い。

USB PD給電のためのケーブル選び

  • 使っているUSB PD充電器の仕様を確認し、3Aを越える電流に対応しているか確認する。

  • 3Aを超えない場合、USB 2.0規格かつ、USB PD対応で60Wとか、3Aまでと書かれている安価なケーブルを探す。

USB2.0かつ、3Aまでだと規格上、eMarkerチップが不要なため価格も安いし(1000円くらい)、製品の選択肢も多い。念のためにUSB PDサポートと書かれているものを選んだ方が良い(規格上、非対応、ということはないけど、念のため)。

短いケーブルはUSB PD対応のモバイルバッテリ接続に便利だし、壁や机の下のコンセントに刺したUSB PD充電器から電源を取る場合は、少なくとも1.8mは欲しいところ。

  • 3Aを越える場合は、USB 2.0規格かつ、USB PD対応で100Wとか、5Aまでと書かれているケーブルを探す。

eMarkerチップの分だけ多少高い(1600円〜1800円くらい)。Windows PCの場合、必要なワット数がスペックから分からないことも多いので、不安ならとりあえず5A対応のものを買っておけば間違いは無い。

  • 長いケーブルは巻いて収納することを考慮してケーブルの硬さなどに注意、なるべくしなやかに巻けるケーブルを選ぶ。

ディスプレイ接続のためのケーブル選び

  • 「オルタネートモード(Alternate Mode)対応」「ディスプレイ接続用」と明記されているか確認する

  • 給電を併用する場合、ディスプレイ側がサポートしている電流を確認(3A、5A)

だいたい2000円くらいで買える。

  • 2.0mまでならUSB 3.2 Gen 1x1(USB3.0、USB 3.1 Gen1)対応のケーブルが比較的安価

USB 3.2 Gen 2x1(USB 3.1 Gen 2)、USB 3.2 Gen 2x2は規格上、ケーブル長が1.0mに制限されるので、規格上2.0mまで対応しているUSB 3.0(USB 3.1 Gen1)対応のケーブルを探した方が良いけど、あまり製品の種類は無い模様。

データ転送(周辺機器接続用)のType-Cケーブル選び

  • USB 3.1、UBS3.2、USB4、Thunderbolt3,4といった規格のケーブルを周辺機器が対応する規格に合わせて買う(それは、そう)。

最近はUSB 3.2 Gen 2x1(USB 3.1 Gen2)対応の安価なストレージも増えてきたので、間違ってUSB 3.0(USB 3.1 Gen1)やUSB2.0までしかサポートしていないケーブルを買わないように注意が必要。

おわりに

Type-Cコネクタを使った規格がどんどん増えていって、全然分からなくなってきた。特にUSB 3.xの名称は非常にわかりづらく、混乱し易い……USB 3.0、USB 3.1、USB 3.2、USB4と詳しい人じゃないとまず分からないし、同じ規格に複数の名前がついている(USB 3.0とUSB 3.2 Gen1が同じと言われても...)。さらに、USBのケーブルを探すのに、Thunderbolt3や、4という規格のケーブルを買うといいよ、と言われても詳しく無い人にとって完全に意味不明になってきた。

だけど、自分の用途に合ったケーブルを選ぶようにしましょう。迷ったらThunderbolt4ケーブルを買っておきましょう、お金で解決。

学ぼうとしたことが頭にぜんぜん入ってこないとき

自分は割と「ゆっくり音読する」が一番効果が有る(と信じている)のだけど、人に依ってはただ黙読するだけで理解できる人もいるし、その回数に違いが有ったり、ノートに自分なりの解釈をまとめ直さないとダメだったり、技術書だったら実際にコードを書いたりそのソフトウェアを動かしてみないといけなかったり...それぞれ人によって集中するポイントが違っているので決定的な方法は無いけど、いずれにしても突然何かが分かることは無いので、それぞの方法や、時間のかけ方でやっていくしかないんだなって思った。

その他にも静かな場所じゃないとダメ、逆にカフェくらいのノイズが無いとダメ、音楽無いとダメとか...人それぞれ条件が有って、面白い。

WEB+DB PRESS総集編[Vol.1~120]

わずか2970円で過去のWEB+DB Pressが120冊揃うの、マジで便利なので、とりあえず買った方がいいですよ。

書き下ろしの「進化するプログラミング言語の魅力」というコラムも、良かったです。