Haskell で org-babel してみた

周りのみんなと Haskell の勉強をしています. めいめいが作った練習問題の解答集を org-mode で書きたくて, Haskell でも org-babel 使ってみました. 何だか yak shaving はじまりの予感です.

例えば,こんなプログラム:

let sumPositive = sum . filter (>0)
sumPositive [1, 2, -1, 5]

org-mode では,こんな感じに書きます:

#+BEGIN_SRC haskell
  let sumPositive = sum . filter (>0)
  sumPositive [1, 2, -1, 5]
#+END_SRC

コード上で C-cC-c で eval されて以下が自動挿入されます. 裏で GHCi が動いているようです.

#+RESULTS:
: 8

ただ, let を毎回入力したくない. あと, IO を書こうとすると,GHCi じゃなく, runhaskell を使いたくなります(?). ですが,ob-haskell.el は,そこまで考慮していないようです (以前MLで議論はあったようですが).

Quickhack

本来なら ob-C.el とかを見ながら頑張るのが筋ですが, 締切前にそんな悠長な事はできないので,

  1. GHCi のフリをしつつ,runhaskell を動かす wrapper runhaskell-ob を用意する.
  2. org-babel の block パラメータに :results output があった場合は, GHCi ではなく, runhaskell-ob を使うように defadvice する.

で切り抜けることにしました.

runhaskell-ob:

#!/usr/bin/env ruby

delimiter = '"org-babel-haskell-eoe"'
prompt = "hello> "

def execute_haskell(body)
  io = IO.popen("runhaskell", "r+")
  io.write(body)
  io.close_write
  result = io.read
  io.close
  return result
end

print prompt

body = ''

while line = gets
  if line =~  /#{delimiter}/
    result = execute_haskell(body)
    result.split(/\n/).each do |str|
      print "#{str}\n"
      print "#{prompt} \n"
    end
    print "#{prompt} \n"
    print "#{delimiter}\n"
    print prompt
    body = ''
  else
    body += line
  end
end

.emacs に追加:

;; use runhaskell when :results is output
(defadvice org-babel-haskell-initiate-session
  (around org-babel-haskell-initiate-session-advice)
  (let* ((buff (get-buffer "*haskell*"))
         (proc (if buff (get-buffer-process buff)))
         (type (cdr (assoc :result-type params)))
         (haskell-program-name
          (if (equal type 'output) "runhaskell-ob" "ghci")))
    (if proc (kill-process proc))
    (sit-for 0)
    (if buff (kill-buffer buff))
    ad-do-it))

(ad-activate 'org-babel-haskell-initiate-session)

これで,以下のように :results output を付けて書くと,

#+BEGIN_SRC haskell :results output
  sumPositive = sum . filter (>0)
  main = do
    print $ sumPositive [1, 2, -1, 5]
#+END_SRC

let がなくても,main があっても一応動作します.

sumPositive = sum . filter (>0)
main = do
  print $ sumPositive [1, 2, -1, 5]

結果:

8

どうやら,締切に間に合いそうです.