GHC8.4とGHCJS
github.com
ここのコメントにDockerを置いてくれている人が居る。
tarをダウンロードして、stack.yamlを書いて、stack setupしてみる。
.stack-work/dist/x86_64-linux/Cabal-2.2.0.1/build/Parser.hs:1445:48: error Parser.hs" "Not in scope: type variable ‘a’
こんなエラーが出た。
どうやら
Can't build `ghc-8.8` branch with `happy-1.19.10` (#16652) · Issues · Glasgow Haskell Compiler / GHC · GitLab
Fix optSemi type in Parser.y (071bef18) · Commits · Glasgow Haskell Compiler / GHC · GitLab
この辺りのバグっぽい。
一行書き換えれば良いんだろうけど、stack build途中でエラーになるのでどうすれば良いのか分からない。
仕方がないので、コンパイルしている長い時間の間に
.stack/programs/x86_64-linux/ghcjs-0.2.0.9011009_ghc-8.4.1/src/ghc/compiler/parser/Parser.y
このファイルが生成されているので、stack buildの途中でエラーが出るプロセスに行く前に手動で書き換えた。
普通はどうするんだろう…?
# 追記
グローバルのhappyをダウングレードするために、プロジェクトのresolverが効いている場所でもう一度stack install happy でバージョン1.19.9を入れればエラーにならない?どうもstackのグローバルプロジェクトのbinの使い方がよくわからない。
なんだかGHCJS開発でinteroを使いたかっただけなのにめっちゃ苦労した。
GHCJSのスタブを使うのは大変っぽい
昨日のghc-base-stubを使う方式だと上手くいかなかった。
GHCJSに再度トライ - ログ日記
例えばghcjs-domを使いたい場合、GHCJSだと ghcjs-dom, ghcjs-dom-jsffi を使うところが
GHC版だと ghcjs-base-stub, ghcjs-dom, ghcjs-dom-jsaddle, jsaddle-dom, jsaddle を使うことになる。
これで一旦はコンパイルが通るんだけども、GHCJS版とGHC版でDOMの型が違うっぽい。
GHCJS版
https://raw.githubusercontent.com/ghcjs/ghcjs-dom/master/ghcjs-dom-jsffi/src/GHCJS/DOM/Types.hs
type JSM = IO -- | This is the same as 'JSM' except when using ghcjs-dom-webkit with GHC (instead of ghcjs-dom-jsaddle) type DOM = IO -- | The 'MonadJSM' is to 'JSM' what 'MonadIO' is to 'IO'. -- When using GHCJS it is 'MonadIO'. type MonadJSM = MonadIO -- | This is the same as 'MonadJSM' except when using ghcjs-dom-webkit with GHC (instead of ghcjs-dom-jsaddle) type MonadDOM = MonadIO -- | The 'liftJSM' is to 'JSM' what 'liftIO' is to 'IO'. -- When using GHCJS it is 'liftIO'. liftJSM :: MonadJSM m => JSM a -> m a liftJSM = liftIO -- | This is the same as 'liftJSM' except when using ghcjs-dom-webkit with GHC (instead of ghcjs-dom-jsaddle) liftDOM :: MonadDOM m => DOM a -> m a liftDOM = liftIO
こんな感じで普通のIOになっている。
ghcjs-dom-jsaddleでは
JSDOM.Types as GHCJS.DOM.Types
のようになっていて、その定義は
https://raw.githubusercontent.com/ghcjs/jsaddle-dom/master/src/JSDOM/Types.hs
-- | This is the same as 'JSM' except when using ghcjs-dom-webkit with GHC (instead of ghcjs-dom-jsaddle) type DOM = JSM -- | This is the same as 'JSContextRef' except when using ghcjs-dom-webkit with GHC (instead of ghcjs-dom-jsaddle) type DOMContext = JSContextRef -- | This is the same as 'MonadJSM' except when using ghcjs-dom-webkit with GHC (instead of ghcjs-dom-jsaddle) type MonadDOM = MonadJSM -- | This is the same as 'liftJSM' except when using ghcjs-dom-webkit with GHC (instead of ghcjs-dom-jsaddle) liftDOM :: MonadDOM m => DOM a -> m a liftDOM = liftJSM -- | This is the same as 'askJSM' except when using ghcjs-dom-webkit with GHC (instead of ghcjs-dom-jsaddle) askDOM :: MonadDOM m => m DOMContext askDOM = askJSM -- | This is the same as 'runJSM' except when using ghcjs-dom-webkit with GHC (instead of ghcjs-dom-jsaddle) runDOM :: MonadIO m => DOM a -> DOMContext -> m a runDOM = runJSM
全てJSMを使うようになっている。
JSMは
https://github.com/ghcjs/jsaddle/blob/master/jsaddle/src/Language/Javascript/JSaddle/Types.hs
ここで GHCJSの場合はIOにしているけれども、GHCの場合はIOと似ているが異なる型になるようだ。
GHCを使う場合には、JSMとIOは変換できないっぽい。
もしかすると、ghcjs-base-stubのように、ghcjs-dom-stubとかを作っていけば良いのかもしれないが、大量に型があって自分で作るのはつらい。
別の方法を考える。
ーーーー
ここまで書いて、ちょっとサンプルを作った。
github.com
type JSM = IO 版。
自動生成されたコードが何万行もあってつらい…。
正規表現置換で一気にいけるところは変更して、無理なところは手動でぽちぽちやった。
折角なので開発中にJSMをprintしたら分かりやすいメッセージを出すようなスタブが出来れば良かったんだけど、自動生成じゃないと無理そう。普通にjsaddleを使うかね…。
少ししか(と言っても数万行)書いてないのに既にコンパイル重くなってきたしね。
型だけ見るなら
{-# LANGUAGE CPP #-} module FormSample where import Control.Monad import Control.Monad.IO.Class (MonadIO (..)) import Control.Monad.Trans.Class (lift) import Control.Monad.Trans.Maybe (MaybeT (..), runMaybeT) import GHCJS.DOM import GHCJS.DOM.Document import GHCJS.DOM.HTMLCollection import GHCJS.DOM.Types import Language.Javascript.JSaddle.Debug formEl :: MaybeT JSM Element formEl = do doc <- MaybeT currentDocument forms <- getForms doc form <- MaybeT $ namedItem forms "form" return form someProcess = undefined app :: JSM () app = do mform <- runMaybeT formEl case mform of Just a -> someProcess a Nothing -> undefined return () #ifdef MIN_VERSION_ghcjs_dom_jsffi runForm :: IO () runForm = app #else runForm :: IO () runForm = runOnAll_ app #endif
jsaddleのデバッグ用関数(?)runOnALLが使える。実行しても特に何も起こらない。
PostgreSQLでビューを使う時の最適化
ちょっと簡単なサンプルが思い付かないけど。
joinが大量にある巨大なビューから少ない件数のデータを取得する場合。
ビュー単体でselectしたら全てインデックススキャンになるようにしていて
select * from large_large_view where large_large_view.post_id in (123, 456,789)
これは高速になるようにしていた。
しかし
select large_view.* from ( select post_id from fav where user_id = ? ) as fav join lateral ( select * from large_large_view where large_large_view.post_id = fav.post_id ) as large_view on true
joinすると遅い。
何故か 結合条件で絞り込む前にビューの深いところでseq scanが走っている。
viewとjoinで遅いというのは都市伝説ではなかった模様。
ビューをバラして一段階層を上げてjoinすれば速いし、ビューにしなくても join (select ...) と入れ子にすると遅かった。
lateral効いてないっぽい。
それで一度は諦めていたんだけど、何故か他の箇所で速く動いているものがあった。
そっちの方はよく見ると
select large_view.* from ( select post_id from fav where user_id = ? ) as fav join lateral ( select * from large_large_view where large_large_view.post_id = fav.post_id limit 15 -- <= 制限 ) as large_view on true
数件で収まらない可能性があるからlimit付けてた。
そうするとプランがガラッと変わってjoinを使わないときと同等になるようだった。
favの方のインデックスでプランナはfavが数件しか無いことが分かり、large_large_viewも数件しかないことが分かる。とプランナが解釈すると思ってたけどそうではなかったみたい。
GHCJSに再度トライ
stack new myghcjs cd myghcjs
myghcjs.cabal の 各build-depends に追加
if impl(ghcjs) build-depends: ghcjs-base if !impl(ghcjs) build-depends: ghcjs-base-stub
js.yaml
resolver: lts-8.11 compiler: ghcjs-0.2.1.9008011_ghc-8.0.2 compiler-check: match-exact setup-info: ghcjs: source: ghcjs-0.2.1.9008011_ghc-8.0.2: url: https://github.com/matchwood/ghcjs-stack-dist/raw/master/ghcjs-0.2.1.9008011.tar.gz sha1: a72a5181124baf64bcd0e68a8726e65914473b3b
lts-9.21は動かなかった。というかコンパイルのリンクが永遠に終わらなかった。
Linking template Haskell -- Stuck for 10+ hrs · Issue #14 · matchwood/ghcjs-stack-dist · GitHub
stack build --stack-yaml=js.yaml
jsが出力されているか確認する。
stack.yaml
resolver: lts-13.24 extra-deps: - ghcjs-base-stub-0.2.0.0 - containers-0.5.11.0
GHCJSのコンパイラを使わないビルドテスト(intero用)
stack build
GitHub - ghcjs/ghcjs-base: base library for GHCJS for JavaScript interaction and marshalling, used by higher level libraries like JSC
ここのサンプルを書いてみる。
{-# LANGUAGE CPP #-} {-# LANGUAGE JavaScriptFFI #-} {-# LANGUAGE OverloadedStrings #-} module Main where import Lib import Data.JSString as S #ifdef __GHCJS__ foreign import javascript unsafe "require('console').log($1)" console_log :: S.JSString -> IO () #else console_log :: S.JSString -> IO () console_log = error "GHCJS required to use console_log" #endif main :: IO () main = do console_log "yo"
コンパイルが通るプラグマを探すのでハマった。
JavaScriptFFI はGHCJSでしか効かないので、intero用に#ifdef も必要っぽい。
Add -fdefer-ffi-errors flag (#14227) · Issues · Glasgow Haskell Compiler / GHC · GitLab
ここに書いているように#ifdefで分ける。
.yaml や .cabal は変更なし。
JSとやり取りする例は新しくなっていて
A few examples of Foreign Function Interface · ghcjs/ghcjs Wiki · GitHub
こっちの方は古くて動かないようだ。
古い記事を参考にしてしまうと結構ハマる。
Hasteを試したログ
GHCJSの重さが気になっていたところで、こんな記事を見かけた。
qiita.com
Hasteが良さそうなので試してみる。
Hasteのプロジェクトの作り方は stack new した後に
stackoverflow.com
該当箇所をここの設定に書き換える。
% stack exec haste-boot ... Installed haste-prim-0.5.4.2 Warning: The package list for 'hackage.haskell.org' does not exist. Run 'cabal update' to download it. Resolving dependencies... haste-cabal.bin: Could not resolve dependencies: trying: time-1.5.0.1 (user goal) next goal: deepseq (dependency of time-1.5.0.1) Dependency tree exhaustively searched.
エラーになった。
再度トライ。
% stack exec haste-boot -- --local Downloading haste-cabal from GitHub Downloading Google Closure compiler... `include' is not a directory
エラー。
% mkdir include % stack exec haste-boot -- --local Downloading haste-cabal from GitHub Downloading Google Closure compiler... utils/unlit: changeWorkingDirectory: does not exist (No such file or directory)
エラーが素っ気ないのでよく分からないけど、--localでhasteのソースを探しているのか…。
https://github.com/valderman/haste-compiler/issues/395
この辺を見ると解決しているような気がする。
stack.yaml を変更してもう一度トライ。
extra-deps: - haste-compiler-0.5.5.1 - ghc-simple-0.3 - shellmate-0.2.3 - HTTP-4000.2.23
stack build stack install haste-compiler stack exec haste-boot
同じエラーになった。
どうにもならないのでソースから入れる。
github.com
git clone https://github.com/valderman/haste-compiler.git cd haste-compiler stack build stack install stack exec haste-boot
何の問題もなく入った。
バージョンが0.6.0.0になっていたので、stack.yamlのバージョンを変えたら入るんだろうか。
resolver: lts-6.14 extra-deps: - haste-compiler-0.6.0.0 - ghc-simple-0.3 - shellmate-0.3.4.2 - shellmate-extras-0.3.4.1 - http-conduit-2.2.4 - http-client-0.5.14 - http-client-tls-0.3.5.3 - tagged-0.8.6
stackoverflow.com
うーん、stack build --copy-compiler-tool haste-compiler は何か違うっぽい。
取り敢えずここまでのソースから入れたあとの設定を最初からやる。
stack new haste-project cd haste-project
compilerは使わないのでhaste-libだけ書く。
.cabal
build-depends: base >=4.7 && <5 , haste-lib
stack.yaml
resolver: lts-6.14 packages: - . extra-deps: - haste-lib-0.6.0.0 - binary-0.7.6.1 - containers-0.5.6.3 - haste-prim-0.6.0.0 - time-1.5.0.1
これでstack build出来るし、import Hasteできた。
GHCのバージョンが古くてinteroが動かなくなったので intero.el を修正する。
(defun intero-copy-compiler-tool-auto-install (source-buffer targets buffer) "Automatically install Intero appropriately for BUFFER. Use the given TARGETS, SOURCE-BUFFER and STACK-YAML." (let ((ghc-version (intero-ghc-version-raw))) (insert (format " Installing intero-%s for GHC %s ... " intero-package-version ghc-version)) (redisplay) (cl-case (let ((default-directory (make-temp-file "intero" t))) (intero-call-stack nil (current-buffer) t nil "build" "--copy-compiler-tool" (concat "intero-" intero-package-version) "--flag" "haskeline:-terminfo" "--resolver" (concat "ghc-" ghc-version) ;; "haskeline-0.7.5.0" "haskeline-0.7.4.0" "ghc-paths-0.1.0.9" "mtl-2.2.2" "network-2.7.0.0" "random-1.1" "syb-0.7")) (0 (message "Installed successfully! Starting Intero in a moment ...") (bury-buffer buffer) (switch-to-buffer source-buffer) (intero-start-process-in-buffer buffer targets source-buffer))
haskeline のバージョンを下げる。
intero自体のバージョンはdefcustomで変更できるようになってるんだけど、この依存ライブラリは固定なので直接編集するしかない。
編集したらbytecompileして起動確認。
初期のファイルを
module Lib ( someFunc ) where import Haste someFunc :: IO () -- someFunc = putStrLn "someFunc" someFunc = writeLog $ toJSString "someFunc"
console.log出力に変えてコンパイル。
#!/bin/bash TARGET=compiled.js set -eux mkdir -p build hastec -O2 -fglasgow-exts \ --opt-minify --opt-minify-flag='--jscomp_off=checkVars' \ -o build/compiled.js \ --outdir=build \ app/*.hs src/*.hs
24KBになった。
自分でclosure compilerを実行したときより誤差程度軽い。
最初のURLの比較のように3KBには遠い。
バージョンが上がって機能が増えて重くなったんだろうか。
とりあえず一旦ここまで。
GHCJSを使えるようにしたログ
ソースからのインストールは出来たんだけど、それをstackで使う方法が分からなかった。
エラーがややこしいので元々入っているcabalを消したりhaskell-platformを消したりしてstackだけ動いている状態にして試した。
コンパイルが1時間近くかかる。長い。
取り敢えず動いたのはいいんだけど、emacsで不備が出る。stackで動かしたい。
https://docs.haskellstack.org/en/stable/ghcjs/#ghcjs
ここからのリンクの
https://github.com/matchwood/ghcjs-stack-dist
この設定をコピペして使う。
jsのビルドはスムーズになった。
だがやっぱり普通のHaskellのソースを書くようには行かない。
どうやらemacsのinteroが動かない。
github.com
closeになっているけどghcjs-base-stubもよく分からなかった。ghcjs --interactiveが無理だと厳しい?
よく考えたら素のHaskellが動くし
http://hackage.haskell.org/package/ghcjs-dom
この辺のGHCJS用のライブラリは普通のGHCで動くんだから、普通のGHCでコンパイルし直してプログラミング中はHaskellプログラムとしておけば良さそうに思える。
コンパイル…コンパイル…コンパイル…長い。
github.com
そしてエラーが出る。
jsaddleのコンパイルでメモリ8GB以上持っていかれる。富豪的過ぎる…。
メモリを15GBにしてもスワップも持って行かれた。でも何とかコンパイル成功。
適当にsrcディレクトリをシンクしてGHCJS用のstackファイルとGHC用のstackファイルを用意して、ソースを書くときはGHC用のディレクトリで作業するようにすれば、何とか出来そうだった。
全体的に参考にしたのはこの辺。
qiita.com
labs.spookies.co.jp
それでstackの初期状態 "someFunc" を出力するプログラムをコンパイルしたらjsが1MBある。Closure Compilerで圧縮しても240Kある。
まあHaskellのbaseとかの環境が全部入っているとそうなるのか。Closure Compilerで未使用関数だと判別できない部分が多かったっぽい。
ちなみにClosure Compilerは
java -jar $DIR/compiler.jar \ --compilation_level=ADVANCED_OPTIMIZATIONS \ --define 'goog.LOCALE=ja' \ --define 'goog.DEBUG=false' \ --jscomp_off=checkVars \ $GHCJSOUT \ > $COMPILEDJS
こんな感じで、 --jscomp_off が必要。普通にやると関数の多重定義でエラーになる。
emacs-purpose(window-purpose)でバッファを開くウィンドウを目的(モード、ディレクトリ)ごとに固定する
昨日の続き。
Emacsの使い方を変えようとして無理だった - ログ日記
似たような質問を発見した。
stackoverflow.com
この人は .c と .h でウィンドウを固定したかったみたい。
ここでは buffer-stack が挙げられている。
これも少し使ってみたけれど、まあその名の通りstackなので自分の目的とちょっと違う。操作もちょっと大変そう。
次のコメントでは、.hファイルの次のファイルを開くのはIDEからEmacsにbad idea を持ち込むことだとまで言われているな…。まあ自分の場合はファイル名の頭文字とtabで探すのでちょっと異なるのだが。
Buffer management - WikEmacs
wikiを眺めていると、See AlsoにKeep a certain kind of buffers in a window: Purposeという項目があることに気付いた。separate bufferやbuffer list groupingとかで見ていたのでスルーしていた。
emacs-purpose(window-purpose)で目的ごとにバッファリストの設定が出来るっぽい。
確かに、何のためにウィンドウ別の履歴が欲しいのかと言うと、ウィンドウごとにそれぞれ別の目的があるからで、こちらの方がより上位(?)の概念である。単に履歴を分割するよりはhtmlを編集しているときにC-x C-f でphpを開いたり C-x 0 で閉じたとしても、phpファイルを開いたらphp用の別のウィンドウで立ち上がってくれる方が有り難い。
何故誰もオススメしていないのか…。
Usage · bmag/emacs-purpose Wiki · GitHub
ここの最後にdesctop-save-modeにはデフォルトで対応していると書いている。
なかなか良い感じに思えるが、軽く使ってみると以下の二点で不満が出てきた。
・バッファ名ではなくてファイルのpathでルールを作りたい
・切り替え時にデフォルトを表示してほしい
なのでカスタマイズするelispを書いた。
;; overwrite (defadvice purpose--buffer-purpose-name-regexp-1 (around path-regexp (buffer-or-name regexp purpose)) "Add filename with regexp feature." (let* ( (mode (unless (stringp regexp) (car regexp))) (mode-match (if mode (eq (purpose--buffer-major-mode buffer-or-name) mode) t)) (bufname (or (and (bufferp buffer-or-name) (buffer-name buffer-or-name)) buffer-or-name) ) (regexps (if (stringp regexp) regexp (cdr regexp))) (target (if mode (buffer-file-name (get-buffer bufname)) bufname)) ) (when (and mode-match target (string-match-p regexps target)) (setq ad-return-value purpose))) ) (ad-activate 'purpose--buffer-purpose-name-regexp-1) (defadvice purpose-read-buffers-with-purpose (around with-default (purpose)) "Prompt with default." (let* ( (other-buffer (delq (current-buffer) (purpose-buffers-with-purpose purpose))) (default (or (buffer-name (car other-buffer)) "*scratch*")) (ret (completing-read (concat "[PU] Buffer (default " default "): ") (mapcar #'buffer-name other-buffer) nil t nil)) ) (setq ad-return-value (if (string= ret "") default ret)) )) (ad-activate 'purpose-read-buffers-with-purpose)
設定のリストががっつりコアに書かれているので、うまくhookできなかった。なのでコアの関数のカスタマイズ。
引数がregexpじゃなくてコールバックだと良かったんだけれど。
文法を調べつつ書いていてあまり自信なし。
let*で逐次的に書いていてlispっぽくない気がする。
設定はこんなの。
(setq desktop-path '(".")) (desktop-save-mode t) (require 'window-purpose) (purpose-mode) (require 'window-purpose-x) (purpose-x-kill-setup) (add-to-list 'purpose-user-mode-purposes '(php-mode . php)) (add-to-list 'purpose-user-mode-purposes '(web-mode . web)) (add-to-list 'purpose-user-mode-purposes '(js2-mode . js2)) (add-to-list 'purpose-user-mode-purposes '(css-mode . css)) (add-to-list 'purpose-user-mode-purposes '(sql-mode . sql)) (add-to-list 'purpose-user-regexp-purposes '((php-mode . "/Page/") . phppage)) (add-to-list 'purpose-user-regexp-purposes '((php-mode . "/Model/") . phpmodel)) (add-to-list 'purpose-user-regexp-purposes '((php-mode . "/Dto/") . phpdto)) (purpose-compile-user-configuration) ;; purpose-switch-buffer-with-purpose to default (define-purpose-prefix-overload purpose-switch-buffer-overload '(purpose-switch-buffer-with-purpose switch-to-buffer )) (global-set-key [S-right] 'split-window-horizontally) (global-set-key [S-left] 'split-window-horizontally) (global-set-key [S-up] 'split-window-vertically) ;(define-key global-map [S-down] 'delete-other-windows) (global-set-key [right] 'windmove-right) (global-set-key [left] 'windmove-left) (global-set-key [up] 'windmove-up) (global-set-key [down] 'windmove-down)
取り敢えずPHPのモデル用のウィンドウ、画面用のウィンドウ、というように指定できるようにした。
major modeとセットなので、ディレクトリに別の拡張子のファイルがあったらそれは別のルールになるようにしている。
本当は .dir-locals.el に書きたかったんだけれど、こっちも書式がよく分からず取り敢えずinit.elに書いた。
やっと設定が出来てきたところなので使い勝手はまた今度。