# n8n ↔︎ Haskell ## n8n → Haskell n8n上で構築されたワークフローを、HaskellのコードやDSL(ドメイン固有言語)として出力・再構築可能にする。 ### 例:n8n → Haskell DSLへの変換イメージ 1. フォームからリクエストを受け取る(Webhook) 2. データをバリデーション(Code Node) 3. APIを呼び出してDBに保存(HTTP Node) 4. Slack通知(Slack Node) このn8nワークフローを、Haskell DSLでこう表現できるようにする: ```haskell workflow "フォーム受付フロー" $ do input <- receiveWebhook "contact-form" validated <- validate input _ <- callAPI "POST /orders" validated notifySlack "受注処理が完了しました" ``` ### 実装方法 1. n8nのワークフローファイル(JSON)をパース1. n8nのワークフローファイル(JSON)をパース ```haskell { "nodes": [ { "id": "1", "type": "webhook", ... }, { "id": "2", "type": "httpRequest", ... }, ... ] } ``` 2. Haskell DSLとして再構成 - JSONをパースして、各ノードをHaskellの抽象構文木(AST)として変換 - それをコード生成(Pretty Print) ## Haskell → n8n Haskell で業務ワークフローを抽象的な DSL(Free Monad)で記述し、それを n8n のワークフロー JSON に変換して自動化基盤として活用する。 **Free Monad** のユースケース: - 業務処理の**抽象的な定義**と**具体的な解釈(インタプリタ)**を分離したい - ワークフローを**記述(宣言)** → **解釈(実行 or 変換)**したい - ロジックの**合成性・再利用性**を高めたい ### 構成イメージ 1. DSL定義(Functor) ```haskell data WorkflowF next = ReceiveWebhook String (String -> next) | CallHttpAPI String String next | NotifySlack String next deriving Functor ``` 2. Free Monad化 ```haskell type Workflow = Free WorkflowF ``` 3. DSLによる業務フロー記述 ```haskell exampleFlow :: Workflow () exampleFlow = do input <- liftF $ ReceiveWebhook "order_created" id liftF $ CallHttpAPI "POST" "/save-order" () liftF $ NotifySlack ("Order received: " ++ input) () ``` 「何をするか」を定義しているだけで、「どうやって実行するかは定義していない。 ### インタプリタ(n8n JSONへの変換器) ```haskell interpretToN8n :: Workflow a -> [N8nNode] interpretToN8n = go 1 where go _ (Pure _) = [] go n (Free (ReceiveWebhook name next)) = N8nNode "Webhook" name n : go (n+1) (next "{{$json.body}}") go n (Free (CallHttpAPI method url next)) = N8nNode "HTTP Request" (method ++ " " ++ url) n : go (n+1) next go n (Free (NotifySlack msg next)) = N8nNode "Slack" msg n : go (n+1) next ``` N8nNode は自作のデータ型で、のちに Aeson でJSONに変換。 ### 出力例(JSON) ```json { "nodes": [ { "type": "webhook", "name": "order_created", "parameters": { ... } }, { "type": "httpRequest", "name": "POST /save-order", "parameters": { ... } }, { "type": "slack", "name": "Slack", "parameters": { "text": "Order received: {{$json.body}}" } } ], "connections": { ... } } ```