Lambdaカクテル

京都在住Webエンジニアの日記です

Invite link for Scalaわいわいランド

Scala 3でEnumの逆引きをするには事前にマッピングを用意してやるといい

最近WASMに興味がでてきて、ゴールデンウィークにWASMランタイムをScala 3で実装している。もう結構な命令を実行できるようになって、バイナリをパースして足し算を実行するくらいならできるようになった。

WASMは機械語なので、オペコードと引数が命令として並んでいる形をとる。自分のコードではオペコードはenumを利用して実装している:

enum OpCode(val code: Byte):
  case End extends OpCode(0x0b)
  case Call extends OpCode(0x10)
  case LocalGet extends OpCode(0x20)
  case LocalSet extends OpCode(0x21)
...

こうすると簡単にオペコードからそのバイトが得られる:

OpCode.LocalGet.code // => 0x20

そうそう、Scalaは0x20みたいなhexadecimal literalが書けるので便利ですね。

他方、バイトからオペコードを得るのは面倒だ。何も考えずに素朴に実装すると、全部のオペコードに対してマッピングを考えなくてはならない:

object OpCode:
  def fromByte(byte: Byte): OpCode = byte match
    case End.code      => End
    case Call.code     => Call
    case LocalGet.code => LocalGet
    case LocalSet.code => LocalSet
...

オエー

Discordで質問したところ、便利な方法を教えてもらった

enum OpCode(val code: Byte):
  case End extends OpCode(0x0b)
  case Call extends OpCode(0x10)
  case LocalGet extends OpCode(0x20)
  case LocalSet extends OpCode(0x21)
end OpCode

object OpCode:
  private val opMap: Map[Byte, OpCode] =
    OpCode.values.view.map(op => op.code -> op).toMap

  def ofCode(code: Byte): Option[OpCode] =
    opMap.get(key = code)
end OpCode

OpCode.ofCode(0x20) // => Some(LocalGet)

コツは、enumのコンパニオンオブジェクトとして内部的にマッピングを生成しておくことだ。opMapofCodeが呼ばれた瞬間に初期化されて計算が行なわれる(オブジェクトは必要になったときにインスタンスになるため)。たぶんマクロを頑張ればコンパイル時に確定させることもできると思う。

加えて、便利そうなライブラリも教えてもらった。

github.com

★記事をRTしてもらえると喜びます
Webアプリケーション開発関連の記事を投稿しています.読者になってみませんか?