来世から頑張る!!

技術ブログを目指して

ScalaでSQLが書きたいんだ!!

DSLとかOR Mapperとかじゃなくて、SQLが直接書きたいんだ!!

嘘です。タイトル詐欺です。

最近neo4jというグラフ指向データベースがお気に入りでして、 アクセス用のクエリとしてCypherという言語を使います。

基本はHTTP+JSONでRESTにアクセスできるのですが、Java用にneo4j-jdbcというライブラリがあり、これを使ってアクセスしようと思うわけです。

そうすると、当然SQLではないのでDSLSQL自動生成とかは使えず、直接SQL(本当はCypher)を書ける機能が望まれるわけです。

neo4jのダウンロード

neo4jは全部Javaで出来ているらしいので、あなたとJavaで検索してJavaをインストールしてください。

その後、neo4j公式サイトから無料版をダウンロードしてきます。

適当な場所に展開して、bin/neo4j statusみたいな感じで実行するとlocalhost:7474でhttpを待ち受けてくれるようになります。

まずはブラウザでhttp://localhost:7474に繋いでパスワード変更をしましょう。

初期状態のユーザーはneo4j、パスワードも同じくneo4jですが、1度入力するとパスワード変更を要求されます。

ログインするとダッシュボードのような画面が表示され、 この画面からサンプルDBみたいなのの作り方やヘルプなどが見られるので始めは適当に触ってみるのがよいと思います。

仮データの登録

neo4jの画面の上部からCypherが入力できるので、データを登録してみましょう。

CREATE
  (p :Person{name: "kazzna", password: "some-password"}),
  (b :Blog{name: "kazzna's blog", url: "http://kazzna.hatenablog.com/"}),
  (p)-[:WRITES]->(b)
RETURN p, b

p, bが変数ですね。
細かい説明はしませんが、:Person型のノードと:Blog型のノードを1個ずつ作って、作った物を取得しています。

ついでにせっかくですのですてにゃんあたりを追加しておきましょう。

CREATE
  (s :Person{name: "stefafan"}),
  (sb :Blog{name: "すてにゃんのガチ勢日記", url: "http://stefafafan.hatenablog.com/"}),
  (s)-[:WRITES]->(sb)
WITH s, sb
MATCH (k :Person{name: "kazzna"}), (kb :Blog{name: "kazzna's blog"})
CREATE (s)-[:READS]->(kb), (k)-[:READS]->(sb)
RETURN s, k, sb, kb

kazznaはstefafafanのブログを読んで、stefafafanはkazznaのブログを読む、と。

:Personpasswordが無いのが特徴ですね。

Scalaから取得しよう!!

sbtは使える前提で話を進めますので、sbtって何?って人はtypesafe activatorでググってください。 で、以下のコマンド中のsbtactivatorに置き換えれば多分大丈夫です。

まずbuild.sbtファイルの設定。

name := "test"
version := "0.0.1"
lazy val root = (project in file("."))
scalaVersion := "2.11.7"

resolvers ++= Seq(
  "neo4j-public" at "http://m2.neo4j.org/content/groups/public",
  "jitpack" at "https://jitpack.io"
)

libraryDependencies ++= Seq(
  "org.scalikejdbc" %% "scalikejdbc" % "2.3.0",
  "com.github.kazzna" % "neo4j-jdbc" % "2.2-SNAPSHOT_1",
  "ch.qos.logback" % "logback-classic" % "1.1.3"
)

大体こんな感じ。

resolversを2つ追加しないといけないのは理由があって、 公式のneo4j-jdbcを使うだけなら1番上だけでいいのですが、 現在公式の最新版ではScalikeJDBCなどが自動で生成してくれる?を使ったPreparedStatementに対応していないのです。

なのでとりあえず?の自動で対応した形式に置き換える対応を行った自作版を使います。
JitPackという不思議な力を借りて、githubから自動で依存関係ライブラリーを作り出してもらいます。
(mavenのローカルリリースは使い方が分からなくて出来ませんでした。)

公式にはPull Requestがマージされたので、次回のバージョン(2.2系?)のリリースから使えるようになるはずです。

後は普通のSQLの時と同様ですね。sbt consoleして

scala> import scalikejdbc._
import scalikejdbc._

scala> case class Blog(name: String, url: String)
defined class Blog

scala> :paste
// Entering paste mode (ctrl-D to finish)

case class Person(
  name: String,
  password: Option[String],
  blog: Option[Blog]
)

object Person extends SQLSyntaxSupport[Person] {
  def apply(rs: WrappedResultSet): Person = Person(
      rs.string("person.name"),
      rs.stringOpt("person.password"),
      rs.stringOpt("blog.name").flatMap { n =>
        rs.stringOpt("blog.url").map(u => Blog(n, u))
      })
}

// Exiting paste mode, now interpreting.

defined class Person
defined object Person

scala> Class.forName("org.neo4j.jdbc.Driver")
res0: Class[_] = class org.neo4j.jdbc.Driver

scala> ConnectionPool.singleton("jdbc:neo4j://localhost:7474/", "neo4j", "password")
14:09:20.332 [run-main-0] DEBUG scalikejdbc.ConnectionPool$ - Registered connection pool : ConnectionPool(url:jdbc:neo4j://localhost:7474/, user:neo4j) using factory : <default>
14:09:20.338 [run-main-0] DEBUG scalikejdbc.ConnectionPool$ - Registered singleton connection pool : ConnectionPool(url:jdbc:neo4j://localhost:7474/, user:neo4j)

scala> implicit val session = AutoSession
session: scalikejdbc.AutoSession.type = AutoSession

scala> val name = "kazzna"
name: String = kazzna

scala> val k = sql"MATCH (person :Person{name: ${name}}) WITH person OPTIONAL MATCH (person)-[:WRITES]->(blog) RETURN person.name, person.password, blog.name, blog.url".map(rs => Person(rs)).single.apply()
14:14:33.949 [run-main-0] DEBUG s.StatementExecutor$$anon$1 - SQL execution completed

// ScalikeJDBCの長いトレース

k: Option[Person] = Some(Person(kazzna,Some(some-password),Some(Blog(kazzna's blog,http://kazzna.hatenablog.com/))))

とりあえず普通に使えそうですね。

気付いたことと言えば、Cypherは更新+取得という文が書けるのですが、 それらはJDBCではupdateとしてしか発行できない為に戻り値は取れないようです。
(queryで発行するとreadonlyだよって怒られます。)

もう少し使ってみて、問題点が出ればまたプルリク投げようかなあと言う感じですね。

みなさんもぜひグラフDB使いましょう。

あ、今回は取り上げてないけれど、実際にはちゃんとインデックス貼りましょうね?
インデックスはほぼRDBと同じ感覚で張れば問題ないので。

Optionがモナドだったなんて・・・orz

いやね、Scalaの話なんですけどね、何の話かってね、Option型がOption("a")とかってやって生成できるの。。。

> val a = "abc"
a: String = abc

> val o = Option(a)
o: Option[String] = Some(abc)

今までずっとSome(a)NoneでしかOption生成できないと思っていた自分が恥ずかしくて(><;;

いや、ね、Monadには単位元というかreturnpurepoint
とにかくscalaapplyに当たる何かがあることが定義上必要なんですよ。

つまり絶対Option(???)で何か生成できるはずなのに、なぜか出来ないって思いこんで居たっていう。。。

で、なんでこんな話をしているかというと、Javaのライブラリを使う時のnull対策。

> val a: String = null
a: String = null

> val o = Option(a)
o: Option[String] = None

この通り、nullの値の変数をOptionにapplyするとちゃんとNoneを返してくれる!!!

つい先日apache poiのためのscalaを(spoiwoは微妙に古いので)自作していたんだけど、 これが結構nullが返ってくる。

その時書いてたコードがこちら

def getRow(idx: Int)(sh: Sheet): Option[Row] = {
  val r = sh.getRow(idx)
  if (r == null) None else Some(r)
}

これ、全部Optionのapllyでいいじゃんっていう・・・

是非これからやる人は読みやすい方を使ってください。。。

WindowsでGoのクロスコンパイル環境構築(Go Ver 1.5専用)

さすがはWindows!ひと手間かけたおいしさだぜ!!

経過をメモっているので、最後だけ読めば事足りるよ!!

参考にしたのはこのあたり

Go のクロスコンパイル環境構築 - Qiita
Windows7 64bit版でGo言語のクロスコンパイルを試す - taknb2nchのメモ

どうして試行錯誤時にずっと見てたページって再度ググると全然出てこないんだろう?
なんか英語のall.bashを実行してとかそういうページをずっと見ながらやってたんだけど、 見つからないのでリンクを貼れない。公式っぽいページ。

前提

Windowsユーザーなのでgccは使いません。

なのでcgoと呼ばれるCで作られたと思わしきgoは使えません。

前作業

Go1.5をインストロールします。

golang downloadってbingに入力すれば、msiみたいなのを入手できます。

適当にC:\Goとかに入れます。たぶんデフォルトでそうなってたから。

環境変数としては、GOROOT=C:\Goがセットされている状態になっていることを確認してください。

Goのコンパイヨ

クロヌコンパイヨに使用するコマンド自体はGOOS=[OS名] GOARCH=[ARCH名] go ~と簡単なのですが、 この使用したいGOOSとGOARCHの組み合わせ毎にGOそのものをコンパイヨしておく必要があります。

以後の記述が面倒なので、GOOS=darwin, GOARCH=amd64に限定して進めます。

コンパイヨはGOROOT\srcフォルダーに移動して、以下のコマンドを実行!!

GOOS=darwin GOARCH=amd64 make.bash

すると、たとえbashがある環境でもWindowsではmake.bat使えよって怒られるので、 コマンドプヨンプヨを開いて実行します。

SET GOOS=darwin
SET GOARCH=amd64
make.bat

はい。3行です。1行では実行できません。

上記コマンドを実行すると、BootStrap用のGOがないよと怒られます。
なんか設定がないと、%UserProfile%\Go1.4をBootStrapのrootと認識するようです。

GOROOT_BOOTSTRAPの設定

これは今からコンパイヨしようとしているGoをコンパイヨするためのGoなので、 もう1個Goの塊が必要?なのです。
SET GOROOT_BOOTSTRAP=%GOROOT%としても動くのかは確認していません。

1.4を落としてくるのも面倒なので、C:\Goを丸々コピーしてC:\GoBootを作ります。

そして、GOOROOT_BOOTSTRAPを設定して実行!!

SET GOOS=darwin
SET GOARCH=amd64
XCOPY /S /E /F /G /H /R /K /Y C:\Go C:\GoBoot
SET GOROOT_BOOTSTRAP=C:\GoBoot
make.bat

GOOSとかは設定済みであれば再度設定する必要はありません。

これでできるかと思いきや、gccがないからcgoが作れないよ!!と怒られます。

CGOを作らない

作れないなら作らなきゃいいじゃない!!ということで、 CGO_ENABLEDを0に設定します。

SET GOOS=darwin
SET GOARCH=amd64
XCOPY /S /E /F /G /H /R /K /Y C:\Go C:\GoBoot
SET GOROOT_BOOTSTRAP=C:\GoBoot
SET CGO_ENABLED=0
make.bat

あ、同じく一度やったやつは再実行しなくて大丈夫です。

これでコンパイヨに成功するはず!!

少なくともうちではしました。

これで

SET GOOS=darwin
SET GOARCH=amd64
go build hello.go

とかが可能になります。

ちなみにマックは高いので持ってません。

おまけ

なぜか頑張ってコマンヨプヨンプヨで実行したけど、 よくよく考えればgoをやっているみんなはgit for Windowsとか入れているはずなので、 git bash使って以下の処理でいいような気がする。

cp -r C:\Go C:\GoBoot
GOOS=darwin GOARCH=amd64 GOROOT_BOOTSTRAP=C:\GoBoot CGO_ENABLED=0 make.bat

試してないのでうまく動かないかもしれないけど。

goコマンド実行時も、SET GOOS=???とか実行してしまうと元に戻さないといけないので、 bashで1行で書いたほうが良さげです。

あ、GoBootはいらなくなったら消す方がいいと思う。 お好みで。

型安全なリストを作りたい Part. 1

最終的な目標としては、行列の計算がしたい。

  1 2 3       1 4       1*1+2*2+3*3 1*4+2*5+3*6       14 32
(       ) * ( 2 5 ) = (                         ) = (       )
  4 5 6       3 6       4*1+5*2+6*3 4*4+5*5+6*6       32 77

こんな感じのやつ。
2行3列の行列は3行2列の行列との積を求めることができて、結果は2行2列の行列になる。

もう少し一般化すると、m行n列 * n行p列 = m行p列で、 1つ目の行列の列数と2つ目の行列の行数が一致していないといけない。

これを普通にコンパイラが判定してくれるようにできればいいなという話。

ただ、全通りつくるとかはどう考えても現実的ではないので、 サイズ付きの行列を作ろうというのが主題です。

ちなみに、shapelessというライブラリにはSizedListみたいな型があるそうなので、
仕事とかですぐに必要であればそちらのソースを読んだりするべきですが、
今回は思考そのものを楽しむことが目的なので特に原典もなく間違いも気にせず進みます。

1. サイズを型で表現する。

いきなり難しいことは考えずに、とりあえずサイズ付きの集合型を考えることから始めます。
EmptyList, OneItemList, TwoItemsListのようなのがList[0], List[1]ってできればいいのですね。

まずは単純に数値0を表す型を作ってみます。

sealed trait Size
sealed trait Zero extends Size

ここまでは決め事なのでこれでいいとして、問題はこの先です。 One, Twoなんて続けていったらいつまで経っても終わりません。

そこで、ちょっと前に書いたScalaの型推論って難しいのチャーチ数を使ってみます。

final case class Succ[S <: Size]() extends Size

これで1がSucc[Zero], 2がSucc[Succ[Zero]]なんてふうに表現できるようになりませんか?
どんどん長くなっていくけどこの際気にしない。

2. サイズを型引数にもつリストを定義

これが正しく動作するのかを試すために、Listを定義してみます。
名前は標準ライブラリとかぶらないように適当につけます。

trait SizedList[S <: Size, +A] {
  def :+:[B >: A](b: B): SizedList[Succ[S], B] = new :+:(b, this)
}
case object SizedNil extends SizedList[Zero, Nothing]
case class :+:[S <: Size, A](head: A, tail: SizedList[S, A]) extends SizedList[Succ[S], A]

object SizedList {
  def empty[A]: SizedList[Zero, A] = SizedNil
}

replで動作確認。。。

scala> val a = SizedList.empty[Int]
a: SizedList[Zero,Int] = SizedNil

scala> val b = 2 :+: SizedList.empty
b: SizedList[Succ[Zero],Int] = :+:(2,SizedNil)

scala> val c = 9 :+: 1 :+: 2 :+: 4 :+: 7 :+: SizedList.empty
c: SizedList[Succ[Succ[Succ[Succ[Succ[Zero]]]]],Int] = :+:(9,:+:(1,:+:(2,:+:(4,:+:(7,SizedNil)))))

とりあえず問題なく動いてはいそうです。
まだ値を取り出すことすらできませんが。

次回予告(いつ!?)

次回はこれにheadtailを足してみましょう。

まだ漠然としか考えていませんが、値がOrderedじゃないとソートできないとかそういうのと同じ機構を使えばできそうです。

reverseが定義できればきっとそれなりのことができるはずです。

Scala勉強会で発表してきた。

初スライド!!!

for の使い方について。 http://kazzna.jp/slide/scala_for/index.html

Haskellモナド系ブログが大体doを理解するならStateだってなってたので便乗です。

勉強会でも口頭で言ったけれど、 flatMapの定義をするなら Monad を覚えないと危険です!!

あと、カッコの書き方について教わったのであとであとでスライドをなおす予定。

次回のスライドを作成中。

Scalaの型推論って難しい

ラムダ計算とかの例によくある チャーチ数Scalaで試してみる。

原点を表す Zero 、次を表す Succ を定義しておけば足し算とかできるってやつ。

まずは試し書き

import Conrtol.Applicative

zero f a = a
succ n f a = f $ n f a

one = succ zero
two = succ one

add a b = liftA2 (.) a b

toInt n = n (\a -> a + 1) 0
toAs n = n (\a -> 'a' : a) []

a = add (succ two) one

main = do
    putStrLn $ show $ toInt a // => 4
    pusStrLn $ show $ toAs a // => "aaaa"

わざと型は明記していない。haskellなどのきちんと型推論してくれる言語であればこれで動く。

もしくは動的型の言語でも。

def zero(f):
    def x(a):
        return a
    return x

def succ(n):
    def x(f):
        def y(a):
            return f(n(f)(a))
        return y
    return x

one = succ(zero)
two = succ(one)

def add(n):
    def x(m):
        def y(f):
            def z(a):
                return n(f)(m(f)(a))
            return z
        return y
    return x

def to_int(n):
    return n(lambda x: x + 1)(0)

def to_as(n):
    return n(lambda x: "a" + x)("")

a = add(succ(two))(one)

to_int(a) # => 4
to_as(a) # => 'aaaa'

Scalaの場合

これがScalaだとまあ大変で。。。

def zero[A](f: A => A)(a: A) = a

この時点で上記の例ではなんでもよかった(使ってない) f の型を固定。
固定しない場合型指定が大変になる(後述)。

なお、戻り値の型は書くべきだと思うけれども、今回は型推論具合を知りたいから書かなくても動作する限り省略。

def succ[A](n: (A => A) => A => A)(f: A => A)(a: A) = f(n(f)(a))

def one[A]: (A => A) => A => A = succ(zero)
def two[A]: (A => A) => A => A = succ(one)

import scalaz._
import Scalaz._  // Applicativeの導入(必須ではないけど・・・)
def add[A](x: (A => A) => A => A)(y: (A => A) => A => A) = (x |@| y) { case (a, b) => a compose b }

def toInt(n: (Int => Int) => Int => Int) = n(a => a + 1)(0)
def toAs(n: (List[Char] => List[Char]) => List[Char] => List[Char]) = n(a => 'a' :: a)(List.empty)

// def a[A]: (A => A) => A => A = add(succ(two))(one) // => 型エラー!!
def a[A]= add[A](succ(two))(one)

println(toInt(a)) // => 4
println(toAs(a)) // => List(a, a, a, a)

型が必須な上に、 val が型引数を受け付けてくれないからギリギリまで def じゃないといけない。

できる限りHaskellでやった時の型に近づけると以下のような感じ?

def zero[F, A](f: F)(a: A) = a
def succ[A, B, C](n: (B => C) => A => B)(f: B => C)(a: A) = f(n(f)(a))

def one[A, B]: (A => B) => A => B = succ(zero)
def two[A]: (A => A) => A => A = succ(one)

def add[A, B, C, D](x: A => B => C)(y: A => D => B) = (x |@| y) { case (a, b) => a compose b }

def toInt(n: (Int => Int) => Int => Int) = n(a => a + 1)(0)
def toAs(n: (List[Char] => List[Char]) => List[Char] => List[Char]) = n(a => 'a' :: a)(List.empty)

// def a[A]: (A => A) => A => A = add(succ(two))(one) // => 型エラー!!
def a[A] = add[(A => A), A, A, A](succ(two))(one)

println(toInt(a)) // => 4
println(toAs(a)) // => List(a, a, a, a)

途中から A => A =>... みたいなのを書くことを考えれば、最初の方が綺麗な気がする。。。

最後に蛇足だけど引数の順序をScalaっぽくしてみる。

def zero[A, F](a: A)(f: F) = a
def succ[A, B, C](n: A => (B => C) => B)(a: A)(f: B => C) = f(n(a)(f))

def one[A, B]: A => (A => B) => B = succ(zero)
// def one[A, B] = succ[A, A, B](zero) _  // <- これでも通るのは通るみたい
def two[A]: A => (A => A) => A = succ(one)
// def two[A] = succ[A, A, A](one) _

def flip[A, B, C](f: A => B => C) = (b: B) => ((a: A) => f(a)(b)) // これどっかにないのかな???
// scalaz.Function2Optsならあるけど・・・ uncurriedみたいなのが必要だよなぁ。
def add[A, B, C, D](x: A => B => C)(y: C => B => D) = flip((flip(x) |@| flip(y)) { case (a, b) => a andThen b })

def toInt(n: Int => (Int => Int) => Int) = n(0)(a => a + 1)
def toAs(n: List[Char] => (List[Char] => List[Char]) => List[Char]) = n(List.empty[Char])(a => 'a' :: a)

// def a[A]: A => (A => A) => A = add(succ(two))(one) // => 型エラー!!
def a[A] = add[A, (A => A), A, A](succ(two))(one)

println(toInt(a)) // => 4
println(toAs(a)) // => List(a, a, a, a)

何度 A => と書いただろう・・・???

atomのMarkdown Previewのフォント設定

atomエディタのMarkdownプレビューのCSS設定がいつの間にか変わっていたのでメモ。

目的は日本語のフォントにしたい。デフォルトだと中国系だからね

検索するといくつか出てくるけど、 setting -> Open Config Folder で、 styles.less を修正する。

/*
* Your Stylesheet
*
* This stylesheet is loaded when Atom starts up and is reloaded automatically
* when it is changed.
*
* If you are unfamiliar with LESS, you can read more about it here:
* http://www.lesscss.org
*/

.tree-view {
     font-family: "Migu 1C", sans-serif;
}

// Markdown Preview
.markdown-preview {
     font-family: "Migu 1C", sans-serif;
     h1, h2, h3, h4, h5, h6 {
          font-family: "Migu 1C", sans-serif;
     }
     code, blockquote, atom-text-editor {
          font-family: "Migu 1M", "Meiryo", monospace;
     }
}

.tree-view はどこのことかわからないのでとりあえず書いただけだけど、 重要なのは .markdown-preview って方。

ここに font-family で好きなフォントを指定。 これで基本的には変わるんだけど、コード部分とかのバッククォート部分が変わらない。

なのでmarkdown-previewのスタイルを確認。

github.com

code とか blockquote とかが怪しいと思い追加してみるも変わらなかった。
結果的には atom-text-editor ってのがコード部分らしい。

まあ、とりあえず全部書いておいた。