Permalink
Switch branches/tags
Nothing to show
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
669 lines (464 sloc) 22.5 KB

自己紹介

  • 名前
    • 藤原 惟
    • すかいゆき (@sky_y)
    • Yuki Fujiwara (本名)
  • 職業
    • フリープログラマ
    • 専門学校 非常勤講師

Pandocに関する活動


発表を始めます


このチュートリアルでやること(2回分の概要)

  • 第1回(今回) Pandocでドキュメントを変換しよう
    • Pandocをツールとして使う(入門編、第2回の基礎知識)
  • 第2回 HaskellでPandocを拡張してみよう (←いまここ)
    • Haskellのやさしい入門(を目指します)
    • 「日常的な実用言語」としてのHaskellを体験してもらいたい
    • Pandocのソースコードも少し読みます

Pandoc公式サイト


Pandocとは(第1回の短いおさらい)


Pandocとは

  • Pandoc - About pandoc
  • 文書変換ツール
    • あるフォーマットで書かれた文書を、別のフォーマットに変換するツール
  • Pandocの特徴は、対応フォーマットが非常に多いこと

Pandocの処理フロー



Pandoc実装の概要

  • 言語: Haskell
    • Pandoc的には、「厳密に型が定義されている」ことがありがたい
    • Haskellは構文解析器(パーサ)を作るのにすごく適している (Parsecなど)
  • モジュール構成
    • Reader: 入力文書を解析し、Haskell上の中間文書に変換する
    • Writer: 中間文書を受け取り、出力フォーマットに変換する

Pandocの処理フロー


Pandocをインストールする

  • ターミナル(Mac/Linux)またはコマンドプロンプト(Windows)
  • パッケージを直接落としてインストール
  • パッケージマネージャでインストール
    • Mac(Homebrew): $ brew install pandoc
    • Windows(Chocolatey): > cinst pandoc
    • Linux(Debian): $ sudo apt-get install pandoc

wkhtmltopdfのインストール

  • PDF出力のために必要(おすすめ)
  • パッケージを直接落としてインストール
    1. ここからパッケージをダウンロード
      • Windowsは未検証ですが、MinGWの方を試してみてください
    2. インストール
  • パッケージマネージャでインストール
    • Mac: $ brew cask install wkhtmltopdf
      • Caskの方なので注意

Pandocで遊ぶ

$ pandoc --version
$ echo "**Hello**" | pandoc -f markdown -t html
<p><strong>Hello</strong></p>
$ echo "**Hello**" | pandoc -f markdown -t html5 -o hello.pdf
(PDFが生成される)

PandocのFilterとは


PandocのFilterとは

  • ReaderとWriterの間で処理を行う外部プログラム
    • Unix/コマンドプロンプトのパイプを使って書ける
    • JSON形式の文書(JSON AST)でやり取りする
      • Haskell以外の言語でも実装可能

Pandocの処理フロー(Filter入り)


Filter: コマンドでいえば

pandoc --filter ./hoge.py -t latex

pandoc -t json | ./hoge.py latex | pandoc -f json -t latex

は等価


Filterを動かしてみよう

  • 公式サンプルから「caps.py」を動かしてみる。
    • Pythonのpipを使用。環境がない人はデモだけ見て下さい
$ sudo pip install pandocfilters
$ curl -O https://raw.githubusercontent.com/jgm/pandocfilters/master/examples/caps.py
$ echo 'abc' | pandoc --filter ./caps.py -t html
<p>ABC</p>

caps.py の実装

from pandocfilters import toJSONFilter, Str

def caps(key, value, format, meta):
    if key == 'Str':
        return Str(value.upper())

if __name__ == "__main__":
    toJSONFilter(caps)

Filterを実装する手順

  • PandocのJSON ASTを読み込めるライブラリを使う
  • 次の仕様を満たす関数を実装し、toJSONFilterの引数に与える
    • 引数を使い、変換前のJSON ASTを受け取る
    • JSON ASTを変換(要素を追加・変更・削除)する
    • 変換後のJSON ASTを返す
  • 補足: toJSONFilterが、標準入力・標準出力を通じたJSON ASTのやりとりを代理してくれる

PandocのASTを知る


PandocのASTを知る

  • 厳密な仕様はここに載っています

ASTは、厳密には2種類ある

  • JSON AST
    • ただのJSONなので、どの言語でも利用できる
    • 冗長、読むのがしんどい
  • Pandoc Native形式
    • Haskellのデータ型Pandocとして定義される
    • パターンマッチングが使える(うれしい)
    • 簡潔で読みやすい

ASTの比較

HTML

<p><strong>test</strong></p>

JSON AST

{"blocks":[{"t":"Para","c":[{"t":"Strong","c":[{"t":"Str","c":"test"}]}]}],"pandoc-api-version":[1,17,0,4],"meta":{}}

Pandoc Native

[Para [Strong [Str "test"]]]

これからやりたいこと

[Para [Strong [Str "test"]]]
  • このようなASTを変換するような関数を、Haskellで書きたい

Haskellの概要


Haskellに対するイメージ

  • こわい
  • 難しい
  • 圏論わからん

実際のHaskell(藤原の主観)

  • こわくないよ!
  • (さすがにちょっと難しい)
    • 書けばちょっと分かる
    • 「アホなままコード書いたらコンパイルが通らなくなる」安心感
  • 圏論分からないけど、Haskell書いてる

注意

  • 以下、藤原の偏見によるHaskell紹介です
  • 真面目にHaskellやっている方からは邪道に見えることをお許し下さい

Haskellは「関数型プログラミング養成ギブス」(持論)

  • Haskellは 純粋関数型言語 の代表
    • 関数が第一(第一級オブジェクト)
    • 副作用がない、参照透明性が常に保たれる

関数が第一(第一級オブジェクト)

関数そのものを

  • 変数に格納可能
  • データ構造に格納可能
  • 名前が無くても存在できる(無名関数・ラムダ式)
  • 関数の引数として渡すことができる(高階関数)
  • 関数の返り値として返すことができる

関数の例

  • GHCi(インタラクティブシェル)で試してみよう
-- 関数doubleを定義
let double x = x * 2  

-- 関数mapの第1引数にdoubleを指定
Prelude> map double [1,2,3,4,5]  
[2,4,6,8,10]

副作用と参照透明性

  • 副作用の例
    • 出力 (C: printf("Hello, world!"))
    • 状態 (C: i++)
      • 変数を読むタイミングによって、その中身が変わりうる
    • これらは 手続き (手順) として記述される
  • 参照透明性(関数について)
    • 関数をいつ読んでも、必ず同じ結果が返ってくる
    • 関数の外に影響を与えない

→ 関数の振る舞いに対して、疑心暗鬼にならなくていい!!


純粋関数型言語のデメリット

  • 他の言語で普通に書ける「状態をもつ変数」「手続き」を含む関数が「ふつう」に書けない
    • Hello Worldすらできない!?
    • 乱数すら使えない!?

そこで、モナドですよ


モナドの役割

  • Haskellでは、あらゆる「状態を持つ変数」「手続き」などを「モナド」の中に閉じ込めた形で書くことができる
    • これにより「実用性」と「関数型言語の良さ」を両立できる
    • 圏論?知らない子ですね

持論: モナドは乗り物

ドラクエ2の船


モナドの使われ方

  • 純粋関数だけの世界がデフォルト
  • 文字を出力したい(IOを使いたい)→IOモナドに「乗る」
    • 実際にはJavaなどと同じように、main関数という特別なIOモナドに乗った状態からスタートする
  • 他のモナド
    • リスト、例外処理、状態、……、何もしない(!?)

余談: エディタについて


エディタについて

  • Markdown: プレビューができるエディタがよい
  • Haskell: 選択肢は比較的多い
  • どちらも扱うなら、Atomがおすすめ

Atomのおすすめパッケージ(拡張機能)

apm installコマンドでインストール可

  • Markdown系
    • markdown-preview-enhanced
      • 手軽ですごいプレビュー+エクスポート
    • markdown-writer: 入力補助
  • Haskell系
    • autocomplete-haskell
    • language-haskell
  • 個人的好み
    • vim-mode-plus
    • vim-mode-plus-ex-mode

StackでHaskellのHelloWorldを書こう


Stackとは

  • 2017年現在、標準的に使われるビルドツール
  • パッケージ間の依存関係を解消し、プロジェクトごとにコンパイラ(GHC)を設定できる
    • (Stackができる前は、依存関係の地獄(dependency hell)に簡単に陥る地獄があった……)
  • 要するに、今からHaskellを始めるなら、これ一択

Stackのインストール


StackでHello World

  • 今回は、最もシンプルなsimpleテンプレートを使用
    • テンプレートによってフォルダ構成が変わるので注意
$ stack new hello simple
$ cd hello
$ stack setup
$ stack build
$ stack exec hello

ソースを見てみよう: src/Main.hs

module Main where

main :: IO ()  -- 関数の型宣言
main = do      -- 関数の定義
  putStrLn "hello world"
  • main関数から実行し始める
  • main関数はIO ()型 (実はこれがIOモナド)
  • コメントは--で始める

Pandoc FilterをHaskellで書こう


Stackのカスタムテンプレートで準備

$ stack new my-filter https://raw.githubusercontent.com/sky-y/stack-pandoc-filter/master/pandoc-filter.hsfiles
$ cd my-filter
$ stack setup
$ stack build
(10分ぐらいかかるかも……)
$ stack install --local-bin-path .

サンプルMarkdownをHaskellで表現

$ pandoc pandoc-markdown-example.txt -f markdown -t native
  • これがHaskell内部における形式(Pandoc型のデータ)

Filterを試してみる

$ pandoc pandoc-markdown-example.txt -f markdown --filter my-filter -o output.md
  • h2, h3見出しが*このように*変わっているはず

ソースを見てみよう: src/Main.hs

module Main where

import Text.Pandoc.JSON

main :: IO ()
main = toJSONFilter behead
  where behead (Header n _ xs) | n >= 2 = Para [Emph xs]
        behead x = x  -- デフォルト(何もしない)

ソースを見てみよう: src/Main.hs

main = toJSONFilter behead
  where behead (Header n _ xs) | n >= 2 = Para [Emph xs]
        behead x = x  -- デフォルト(何もしない)
  • いじるのはbehead関数だけ
  • behead関数は、パターンにマッチする部分について引数を取る
    • その部分に対して変更したものを返す
  • 今回の場合、「見出しレベルが2以上(h2以上)」を、「強調(斜体)で返す」
    • HTMLでいえば <h2>hoge</h2><p><em>hoge</em></p>

カスタマイズしてみよう

main = toJSONFilter behead
  where behead (Header n _ xs) | n >= 2 = Para [Strong xs]
        behead x = x  -- デフォルト(何もしない)
  • Emph xsStrong xs
    • 意味: 斜体 *斜体*強調 **強調**
$ stack build
$ stack install --local-bin-path .
$ pandoc pandoc-markdown-example.txt -f markdown --filter my-filter -o output.md

Filterを自力で書くには

  • pandocコマンドで、入力文書をPandoc Native形式(-t native)で出してみる
  • Text.Pandoc.Definition を読んで、構造を理解する
  • 目的の構造を下書きしてみる
  • Haskellで書く(ほぼそのまま書けるはず)

実用例: はてなブログMarkdown用フィルタ

  • 書きました: sky-y/pandoc-hateblo: はてなブログ用Pandocフィルタ
  • はてなブログの仕様に合わせるフィルタ
    • 見出しをh3始まりにする
    • h3h5以外の見出しを、ただのテキストにする
  • 個人的には、WorkFlowy(アウトライナー)→OPMLでエクスポートしてから、Pandocに通している
  • 時間があればデモ

pandoc-hateblo/Lib.hs at master · sky-y/pandoc-hateblo

hateblo = toJSONFilter (behead <$> shiftHeader)
  where behead (Header n _ xs) | n >= 5 = Para xs
        behead x = x

        shiftHeader (Header n attr xs) | n >= 2 = Header (n + 1) attr xs
        shiftHeader x = x

今後の学習のための情報源


今後の学習のための情報源1


今後の学習のための情報源2


今後の学習のための情報源3


質問・作業・もくもく会

  • Slack(#field)に書いて下さい
  • 終了後: TwitterでもOKで