たにしきんぐダム

プログラミングやったりアニメやゲーム見たり京都に住んだりしてます

WartRemover で Scala を静的解析

この記事は CAMPHOR- Advent Calendar 2016 7日目の記事です。

WartRemoverScala のASTレベルの静的解析ツールで、WartRemover に組み込まれているパターンに加えて、自分で定義したパターンをビルド時に検出することができます。
これを使えばscalacはエラーや警告を出さないけど検出してほしいコーディング規約などをビルド時に検出することができるようになって便利。

github.com

もとから組み込まれてるパターンはGitHubのREADMEに詳しく書かれています。

使ってみる

詳しくは GitHubREADME を参照。

project/plugins.sbt に以下を追記

addSbtPlugin("org.wartremover" % "sbt-wartremover" % wartremoverVersion)

build.sbt に以下を追記

wartremoverErrors ++= Warts.all

これだけでビルド時にビルド対象のscalaプログラムを見て、すべてのパターンにマッチするASTを探索し、マッチしたパターンをエラーとして出力してくれるようになります、簡単ですね。

上の例だとすべてのプログラムに対して、すべてのパターンを検出しますが

  • 特定ファイルは検出対象から外す
  • 一部のパターンのみ/一部のパターンを除いてすべてのパターンを検出
  • あるパターンはエラー、あるパターンは警告を出すようにする

などのことが build.sbt に記述するだけでシュッとできるようになります。
詳しい設定方法はREADMEを(ry

カスタム Wart を作ってみる

ここ を見ると wart rule を自分で追加できるようです。

今回は HOGE_HOGE みたいな全部大文字とアンスコのみの変数名を見つけてみます。
Scala の定数は 公式のscala code styleUpperCamel だよって言われているのですが、僕はよくミスって LIMIT みたいな定数名を作ってしまうし、scalac も scalastyle も warning を出さないからです。(もしかして僕の設定が足りてないだけ?)(まあそこは本題じゃないし)

完成したものはこちら

github.com

WartRemoverはscalaのASTとのマッチングによって検出を行うので、まずは val HOGE = 1 みたいなプログラムがどういう AST になるのか確認しましょう。

scala> import scala.reflect.runtime.universe
import scala.reflect.runtime.universe

scala> universe.showRaw(universe.reify { val HOGE = 1 }.tree)
res1: String = Block(
List(ValDef(Modifiers(), TermName("HOGE"), TypeTree(), Literal(Constant(1)))),
Literal(Constant(())))

なんとなく ValDef(Modifiers(), TermName(), TypeTree(), Litenarl()) にマッチさせてやれば良いことがわかりますね。
(Modifiers には final private みたいな修飾子 が入ります。TypeTree はよく分からない...)

ちなみにScalaのリフレクションを使った構文木へのアクセスは公式ドキュメントが詳しい

概要

よっしゃ、それでは ValDef(Modifiers(), TermName(), TypeTree(), Litenarl()) のうち TermName が全部大文字かアンスコだけな構文木にマッチしてくれるwartを書いていきましょう!
ディレクトリ構成はこんな感じ、LargeValueName.scala は wartルールを記述するプログラム、wartsは別プロジェクトとして管理しています。

.
├── build.sbt
├── project
│   ├── build.properties
│   └── plugins.sbt
├── src/main/scala/Main.scala
└── warts
    └── src/main/scala/LargeValueName.scala

LargeValueName.scala

自分で wart を作るときはだいたい以下のような必要があります。

  • wart object は WartTraverser を継承
  • def apply(u: WartUniverse): u.Traverser のみをメソッドとして持ち
    • WartUniversereflect.api.Universe を内部にもつ
  • apply が返す Traversertraverse(tree: Tree): Unit を override
    • 引数として与えられたASTと、検出したいパターンとのマッチングさせる
package warts

import org.wartremover.{WartTraverser, WartUniverse}

object LargeValueName extends WartTraverser {
  def apply(u: WartUniverse): u.Traverser = {
    import u.universe._

    new Traverser {
      override def traverse(tree: Tree) {
        tree match {
          case t @ ValDef(_, TermName(s), _, _)
            if s.trim.matches("[A-Z_]+") =>
            u.error(tree.pos, s"Value name $s should be upper/lower camel case")
            // 他のwartもチェックするかもしれないので super.traverse を呼ぶ
            super.traverse(tree)
          case _ =>
            super.traverse(tree)
        }
      }
    }
  }
}

build.sbt

それじゃあこれを build.sbt に登録してみます、今回は LargeValueName.scala は sbt のマルチプロジェクトを使ってメインプロジェクトとは別で管理します。

  • wartremoverClasspaths にカスタム wart の classpath を追加
  • wartremoverWarnings += Wart.custom("your.own.wart")

build.sbt はこんな感じ、

val wartremoverVersion = "1.2.1"
val customWartProjectName = "MyWarts"

lazy val commonSettings = Seq(
  scalaVersion := "2.11.8"
)

lazy val root = (project in file(".")).
  settings(commonSettings: _*).
  dependsOn(warts).
  settings(
    name := "wartremoverPlayground",
    version := "1.0",
    // warts project の jar ファイルが欲しい
    // wartremoverClasspaths += 
    //   "file://" + baseDirectory.value + "/warts/target/scala-2.11/mywarts_2.11-1.0.jar",
    wartremoverClasspaths ++= {
       // lazy val warts = ... settings( exportJars := true) と
       // dependsOn(warts) により dependencyClasspath in Compile に上記のjarが追加されてるはず
      (dependencyClasspath in Compile).value.files
        .find(_.name.contains(customWartProjectName.toLowerCase()))
        .map(_.toURI.toString)
        .toList
    },
    wartremoverWarnings += Wart.custom("warts.LargeValueName")
  )

lazy val warts = (project in file("warts")).
  settings(commonSettings: _*).
  settings(
    name := customWartProjectName,
    version := "1.0",
    exportJars := true,
    libraryDependencies ++= Seq(
      "org.wartremover" %% "wartremover" % wartremoverVersion
    )
  )

warts project に移動して jar ファイルをいちいち生成して、頑張ってjarへのpathを渡しても動くのですが大変、自動化したすぎます。

wartremoverClasspaths += "file://" + baseDirectory.value + "/warts/target/scala-2.11/mywarts_2.11-1.0.jar"

この部分で、wartsプロジェクトの生成したjarファイルを見つけています。

wartremoverClasspaths ++= {
  (dependencyClasspath in Compile).value.files
     .find(_.name.contains(customWartProjectName.toLowerCase()))
     .map(_.toURI.toString)
     .toList
}

動かしてみる

Main.scala

package playground

object Main {
  val LIMIT = 1
}

これで sbt compile すると

[warn] /path/to/Main.scala:4: Value name LIMIT should be upper/lower camel case
[warn]   val LIMIT = 1

おお、警告出してくれた!
これを使って、簡単にパターンとして記述できるコーディング規約などは静的解析で検出してくれると良いですね!

明日は id:ryota-ka の「 Vim script でジェネレータを作ったり、遅延評価してみる」です。

参考

OS自作入門記(1~2日目)

「30日でできる! OS自作入門」を読んでいく、やったことをメモすれば自分の勉強にもなるしこれを見た誰かの役に立つかもしれない...?

モチベーション

自分でオリジナルのOSを作りたいというよりは、コンピュータのことをもっとよく知りたい、恥ずかしながらコンピュータのこと全然知らないし。
一応大学でOSの授業を受けて勉強はしたのですが、いまいち実感がわかないしとりあえず作りながら勉強していこう!

開発環境

この本ではWIndowsでの開発を行っているのですが本を読み始めた時、手近にWIndows機が無かったのもあって仮想マシン上のUbuntuでやることに

続きを読む

Scala関西勉強会でscala.Eitherとscalaz.\/の違いを話してきた

Scala関西勉強会で scala.Eitherscalaz.\/ の違いについて話してきました!
connpass.com

この話題、ブログとか漁ってみると3年前あたりに活発に議論されてる話だった...
僕自身for式の中でパターンマッチさせようとしてハマったものの(僕の検索能力の低さもあるけど)それを解説している記事にぶつかるまで時間がかかってしまったので、これについて解説する記事が一つでも増えると良いなーと思って発表しました。

考え事

\/単位元も定義されてる\/-(Monoid[B].zero)

https://github.com/scalaz/scalaz/blob/series/7.3.x/core/src/main/scala/scalaz/Either.scala#L411-L418

-\/(Monoid[A].zero)じゃないのかーって思ったけど、+++の実装を見てみると確かに\/-(Monoid[B].zero)\/appendについての単位元になってるなぁ@@

https://github.com/scalaz/scalaz/blob/series/7.3.x/core/src/main/scala/scalaz/Either.scala#L230-L238


今回の勉強会、バリュエーション豊かで基礎から応用まで幅広い内容のセッションがあってすごく良かった!
会場提供のエムオーテックス株式会社様、主催の@aa7thさん、@ryu1_okdさん、ありがとうございました!

参考

余談

発表中両手あげてバンザイして全身で\/を表現したりしてた

また参加します!

空間インデックス(R-tree)入門

R-treeとは空間データを効率良く検索するためのインデックス構造。R-tree について調べたのまとめておく。

目次

  • 目次
  • 参考資料
  • ナイーブな例
  • R-tree の概要
  • 参照処理
    • 点検索
    • 範囲検索
  • データの挿入・削除
    • 挿入処理
    • ノードの分割
      • Exhaustive Algorithm
      • Quadratic-Cost Algorithm
      • Linear-Cost Algorithm
    • 削除
    • 更新処理
  • まとめ

参考資料

続きを読む

Wantedlyで筋トレしてきた話

3/2(水) ~ 3/4(金) にWantedly株式会社のインターンに参加してきました.

www.wantedly.com

3日間ではどうしても実践的なことはできないし、プロトタイピングや技術の研修みたいなことをして筋トレして結果にコミットしましょうとのこと.

オフィスは白金台にあり、綺麗なところだったけどランチの値段も異常だし家賃も異常らしく住むのは難しそう
白金台で安いランチ尋ねたら東大の学食勧められたりした.

ちなみにWantedlyはジョジョが好きらしく、僕がメンターさんと面談することになった会議室の部屋の名前はクレイジー・ダイヤモンドだった. アニメ4部楽しみですね.

1日目

1日目はプロトタイピング お題として「シゴトでココロオドル」というWantedlyっぽいテーマが与えられて1日でアプリケーションのプロトタイプを作るという感じでした。

プロトタイピングの成果はCEOの仲さんから何度かフィードバックを頂くこともでき、普段アイデアが思いつかずアプリ作れないマンとしては良い体験でした!

やっぱりユーザーが使う理由の動機付けとかって大事だよね。趣味で技術ドリブンで作るのは楽しいけどまず第一に考えるべきなのはユーザー様のことだよねってなった。

2日目

2日目はReact講義&Reactなどを用いた実践。夕方頃まではReact速習会受けてた、教材も分かりやすかったし上手くReact学習できて良かったです!!

その後はWantedlyの新卒学生用ページをAPIが与えられるので、Reactを使ってクライアントサイドを実装しなおそうということに。

モダンなフロントは普段あまり書いたこと無かったけれど最近マイクロサービスアーキテクチャとか流行ってるし良い練習になりました。

(ちなみに開発途中に本番の方にちょっとしたバグをいくつか見つけてメンターさんに報告したら次の日には直ってたので、こうやってシュッシュとissueが立ってすぐに本番に取り込まれるスピード感はベンチャーならではだなぁ)

3日目

前日の開発の続きとその発表、他のチームが思ったよりも高い完成度のものを発表してて、普段フロントやらないと言えども精進せねばならないなぁとか思った。

懇親会

見たこと無い食べ物が大量にあって凄かった... f:id:tanishiking24:20160314014815j:plain

Wantedlyのインフラ構成の話とか少しだけ聞くことができて楽しかったです!

まとめ

3日間という短い期間でしたがベンチャー気質の強い集団の中で過ごすことができたのは純粋に楽しい体験でした。
3日間ありがとうございました!