Magnolia Tech

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

scala 2.13から導入されるArraySeq.unsafeWrapArrayは何が'unsafe'なのか?

blog.magnolia.tech

先日のエントリで、ArraySeq.unsafeWrapArrayを取り上げましたが、そもそも何が'unsafe'なの?immutableなデータ構造にラップするので、safeなのでは?と疑問に思ったところ、いつものごとくKenji Yoshida(@xuwei_k)さんに教えて頂きました。

なるほどーいくらラップしても元のArrayがmutableであることには変わりないので、そこは理解して使えよ!ってことのようです。その注意喚起の意味でのunsafeですね。

ちなみにArraySeq.unsafeArrayのscaladocにはそれなりのメソッドの危険性が書かれています。

abstract defunsafeArray: Array[_] The wrapped mutable Array that backs this ArraySeq. Any changes to this array will break the expected immutability. Its element type does not have to be equal to the element type of this ArraySeq. A primitive ArraySeq can be backed by an array of boxed values and a reference ArraySeq can be backed by an array of a supertype or subtype of the element type.

しかしどちらかというと、ArraySeq.unsafeWrapArrayのscaladocに書いて欲しいところですね。

というわけでアドバイスを頂いたこともあり、初めてのScala本体へのPRを書きました。

github.com

レビューは通っているので、Scala 2.13.1あたりには取り込まれるのではないでしょうか。

1行の、ドキュメント追加とはいえ、マージされると嬉しいなぁ。

Scala 2.13からArrayからIndexedSeqへの暗黙の変換が非推奨となる

いよいよ正式リリースが近づいてきたScala 2.13ですが、既存のコードをリリース候補版のScala 2.13でコンパイルすると以下のようなメッセージを見かけるようになりました。

scala> def makeSeq(a: Array[Int]): Seq[Int] = if (a == null) Nil else a
                                                                      ^
       warning: method copyArrayToImmutableIndexedSeq in class LowPriorityImplicits2 is deprecated (since 2.13.0): Implicit conversions from Array to immutable.IndexedSeq are implemented by copying; Use the more efficient non-copying ArraySeq.unsafeWrapArray or an explicit toIndexedSeq call
makeSeq: (a: Array[Int])Seq[Int]

copyArrayToImmutableIndexedSeqなんてメソッド、呼び出していたっけ?LowPriorityImplicits2っていうクラスって何だっけ?そもそも、このメッセージは何を意味しているのか?…調べた結果をまとめました。


まずはScala 2.13へのマイグレーションガイドをチェックします。

https://confadmin.trifork.com/dl/2018/GOTO_Berlin/Migrating_to_Scala_2.13.pdf

20ページに関連する記載が有ります。

Wrapped Java varargs pretend the array is immutable • Using the new s.c.i.ArraySeq • You can do the same with ArraySeq.unsafeWrapArray • A deprecated implicit conversion makes a copy of the array

Scala 2.12までは、ArrayをSeqへ渡すと、scala.collection.mutable.WrappedArrayに変換されていました。これはScala 2.12のscala.Predefで定義されているImplicit Conversionの挙動です。

scala> val r: Seq[Int] = Array(1, 2)
r: Seq[Int] = WrappedArray(1, 2)

Scala 2.13ではSeqがImmutable化されたことを受けて、scala.collection.immutable.ArraySeqに変換されます。

scala> val r: Seq[Int] = Array(1, 2)
                              ^
       warning: method copyArrayToImmutableIndexedSeq in class LowPriorityImplicits2 is deprecated (since 2.13.0): Implicit conversions from Array to immutable.IndexedSeq are implemented by copying; Use the more efficient non-copying ArraySeq.unsafeWrapArray or an explicit toIndexedSeq call
r: Seq[Int] = ArraySeq(1, 2)

これはScala 2.13のscala.Predefで定義が追加された以下のコードによるものです。

private[scala] abstract class LowPriorityImplicits2 {
  @deprecated("Implicit conversions from Array to immutable.IndexedSeq are implemented by copying; Use the more efficient non-copying ArraySeq.unsafeWrapArray or an explicit toIndexedSeq call", "2.13.0")
  implicit def copyArrayToImmutableIndexedSeq[T](xs: Array[T]): IndexedSeq[T] =
    if (xs eq null) null
    else new ArrayOps(xs).toIndexedSeq
}

LowPriorityImplicits2copyArrayToImmutableIndexedSeqが出てきました。暗黙の変換によりコピーが発生するので、toIndexedSeqでコピーして変換するか、それではパフォーマンス的に問題になる場合はArraySeq.unsafeWrapArrayを使ってコピー無しにimmutable化するか明示的に示しましょう、ということですね「ArraySeq.unsafeWrapArray」って何がunsafeなの?という話は、また別のエントリを書きます)。

確かに、警告が出なくなります。

scala> val r1: Seq[Int] = Array(1, 2).toIndexedSeq
r1: Seq[Int] = ArraySeq(1, 2)

scala> val r2: Seq[Int] = scala.collection.immutable.ArraySeq.unsafeWrapArray(Array(1, 2))
r2: Seq[Int] = ArraySeq(1, 2)

しかし、元々LowPriorityImplicits2というクラスも、copyArrayToImmutableIndexedSeqというメソッドも、Scala 2.12まではなかったし、Scala 2.13でも明示的に使うものではありません(implicit conversionですしね)。

定義された瞬間に非推奨になってしまうクラスと、メソッド…しかもユーザーコード上には出てきません…「実は、最初からそんなメソッドなんか無かったんですよ…」って言うと何だか押井守っぽいですね。

というわけで上記のメッセージが出たら、明示的に変換方法を指定しましょう、という話でした。

Scala 2.13からscala.SeqはImmutableになった

Scala 2.12まで

Welcome to Scala 2.12.8 (OpenJDK 64-Bit Server VM, Java 12.0.1).
Type in expressions for evaluation. Or try :help.

scala> val seq1: scala.Seq[Int] = scala.collection.immutable.Seq(1, 2)
seq1: Seq[Int] = List(1, 2)

scala> val seq2: scala.Seq[Int] = scala.collection.mutable.Seq(1, 2)
seq2: Seq[Int] = ArrayBuffer(1, 2)

Scala 2.13から

Welcome to Scala 2.13.0-RC3 (OpenJDK 64-Bit Server VM, Java 12.0.1).
Type in expressions for evaluation. Or try :help.

scala> val seq1: scala.Seq[Int] = scala.collection.immutable.Seq(1, 2)
seq1: Seq[Int] = List(1, 2)

scala> val seq2: scala.Seq[Int] = scala.collection.mutable.Seq(1, 2)
                                                              ^
       error: type mismatch;
        found   : Seq[Int] (in scala.collection.mutable)
        required: Seq[Int] (in scala.collection.immutable)

もうmutable.Seqは代入できない…っていうか今までできたんだ…知らなかった…

ELECOMのUSB 3.1, PD対応のType-Cケーブルを買った

f:id:magnoliak:20190531204917j:plain

先日AnkerのUSB PD 60W対応の充電器を買ったので、卓上の電源周りを見直そうと、USB Type-Cケーブルも新調した。純正では2mも有って卓上では取り回しづらいので、ELECOMのUSB 3.1対応、USB PD 5A対応の、所謂全部入りのケーブル…以前はけっこう高かった印象だったけど今ならAmazonで1342円とめちゃくちゃ安い。これなら規格をUSB 2.0とかに落とさなくてもいいかなっていう値段。

ただ、相変わらずケーブルの刻印を見ても何の規格に対応しているのか、さっぱり分からないんだけど…(かろうじて、10って書いてあるから10G...つまりUSB 3.1ってこと?くらい)

f:id:magnoliak:20190531204633j:plain

とにかくUSB Type-Cケーブルは混沌としているので、1.0mを越えない用途であればこのケーブルを買っておけば充電も、周辺機器の接続も間違い無い。そして、ケーブルの刻印が致命的に分かりづらいので、違う規格のものを混ぜないで統一して使った方が良いですね。

MacBookの充電器を追加で買うなら現状Anker一択じゃないかなーって。

The Art of UNIX Programming

The Art of UNIX Programming

The Art of UNIX Programming

しばらく絶版になっていた「The Art of UNIX Programming」がアスキードワンゴから再発された。アスキードワンゴはこの手のUNIX文化に関する書籍をどんどんリリースしてくれるので、本当にありがたい。みんなまた絶版にならないうちに買いましょう。

本書の構成は…

  • 第1部「コンテキスト」⇒所謂UNIX文化や歴史の解説
  • 第2部「設計」⇒各機能のをUNIX的な思想の観点から紹介
  • 第3部「実装」⇒C言語や、各種開発ツールチェーンの紹介
  • 第4部「コミュニティ」⇒オープンソース活動や、ドキュメントに関する紹介

原著が2004年と今ではわりかし古めなので、第2部以降は全部読み切るより、自分の興味の有るトピックを拾い読みするくらいが丁度いいかも(けっこうページ数は多い)。

しかし、第1部の「コンテキスト」はとにかく重要。ここだけは全部しっかり読み切った方がいい。現代でも通用するソフトウェアに関する思想が詰まっている。

個人的には、冒頭に出てくる下記の主張が最高に大好きだ。

その後もRob Pikeによる「プログラミングの原則」や、筆者によるUNIXに関する17個の考え方なども学びの多いことが並んでいる。

特に、「明確性の原則:巧妙になるより明確であれ」や、「分離の原則:メカニズムからポリシーを切り離せ。エンジンからインタフェースを切り離せ」といったあたりが秀逸なメッセージ。

と言う訳で数あるUNIX文化の解説書の中でもけっこうな物量の本だけど、ほんと冒頭の第1部だけでもいいので、じっくり繰り返し読んだ方が良い本。お勧めです。

Art of UNIX Programming, The (Addison-Wesley Professional Computing Series)

Art of UNIX Programming, The (Addison-Wesley Professional Computing Series)

Scalaで、既存のクラスに新しい振る舞いを追加する

Rubyにはrefinementsという仕組みが有って、特定のスコープ内だけで既存クラスのメソッドの振る舞いを変えたり、追加したりできます。

例えばStringクラスに、toBlahという、文字列の前に"Blah!"という文字列を追加するというメソッドが欲しくなったとしましょう(そんなシチュエーションは無いと思いますが)。そんな時はこんなコードで実現できます。

module BlahString
  refine String do
    def toBlah
      "Blah " + to_s
    end
  end
end

class UninterestingMessage
  using BlahString

  def make_message(string)
    string.toBlah
  end
end

puts UninterestingMessage.new.make_message("Wolrd!")
# => Blah! Wolrd!

元々、Rubyオープンクラスという仕組みが有って、自由に後からクラスの挙動を変えることができるのですが、影響がグローバルなので、このような影響範囲を限定する仕組みが後から(Ruby 2.1から)導入されました。

Scalaにおける実現方法…implicit class

Scalaにおいても既存のクラスにメソッドを追加するimplicit classという仕組みが有ります。

scala> implicit class BlahString(val string: String) {
     | def toBlah = "Blah " + string
     | }
defined class BlahString

scala> "World!".toBlah
res0: String = Blah World!

scala.language.higherKindsについて

久しぶりにDwangoさんのScala研修テキストを読んでいたら、今まで理解していなかったオプションが出てきたので調べてみた。

dwango.github.io

そのオプションとはこれ。

import scala.language.higherKinds

scala.language.higherKindsについて、Scaladocにはこう書かれている。

Only where this flag is enabled, higher-kinded types can be written.

Why keep the feature? Higher-kinded types enable the definition of very general abstractions such as functor, monad, or arrow. A significant set of advanced libraries relies on them. Higher-kinded types are also at the core of the scala-virtualized effort to produce high-performance parallel DSLs through staging.

そして、対象となるコードはこれ。

trait Functor[F[_]] {
  def map[A, B](fa: F[A])(f: A => B): F[B]
}

scala.language.higherKindsを有効にしないでコンパイルすると以下のような警告が出る。

scala> trait Functor[F[_]] {
     | def map[A, B](fa: F[A])(f: A => B): F[B]
     | }
<console>:11: warning: higher-kinded type should be enabled
by making the implicit value scala.language.higherKinds visible.
This can be achieved by adding the import clause 'import scala.language.higherKinds'
or by setting the compiler option -language:higherKinds.
See the Scaladoc for value scala.language.higherKinds for a discussion
why the feature should be explicitly enabled.
       trait Functor[F[_]] {
                     ^
defined trait Functor

'higher-kinded type'は日本語に訳す(?)と、「高カインド型」という…訳になっていない気もするけど。

高カインド型については、下記のサイトの解説を参考にした。

eed3si9n.com

高カインド型は型コンストラクタを受け取る型コンストラクタだ

型クラスを型パラメータを受け取るようなクラス(ListとかOptionとか)に対して実現するための仕組み、とのことだった。

なぜこれがオプションによってコントロールされるかは、以下のようにScaladocの続きに書かれている。

Why control it? Higher kinded types in Scala lead to a Turing-complete type system, where compiler termination is no longer guaranteed. ....So an explicit enabling also serves as a warning that code involving higher-kinded types might have to be slightly revised in the future.

コンパイラの完了を保証しないし、将来変わる可能性も有るから明示的に有効にしないと警告を出す、ということだそうだ。

一つ勉強になった。