elm (2)

暑くてやる気が、Nothing, Maybe.

try elm on OpenBSD

Advent Calendar elm なんてので検索してたら、結構昔からelmが注目されてる事を知った。そんな中で、

Elm19.0をARM 64bit プロセッサでソースからビルドする

なんてのをやってる勇気ある人を知った。オイラーもやってみるか。肝はこれだな。

git clone https://github.com/elm/compiler.git

手始めにcabal-installの最新版を入れる。備え付けの2.4.0では失敗したからね。最新の3.2.0に鞍替えさ。 ulimit -d で使えるdataエリアを増やしておかないと、ghcがout of memoryって言ってくるぞ(途中で失敗した)。

ob$ ./bootstrap.sh -j 2 --no-doc
 :
===========================================
The 'cabal' program has been installed in /home/sakae/.cabal/bin/
You should either add /home/sakae/.cabal/bin to your PATH
or copy the cabal program to a directory that is on your PATH.

やっとインストールでけた。残骸は軽く1.2Gを越えていた。まあ、tls1.2まで含めたブラウザー環境とパッケージのハンドリングアプリだから、こうなるのは仕方が無いか。それにしても浮世離れしてると思うのはオイラーだけ?

まずはPATHを通してから、cabal update する。

ob$ cabal install
 :
Starting     aeson-1.5.3.0 (lib)
Building     aeson-1.5.3.0 (lib)
Installing   aeson-1.5.3.0 (lib)
Completed    aeson-1.5.3.0 (lib)
cabal: Failed to unpack exe:worker from elm-0.19.1. The exception was:
Invalid file name in tar archive: "elm-0.19.1/../"
ob$ cabal install -v
In order, the following will be built:
 - elm-0.19.1 (exe:worker) (requires build)
creating /tmp/cabal-install.-88040/dist-newstyle/build
creating /tmp/cabal-install.-88040/dist-newstyle/tmp
Extracting /home/sakae/compiler/dist-newstyle/sdist/elm-0.19.1.tar.gz to
/tmp/cabal-install.-88040/dist-newstyle/tmp/src-88040...
CallStack (from HasCallStack):
  die', called at ./Distribution/Client/ProjectOrchestration.hs:1041:55 in main:
Distribution.Client.ProjectOrchestration
cabal: Failed to unpack
elm-0.19.1-306ceab435d4b840b4a39db2dcfa6a977053f6f8a18ff078d904a81158080372.
The exception was:
Invalid file name in tar archive: "elm-0.19.1/../"

諦めましょ。

link

elm-format

資料によると、elm-formatってのを入れておくと、コードを整形してくれるとな。goのあれと同じで、どんなスタイルが良いかという論争に終止符を打つやつらしい。

sakae@pen:/tmp$ sudo npm install -g elm-format
  :
/usr/bin/elm-format -> /usr/lib/node_modules/elm-format/bin/elm-format

> elm-format@0.8.3 install /usr/lib/node_modules/elm-format
> binwrap-install

ERR Error: EACCES: permission denied, mkdir '/usr/lib/node_modules/elm-format/unpacked_bin'

んが、エラーだよ。こんなの誰も言及していない。そんな事を言われたってねぇ。取り合えず -g を外して、ローカル(/tmp)に入れてしまえ。

sakae@pen:/tmp$  npm install elm-format
  :
+ elm-format@0.8.3
added 68 packages from 67 contributors and audited 68 packages in 7.675s

sakae@pen:/tmp$ cd node_modules/
sakae@pen:/tmp/node_modules$ sudo cp -r elm-format/ /usr/lib/node_modules/
sakae@pen:~$ ls -l /usr/local/bin/elm-format
lrwxrwxrwx 1 root root 47 Aug 12 06:55 /usr/local/bin/elm-format -> /usr/lib/node_modules/elm-format/bin/elm-format

必要な物だけを正規の場所にコピーして、リンクを張ってお茶を濁す事にした。やれやれ。

こういうの、実際に手を動かしてみないと分からないものだね。

study

Elm初心者でもできるprintデバッグ

プログラミング未経験者にElmを布教してみた(実録)

なんてのを教材に真似してみるか。Examples にも豊富に有るけどね。

mkdir basic; cd basic; elm initした後、 例題をsrc/Main.elmに置き、emacsで閲覧。ソースはちょっと密なので、M-x elm-format して、新生活洋式にします。コンパイルエラー除けで、ファイル冒頭に

module Main exposing (Model, Msg(..), convertInputToMsg, fortune, init, main, update, view)

が追加されました。常日頃から、こういうのを冒頭に書いておくようにしましょう。それがエチケットってものです。

debugの働きを見る為、viewの中をちょいと改変

, div [] [ text (Debug.log "log label" (fortune model.fortuneNum)) ]

これで、C-c C-n すると、Restarting elm-reactor と言ってきて、firefoxが勝手に起動。 結果を閲覧出来ました。

5626 pts/2    S+     0:01 emacs src/Main.elm
6847 pts/3    Ssl+   0:00 /usr/local/bin/elm reactor --port=8000

こんな風になってた。firefoxは、 http://localhost:8000/src/Main.elm にアクセスしてたぞ。いずれもemacsの仕業か。

firefoxのdevel tools から、Consoleを選ぶと、log label: "大吉"なんて文字が伺えた。 それより嬉しいのは、Inspector を選んで、htmlを見た時だ

<div>
  <input type="number">event
  <button>占う</button>event
  <div>大吉</div>
</div>

こんなのが見て取れた。eventの所はfirefox独自のサービスみたいで、ボタンの所をクリックすると、clickイベントだよ。して、それを処理するJSコードは、何たらかんたらだよと教えてくれる。

字が小さくで年寄りが見るには辛い。封印しておこう。

compiler

ソースからelmを作る試みが、ことごとく失敗してた。悔しいのでソースをちまちまと見ている。そしたら、 compiler/terminal/impl/Terminal.hsに面白いコメントを発見。何かhaskell語だけどelm風に書かれていて、見てて違和感が無い。

-- AUTO-COMPLETE

_maybeAutoComplete :: [String] -> (Int -> [String] -> IO [String]) -> IO ()
_maybeAu toComplete argStrings getSuggestions =
  if length argStrings /= 3 then
    return ()
  else
    do  maybeLine <- Env.lookupEnv "COMP_LINE"
        case maybeLine of
          :

引数の文字長が丁度3文字の時、補完を試みるって読めるんだけど、この関数を使ってる素振りは全く無い。デッドコードは削除しておいて欲しいぞ。そうじゃないと幽霊を追いかけるはめになりますからね。

elmのエラーメッセージは(haskellと違って)非常に親切との事。そこら辺どうなってるのかな? 一つエラーを発生させてみた。

-- MODULE NOT FOUND ----------------------- /tmp/elm-tut/my-test/src/Main.elm

You are trying to import a `Url` module:

3| import Url
          ^^^
I checked the "dependencies" and "source-directories" listed in your elm.json,
but I cannot find it! Maybe it is a typo for one of these names?

    Set
    Array
    Dict
    Main

Hint: Maybe you want the `Url` module defined in the elm/url package? Running
elm install elm/url should make it available!

エラーの語り口が、あなたと私って友達みたいだ。あんたがやりたい事をまず述べてる。それに対しして私(elm君)がチェックしてみたよ。

ソース上の依存関係とelm.jsonを突き合わせたよ。けど見つからない。ひょっとしたら打ち間違いしてない。候補は、Set,Array … てな具合ね。

更にヒントをあげるよ。Urlって多分elm/urlの事でしょ。そんならelm elm/urlしなよってね。

sakae@pen:/tmp$ cd ~/src/compiler/
sakae@pen:~/src/compiler$ find . -name '*.hs' | xargs grep 'You are trying to import a'
./compiler/src/Reporting/Error/Import.hs:              "You are trying to import a `" ++ ModuleName.toChars name ++ "` module:"
./compiler/src/Reporting/Error/Import.hs:              "You are trying to import a `" ++ ModuleName.toChars name ++ "` module:"
./compiler/src/Reporting/Error/Import.hs:              "You are trying to import a `" ++ ModuleName.toChars name ++ "` module:"
./compiler/src/Reporting/Error/Import.hs:              "You are trying to import a `" ++ ModuleName.toChars name ++ "` module:"

4か所も同じ文言が表れているな。どういう事? ソース嫁。

toReport :: Code.Source -> Error -> Report.Report
toReport source (Error region name unimportedModules problem) =
  case problem of
    NotFound ->
      Report.Report "MODULE NOT FOUND" region [] $
        Code.toSnippet source region Nothing
          (
            D.reflow $
              "You are trying to import a `" ++ ModuleName.toChars name ++ "` module:"
          ,
            D.stack
              [
                D.reflow $
                  "I checked the \"dependencies\" and \"source-directories\" listed in your elm.json,\
                  \ but I cannot find it! Maybe it is a typo for one of these names?"
      :
   Ambiguous path _ pkg _ ->
      :
   AmbiguousLocal path1 path2 paths ->
      :
   AmbiguousForeign pkg1 pkg2 pkgs ->
      :

上のソース開陳では省略しちゃったけど、ヒントの文言もcase NotFound節の中に埋め込まれていた。

data Problem
  = NotFound
  | Ambiguous FilePath [FilePath] Pkg.Name [Pkg.Name]
  | AmbiguousLocal FilePath FilePath [FilePath]
  | AmbiguousForeign Pkg.Name Pkg.Name [Pkg.Name]

haskellらしく、冒頭の宣言を見ておくべきだな。

たまたまImportsのエラーを見たけど、もっと大きいくくりが有る。Errors.hsには

data Error
  = BadSyntax Syntax.Error
  | BadImports (NE.List Import.Error)
  | BadNames (OneOrMore.OneOrMore Canonicalize.Error)
  | BadTypes L.Localizer (NE.List Type.Error)
  | BadMains L.Localizer (OneOrMore.OneOrMore Main.Error)
  | BadPatterns (NE.List Pattern.Error)
  | BadDocs Docs.Error

こんな定義が有り、toReports内で、適切に分岐してた。ここまでくると同じ名前の関数が頻出するので、grepとかで追うのは辛い。かと言ってetagsとかgtagsでは、elm風なソースを的確にハンドリングしてくれないので、すいすいとソース上を飛び回るのは苦しい。

上手い手は無いものだろうか?

reportが複数形になってのに辛うじて目についた。

./compiler/src/Reporting/Error/Docs.hs:  , toReports
./compiler/src/Reporting/Error/Docs.hs:toReports :: Code.Source -> Error -> NE.L
ist Report.Report
./compiler/src/Reporting/Error/Docs.hs:toReports source err =
./compiler/src/Reporting/Error.hs:toReports :: Code.Source -> Error -> NE.List Report.Report
./compiler/src/Reporting/Error.hs:toReports source err =
./compiler/src/Reporting/Error.hs:      Docs.toReports source docsErr
./compiler/src/Reporting/Error.hs:      toReports (Code.toSource source) err
./compiler/src/Reporting/Error.hs:      toReports (Code.toSource source) err

相変わらずReportingの中だけだなあ。エラーってコンパイル中に検出されるものだから、もっと上位の所から呼び出していてもいいと思うんだけど。。。何か勘違いしてる?

hasktags

ふと出来心で、haskell etags なんてのを検索してみた。だってhaskellってコンパイラーみたいなの得意でしょ。etagsはソースをパースして関数の定義場所とかを抽出してくれる奴。だったら、そういうアプリを作るなんて、格好のHaskell教材と思ったから。

hasktags: Produces ctags "tags" and etags "TAGS" files for Haskell programs

やっぱり有ったね。こちらは、etagsだけじゃなくctagsにも対応してる。haskell専用アプリだ。みんな好きねぇ。オイラーも早速ご相伴にあずかる事にした。

ob$ cabal install --installdir=/home/sakae/.cabal/bin

ob$ ls -l .cabal/bin/hasktags
lrwxr-xr-x  1 sakae  sakae  112 Aug 14 05:40 .cabal/bin/hasktags@ -> ../store/ghc-8.6.4/hasktags-0.71.2-1....d7de02a9d03/bin/hasktags

storeってなんかM$のあれを思い出しちゃって、いやだな。まあ、店と脳内変換されるから、いやなんであって、格納庫ぐらいに思っておけ。

試運転は、自分自身(hasktags)にします。srcの所に降りて行って

ob$ hasktags --etags .

これだけで、TAGSが作成された。findとかと組み合わせて *.hsを抽出してやるなんて面倒いらず。

後は、適当なhsファイルを開いておいて、M-. で定義場所へ飛んで行ける。 例えば、Hasktags.hs の冒頭あたりに定義されてる moduleで輸出されてる大事なあれの定義を参照。

module Hasktags (
  FileData,
  generate,
  findThings,
  findThingsInBS,

  Mode(..),
  TagsFile(..),
   :

括弧が付いてるやつって何? Mode(..)にカーソルを持って行って、M-.

data Mode = Mode
  { _tags             :: Tags
  , _extendedCtag     :: Bool
  , _appendTags       :: IOMode
   :

ふーん、型も大事だから、簡単に調べられるって事だな。

で、haskellのソースを効率的に追いかける方法が見えてきた。いきなりgrepは効率悪そう。 こちらの方も読み方について言及されてた。 yaakaito のためのHaskellコードの読み方

moduleとかimportに列挙されてる型や関数名に注目だな。

import           System.IO                  (Handle, IOMode, hClose, openFile, stdout)
import           Tags                       (FileData (..), FileName,
                                             FoundThing (..),
                                             FoundThingType (FTClass, FTCons, F\
TConsAccessor, FTConsGADT, FTData, FTDataGADT, FTFuncImpl, FTFuncTypeDef, FTIns\
tance, FTModule, FTNewtype, FTPattern, FTPatternTypeDef, FTType),
                                             Pos (..), Scope, mywords,
                                             writectagsfile, writeetagsfile)

System.IOなんかはシステム備え付けなんで、見ても御利益は期待薄。今回書下ろしのTagsに注億ですよ。大文字で始まってる輸入品は型の類。小文字で始まるやつは関数って区別が着く。

mywordsなんていう、いかにもって関数は何してる? すかさずJump。

-- my words is mainly copied from Data.List.
-- difference abc::def is recognized as three words
-- `abc` is recognized as "`" "abc" "`"
mywords :: Bool -> String -> [String]
mywords spaced s =  case rest of
                        ')':xs -> (blanks' ++ ")") : mywords spaced xs
                        "" -> []
                        :

定義を見た所で、それがどんな風に使われているか、調べればよい。これで効率アップだな。

実際のelmファイル群(元の名前はcompilerだったけど、紛らわしいので改名しておいた)で、toDocを探してみる。

/home/sakae/src/elm/compiler/src/Elm/Compiler/Type.hs
60: toDoc
/home/sakae/src/elm/compiler/src/Reporting/Error.hs
95: toDoc
/home/sakae/src/elm/compiler/src/Reporting/Render/Type/Localizer.hs
53: toDoc
/home/sakae/src/elm/compiler/src/Type/Error.hs
79: toDoc
/home/sakae/src/elm/terminal/src/Diff.hs
208: toDoc

複数有るって事ですねぇ。n,pで、簡単に渡り歩ける。

なお、このhasktagsはhaskell屋さん御用達のツールのようで、パッケージになってるから、わざわざコンパイルとかしなくても簡単に入る。 使い方は

Usage: hasktags [(-c|--ctags) | (-e|--etags) | (-b|--both)] [-x|--extendedctag]
                [-a|--append] [-f|-o|--file|--output FILE|-] [--cache]
                [-L|--follow-symlinks] [-S|--suffixes ARG] [-R|--tags-absolute]
                [--options FILE] <files or directories...> [--version]
  directories will be replaced by DIR/**/*.hs DIR/**/*.lhsThus hasktags . tags a
ll important files in the current directory.

  If directories are symlinks they will not be followedunless you pass -L.

  A special file "STDIN" will make hasktags read the line separated filelist to
be tagged from STDIN.

更に通な人は、 haskdogs: Generate tags file for Haskell project and its nearest deps なんてのも、使っているらしい。

etc