IT練習ノート

IT関連で調べたこと(実際は嵌ったこと)を書いています。

extensibleの小さなサンプルを書いてみた

モナドのスタックの取り回しが面倒に感じているので、そろそろ何とかしたいと思っていました。ここでようやく重い腰を上げて、extensibleにチャレンジです。ウェブ上にある情報は、複雑だったので、もっと小さなサンプルを書き下してみました。

mtlcompatibleの意味は、MonadReaderMonadStateクラスを使って実装した場合を意味しています。Readerモナドを直接使っている場合は、コードの修正が必要です。

extensibleの場合は、書き方が2通りあると思いますが(あってる?)、型クラス制約を使ったAssoicate+TypeApplicationのパターンがベターと思いました。

小さなサンプルだと、extensibleの場合記述量が多くなり、まったくメリットは感じられませんが、規模が大きくなるとメリットが上回ると思います。

小さなサンプルが書けて、心理的障壁はなくなったので、今後は使っていくと思います。

extensible_sample.hs

NTextを指定してもTDSプロトコル用はvarcharになっている

なんでだろう?

using System;
using System.Data;

using System.Data.SqlClient;

namespace ConsoleApp1
{
    class Program1
    {
        static void Main(string[] args)
        {
            using (SqlConnection conn = new SqlConnection())
            {
                conn.ConnectionString = "Server=DESKTOP-PA432R1;Database=TDS;User ID=sa;Password=haskellsa";
                conn.Open();
                SqlCommand command = NewMethod3(conn);
                using (SqlDataReader reader = command.ExecuteReader())
                {
                    Console.WriteLine("FirstColumn\tSecond Column\t\tThird Column\t\tForth Column\t");
                    while (reader.Read())
                    {
                        Console.WriteLine(String.Format("{0} \t | {1} ", reader[0], reader[1]));
                    }
                }
                Console.WriteLine("Data displayed! Now press enter to move to the next section!");
                Console.ReadLine();
                Console.Clear();
            }
            Console.ReadLine();
        }

        private static SqlCommand NewMethod3(SqlConnection conn)
        {
            SqlCommand command = new SqlCommand("select x_id, x_ntext from tds.dbo.tbl_string where x_ntext like @foo", conn);
            SqlParameter param = new SqlParameter("foo", SqlDbType.NText);
            param.Value = "abc"; 
            command.Parameters.Add(param);
            return command;
        }
    }
}

f:id:naotoogawa:20190126152206p:plain
NTextで指定したがパケット上の型はvarcharになっている

JavaParserを使ってみる

Javaのコードレビューをしていて、指摘事項の水平展開をする時に、エディタ等のgrepで検索して、類似の指摘箇所を探したりします。 ただ、それだと、複数行にまたがり相関をもった内容の抽出に手間がかかります。

例えば、前のプロジェクトでは、stream処理の中に、さらにstream処理を使っているケースでは、可読性が著しく下いコードがたくさんありました。当初は、一律禁止するつもりはなかったのですが、一律禁止することにしました。禁止するので、このルールが守られているかチェックが必要になり、grepソースコードの当たりをつけて、その前後を目視確認する作業に手間がかかってしまいました。

なんらかのツールをカスタマイズすればよかったのですが、カスタマイズのルールを学ぶのが面倒だったので、結局、grep+目視をしていました。

結局、込み入ったチェックやプロジェクトのルールに従っていることをチェックをするには、Javaソースコードをパースするしかありません(というか、各種ツールもパースしてますが)。

そこで、次回に活かせるようにJavaソースコードをパースの練習をしてみました。

パーサーはJavaParserが良さそうだったので、使ってみました。基本的な使い方は、

  • ソースコードを読み込み、
  • Visitorを適用するだけです。
  • 各種ノードに対するドメソッドがたくさん用意されているので、それをオーバーライド実装するだけです。

Visitorパターンなので、同じノードでも階層が異なった時の対処が面倒くさいのですが。

最初はどのようなノード構造になっているかわからないので、

  • いったんパースした後のノードを全部出力しておく
  • ツリーを出力しておく

と良いと思います。

下のコード例では、

  • ノード全体の出力(デバッック用)
  • ツリーをJSONとして表示(デバッック用)
  • メソッドの行数(練習)
  • forEachのなかにforEachがある部分(前のプロジェクトでチェックしたかった箇所)

を出力しています。

いろいろできると思います。

  • 長いラムダ式を抽出
  • 嘘をついている変数名の抽出(例、Mapなのに変数名がListとなっている)
  • コメントだけを抜き出して、文章をチェックするとか(オフショア案件の時)

JavaParser Sample

JOOXで要素をwrapする。

過去記事で、jooxを取り上げたました。これを案件で補助ツールとして使っています。一律にxmlを補正したい時や、xmlの記述がプロジェクトのルールに従っているかをチェックするためにつかっています。

JavaでjQueryライクにXMLを操作する - IT練習ノート

ただ、jQueryにあるwrap相当のものがありません。もちろん、完全に無いわけではありません。

Match tags = $(document).find("body");
tags.wrap("aaaa"); // OK

というような、タグをwrapすることはできます。しかし、属性があるタグをwrapするインターフェースがありません。次のコードは実行時エラーになります。

tags.wrap("<aaaa/>");  // NG
tags.wrap("<aaaa id='abc'/>");  // NG

Matchインターフェースを実装したImplクラスを拡張もしたかったのですが、実装が正しく隠蔽化されているので、拡張する余地がないようい思えました。

仕方がないので、簡易メソッドを作成しました。

コードを示します。

嵌った点としては、異なる要素は直接追加できないことでした。

xml - Java: how to wrap all elements with <sometag> in org.w3c.dom? - Stack Overflow

Javaのstreamで嵌る

IntStreamStream<T>インターフェースに親子関係がない

 java.lang.AutoCloseable
     java.util.stream.BaseStream<T,S>
         java.util.stream.DoubleStream
         java.util.stream.IntStream
         java.util.stream.LongStream
         java.util.stream.Stream<T>

そのために(?)、IntSteramに3つの引数をもつreduceがない。これに気がつかず、相当時間を無駄にした。

順次および並列の集約操作をサポートするプリミティブint値要素のシーケンスです。これは、Streamに対してintプリミティブ特殊化を行ったものです。

とドキュメントにはあるが、特殊化といっても型だけ特殊化されているのではない。

多分、このようなクラス階層になっているのは、IntStreamIntegerではなくintだからなんだろうけど。

Streamインターフェースの2つの引数を持つreduceと3つの引数を持つreduceで2番目のパラメータ部分の型が異なる。

2引数

reduce(T identity, BinaryOperator accumulator)

3引数

reduce(U identity, BiFunction; < U,? super T,U > accumulator, BinaryOperator < U > combiner)

2引数の場合、BinaryOperatorの型がTなので、accumlatorの型はStreamの型と同じ必要がある。

また、実際のコーディングでは、3引数のcombinerのコーディングが定型的になる(と思うので)冗長になる。

intelliJ型推論が微妙にずれている

3引数reduceでパラメータの3番目が間違っているのに、2番目のエラーがでる。javacでは3番目のパラメータのコンパイルエラーになる。

f:id:naotoogawa:20181202152735p:plain
第3パラメータの問題なのに第2パラメータのエラーと表示される

f:id:naotoogawa:20181202152730p:plain
コンパイルした時のエラー

単位元?

T reduce(T identity, BinaryOperator accumulator)

指定された単位元の値と結合的な累積関数を使ってこのストリームの要素に対してリダクションを実行し、リデュースされた値を返します。これは、次の操作に相当します。

足し算の単位元は0だけど、0でなくても動作する。

        int r = Stream.of(1,2,3).reduce(10, (x,y) -> x + y);
        System.out.println(r); // 16

サンプル

Snake caseCamel caseの返還をstreamやラムダで書くといい感じになるかなと思ったのですが、そうでもないですね。

    private static String toCamel(String str) {
        return Arrays.stream(str.split("_"))
            .map(x -> x.substring(0,1).toUpperCase() + x.substring(1).toLowerCase())
            .collect(Collectors.joining());
    }

    private static String toSnake(String in) {
        String out = in.codePoints()
            .mapToObj(c -> (char)c) // IntStream to Stream<Character>
            .reduce("" ,
                (acc, v) -> acc + (Character.isLowerCase(v) ? v : "_" + Character.toLowerCase(v)) ,
                (s1, s2) -> s1 + s2
            );
        return out;
    }

もっと簡潔にかけるのなら教えて下さい。

感想

Javastreamは、慣れてないだけかもしれないが、難しい。というか面倒。

reduceは可読性が上がらなさそう。結局2変数の関数を作らないといけないこともあるので。

stream処理を考えた時には、reduceを使わない範囲で使うと良いのではないか。

reduce使うくらいならforでループして処理するのが良さそう。

JavaでjQueryライクにXMLを操作する

github.com

jOOXというライブラリを使えば良さそうです。

嵌ったところは

DOCTYPEがあると読み込んでくれないので、その場合、自分でxmlファイルを読み込む必要がある。

childrenメソッドでセレクタでタグを2個指定するのは動かない模様。(findなら複数指定できる模様)

出力は、xml宣言とかはつけてくれない。なので自分で出力する必要がある。

joox example

Unfoldの使いどころ

haskellのコーディングでfoldrは頻繁に使う印象がありますが。その双対(で、あってるのかな?)のunfoldは、あまり使わない印象があります。というか、使いどころのイメージがつかめませんでした。

この前、n進数間の変換に使えることに気づきました。 が、n進数間の変換にunfoldを使うのは、教科書の定番のようです。教科書をしっかり読むべきでした。

Programming in Haskellの7.9の練習問題6など。

[https://www.amazon.co.jp/%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%9F%E3%83%B3%E3%82%B0Haskell-Graham-Hutton/dp/4274067815:title]

実際にやりたかったことは、数値をバイナリにすることだったのですが、

generate :: (Num a, Integral b, B.Bits b) => b -> Maybe (a, b)
generate val =
  if val == 0
    then Nothing
    else Just (fromIntegral $ val B..&. 0xFF, val `B.shift` (-8))

を定義しておくと、

 > :t Data.List.unfoldr generate 0x0efed
Data.List.unfoldr generate 0x0efed :: Num a => [a]
 > Data.List.unfoldr generate 0x0efed
[237,239]
 >
 > :t Data.ByteString.unfoldr generate 0x0efed
Data.ByteString.unfoldr generate 0x0efed :: B8.ByteString
 > Data.ByteString.unfoldr generate 0x0efed
"\237\239"

ができます。

unfolrを使い分けることによって、結果を変えることができます。