30 日で Android むけ Haskell コンパイラの 0.9 版をリリースするにっき

(In English / In Japanese)

Haskell コンパイラを書こう! という記事を書いてから、 あっとゆーまに4年間ほどたってしまいましたが、 そろそろ、一応の完成をめざしたいと思います。

できれば、今年のアドベントカレンダーで第1版をと思っていたのですが、 これはまったくもって無理そう。 Haskell 2010 の仕様のうち未実装部分がまだかなり多く、 とれていないバグも、記述がいい加減な部分も多い。 だからといって、ずるずる引き延ばしては一生たどりつかなさそうなので、 第1版をめざすためのベースキャンプ的な区切りとして 0.9 版をリリースしたいと思います。

バグやら、わからんところやら、機能不足やら、まだまだてんこ盛りですが、 どうにか、なんらかの意味*1での整理をつけて 0.9 版を出すぞ!

ところで、ほんとにあと30日かなぁ。Off-by-one やらかしているきがする。 1-origin ならいける?

あと、このにっき、毎日書くとは限りません。書くかな、どうかな。(どっちゃでもいい)

2020-12-15 (Tue)

30日め

Android むけ Haskell コンパイラをリリースしました!

2020-12-13 (Sun)

28日め

List comprehension における fail の扱い

以下のように fail するようなパターンマッチを含むリスト内包式が ランタイムエラーする不具合があって、朝起きたら直せそうな気がしたので直した (Issue #119)。

xs = [Just 3, Just 2, Nothing, Just 9, Nothing, Nothing, Just 8]
main = print $ sum $ [y | Just x <- xs, let y = x * 2]

ひとつでも多く直したくなるところなのだけど、今日含めてあと二日間しかないので、 ほんとに、もうコードは変更せず、ドキュメント整備だけにしたほうがいいと思う。

ABC185C

今日は ABC 不参加だったのだけど、これの C 問題 を明日書く記事の題材にしようかとおもって、書いてみた。

getInteger :: IO Integer
getInteger = do
  s <- getLine
  return $ read s

main = do
  l <- getInteger
  let ans = product [(l-11)..(l-1)] `div` product [1..11]
  print ans

Bunny でも問題なく動いた:

$ bunny testrun abc185c.hs
/c/Users/unnoh/bunny/0.9.0/bin/bunnyc -d ./jout/abc185c --xno-implicit-prelude /c/Users/unnoh/bunny/0.9.0/lib/Prelude.hs
/c/Users/unnoh/bunny/0.9.0/bin/bunnyc -d ./jout/abc185c --xlibrary-path /c/Users/unnoh/bunny/0.9.0/lib abc185c.hs
17
4368

bunny testrun は、コンパイルからやりなおすことになるので、 コンパイル済のプログラムを再度実行するオプションが欲しいな…。

2020-12-12 (Sat)

27日目

結合性解決のバグ

print $ a*b + c*d が型エラーしていた件は、結合性解決のバグだった。 The language report をいい加減にみて実装していたのを、きちんと書き直して解決。 (issue#116)

2020-12-11 (Fri)

26日め

必ずでる警告について

現状の版では、必ず以下のような警告がでる。 これは、初めてでたときには警告ではなくエラーでとまっていたのだが、 実害はないようなので警告に変えて様子を見ていたもの。

warning: kiexpr' :AppTy (Tycon (Name {origName = "Ratio", namePos = (265,17), isConName = True})) (Tycon (Name {origName = "Integer", namePos = (265,23), isConName = True})) []
warning: kiexpr' :AppTy (Tycon (Name {origName = "Ratio", namePos = (265,17), isConName = True})) (Tycon (Name {origName = "Integer", namePos = (265,23), isConName = True})) []

こいつは、Rename モジュール中で型シグネチャに出くわしたときに型変数の Kind を推定する部分ででる。現状は、かなりやっつけ仕事になっていて、 Maybe aEither a b の a や b の種を * と推定して、 (Monad m) => m a なんかの m の種を* -> * と推定できればいいよねというくらいの実装になっている。

そういうわけなので、Ration Integer みたいな型については、なにもすべきことがないので、スルーしておけばよく、警告を出すのは鬱陶しいだけなのでやめるように変更した。

そもそも、種の推定がいい加減すぎるので、きちんと作り直す必要がある。 ひとまず、現状の推定ロジックではひっかかるケースをつくって issue をあげておいた。

v0.9.0 のリリースまで数日しかないので、バグは直してもあとひとつ、116 は、できれば直したい。他には手をつけず、リリースのテイを整えることに集中したい。

2020-12-08 (Tue)

23日め

Project Euler problems

Project Euler の回答例を順に試していって、Bunny の現バージョンで未対応項目 (未実装、かつ、version 0.9.0 に間に合わない機能)やバグを踏んでいこうと思ったら…。Problem 1 でいきなり踏んだ。

-- problem_1 : https://wiki.haskell.org/Euler_problems/1_to_10#Problem_1                                                                                                                     
problem_1 = sum [x | x <- [1..999], x `mod` 3 == 0 || x `mod` 5 == 0]
main = print problem_1

これを testrun しようとすると、以下のような型エラーとなる:

$ bunny testrun problem1.hs 
/home/unno/bunny/0.9.0/bin/bunnyc -d ./jout/problem1 --xno-implicit-prelude /home/unno/bunny/0.9.0/lib/Prelude.hs
warning: kiexpr' :AppTy (Tycon (Name {origName = "Ratio", namePos = (265,17), isConName = True})) (Tycon (Name {origName = "Integer", namePos = (265,23), isConName = True})) []
warning: kiexpr' :AppTy (Tycon (Name {origName = "Ratio", namePos = (265,17), isConName = True})) (Tycon (Name {origName = "Integer", namePos = (265,23), isConName = True})) []
/home/unno/bunny/0.9.0/bin/bunnyc -d ./jout/problem1 --xlibrary-path /home/unno/bunny/0.9.0/lib problem1.hs
warning: kiexpr' :AppTy (Tycon (Name {origName = "Ratio", namePos = (265,17), isConName = True})) (Tycon (Name {origName = "Integer", namePos = (265,23), isConName = True})) []
warning: kiexpr' :AppTy (Tycon (Name {origName = "Ratio", namePos = (265,17), isConName = True})) (Tycon (Name {origName = "Integer", namePos = (265,23), isConName = True})) []
bunnyc: context reduction: IsIn "Prelude.Integral" (TCon (Tycon "Prelude.Bool" Star))
testrun: failed to compile problem1.hs

warning が必ずこれだけでるのも鬱陶しいので、これも 0.9.0 までには直したい。

エラーは bunnyc: context reduction: IsIn "Prelude.Integral" (TCon (Tycon "Prelude.Bool" Star)) となっているところ。Integral と Bool を演算しようとしたぜと言っているようだが…。これは見覚えがある。 27日の ABC184A のコード で、print $ a*d - b*c と書くとエラーしたので、 print (a*d - b*c) と書き直すことになってしまったのだが、それと同じ現象だ。

以下のように冗長(であるはず)な括弧を付けると通る:

-- problem_1 : https://wiki.haskell.org/Euler_problems/1_to_10#Problem_1                                                                                                                     
problem_1 = sum [x | x <- [1..999], (x `mod` 3 == 0) || (x `mod` 5 == 0)]
main = print problem_1

どうも、関数適用と fixity resolution の組み合わせが良くないのかしら…。 括弧の付け方を以下のように変えても OK だった。

-- problem_1 : https://wiki.haskell.org/Euler_problems/1_to_10#Problem_1                                                                                                                     
problem_1 = sum [x | x <- [1..999], (x `mod` 3) == 0 || (x `mod` 5) == 0]
main = print problem_1

…やや根深いかもしれないなぁ。(これを修正するには、変更範囲が思っていたより広いのかもしれない)

2020-12-07 (Mon)

22日め

README.txt を少し書き進めた。Usage のところ、説明文がまだ全然ないので書く必要がある。 コマンド例も adb で install するところまで書いたほうがいいだろう。 あとは、Bunny とは何かという簡単な説明と、Version 0.9.0 について、そして、プロジェクトの web サイトへの誘導を書き足す。

memo/ やら、compiler/walk/ みたいな形骸化してしまっているディレクトリは、 ファイルを退避したうえで消してしまおう。

2020-12-06 (Sun)

21日め

アドベントカレンダー の担当日まで、残すところ10日となった。そろそろ、記事を書き始めようということで、 空のページだけつくってみたり。その記事は、非公開のまま書き進めて、当日公開する予定。

まずは、タイトルしたにおく画像をきめた。いくつか探して、Adobe Stock で購入 (といっても、1点だけ買う手段がなくて、入会後最初の月は無料ということで、無料だったのだけど)。なんでもサブスクなんですなぁ。

ところで、はじめっから名前を決めていたにもかかわらず、自分のつくっているコンパイラを名前で呼んでいませんでしたが、Bunny という名前です。こんご、文書中でも単に Bunny と呼びます(これまでは、「私がつくっているコンパイラでは…」のように書いていた)。

んで、アドベントカレンダーむけの記事とは別に、Bunny を公開するにあたって作るドキュメントの章立てやら、英語での表現やらの参考にしたくて、いくつかのレポジトリをみてみた(GitHub で "compiler" で検索して★がたくさんついているものとして挙がったやつ):

これらに、Gauche をくわえた4つくらいを参考にしてドキュメントを書いてみよう。あまりたくさん見ようとしても、見切れないだろうし。

あと、10日、あっというまなんだろうな。

2020-12-03 (Thu)

18日め

主に Linux 上でスクリプトの整備や動作確認をしていたのだが、 Windows 上でも動作確認をしたり、その過程でみつかった不具合を直したり。

とくに、Android プロジェクトをつくって動作させる部分は、Windows 上で一度も試していなくて、というか、Windows 上には Android Studio のインストールすら未だだった。 が、わりと、あっさり動いた。たすかる~。

忘れないうちに、利用者むけドキュメントを書こうと思い、まずは 導入ガイドのドラフト を書いた。

次は、これをつかって Android プロジェクトを作成、APK をつくって動かすところのドキュメントを書こう。

これらのドキュメントを書いたら、いちど、Linux 上にあたらしいユーザをつくって、 ドキュメント通りにビルド・インストールできるか、試してみないといけないですね。

あと、うさぎちゃんの画像どないしょ、みたいな。 これ の S サイズにしようか、など。 これ かも。

あと、https://wiki.haskell.org/Euler_problems/1_to_10 を順に試していくのはいいかも

2020-12-02 (Wed)

17日め

リリースにむけて、自分専用で機能毎にバラバラだった wrapper scripts を、 ひとつのコマンドに統合、また、それをパスのとったディレクトリにインストールするためのインストーラースクリプトなどを整備した。

2020-12-01 (Tue)

16日め 進捗なし

2020-11-30 (Mon)

15日め

コンパイルから Android プロジェクトの作成までを自動化

前回、Haskell からコンパイルされたプログラムと UI のプロトタイプを手動で結合して Android プロジェクトを作成したが、今日はその作業を自動化して、 コンパイルから Android プロジェクトの生成までをひとつのコマンドで実行できるようにした:

ScreenShot20201130

Android プロジェクトができたら、そのディレクトリに移動して、

./gradlew assembleDebug

とすると、デバッグ用 apk ができるので、それを adb コマンドで、実機、または、エミュレータにインストールすることで実行できる。

現状では、ホスト上でのテスト用コンパイル、ホスト上でのテスト実行、そして、 今回の Android プロジェクトの作成が、ばらばらのコマンドになっているので、 それを1つのコマンドに統合し、コマンドオプションなどを整理して使いやすくしよう。

いまは、出力先ディレクトリや、Prelude.hs の在処が決め打ちだったりするので、 ~/bunny/bin などにコマンドを置いたときにも、ちゃんと動作するように。

2020-11-29 (Sun)

14日め 進捗なし。

2020-11-28 (Sat)

13日め 進捗なし。

2020-11-27 (Fri)

12日め

Hakell プログラムをコンパイルしたものを Android 上で動かす

今日は、昨日までにつくった UI プロトタイププロジェクトに、自作 Haskell コンパイラが出力したコードを結合して動かしてみる。

動かすプログラムは、昨日までと同じでは面白くないので、違うものにした (これは、ABC184A を解くプログラム)。

getIntList :: IO [Int]
getIntList = do s <- getLine
                return $ (map read . words) s

main = do
  [a, b] <- getIntList
  [c, d] <- getIntList
  print (a*d - b*c)

これを自作のコンパイラでコンパイルした結果と、ランタイムライブラリ を、プロジェクトの app/src/main/java の配下にコピーし、いくらか手作業で手直したら、"run" してみる。

うごいた!

screen shot

次は、今日手作業でやったような手直しやファイルのコピーを、手作業しなくていいように修正、自動化しよう。

2020-11-26 (Thu)

11日め

メインルーチンを UI スレッドとは別スレッドでうごかす

メインルーチンを UI スレッドである MainActivity 上でそのまま動かすと、 UI が止まってしまうため、別スレッドで動作させることにする。

そのために、IntentService を追加し、そのなかにメインルーチンを置く。

public class MainIntentService extends IntentService {
    private static Deque<String> dequeIn = new ArrayDeque<>();
    public static TextView textView;

    public MainIntentService() {
        super("MainIntentService");
    }

    /**
     * Starts this service to perform action Main.
     * If the service is already performing a task this action will be queued.
     *
     * @see IntentService
     */
    public static void startMain(Context context) {
        Intent intent = new Intent(context, MainIntentService.class);
        context.startService(intent);
    }

    public static void setTextview(TextView tv){
        textView = tv;
    }

    @Override
    protected void onHandleIntent(Intent intent) {
        handleActionMain();
    }

    /**
     * Handle action Main in the provided background thread.
     */
    private void handleActionMain() {
        while (true){
            String s = getLine();
            String[] t = s.split(" +");
            double sx = Double.parseDouble(t[0]);
            double sy = Double.parseDouble(t[1]);
            double gx = Double.parseDouble(t[2]);
            double gy = Double.parseDouble(t[3]);
            double ans = (sx*gy + gx*sy) / (sy + gy);
            String r = Double.toString(ans);
            putStrLn(r);
        }
    }

    public static void putline(String s){
        dequeIn.addLast(s);
    }

    private String getLine(){
        while (dequeIn.size() == 0){ /* wait */ }
        String r = dequeIn.removeFirst();
        return r;
    }

    private void putStrLn(String str){
        textView.append(str + "\n");
    }
}

この例では、handleActionMain の中に、いわゆるメインルーチンをそのまま書いている。 ここでは while(true) による無限ループとなっているが、今回は UI スレッドとは別のスレッド上でこれを動かすので、これによって UI がとまってしまうことはない。

それにともなって変更した MainActivity は以下の通り。 MainActivity の onStart 関数から、Main サービスをスタートさせている。 それ以外には、標準入出力をやりとりするための関数をいくつか用意している。

public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }

    @Override
    protected void onStart() {
        super.onStart();
        TextView textView = findViewById(R.id.textView);
        MainIntentService.setTextview(textView);
        MainIntentService.startMain(this);
    }

    /** Called when the user taps the Send button */
    public void sendMessage(View view) {
        EditText editText = (EditText) findViewById(R.id.editText);
        String message = editText.getText().toString();
        editText.setText("");

        TextView textView = findViewById(R.id.textView);
        textView.append(message + "\n");

        MainIntentService.putline(message);
    }
}

これで、表面的には昨日のプロトタイプと同じ動作をするものができた。 Service と MainActivity のやりとりが、Intent ではなくて直接的なやりとりになっていて、かなりお行儀が悪い気がするけれど、mutext lock をとってやるようにすれば、 一応*1 OK なんではないだろうか。

今回は、Service の handleActionMain 関数の中に、サンプルプログラムが直接書いてあったが、ここから、Haskell からコンパイルされたプログラムを呼ぶようにすれば良い。

明日以降は、まず、手動で、Haskell からコンパイルされたプログラムを組み入れてみる。 それができたら、コンパイル結果を組み入れる処理を自動化すれば良い。

2020-11-25 (Wed)

10日め

Android 上ランタイム UI のプロトタイプ(つづき)

MyFirstApp を改造して、ランタイム用の UI をつくる。MainActivity 上に、 入力用の editText とボタン、そして、表示用の TextView をならべて、 stdin / stdout を簡易的にエミュレートする。

そして、ABC183B を解くような簡単なサンプルプログラムをくっつけて動作させてみる。

入力3を送る直前入力3をおくった後

MainActivity.java の主な内容は以下のとおり:

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        // mainProgram();
    }

    public static Deque<String> dequeIn = new ArrayDeque<>();

    /** Called when the user taps the Send button */
    public void sendMessage(View view) {
        EditText editText = (EditText) findViewById(R.id.editText);
        String message = editText.getText().toString();
        editText.setText("");

        dequeIn.addLast(message);

        TextView textView = findViewById(R.id.textView);
        textView.append(message + "\n");

        mainProgram();
    }

    public void putStr(String s) {
        TextView textView = findViewById(R.id.textView);
        textView.append(s);
    }

    public void putStrLn(String s) {
        putStr(s + "\n");
    }

    public String getLine() {
        while (dequeIn.size() == 0) {
            // wait
        }
        String r = dequeIn.removeFirst();
        return r;
    }

    public void mainProgram(){
        String s = getLine();
        String[] t = s.split(" +");
        double sx = Double.parseDouble(t[0]);
        double sy = Double.parseDouble(t[1]);
        double gx = Double.parseDouble(t[2]);
        double gy = Double.parseDouble(t[3]);
        double ans = (sx*gy + gx*sy) / (sy + gy);
        String r = Double.toString(ans);
        putStrLn(r);
    }
}

はじめ、mainProgram の中で while(true) で無限ループさせて、 かつ、onCreate の最後で呼び出していたんですが、それでは onCreate が終わらなくてアプリが動かなかった。

これは、まず、onCreate ではなく、適切なハンドラから呼ぶようにするのと、 べたっと無限ループするのではなく、処理待ち時にスレッド切り替えのようなのが行われるようにすべきなんだと思う。Android アプリにおけるお作法をしらべなくちゃ。

2020-11-24 (Tue)

9日め

Android アプリをつくる

Android ディベロッパーガイド をみながら、 android アプリをつくってみる。まずは、ガイドにそって、その通りに。 今日は、「初めてのアプリを作成する」をひととおり。

myfirstapp1myfirstapp2

今後これを少しずつ改造していって、いまつくっている Haskell コンパイラのランタイムライブラリに接続する簡易 UI をつくっていく。

2020-11-23 (Mon)

8日め 進捗なし

2020-11-22 (Sun)

7日め 進捗なし

2020-11-21 (Sat)

6日め

シノニムまわりの修正

String のような単純なシノニムは OK だったけど、 ReadS a のように型変数をとるシノニムに未対処だった問題への対処。

昨日、このための Rename.hs / RenUnil.hs 修正をほぼすませた。 当初、もとのコードがそうだったように、renSigdoc 中でシノニムを解決 (ここでの処理がより正しくなるよう修正)しようとしたが、これだと、 ほかの場所に修正の影響が及んで、別の不具合がでたりして、うまくなかった。

そこで、A.SynonymDecl 処理の最初に、シノニムを解決してしまうようにした:

    renDecl (A.TypeSigDecl ns (maybe_sigvar, sigdoc')) = do
      ns' <- mapM renameVar ns
      sigdoc <- actualType sigdoc' -- Here!
      let kdict = kiExpr sigdoc []
      ps <- renSigvar maybe_sigvar kdict
      t <- renSigdoc sigdoc kdict
      return [(n, Just (ps :=> t), []) | n <- ns']

これでいまのところ(いまためしている入力プログラムに対して)は動作するのだが、 kiExpr のところで kind inference ができないパターンにぶつかっている。 (いまは警告だけ出して無視するようにしている) この kiExpr は、かなり初期のころに、えいやっと適当にごまかして作ったものなので、 いずれ、きちんと作り替えないといけないだろうと思っている。

また、もう一か所、シノニムを解決しないといけない場面として、 data 宣言にシノニムが出現する場合がある。そちらも同様に対処したつもりだが、 data 宣言に複雑なシノニムが出現するケースは、まだうまく動かない。

以上の状況をうけて、テストケースを追加したり、lib/Prelude.hs を修正したりした。

上記 testcases/typesyn*.hs のうち、ok だったものは test/samples (make check で用いられる) に加える。

2020-11-20 (Fri)

5日め

シノニム周りの対処をした。明日、すこし追加作業をして、その内容を書きます。

2020-11-19 (Thu)

4日め

今日はほどんど進捗なし。お天気だったので、深大寺にあそびにいってました!

深大寺 その鐘

ほんの少しだけ進めたところ

より複雑なシノニムに対応するために、まず、シノニム辞書を [(A.Type, A.Type)] から [(A.Type, ([A.Type], A.Type))] に変更。

また、冗長な括弧があるケースにも対応しないといけないので、 そのようなテストケースを用意した:

type ((Foo a) (b)) = String -> (a, b)

foo :: (Foo (Int)) (Char)
foo s = (length s, head s)

main = print $ foo "Hello!"

2020-11-18 (Wed)

3日め

type シノニムの問題を調査

「Android 上で動かせるようにするためのもろもろ」を次にやろうかなと思っていましたが、 今日は少ししか時間がとれなかったので、小さめの課題に取り組むことにします。

type シノニムがうまく動かなかった問題の原因と修正方針をたてたい。 type String = [Char] のように、左辺に型変数が含まれない場合はいまもちゃんと動いているので、そうでないケースを含む小さいテストプログラムを用意:

type Foo a b = String -> (a, b)

foo :: Foo Int Char
foo s = (length s, head s)

main = print $ foo "Hello!"

これをコンパイルしようとすると、以下のようにエラーする:

# 1. tcompile
source file: testcases/typesyn.hs
dst dir: /typesyn
doCompile ... done.
implicitPrelude ... done.
doCompile ... bunnyc: renSigDoc $ A.Tycon Foo
CallStack (from HasCallStack):
  error, called at src/Rename.hs:711:33 in main:Rename

直さないといけないのは、

actual_ty は A.Type -> A.Type 、つまり、Absyn の段階で置き換えを実施している。これは、修正後も踏襲します。

まず、前者ですが、こんな感じ:

    scandecl (A.SynonymDecl t1 t2) = do
      st <- get
      let syn = rnSyn st
      put st{rnSyn=((t1, t2):syn)}
      return ([], [], [])

type String = [Char] のようなシノニム宣言に対しては、 (String, [Char]) のようなエントリがつくられ、あとで置き換えが期待通りに行われます。

ですが、type Foo a b = String -> (a, b) に対しては、 (Foo a b, String -> (a, b)) のようなエントリが作られることになり、 これは、機能しません(していません)。

これを後に利用する場面は2か所あるのですが、そのうちのひとつは以下の部分:

renSigdoc (A.AppTy e1 e2) kdict = do
  t1 <- renSigdoc e1 kdict
  t2 <- renSigdoc e2 kdict
  return (TAp t1 t2)

renSigdoc t@(A.Tycon n) kdict = do
  issyn <- isSynonym t
  if issyn
    then do t' <- actualTy t
            renSigdoc t' kdict
    else do let n' = origName n
            t <- lookupTConst n'
            return $ fromMaybe (error $ "renSigDoc $ A.Tycon " ++ n') t

コンストラクタに対して、isSynonym t で前述の辞書を引いています。 Foo でひいても辞書には Foo a b しかないのでヒットしないというのが現状。

そこで、辞書登録時には、 (Foo a b, String -> (a, b)) ではなく、 (Foo, Synonym [a, b] (String -> (a, b))) のようなエントリをつくるようにする。

使う側も、現状のように A.Tycon まで分解してから synonym かどうかを検査するのでなく、 A.AppTy (A.AppTy c v1) v2) の時点で、型コンストラクタがシノニムかどうかを検査し、もしそうなら置き換えを行うようにする必要があります*1

なお、ここでは renSigdoc の方だけ述べましたが、現状ではもう一か所シノニム辞書を使用している(Rename.hs で isSynonym を検索するとわかる)ので、そちらについても同様の対処が必要。

今日は、これだけ(時間切れ)。実際に修正するのは明日以降にします。

2020-11-17 (Tue)

2日め

Int, Integer を Read クラスのインスタンスにする

昨日目標となるサンプルプログラムを定めたときに、Int が Read のインスタンスになってなかったところで引っかかったので、今日はそこを対処する。 これには、lib/Prelude.hs にインスタンス宣言、および、 そこから呼ばれるサポート関数を追加すればよいはず。

というので、Haskell98 にあるコードなど も参照しながら*1記述していったのだが、 そうすると実行時にパターンマッチが網羅的でないところに落ちた旨のエラーになってしまった。

どうも、readSigned の定義に出現するような、 リスト内包式におけるパターンマッチの処理がよろしくないらしい。

readSigned :: (Real a) => ReadS a -> ReadS a
readSigned readPos = readParen False read'
                     where read' r  = read'' r ++
                                      [(-x,t) | ("-",s) <- lex r,
                                                (x,t)   <- read'' s]
                           read'' r = [(n,s)  | (str,s) <- lex r,
                                                (n,"")  <- readPos str]

たとえば、("-", s) <- lex r は、lex r の返した結果が ("-", s) にマッチしなかったときに fail する。だからといって 実行時エラーになっては困るのだが、いまのコンパイラの吐くコードではエラーしてしまう。

ひとまず、以下のように書き直して、緊急回避することに。readParen にも同様の問題があった(ので、使わないようにした):

readSigned :: (Real a) => (String -> [(a, String)]) -> (String -> [(a, String)])
readSigned readPos = {- readParen False -}  read'
  where read' r | head r == '-' = [(-n, s) | (n, s) <- readPos (tail r)]
                | otherwise     = readPos r

ともあれ、これで、ターゲットのプログラムを動かせる状態に近づいた。 今日の版では、以下のプログラムを動かすことができる:

getIntList :: IO [Int]
getIntList = do s <- getLine
                return $ (map read . words) s

main = do
  [sx, sy, gx, gy] <- getIntList
  print (fromIntegral (sx*gy + gx*sy) / fromIntegral (sy + gy) :: Double)

実行結果は以下のような感じ。ABC183B の入力例それぞれについて、期待通りの結果となっている模様:

$ ./trun testcases/abc183b_t.hs
# 1. tcompile
source file: testcases/abc183b_t.hs
dst dir: /abc183b_t
doCompile ... done.
implicitPrelude ... done.
doCompile ... done.
# 2. jout/compile
#!/bin/bash -v

target=$1
d=`dirname $1`

s=":"
if [ -d /c ]; then
    s=";"
fi

javac -J-Duser.language=en -cp "../../brt/src$s$d" $target
# 3. jout/run
1 1 7 2
3.0
$ cd jout/
$ ./run abc183b_t/Sample.java
1 1 3 2
1.6666666666666667
$ ./run abc183b_t/Sample.java
-9 99 -999 9999
-18.705882352941178

今日すすんだところ:

今日確認した不具合:

新たに作りこんだものというわけではないが、今日の作業で気づいた/思い出したものも含めてメモっておく:

次は、これを Android 上で動かせるようにするためのもろもろ(ランタイムに Android 上で動かすための機能追加を若干、あと、Android 向けのビルド手順(スクリプト?)の確立など)に取り掛かろう。

よだん

次の 0.9 版むけには、「このプログラムをうごかすぞ」という具体的な目標として、 かなり小さなサンプルプログラムを設定した。それは、それでいいと思うのですが、 1.0版にむけては、簡単なデモアプリのようなものを作りたいなと思う。

小さなゲームとかどうかなとおもって、実は、タイトルとロゴだけは決めてあったりします。 こんなの:

title logo

いやー、どんなゲームなのかなぁ(棒)。

2020-11-16 (Mon)

1日め

たーげっと・ぷろぐらむ

やっぱりですね、「このプログラムをうごかすぞ」という具体的な目標をいっこきめましょう。

こないだの ABC の B 問題をとくやつにしよう、そうしよう。これで:

getIntList :: IO [Int]
getIntList = do s <- getLine
                return $ (map read . words) s

main = do
  [sx, sy, gx, gy] <- getIntList
  print $ fromIntegral (sx*gy + gx*sy) / fromIntegral (sy + gy)

今日時点では、まだこのプログラムをコンパイルすることができない。 たぶん、以下が原因 *1

以下のように変えると、いまでも動く(まだ Android 上ではなく、Window / Linux などコンパイラが動く環境上で、ですが)

getIntList :: IO [Int]
getIntList = return [1, 1, 7, 2]

main = do
  [sx, sy, gx, gy] <- getIntList
  print (fromIntegral (sx*gy + gx*sy) / fromIntegral (sy + gy) :: Double)

こんな感じ:

実行結果

そのたの目標

あと一か月しかないので、いまからバグやら未実装機能やらをつぶしていては、 きっと、たとえ 0.9 版であっても「リリース」のテイをなさなくなってしまう。なので、そういうことは、もうやらない。 以下のことは、やる。

これらの結果が、12/15 のアドベントカレンダーの記事になる予定。 ほいで、5月くらい*3 には 1 版だしたい。