ScalaでSQLが書きたいんだ!!
DSLとかOR Mapperとかじゃなくて、SQLが直接書きたいんだ!!
嘘です。タイトル詐欺です。
最近neo4jというグラフ指向データベースがお気に入りでして、 アクセス用のクエリとしてCypherという言語を使います。
基本はHTTP+JSONでRESTにアクセスできるのですが、Java用にneo4j-jdbcというライブラリがあり、これを使ってアクセスしようと思うわけです。
そうすると、当然SQLではないのでDSLのSQL自動生成とかは使えず、直接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のブログを読む、と。
:Person
のpassword
が無いのが特徴ですね。
Scalaから取得しよう!!
sbtは使える前提で話を進めますので、sbtって何?って人はtypesafe activatorでググってください。
で、以下のコマンド中のsbt
をactivator
に置き換えれば多分大丈夫です。
まず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には単位元というかreturn
?pure
?point
?
とにかくscalaのapply
に当たる何かがあることが定義上必要なんですよ。
つまり絶対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を実行してとかそういうページをずっと見ながらやってたんだけど、
見つからないのでリンクを貼れない。公式っぽいページ。
前提
なので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)))))
とりあえず問題なく動いてはいそうです。
まだ値を取り出すことすらできませんが。
次回予告(いつ!?)
次回はこれにhead
やtail
を足してみましょう。
まだ漠然としか考えていませんが、値が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のスタイルを確認。
code
とか blockquote
とかが怪しいと思い追加してみるも変わらなかった。
結果的には atom-text-editor
ってのがコード部分らしい。
まあ、とりあえず全部書いておいた。