Pixel Pedals of Tomakomai

北海道苫小牧市出身の初老の日常

今日は「builderscon tokyo 2017 一日目」の日です

慶応日吉キャンバスにおりますので、自分用のメモを残しておきます。

Opening / lestrrat さん

諸事情で英語(スライドは日本語)。

  • builderscon は YAPC::Asia の直系のカンファレンス
  • いろんな技術の話を集めたものがbuilderscon
  • 近年、様々な技術が互いに関わり合うようになっている
  • 新しいものを発見する場 (Discover something new)
  • #builderscon @builderscon
  • スポンサーランチあるよ
  • ベストスピーカー賞投票してね
  • パネルで写真撮ってね
  • STAFFは腕章つけてるよ
  • 問い合わせは Twittergoogle group のメールで
  • LTの通知していた時間が間違っていたので、ごめんなさい
    • LTはその場の熱意で登壇するのが一番いいです!

Desktop Apps with JavaScript / felixrieseberg さん

  • Slackは会社としてはまだ3年目
  • 仕事をよくする => チャットアプリを提供するのがいいと考えた
  • デスクトップ版の開発チームは4人
    • 効率の良い開発方法を常に考える必要がある
  • Windows, macOS, Linux
  • それぞれの環境に数人担当をつけるのが普通だが、Slackはそうしてない
  • それぞれ、見た目やIMEがちょっとずつ違う
  • JSでDesktop App ?
    • JSをやってる人から驚かれる
    • ネイティブの開発者に驚かれる
    • JSで書かれているプロダクトはどんどん増えている (adobe, nvidia, etc…)
    • 好むと好まざるとすでにJSはあなたのPCで走っている
  • Electron
    • NodeJS : 好まれている。簡単
    • Chromium : レンダリングエンジンだけで利用
    • C++ : 余り興味は思われてない。C++はひどい。誰も楽しんでない。が、速く、よく使われていて枯れている
    • ダイアログなど基本的な部品はC++でアクセスできる
  • Electron は Node.js のプロセスである
    • Electron APIを使える。ウインドウを開ける
    • Renderer プロセス側 ではChrome, Nodeの両方の機能を使える
    • プロセス間で通信できる
    • 複数のウインドウを開ける
    • メインスレッドは分けられているので、安全。かつ、高速
  • Slack : Electron, TypeScript, Node.js, C++
    • ビデオコールは C++ で実装
    • VSSの検索機能はC++
  • JSで書かれたコードが遅ければ、全てがJSで書かれてしまっている
    • 複雑なところはC++で書くべき
  • Monaco Editor / VS codeの原型
    • 20分あれば簡単にできるので作ってみる
  • package.json のみがある状態
    • main に実行されるファイルが書いてある
  • index.js を書くと動く
  • node の代わりに electron コマンドを使って実行する
  • electron モジュール
    • app オブジェクト : ライフサイクル
    • ready イベント : 最初に受けるイベント
    • new BrowserWindow () : chromium のウインドウが出る
  • index.html を作る (テンプレから)
    • 普通のHTML
  • HTMLから読んでいるCSSを作る style.css
    • ここまでだけでは何も起きない
  • mainWindow.loadURL('file://${__dirname}/index.html')
  • electron 下では index.htmlrequire('./render.js') と書くことができる
  • console.log() で絵文字は扱えない
  • monaco-loader パッケージを利用
    • 実行すると monaco オブジェクトが得られる
    • monaco.editor.create<div> を渡してエディタにする
    • theme: automaticLayout: など必要なオプションを渡す
  • ファイルのドラッグへの対応
    • 何もしないと Chromium によってナビゲーションされるだけ
    • メインスレッドからWindowのライフサイクルをコントロールする
    • 'will-navigate' e.preventDefault() ページ遷移を禁止する
    • ファイル名はコールバックで取得できる
    • が、ファイルを開くのはメインプロセスではなくレンダラプロセス => プロセス間通信
  • プロセス間通信
    • file:/// を捨てる
    • メインプロセス mainWindow.webContents.send('open-file', url)
    • レンダラ ipcRenderer.on('open-file' ...) で受け取る
  • electron 関連 : 1700パッケージある
    • 透過ウインドウなど
  • electron-forge : Electronを試したい時
    • プロジェクトの初期化やビルドなど
  • まとめ
    • Electron で JS を Desktopで作るのは楽しい
    • Electronモジュールは充実している(C++などより活用できるものが多い)
    • electron-forge を使おう!
    1. メモリ消費量を削減する努力は?
      1. アプリをreactに移している。会社の成長のほうが早くて追いついてない。これから最適化していくことになりそう
    1. C#やSwiftと比べたときのパフォーマンスは?
      1. どの部分をどの言語で書くかが重要。遅いときはC++を使って書く。遅いのはJSではなくOSである可能性もある
    1. Electronをそのまま使ってますか?
      1. githubと直接Electronの開発をしている。Electronの開発者は東京にいる
    1. Electronのアプリのテストはどうしてる?
      1. Spectronを使う, Electronの内部でテストする, Jestを使ったモック, の3つの方法
  • Electronは素晴らしいアプリが作れる
    • electron-code-editor を使ってエディタを作って、面接でエディタを聞かれたら自分で作ってますと答えよう

横山三国志に「うむ」は何コマある?〜マンガ全文検索システムの構築 / heruheru3 さん

  • 「うむ」は458回 http://mitchell-fts.appspot.com/
  • 横山光輝三国志を選んだ理由
    • コマが矩形
    • ぶち抜き、突き抜けコマが少ない
    • 作画が一定 (張飛だけ身長が縮んでいる)
    • 線画のみなので2値化が効く
    • どこを切ってもネタになる
  • GAE, Google CV API (画像を切る), さくらレンタルサーバ(画像)
  • コマ分割アルゴリズム
    • 画像を反転 (左上を0にしたい)
    • 斜めの線を上から落としてきて、ぶつかった順にコマを拾う
    • 切り抜く
    • 次のコマを探すために走査する (終わるまで続ける)
  • 三国志
    • 99.9% 成功
    • 52000コマ
    • 200ページあたり1分
  • 三国志以外
    • 矩形じゃないとうまくいかない
    • 波線ていどならうまくいく
  • 新しい手法 (v2)
    • openCVのflood_fillを使って、外枠を塗りつぶしで判定
    • 斜めの枠でもきれいに取れる
    • 1冊200ページが10秒
    • v3も開発中
  • OCR
  • Google Computer Vision OCR
    • 縦文字もほぼO.K. (コマ分割後)
    • 1,000枚まで無料
  • OCR後の処理
    • 余分に検出させて、消す
    • 横長の領域を削除(横文字と認識されてる)
    • MeCabで日本語以外の文字を捨てる
    • 文字同士の距離を見て、文字の塊を作る
  • 1,000枚ごとに$1.5。52,000枚だと8,913円
    • 36,000 枚にしか文字はない
  • 全文検索
  • Python Whoosh
  • N-gram インデックス
    • N文字ずつに区切り、インデックス化
  • 検索性能 0.044s 結構いいのでは
  • WEBのガワはGAEを利用
    • PythonのFlask (簡単)
    • 20行強で十分
  • UI側
    • OSSを多様
    • materialize (googleスタイルシート)
    • masonry 画像をタイル状に敷き詰める
    • imagesloaded, jquery.bottom 画像呼び込み後に次の読み込み
    • blueimp-gallery
  • 活用
    • Twitter, LINE, Slack にそのまま貼れるよ
  • 応用
    • 吹き出しの文字だけ消す (すでに文字を消すボタンが付いてる)
      • コラ作成
      • 文字を消すボタンは、ファイルを2つ用意しておいて、切り替えるだけ
    • 自動翻訳機能 Microsoft Translator Text API
      • このAPIは60ヶ国語に対応している
    • このコマが何巻のどこだっけ?機能
  • ドラゴンボールフリーザのとこに悟空が来るところ」のデモ
    • 枠外の塗りつぶしはじめは、なにもないところから
    • セリフの突き抜けなどもうまく拾えている
    • 「・・・」がOCRで取れない(消しきらない)
  • エロ漫画はダメ (コマがない)
    1. 著作権の問題で調べたことなどは?
      1. 何も調べてない。今回公開したものも削除されるでしょう。が、声はすでに頂いている
    1. コマ分割、OCRの評価は、人力?
      1. はい。確認のためにお金がいる。
    1. 大量の目視確認で工夫したことは?
      1. あまり確認してない。MeCabに確認はさせた。三国志なら全部覚えてるので

Haskellを使おう / hiratara

自分のトークでした。 https://speakerdeck.com/hiratara/haskellwoshi-ou

Building high performance push notification server in Go / cubicdaiya さん

  • 絵文字を付けるとコンバージョンが上がる
  • 直接端末ではなく、pushサービスを通って通知が行く
    • iOS, android のものだけでなく、AWSなどthird partyのものも
  • APNs : Binary Provider API と APNs Provider API がある
    • 後者はHTTP2でcurlでも叩ける
  • GCM/FCM
    • 新しいものはFCMを使うべき
    • こちらもHTTP2
  • レイテンシが高いので、Keep-aliveにする
  • 10 millions のターゲット
  • 昔はPHP内から直接push notificationを送ってた
    • 応答に数秒かかる
  • その後、Queueを導入
    • Workerがde-queueして通知を送る
    • queue は Q4M
    • php-parallel-prefork
    • PHPでは十分なスループットが出ない
    • PHPは並行処理が苦手
  • App => nginx => Gaurun => APNs / GCM という構成
    • Gaurun から APNs / GCM は HTTP2
  • Gaurun github.com/mercari/gaurun
    • goで書かれたpush通知サーバ
    • JSONベースのAPIサーバ
    • POST /push すると APNs や GCM に送ってくれる
    • GET /stat/app, GET /stat/go : 情報の取得
    • GET /config/pushers スループットを変更
  • Go の理由
    • goroutineがたくさん作れる
    • 5年前のMacで 5万QPS
  • Gaurunはプロクシ+キュー
    • キューはChannelを使って実現
  • workerとpusher
    • workerがdequeしてpusherを起動
  • コネクション管理
  • GET はキューの数、成功失敗の数、goroutineの数、など
    • 数値が変えれば監視は簡単
    • 監視はMackerel
  • High PerformanceにするにはTOMLをいじる
    • デフォルトでは保守的な設定
    • core.workers, core.queues, core.pusher_max
    • (ios|android).(timeout|keepalive_conns|keepalive_timeout|retry_max)
    • core.workers × core.pusher_max
    • keepalive_connsAppleGoogleに怒られない程度に
    • PUTpusher_max をでかくできる
  • Bulkで enqueue もできる
    • notification_max による制限有
  • 無効トークンの削除
    • Gaurunのエラーログを拾う
    • ログをS3に投げておいて、バッチでMySQLから削除
  • まとめ
    • push通知は高レイテンシ、高並列性
    • Goがよい
    1. gaurunにどう送ってる?
      1. クライアントがMySQLから取るだけ
    1. 全体に配信するときも同じ仕組み?
      1. WEB UIがある。
    1. 重複制御?
      1. 1台に重複はないが、複数デバイスを持っていれば受け取ってしまう
    1. FCMはhttp2?
      1. goがhttp2に対応しているので。コネクション数もいい感じに
    1. queueとworkerが1つのバイナリ。メッセージのロストへのケアは?
      1. ロストする。AccessLogに書いてる
    2. リクエストが来た、Queueに入れた、Successした
    3. CLIでスキャンできる
    1. pusherとworkerを分けるのは?
      1. 歴史的な経緯。workerが昔は同期的にpushしていた。古いAPIの都合
    2. 新しいAPIでは制限がなかったのでpusherをforkするようにした
    1. ボトルネックは?
    2. ネットワーク通信。CPUやメモリは使いきれてない
    1. スケーリングは?
      1. 一斉配信でも3~4台で済むので、困ってない
    1. デプロイでキューをロストしない方法は?
      1. nginxから外す。GaurunにもGracefulリスタートがある(queueを吐いてから終了)

LT

「ドラを忘れたので、時間が来たらエアドラします。」

分割QRコードの読み取り方 / Satoshi Shmdさん

  • QRコードは16分割できる
  • 日本のデンソーが作った。Quick Response
  • 仕様書で「構造的連接」として定められている
  • メタデータ3 だと分割。最大16分割
  • 4分割のQRコードを読む例
  • 横に並んでいると高速に読み込める
  • 小さく分割したほうがエラー率が低い
  • 標準APIを使って、internalからメタデータを拾って実装してる
  • 「これ、発表できて良かったです」

RaspberryPi+AWSでIoT(っぽい)GPSロガーを作ってみた / Toshihiko Kimuraさん

  • ラズパイにGPSアンテナを付けたもの
  • はんだ付け
    • 10数年ぶり
    • ミスってる
    • あきらめた
  • 慢心せずハンダゴテ練習しましょう。ハンダ吸い取り線は便利
  • サーバレス → 7円だったのでよかった
  • 座標が正しいのか確認は?
  • DynamoのデータをCSVで落として、Google API
  • 今日の仙台から東京の移動のロギングは失敗したので割愛
  • 「おちこんだりした」『「おちこんだり」になってますね、大丈夫ですね』
  • スマホでよくね? はい!
  • 自分で作ってなんぼ

できる!!!Validation!!! / sasezakiさん

  • LTは15分という世界から来ました
  • 「世界は真偽で満ちているのにバリデートとは?」
  • バリデーション == 入力値検証
    • assert, guard, 例外
  • バリデーションをきちんとしないと末代まで遺恨を残す
  • 「空白ははじいて」空白の種類が多い
  • 「アルファベットで」日本語は
  • 「この正規表現を使って」死ぬ
  • 「正義とは何か」
  • 「人類の存在が誤りだった」

カンファレンスアプリを作ったぞ!! / morizoooさん

  • buildersconアプリは明日までの命なので使って下さい
  • WEBのモバイルは使いにくい。最高のタイムテーブルが欲しかった
  • POをしたかった
  • PO業務
    • 「頑張らせる」
  • 7/21 時点でiOSしかなかった
  • Android版 冗談だと思った
  • Droidkaigiを参考に
  • 最高ではないけどなんとかできた
  • 今日のセッションで学んだ
    • iOSデザインの流用と、回転時の処理がよくない
    • 直したがリリースは間に合わなかった

アイドルフェスのタイムテーブル管理アプリを作った話 - sugyanさん

  • 今日から東京アイドルフェスティバル
  • 去年までドルオタだった
  • タイムテーブルがある
  • 公式の管理ツールが存在する
    • 個人ツールも存在する
  • 去年のものを流用して今年の分も
  • reactでやってる(無駄にSPA)
  • Twitter共有機能を追加
    • 普通は画像は流せない
    • ダミーアカウントで画像URLを作り、それを共有する仕組み
  • 今年、めちゃくちゃダウンロードされた。出演者にも使ってもらえた
  • 自作サービスでも、ちょっとした工夫で効果が上がる

アドテクやってるエンジニアだけど、どうしてもみんなに伝えたいことがある。 / HonMarkHuntさん

  • アドテクって何?
    • インタネット広告の配信・最適化技術の総称
  • どうしても伝えたいこと
    • スマホのバナー広告が多重影分身広告
    • 動画プレイヤーにかぶさる広告のクローズボタンがかなり小さい
    • 「ほんとにごめんなさい!!!!」といいたかった
    • クリックするとお金が儲かるのでクリックさせる
  • テレビ広告は関連内ユーザにも届いてしまう
  • インタネット広告は適切な人にのみ広告が届く
    • ユーザからのフィードバックを利用できる
  • 広告主の利益最大化、ユーザへの配信の最適化
    • 「ムカつくと思うけど、頑張るので見守って下さい」

Discovered Something New? / tomzohさん

  • YAPC::Asia 2015 のベストトークショウが面白かった
  • 別のカンファレンスでやった → よかった
  • 名札が好き → 名前とSNSアイコンが紐付かない
  • ベストトークショウと名札をやりたい
  • SNSアイコンとQRが難しい
  • 印刷方法は?
    • 言葉を知らないのでググれない
    • 「バリアブル印刷」という
  • 画像の集め方は?
    • SNSのアカウントを入力するようにした
    • バリデーションがなかった → PHPが解決!
    • Twitterで取れる画像がバラバラ過ぎ
    • サイズ、画像フォーマット
    • 500px以上だと印刷して撃つ香椎
    • github, facebookはきつい
  • チケット販売期間と入稿起源の問題
    • 遅くチケットを買うと印刷できない
  • ベストトーク賞投票お願いします
  • 牧さん「じゃーーーーーん」

吾輩が作ったものを淡々と / codehexさん

  • 沖縄から上京するのでよろしく
  • 全てのgoroutineにシグナルを通知する
    • シグナルを拾って全てのgoroutineに通知してくれるくん
  • perlのProcletライク → 止めても止まらないから設計やり直し
  • batteryコマンド → mattnさんがWindows版を
  • Neo-Cowsel / 音楽はバックグラウンドで流しているので消えない
  • 世に役立つものを作ります

Node8.3.0について / @about_hiroppy

  • current v9.0.0 / latest v8.2.1 / LTS v4, v6 v8
  • V8がv6.0にアップグレード。パフォーマンスプロファイルの変更
  • TurboFan、Ignitionがあり、初めてデフォルトになる
    • JIT CompilerでCrankshaftから置き換え
    • try-catch 最適化など、最適化が可能になる
  • Ignition : androidなどの環境のため、バイトコード化してメモリを小さくなる
  • v5.9では色々パフォーマンスが悪化したが、v6.0以降では期待できる
  • ベンチマークは基本的には改善されている
  • 8月中に8.3.0
  • 牧さん「はいじゃーーーーーん」

正規表現の変形で作る独自記法のWiKi parser / shokaiさん

  • wikiの独自記法を乱立させたい
  • ページ間リンクを充実させたかった
  • 覚えやすい記法を採用 (タイトルとリンクの順序が逆でもいいとか)
  • 正規表現のカッコの位置はよく似ているが場所が違う
  • ?: を使ってキャプチャを抑止する
  • 「なんかもうわけわからない感じ」
  • 一個の記法でノードのツリーが書けるように
  • 公開しているライブラリを使って記法をたくさん作るといいんじゃないですかね

バックエンドからのSEO対策 〜JSON-LDでのschema.org入門〜 / uessy-akrさん

  • schema.org
    • 検索エンジンにページに何を掲載しているのかを伝えるマークアップ方法
    • google検索結果画面のリッチスニペッツ、リッチカードとして表示される内容を指定できる
  • 料金、ページ数、バンドメンバー、アダルトエンタメ施設、など、すごくこまい指定が可能
  • 通貨コード、営業時間
    • Microdata : つらい
    • RDFa : つらい
    • JSONLD : JSONなのでだいぶ幸せ 「見た瞬間キタ」
  • JSON-LDはHTMLと分離できるので、きれい
  • 牧さん「マイクがkamipo持ちですね」

Microsoft Azure って知っていますか? / Tsubasa Nomuraさん

今日は「Haskell入門ハンズオン!」の日です

今日は「Haskell入門ハンズオン!」の日です

https://shinjukuhs.connpass.com/event/58224/へメンター枠で来ていますので、自分用のメモを残しておく。

はじめに / 木下さん

  • #Haskel
  • 講師重城さん+メンター5人居ます
  • haskellでわからないことはteratailで質問しましょう
  • 8/28 に Haskell入門者LT大会やるよ

Haskellの概要 / 重城さん

  • 自己紹介 : Gentoo on KVM on Gentoo, TUT-code, Xmonad, rxvt-unicode, Tmux, Mutt, HHKB
  • ハンズオンの中身 : 完全入門者向けだけど、Haskellの面白さは伝えたい
  • stack --version stack ghci :quit
  • 4492 'c' pi 電卓的な使い方、 it
  • カーソルキーで履歴を辿れる
  • repeat "Haskell" からの Ctrl+C
  • サンプルコード
  • pwdcdディレクトリ移動して、 stack ghci
  • 行儀が悪いのでTABを8文字幅で使うよ!(本当は4スペースが行儀がいい)
  • myFavoriteFruit = "apple":load で読み込み
  • "banana" に変更してmyFavoriteFruit` をしても変わらない
  • :reload すると切り替わる
  • double x = x * 2 関数
  • (\x -> x * 2) 8 関数リテラル
  • 演算子と関数は同じ
  • 演算子は中置で記号列。 () で関数になる
  • 関数は `` でe演算子になる
  • lucky :: Integer 型宣言
  • Maybe Just Nothing
  • タプル taro = ("Taro Yamada", 35)
  • パターンマッチ
    • helloTo (Just n) = "Hello, " ++ n ++ "!"
    • helloTo Nothing = "Hello, customer!"
    • human (n, a) = n ++ "(" ++ show a ++ ")"
    • リテラル safeRecip 0 = Nothing
    • ワイルドカード isNothing (Just _) = False
    • アズパターン atPattern jx@(Just x) = show jx ++ ": " ++ show x
  • "" は文字列、 '' は文字
  • パターンが抜けているときは部分関数になる。マッチしないと例外
  • 例外はI/Oモナドで拾える
  • 「代入じゃなくて束縛と言わないと怒られる」
  • show は多相関数なので Maybe Integer にも Integer にも使える
  • ガード safeSqrt x | x < 0 = Nothing
  • case式 yesNo c = case c of 'n' -> Just False
  • 多相: ignoreSecond :: a -> b -> a ignoreSecond x y = x
  • 型シノニム: type Human = (String, Integer)
  • モジュールの導入 import Data.Maybe (fromMaybe)

ここで休憩。

  • 多相関数を使うと、ソースコードがきれいになる
  • const の2つの側面
    • 第二引数を無視する関数
    • 常に定数を返す関数 (const 関数への部分適用)
    • ignoreSecond と同じ定義
  • 関数適用演算子 $
    • f x なので必要ない
    • ($) f x
    • recipe $ 3 + 5 カッコが取れる
  • 部分適用
    • bmi h w = w / (h / 100) ^ 2
    • bmiTaro' = bmi 170 2つある引数のうちひとつだけを与えた
    • (125 /) 第一引数に対する部分適用
    • (/ 5) 第二引数に対する部分適用
  • 関数合成 : (* 2) . (+ 3)
    • 関数は後ろから適用される
  • 繰り返し
    • 再帰よりリストは簡単
    • sumN n = sum [1 .. n]
    • 3の倍数以外の総和 filter ((/= 0) . (`mod` 3))
    • 5で割ったあまりの総和 map (`mod` 5)
  • モンテカルロ法の例
    • 正方形内の点を列挙、円に入るもののみをろ過、残った点を数える
  • 無限リスト take 10 [1 ..]
    • 必要になるまで評価しないので、無限時間はかからない
    • 処理と終了条件を分離できる
  • リストへの追加 5 : [3, 2, 9]
  • 空リスト []
  • パターンマッチ headTail (x : xs)
  • 2段のリストを1段に concat [[1,2,3], [4,5], [6,7]]
  • すべての組み合わせで concatMap
    • (`concatMap` [1, 2, 3]) $ \x -> (`concatMap` [4, 5, 6]) $ \y -> (`concatMap` [7, 8]) $ \z -> [x * y * z]
  • 糖衣構文 リスト内包表記 [x * y * z | x <- [1,2,3], y <- [4, 5, 6], z <- [7, 8]]

時間がないので再帰は飛ばすことに。「近現代史は教科書読んでおいてね、と同じ」。

  • Haskell以外の言語では、式の評価と入出力が分けられている
    • 3 + 5 を評価しても、誰かの銀行口座が0円になることはない
  • 評価と実行は分かれている (が、対話環境が隠している)
    • 対話環境は、評価結果が入出力をする「機械」だったときに実行する
  • putStrLn は文字列を引数として取り、それを表示する機械を返す
  • getLine IO String 機械が出した結果を対話環境が表示
  • 値を機械から機械へ渡す >>=
  • getLine >>= \x -> で改行をして書くとわかりやすい
  • 糖衣構文 do 記法
    • >>=>> を隠している
  • 実行可能ファイルを作る stack ghc -- *.hs
    • TAB使いなので -fno-warn-tabs
  • 標準入力からの入力を変換するプログラムの書き方
    • interact
    • 3回 love というと I love you しか返ってこなくなる例

ここからは各自でハンズオン。

去年の9月にconduitの演算子が変わっていた

conduitといえば $$$= =$= のような演算子を使い分けなければいけなくて 面倒だなものだと思っていた のだが、去年の9月から .| だけを使えばいいように変わっていた。これは直感的で使いやすい。

module Main (main) where
import Data.Char (ord, chr, toUpper)
import Data.Conduit (Conduit, runConduit, (.|))
import qualified Data.Conduit.Binary as B
import qualified Data.Conduit.List as L
import qualified Data.ByteString as BS
import Data.Word (Word8)
import System.IO (stdin, stdout)

main :: IO ()
main = runConduit $ do
  B.sourceHandle stdin
    .| B.takeWhile (\c -> c /= word8 '.')
    .| filterUc
    .| B.sinkHandle stdout
  return ()
  where
    word8 = fromIntegral . ord

filterUc :: Monad m => Conduit BS.ByteString m BS.ByteString
filterUc = L.map ucString
  where
    uc :: Word8 -> Word8
    uc = fromIntegral . ord . toUpper . chr . fromIntegral
    ucString :: BS.ByteString -> BS.ByteString
    ucString = BS.map uc

演算子との対応関係は README にきちんと書いてある。

自由モナドの定義であるところの Control.Monad.Free.Church.foldF

圏論勉強会の資料 によれば、 X と自由な構成 FXについて、 f :: X \to |Y| を与えると \overline{f} :: FX \to Y が得られるとある。

自由モナドの文脈でこれを考えると、関手 X からモナド Y (の構造を忘れて関手と思ったもの)への自然変換を定義すれば、自由モナド FX からモナド Y への自然変換(正確にはモナドモーフィズム)が得られるという意味となる。

free パッケージにこの対応関係に相当するものは入ってないのかなと探してみたら、 Control.Monad.Free.Church というモジュールで定義されていた。

foldF :: Monad m => (forall x. f x -> m x) -> F f a -> m a

The very definition of a free monad is that given a natural transformation you get a monad homomorphism.

https://www.stackage.org/haddock/lts-8.19/free-4.12.4/Control-Monad-Free-Church.html

あまり深く追ってないけど、このモジュールで定義されているのは内部表現が違う(チャーチエンコーディングされた)自由モナドらしい。後、モナド変換子版 FreeT ではだめでモナド Free じゃないと定義できないということもありそう。

これを使うと、 FunctorMonad としてどう解釈するかを定義するだけで、自由モナドから任意のモナドへの変換が得られる。

{-# LANGUAGE TypeApplications #-}
module Main (main) where
import qualified Control.Monad.Free.Church as F

data HelloProgram a = HPGetLine (String -> a) | HPPrint !String a

instance Functor HelloProgram where
  fmap f (HPGetLine g) = HPGetLine (f . g)
  fmap f (HPPrint s x) = HPPrint s (f x)

helloApp :: F.F HelloProgram ()
helloApp = do
  line <- hellGetLine
  hellPrint line
  where
    liftF' = F.liftF @HelloProgram @(F.F HelloProgram)
    hellGetLine = liftF' $ HPGetLine id
    hellPrint s = liftF' $ HPPrint s ()

toIO :: HelloProgram a -> IO a
toIO (HPGetLine f) = f <$> getLine
toIO (HPPrint ss x) = putStrLn ss >> return x

main :: IO ()
main = F.foldF toIO helloApp

よくある自由モナドの使い方では、変換規則は自由モナド F.F HelloProgram に対して直接用意するが、このコード例では関手 HelloProgram の自然変換のみを変換規則として定義している。この方法では自由モナドの構造が使えないので、関手側に Done のようなデータを用意してそこで処理を打ち切る、といったような解釈の仕方を定義することはできない。そのようなことが必要であれば、 MaybeT IO モナドなど、それ相応の機能を持つモナドへ変換する必要がある。

IOモナドで使うときだけログを吐く関数を定義する

純粋な関数として定義できるんだけど内部でやってることが複雑な場合、何が起きてるかわからないと心配だからとログを吐く機能をつけると、その時点でそいつは IO アクションになってしまう。ログを吐くという副作用を持つのだから IO になるのは当たり前でそれを避けるべきではないのだけど、ログを吐かなくていいいシチュエーションでは、その計算を純粋な関数として使えたほうが理想的ではある。

そんなことを Identity型クラス 使えば簡単にできるんじゃねと思いついたんだけど、 monad-loggerそもそも機能が提供されてた。

runLoggingTrunNoLoggingTモナドclass MonadLogger が持つロギング用のアクションを追加できるのだけど、前者はモナドclass MonadIO のとき、後者は任意の class Monad について使えるようインスタンスが定義されている。

以下の例で addM は足し算するだけのアクションだが、引数をロギングするようになっている。 addaddMIdentity モナドを代入してそれを純粋な関数にしたもの1

{-# OPTIONS_GHC -Wall #-}
{-# LANGUAGE OverloadedStrings #-}
import Control.Monad.Logger
import Criterion
import Criterion.Main
import Data.Monoid ((<>))
import qualified Data.Text as TX
import Data.Functor.Identity

showT :: Show a => a -> TX.Text
showT = TX.pack . show

addM :: (MonadLogger m, Num a, Show a) => a -> a -> m a
addM x y = do
  logDebugN $ "x=" <> showT x <> ", y=" <> showT y
  return $ x + y

add :: (Num a, Show a) => a -> a -> a
add x y = runIdentity . runNoLoggingT $ addM x y

main :: IO ()
main = do
  runStdoutLoggingT $ do
    n <- addM 1 2
    logDebugN $ showT (n :: Int)
  
  defaultMain
    [ bgroup "add" 
      [ bench "add" $ whnf (add 1) (2 :: Int)
      , bench "(+)" $ whnf (1 +) (2 :: Int)
      ]
    ]

素の足し算と比べるとオーバヘッドはある。

$ stack ghc -- -o pure-logger pure-logger.hs
[1 of 1] Compiling Main             ( pure-logger.hs, pure-logger.o )
Linking pure-logger ...
$ ./pure-logger
[Debug] x=1, y=2
[Debug] 3
benchmarking add/add
time                 87.08 ns   (83.77 ns .. 91.27 ns)
                     0.988 R²   (0.975 R² .. 0.999 R²)
mean                 84.59 ns   (82.95 ns .. 87.74 ns)
std dev              7.355 ns   (4.199 ns .. 12.95 ns)
variance introduced by outliers: 88% (severely inflated)

benchmarking add/(+)
time                 22.31 ns   (20.54 ns .. 23.96 ns)
                     0.970 R²   (0.964 R² .. 0.980 R²)
mean                 21.62 ns   (20.51 ns .. 22.97 ns)
std dev              3.736 ns   (3.185 ns .. 4.343 ns)
variance introduced by outliers: 97% (severely inflated)

が、 -Oコンパイルしたところ、オーバヘッドはきれいに消え去った。やはり最適化がきちんと効くとGHCは強い。

$ stack ghc -- -O -o pure-logger pure-logger.hs
[1 of 1] Compiling Main             ( pure-logger.hs, pure-logger.o )
Linking pure-logger ...
$ ./pure-logger
[Debug] x=1, y=2
[Debug] 3
benchmarking add/add
time                 5.809 ns   (5.663 ns .. 5.981 ns)
                     0.997 R²   (0.995 R² .. 0.999 R²)
mean                 5.728 ns   (5.662 ns .. 5.835 ns)
std dev              277.3 ps   (193.9 ps .. 404.2 ps)
variance introduced by outliers: 74% (severely inflated)

benchmarking add/(+)
time                 6.036 ns   (5.986 ns .. 6.096 ns)
                     0.999 R²   (0.998 R² .. 0.999 R²)
mean                 6.163 ns   (6.090 ns .. 6.334 ns)
std dev              367.0 ps   (221.5 ps .. 643.8 ps)
variance introduced by outliers: 81% (severely inflated)

これでログを吐きつつ計算するI/Oアクションと純粋な関数が、一切のオーバヘッドなしに同時に手に入った。


  1. Show 制約がはずれないのは悲しいが、ロギングするための項が入ってしまっているので仕方ないだろう。あるいは、この項が型ごと差し替えられるようになれば可能なのかな?

C言語の多次元配列の型はどう読むのか

int a[2][3] って、「整数2個の配列( int a[2] )を 3個の配列( [3] )にした、と読めるけどどうなのか1http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1124.pdf と比べて確認。

116ページのArray declaratorsの節によると、 T D[n] という形式のときは「… array of T」となると書かれている。ここで T は int で D は a[2]n3 なので、「整数3個の配列… 」となる。 … の部分は仕様上で “derived-declarator-type-list” と呼ばれている部分で、 T D がどういう型かによって定められる。 int a[2] は「2-array of int」だけど、 … は最後の int は除くことになっているので、まとめると「2-array of 3-array of int」であって、「整数3個の配列の2個の配列」という意味になる。

なんとも歯切れの悪い定義だけど、 int の何かを定義しているってことでこうなっているのだろうか。考え方としては、 「int D[3]」 みたいな定義があるともうこれは整数3個からなる配列であり、Dをどう書くかでそいつをどう調理するかが決まるってだけ。 a[2] と書けばそれを 2 個の配列にするし、 *a って書けばそいつへのポインタになる。


  1. 添字でのアクセスを考えると違うだろってのはすぐ想像つくけど。