fp-tsの続き

1

昨日の続きです。

hiroqn.hatenablog.com

昨日のShowはあくまで標準主力にOutputされることを意識したインターフェースでした。

これです。

export class Show<A> {
    public readonly _tag: 'Show' = 'Show';
    public readonly _A!: A;
    public readonly _URI!: FsFURI;
    constructor(readonly message: string, readonly more: A) {}
}

これをWriterのようにあとですべてのmessageを取得するように変えたい時にどうすれば良いでしょうか。

実はinterpretTaskEitherTaskEither<string, A>というMonadになっているのは、Free型を解いていく際にMonadのchainを使って継続させているためのなのです。

昨日のfoldFreeの第一引数がMonadになっているのはそう言った理由です。

一旦


function tell(message: string) {
    return function<A>(x: A) {
        return free.liftF(new Show(message,  x));
    }
}

const program = read('./package.json').chain(content => tell(content)(content)).chain(content => write('./package2.json', content)));

と書き換えてみます。

今回のShow型のmessageがstring[]というMonoidに足されていくように解釈して欲しいので

TaskEither型にWriterTを使って TaskWriterEither<string, string[], A>のような型とMonadにするためのchain関数があればいいのですが

すみません!!!時間不足でできませんでした!!!

fp-tsとfree

1

fp-tsにはFree Monadを使っていきたいと思います。

Free Monadに関する説明は今回は省きます。

Free Monadと書いていますが、実はFreerMonadという感じがしていますがfp-tsがFreeという表現になっているのでFreeでいきます。

2

今回作るのは下のfp-tsのexampleや以下のgistを参考にしてPromiseを使った例を作っていきます。

検索した感じではPromiseを使った例がなかったので。

github.com

Free Monad example, Flow and fp-ts, npm install fp-ts · GitHub

3

fileのread, write周りを例に今回は作っていきます。

実はデータ型を定義するところまでは一緒です。それがFreeの強みなんですが

declare module 'fp-ts/lib/HKT' {
    interface URI2HKT<A> {
        Fs: FsF<A>;
    }
}

export const FsFURI = 'Fs';

export type FsFURI = typeof FsFURI;

export class Read<A> {
    public readonly _tag: 'Read' = 'Read';
    public readonly _A!: A;
    public readonly _URI!: FsFURI;
    constructor(readonly path: string, readonly more: (p: string) => A) {}
}

export class Write<A> {
    public readonly _tag: 'Write' = 'Write';
    public readonly _A!: A;
    public readonly _URI!: FsFURI;
    constructor(readonly path: string, readonly content: string, readonly more: A) {}
}

export class Show<A> {
    public readonly _tag: 'Show' = 'Show';
    public readonly _A!: A;
    public readonly _URI!: FsFURI;
    constructor(readonly message: string, readonly more: A) {}
}

export type FsF<A> = Read<A> | Write<A> | Show<A>;

function read(path: string) {
    return free.liftF(new Read(path, identity));
}

function write(path: string, content: string) {
    return free.liftF(new Write(path, content, undefined));
}

function show(message: string) {
    return free.liftF(new Show(message, undefined));
}

こういう感じになります。

あとはこれを解釈する部分を作っていきます。

Promiseをfp-tsで使う場合、選択肢としてTask or TaskEitherになるのですが一応真面目にError処理ができるTaskEItherを使っていきます。

FsF<A> => TaskEither<string, A>となるように型を合わせれば動くと思います。

function interpretTaskEither<A>(fa: FsF<A>): TaskEither<string, A> {
    switch (fa._tag) {
        case 'Read':
            return new TaskEither(
                new Task(async () => {
                    try {
                        const file = await fs.promises.readFile(fa.path, { encoding: 'utf8' });
                        return right(fa.more(file));
                    } catch (e) {
                        return left(`read error: ${e.message}`);
                    }
                }),
            );
        case 'Write':
            return new TaskEither(
                new Task(async () => {
                    try {
                        await fs.promises.writeFile(fa.path, fa.content);
                    } catch (e) {
                        return left(`write error: ${e.message}`);
                    }
                    return right(fa.more);
                }),
            );
        case 'Show':
            return fromIO(
                new IO(() => {
                    console.log(fa.message);
                    return fa.more;
                }),
            );
    }
}

あとはfoldFreeするだけです。 foldFreeに渡す値が型を見た時にわかりにくいのが難点で、ぱっと見理解するのがかなり難しめにできています。

export declare function foldFree<M extends URIS3>(M: Monad3<M>): FoldFree3<M>;
export declare function foldFree<M extends URIS3, U, L>(M: Monad3C<M, U, L>): FoldFree3C<M, U, L>;
export declare function foldFree<M extends URIS2>(M: Monad2<M>): FoldFree2<M>;
export declare function foldFree<M extends URIS2, L>(M: Monad2C<M, L>): FoldFree2C<M, L>;
export declare function foldFree<M extends URIS>(M: Monad1<M>): <F extends URIS, A>(nt: <X>(fa: Type<F, X>) => Type<M, X>, fa: Free<F, A>) => Type<M, A>;
export declare function foldFree<M>(M: Monad<M>): <F, A>(nt: <X>(fa: HKT<F, X>) => HKT<M, X>, fa: Free<F, A>) => HKT<M, A>;

ですが先ほどTaskEither型を使っているので、TaskEitherが定義されているところにある

export declare const taskEither: Monad2<URI> & Bifunctor2<URI> & Alt2<URI> & MonadIO2<URI> & MonadTask2<URI>;

を使えば大丈夫です。

つまり今回は型からわかるように以下の定義を使っていることがわかります。

export declare const taskEither: Monad2<URI> & Bifunctor2<URI> & Alt2<URI> & MonadIO2<URI> & MonadTask2<URI>;

最終的に実行する部分は以下のようになります。

const program = read('./package.json').chain(content => show(content).chain(() => write('./package2.json', content)));

async function main() {
    const result: Either<string, undefined> = await free
        .foldFree(taskEither)(interpretTaskEither, program)
        .run();

    if (result.isLeft()) {
        return console.error(result.value);
    }
}

今回TaskEitherを使っているのでawaitの部分でcatchする必要がありません。もしTaskを使っていた場合catchする必要があります。

今回のcodeはgistにあげてあります。

FreeFs.ts · GitHub

package.jsonがある環境ではfileの中身が標準出力に出されて、package2.jsonが作られていると思います

package.jsonがない場合、read error: ENOENT: no such file or directory, open './package.jon'以下のようなerrorになると思います。

netflix in train 4

1

chef's table シーズン5を見た

www.netflix.com

ていうかこれシーズン5なのね、今気づいてしまった。

これは料理のドキュメンタリではなく、シェフのドキュメンタリである。

だけど「シェフの」と言ってしまうのも変に感じる作品だった

確かに主体の職業はシェフであることに間違いない。

だけど、客がいて料理があっての労働的側面よりは文化的な側面が強調された作品だった。

シェフという職業を通して食文化をとらえてきた人のドキュメンタリだった。

シーズン5がそういうテーマなのかもしれない

2

ある生活に結びついた文化をとってきた時に、食文化の存在は大きい

だけど文化としての料理が何をさすのかはあやふやで、レシピをさすだけなのか、家庭で出された事実をさすのか、ある料理店をさすのかはわからない。

ありきたりだが日本で肉じゃがという料理を考えた時に、家庭で出されている事実はあっても代表的なレシピは存在はしない。

過去の肉じゃが発明時のレシピが今の肉じゃがを基礎づけてはあっても今の肉じゃがをさすとは到底思えない。

だけど普遍的に人々は存在を認識はしていて、それこそが文化としての料理なのかもしれない。

そのような中、料理店を構えて自分の作った肉じゃがが”肉じゃが”だと示していく行為は、一見各々が自分で料理する中で生み出す肉じゃがと変わらないように思えるが、それこそが文化として引き継ぐ行為なんじゃないかと思った。

あくまでふわふわした存在から形を作り出しそれを示していく行為をしてくれているのがシェフなんじゃないだろうか。

当然、職業かどうかは関係ないが

鮭に対するアンサー

1

先日作った鮭の昆布締めに対する考察

hiroqn.hatenablog.com

経緯

鮭は油が多いしサクのまま巻くので長時間寝かせる必要があるのでは?という考えから24時間かけたが、昆布と鮭自体のにおいで田舎っぽくなってしまった。

ので、調味料を足していい感じにする

 ## 2

試してそんなだったもの

日本酒と塩

アルコール飛ばしてないと調味料としては微妙かも

煎り酒は時間がかかるので断念 

マヨネーズ

全てをふっ飛ばす。昆布締めの意味なし

 

たまり

シンプルに醤油

悪くないけど鮭と醤油はちょっと違うかな

結局両方グルタミン酸なので旨味がバッティングしてるような

タイムと塩

一口目がタイムで後味が昆布、意味がない ディルがあったらな〜みたいな気持ち。ディルは常備してない

レモン汁と塩

ちょっと酸味が強すぎるかも

3

良かったやつ

エシャレット

エシャレットを鮭で巻いて、たまりを少し塗ったり塩振ったり

玉ねぎ感覚で使ったが苦手な人は無理かも知れないけど、さっぱりしてて田舎臭さが消えた  

オリーブオイル、塩、胡椒、バルサミコ酢

これが一番合った。後から昆布の旨味がしっかり味わえるし鮭自体の味もしっかりしている。 酸味が大事に感じる。けどレモン汁ではダメで酢酸が必要説。 今あるバルサミコ酢は結構マイルドなやつで甘めなのでそれもあっている感がした。

f:id:hiroqn:20181218232658j:plain

bs-winstonを作ろうとする過程

1

winstonjsありますよね。jsのlogライブラリ。

github.com

BuckleScriptから使いたくなりません?

というわけで、作りながら書いていきます。

今回は作る過程を描く感じで完成してpublishはしないです。

2

READMEを読めばぼんやり使い方はわかると思うので、まずはtsの型定義から見ていきます。

https://github.com/winstonjs/winston/blob/master/index.d.ts

createLoggerに色々オプションを渡すとLoggerが出てくる感じです。

今回注目して欲しいのがlevels optionで、これがかなり胆になってきます。

型的にはこれですね

interface AbstractConfigSetLevels {
    [key: string]: number;
}

objectで表現されたMap<string,number>型なんですが、winstonはこれのkey情報を使ってmeta的にLoggerにmethodを生やしてきます。

つまり

levels: {
  hoge: 0,
  fuga: 1,
}

というオプションを渡せばlogger.hogelogger.fugaみたいなmethodが生えるわけですが、それに対して型をつけるのは厳しいのでその部分は諦めます

ただ、どのlevelがlogに使えるかやどのlevel以上のlogを出力するかというオプションに関わってくるので上のlevelsオプションは作る必要があります。

2

logに使うlevelを文字列で渡す方法がありますが、そんな安全じゃない方法は取りたくないので今回はそれにうまく制約をつけていきます。

イメージとして、 log `Alert "message"みたいな感じなれば嬉しいですね

ただ、どのようなlevelが使えるかというところはカスタマイズできるように作れるようにします。

Variantからstringにする部分を自動生成するppxが最初から入っているのでこれを使っていきます

一旦Syslogに対応するということで

    type t = [
        | `Emerg [@bs.as "emerg"]
        | `Alert [@bs.as "alert"]
        | `Crit [@bs.as "crit"]
        | `Err [@bs.as "error"]
        | `Warn [@bs.as "warning"]
        | `Notice [@bs.as "notice"]
        | `Info [@bs.as "info"]
        | `Debug [@bs.as "debug"]
    ] [@@bs.deriving jsConverter]

こういう感じにしていきます。

上記から本来はlevelsオプションを生成したいわけですがそれはmetaプロになっちゃってきついのです。あとlevelの順序を定義する必要もあります。

なので一旦どのlevelが使えるかというenabledという概念を用意してt listにします。

let enabled = [Emerg; Alert;Crit; Err;Warn; Notice;Info; Debug]

そしてここからlevelsを生成できます。

let levels = enabled |> List.mapi (fun i l -> (Level.string_of_t l, i)) |> Js.Dict.fromList

結果は

{ emerg: 0,
  alert: 1,
  crit: 2,
  error: 3,
  warning: 4,
  notice: 5,
  info: 6,
  debug: 7 }

こうなります。

3

そういえばFFIの部分を書いてなかったので書いていきます。

winston_internal.ml

type transport

(* ここのオプションは諸事情で一部省略 *)
type mk_console_transport_option = {
    eol: string [@bs.optional]
} [@@bs.deriving abstract]

external console_transport: mk_console_transport_option -> transport = "Console" [@@bs.new][@@bs.module "winston/lib/winston/transports/index"]

(* winston本体 *)

type mk_option = {
  levels: int Js.Dict.t;
  level: string;
  transports: transport array;
} [@@bs.deriving abstract]

type winston

external create_logger: mk_option -> winston = "createLogger" [@@bs.module "winston"]

type mk_log_entry = {
    level: string;
    message: string;
} [@@bs.deriving abstract]

external log: winston -> mk_log_entry -> unit = "" [@@bs.send]

これはもうこんな感じとしかいえないですね。

4

あとは組み合わせると一旦は動くのですが、levelのカスタマイズができません。

そのためにcreate_loggerをFunctorにします

module type LogLevel = sig
    type t
    val string_of_t : t -> string
    val enabled : t list
end

module Make(Level: LogLevel)(Conf: sig 
    val transports: Winston_internal.transport list 
    val level: Level.t
end) = struct
    type t = Level.t

    type log_entry = {
        message: string;
        level: Level.t;
    }

    let _levels = Level.enabled |> List.mapi (fun i l -> (Level.string_of_t l, i)) |> Js.Dict.fromList

    let w = Winston_internal.(create_logger @@ mk_option
        ~levels: _levels
        ~level: (Level.string_of_t Conf.level)
        ~transports: (Conf.transports |> Array.of_list)
        )

    let log level message  = 
        let entry = Winston_internal.mk_log_entry ~level: (Level.string_of_t level) ~message in
        Winston_internal.log w entry
end

こうすることでLogLevelを実装すればいい感じのLoggerモジュールができる設計になります。

Syslogモジュールは

module Syslog = struct

    type t = [
        | `Emerg [@bs.as "emerg"]
        | `Alert [@bs.as "alert"]
        | `Crit [@bs.as "crit"]
        | `Err [@bs.as "error"]
        | `Warn [@bs.as "warning"]
        | `Notice [@bs.as "notice"]
        | `Info [@bs.as "info"]
        | `Debug [@bs.as "debug"]
    ] [@@bs.deriving jsConverter]

    let enabled = [`Emerg; `Alert; `Crit; `Err; `Warn; `Notice; `Info; `Debug]
    let string_of_t x = tToJs x
end

という風に定義できるので、

module SyslogLogger = Make(Syslog)(struct
    let transports = Winston_internal.([console_transport @@ mk_console_transport_option ();])
    let level = `Debug
end)

SyslogLoggerはこのように生成できます。

使用例

let () =
    SyslogLogger.log `Emerg "yabai";;

LogLevelさえ満たせばいいのでNpmLogLevelのようなものを作れば以下のようなnpm log levelにも対応することできます。 https://github.com/winstonjs/triple-beam/blob/master/config/npm.js

5

実はwinston-transport(上記のconsole_tranport)に対してもlog levelが設定できるのですが一旦TODOにしました。 またwinstonのformat optionのFFIを書くのがちょっと骨が折れるのでこれもTODOにしています。

昆布締めアンボックス

1

昨日仕込んでおいた、サーモンの昆布締めをアンボックスしていきます

ちなみに、これを見てサーモンの昆布締めが案外いけるっぽいのでtryしました。

negineesan.hatenablog.com

2

f:id:hiroqn:20181216235448j:plain

f:id:hiroqn:20181216235451j:plain

f:id:hiroqn:20181216235454j:plain

f:id:hiroqn:20181216235458j:plain

3

サクだったので24時間巻いておいたというのもあるかもしれないけど、

正直、なんか田舎臭さというか

一口目のファーストインプレッションが柿の葉寿司っぽい、、、

(祖父母が奈良の人間で幼少期に度々奈良に連れていかれ、やたらと奈良では柿の葉寿司を食わされ鯖の柿の葉寿司が苦手だった自分は鮭の柿の葉寿司ばかりを食べていた記憶が唐突にフラッシュバックする)

一旦落ち着いて薬味とかタレとかソースとかの方向性を考えることにした。

もともとサーモンなので玉ねぎとマヨネーズで👍みたいなのを考えていたのだけど、昨日のカレーに玉ねぎを全部使ってしまってない(愚か)&マヨ合わんなって即なった

4

というわけで、色々つけながら何があうか試していきたいと思っていたが時間がかかってきたので一旦アンボックスだけにしました。

内容なさすぎ

続編書くしかない

カレー

1

カレーの説明をすると、自分の好きな食材をスパイスで煮込んだものです。以上。

f:id:hiroqn:20181215231954j:plain

材料

2

野菜系の処理をしていきます

f:id:hiroqn:20181215231957j:plain

玉ねぎはみじん切りして電子レンジへ

f:id:hiroqn:20181215232008j:plain

こんな感じ

f:id:hiroqn:20181215232011j:plain

セロリも同等の操作を加えます。

f:id:hiroqn:20181215232014j:plain

ホールトマトは手で潰して、煮詰めていきます

f:id:hiroqn:20181215232020j:plain

はい

f:id:hiroqn:20181215232034j:plain

これがゴール

3

次は肉です

f:id:hiroqn:20181215232001j:plain

牛すじは煮こぼして

f:id:hiroqn:20181215232004j:plain

洗う

f:id:hiroqn:20181215232023j:plain

せせりにも同等の操作を加え切りました。

4

スパイス、一番難しい

f:id:hiroqn:20181215232026j:plain

クミン、ターメリック、カイエン、カルダモン、あとレッドアイ

フェンネルクローブは少しだけ

f:id:hiroqn:20181215232043j:plain

ニンニクとエシャロット

5

その他の素材として

f:id:hiroqn:20181215232038j:plain

昆布だしを凍らせてあったもの

f:id:hiroqn:20181215232048j:plain

ペコリーノロマーノ

余談ですが、イタリア料理のドキュメントを見ていたら、イタリア人はペコリーノをFORMAGIOのカテゴリに入れてませんでした。

formagioはチーズをさすと思ってたのですが、牛の乳から作られているのに限るのかもしれない。

f:id:hiroqn:20181215232052j:plain

いい感じになったら全てを投入して、パルミジャーノレジャーがぶっこまれてます。

6

ライスを炊いてませんでした、、、、