ログ日記

作業ログと日記とメモ

GHCJS + Template Haskell でハングアップ

hangs when linking Template Haskell 8.0/8.2 · Issue #668 · ghcjs/ghcjs · GitHub
このissueは見てたはずなんだけど、流し読みしていて頭に入ってなかった。

実際にTemplate Haskellを使ってみると問題が起きた。

なので再インストールする。
その前に、
https://n314.hatenablog.com/entry/2019/06/06/211205
ここで書いたhappyのエラー解消のために ghcjsのresolverがと同じバージョンで(プロジェクトのディレクトリで) stack install happyする。

その後に GHCJSを入れ直す。
(入れ直す必要あるかは不明。単にnodejsのバージョンを変えたあとにstack buildしたらコマンドが無い系のエラーで進まなくなり、どこでnodeのpath情報が使われているか分からなかったので。)

nodejs は nodebrew
https://github.com/hokaccha/nodebrew
で v8.9 を指定した。元々使っていたバージョンは10.15

stack setup --stack-yaml=js.yaml --ghcjs-boot-clean --reinstall

このコマンドでcleanしてghcjs-bootをやり直すことは出来るんだけど、どうやら解決しないっぽい。


tackで入れたghcjsのsetup-exe(例:~/.stack/setup-exe-cache/x86_64-linux/setup-Simple-Cabal-1.22.4.0-ghcjs-0.2.0.20151230.3_ghc-7.10.2)はビルド時に存在していたnodeコマンドのパスがshebangとしてハードコーディングされていて、~/.ghcjs/x86_64-linux-0.2.0.20151230.3-7.10.2/ghcjs/nodeファイルにもnodeコマンドのパスが書かれています。


ですので、ghcjsとツール群が認識しているnode.jsのパスも一緒に変更しないと、nodeコマンドが見つからないというエラーを吐くようになったり、いつまでも差し替え前のnodeコマンドを参照したりします。


必要最低限の修正に留めるのは調査するのが面倒だったので、今回は下記の3つを削除してからghcjsの再インストールを行う事で新しいnode.jsのパスを認識してもらう事にしました。

  • ~/.ghcjs
  • ~/.stack/setup-exe-cache/x86_64-linux/setup-Simple-Cabal-1.22.4.0-ghcjs-0.2.0.20151230.3_ghc-7.10.2
  • ~/.stack/setup-exe-cache/x86_64-linux/setup-Simple-Cabal-1.22.4.0-ghcjs-0.2.0.20151230.3_ghc-7.10.2.jsexe


これでnode.jsのバージョン管理システムを乗り換えたりしてnodeコマンドのパスが変わっても新しい方のパスを認識させることが出来ます。

ghcjsがnode.jsのバージョンによってはうまく動かない事への対処 - siphilia.github.io

なので全部消してからやり直し。
なんかしょっちゅう消したりインストールしたりしているな…。



Template Haskell を使って書いたコードは
https://github.com/nishimura/ghcjs-form-sample1/blob/31b79371e999f1ab5a270031e60be684a51adf1b/app/Generate1.hs
この辺。

GitHub Pull Request ではなく git request-pull

Linus

Git comes with a nice pull-request generation module, but github
instead decided to replace it with their own totally inferior version.

https://github.com/torvalds/linux/pull/17#issuecomment-5654674

Gitにはniceな pull-requestモジュールがあるのにGitHubは劣化版に置き換えた、と言っていて

I believe he's referring to git-request-pull.

https://github.com/torvalds/linux/pull/17#issuecomment-5660608

それは git request-pull のことらしい。
というのを読んだので、使い方のメモ。

git request-pull [-p] <start> <url> [<end>]

このコマンドでリクエストを生成する。例えば

% git request-pull origin/master https://github.com/nishimura/ghcjs-form-sample1 use-template-haskell
The following changes since commit 555c1c69f771b519e4c22a534f8d6b3837497361:

  Merge branch 'monad-trans' (2019-06-18 21:19:37 +0900)

are available in the git repository at:

  https://github.com/nishimura/ghcjs-form-sample1 use-template-haskell

for you to fetch changes up to 64b5751f8bb115e354054572b78488d4c2a81980:

  add code generator by template haskell (2019-06-21 20:17:40 +0900)

----------------------------------------------------------------
Satoshi Nishimura (1):
      add code generator by template haskell

 app/AppMain.hs   |  8 ++++----
 app/Generate1.hs | 45 +++++++++++++++++++++++++++++++++++++++++++++
 app/Generate2.hs | 59 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 app/Helper.hs    | 46 ++++++++++++++++------------------------------
 package.yaml     |  1 +
 5 files changed, 125 insertions(+), 34 deletions(-)
 create mode 100644 app/Generate1.hs
 create mode 100644 app/Generate2.hs

こんな感じに、git log --stat のようなログが生成される。
ここにアップしたから受け取ってというメッセージになっている。


これを見てpullする人は、何か適当にブランチを切って書いてあるメッセージの通りにpullすれば良い。

git checkout -b work/merge1 555c1c69f771b519e4c22a534f8d6b3837497361

git pull https://github.com/nishimura/ghcjs-form-sample1 use-template-haskell

これで問題なければdevelopやmaster等にマージする。



分散バージョン管理っぽい。


Linusが時々タグ云々と言っているのは、git tagではなくて
とあるエンジニアの備忘log: git の Acked-by と Reviewed-by
ここの reviewed-by とかのこと?

CodeReview - SambaWiki
この辺りも詳しい。
Signed-off-by は git commit --signoff があるけど Reviewed-by などは手動だって。

自動化は
Elasticity: Adding Reviewed-by and Acked-by Tags with Git
こんな感じ。


これって一連のコミット(3コミットとか)をレビューしてマージする場合はrebaseとかでコメントを全部書き換えることになるのか。
この方針だとGPGのsignとかコミットに付けてもマージ時に全部消えるってことか。

GHCJS + Jsaddle

Jsaddleを動くようにした。
昨日書いた https://n314.hatenablog.com/entry/2019/06/06/210731 スタブはあまり意味はなかった。
理解は進んだけども。
webkit.idl などのWebIDLにDOMの定義があるから、そこから自動生成しているっぽい。
ということは、基本的にjsaddle-domはDOMの仕様を網羅しているということになる。


interoやreplでprintデバッグのようなことはせずに、素直にjsaddleをブラウザで動かした方が早い。

www.reddit.com
ここにもそのようなことが書いてあった。



というわけでサンプルを作った。
github.com
これは

github.com
ここのコードを持ってきたんだけど、そのままでは動かないから結構変更した。

hoogleしたりググったり、でも分かりづらいからghcjs-domのリポジトリ持ってきてgrepして直接ソースを見ることが多かった。
説明は無く型が合えば動くだろうっていう姿勢、なかなかつらいものがある。まあ実際に型が合えば動いたんだけど…。
Haskellerは割とカジュアルに互換性を崩すっていうものの一端を見た。

型チェックが厳しくて、createElementで生成したElementはイベントのターゲットにできない。イベントが扱えるのはHTMLElementなので。
いつもはNodeとかElementとかHTMLElementとか気にせず同じものとして扱ってたけども、違うものなんだよねえ…。Haskellを使えば他の言語のバグが見つかるっていうのはこういう感じなんだろうか。


せっかくなのでサンプルは少し変更してHTMLファイルを読み込むようにしている。
動作確認しやすく、既存のサイトの一部に組み込みやすいように。

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
こっちの方は古くて動かないようだ。
古い記事を参考にしてしまうと結構ハマる。