yunomuのブログ

趣味のこと

simple-config v1.0.0

一人で開発していても共通ライブラリというものは増えていくものでして、でも大体はプロジェクト内だけで収まってしまうようなものだったり、外に出すための一般化がとても面倒臭かったり、ファイル1個とか関数いくつかだからコピペでいいかってなったりして、なかなかやる気が出ないんですけど、

でもたまになんか非常に一般的な感じのものが出来上がってしまうことがあって、「あーこれそのまんま他のプロジェクトに持っていって使いたいなぁ」と思うんですけど、そうなると逆にパッケージとして分離してまとめておかないとちょっと面倒で、急速にパッケージ化欲求が高まってきて、仕方ねぇかってなるのです。

先日のconfig parserの件、簡易設定ファイルパーサジェネレータ - yunomuのブログ
を、使えるようにしてリリースしました。

HackageDB: simple-config
http://hackage.haskell.org/package/simple-config

JavaならjarコピペでいいしRubyならgemコピペでいいんだけど、Haskellだとcabalでローカルから入れる方法とかよくわかんないし、というか今はコードをgithubに上げてるからそれがビルドできないとかアレだよねぇということで、はじめてhackage登録もやってみました。

Hackage登録のやり方

余談ですが、
HackageDB: checking and uploading packages
ここに書いてある通りです。

ただし、アップロードするためにはアカウントが必要で、アカウントを取得するためにはRoss Patersonさんにメールを送る必要があります。
というのがここに書いてある。
HackageDB: User accounts

アカウント名はCamelCaseで、以下のリストを見て被ってないやつにすると良いらしいです。
http://hackage.haskell.org/cgi-bin/hackage-scripts/list-users

で、「アカウントください。usernameはYusukeNomuraがいいです」というのを、多分英語で書くといいと思います。私は10分くらいで返信が返ってきました。が、その後半日経ってもリストには名前が出ませんでした。でもアップロードはできたので問題は無いんだと思います。

あとは、Executableなら別にいいんですが、moduleをexportする場合は一応haddock書いておいた方がいいんじゃないでしょうか。
baseのバージョンは書かないと怒られます。
check用のフォームを用意してくれてるので、色々怒られてちゃんと整えてからアップロードするといいと思います。

それと注意点として、同じバージョンは2度アップロードできないので、バージョン番号は慎重に付けた方がいいです。あとリリース時に変なバージョンをアップロードしてしまわないように注意するのは言わずもがな、というか、本当に注意したほうがいい。

中身のこと

最終的にどんな感じになったかというのは一応haddockに書いたんですけど、こっちにも書いておくと、

{-# LANGUAGE TemplateHaskell, QuasiQuotes #-}
import Text.Config

mkConfig "configParser" [config|
TestConfig
    uri  URI
    text String
    list [String]
|]

という感じに書くと

data TestConfig = TestConfig
    { uri  :: String
    , text :: String
    , list :: [String]
    }

configParser :: Parser TestConfig
configParser =

といったようなものが生成される。

これを使うと

uri: http://example.com/
text: ume
list: coffee, chocolate, javakula

みたいなのがパースできる。Parserはparsec3のやつです。

QuasiQuote

前回の記事のやつに加えて、今回は設定をQuasiQuoteで書けるようにしてみました。persisitentを真似したのです。persistent使ったことないけど。

QuasiQuoteを作るのは多少TemplateHaskellの心得があれば割と簡単で、

data QuasiQuoter = QuasiQuoter { quoteExp  :: String -> Q Exp,
                                 quotePat  :: String -> Q Pat,
                                 quoteType :: String -> Q Type,
                                 quoteDec  :: String -> Q [Dec] }

この型を返す関数を定義すれば良い。

今回はクォート部分が式になってくれればよいので、っていうかこれ式以外を接合することってあんましないんじゃないかな。まあそれはともかく、quoteExpだけを実装すれば良い。
Stringを取って式を返す関数を登録してあげればいいということで、今回はConfTmpを返してほしいので、ちょうど手元にあったConfTmpを返すパーサconfTmpParserを使ってこんなかんじで実装してみました。

config :: QuasiQuoter
config = QuasiQuoter
    { quoteExp = \str -> [|confTmpParser str|]
    , quotePat = undefined
    , quoteType = undefined
    , quoteDec = undefined
    }

confTmpParser :: String -> ConfTmp
confTmpParser str = ...

これで[config| … |]みたいなのを書くとConfTmpがその部分に読み込まれるという形になります。かっこいい。

まとめ

今回作ったのはこれです。
https://github.com/yunomu/simple-config/tree/v1.0.0

簡易設定ファイルパーサジェネレータ

なまえがややこしい。

設定ファイルって面倒くさいからあんまし書きたくなくて、だいたいはコマンドライン引数で済ませる方向で生きてきました。
が、それはそれで面倒だったりして、いやまあ結構便利なライブラリがそれぞれの言語や環境ごとにあったりするのでいいといやいいんだけど、今回はそれは別として、
やっぱり設定ファイルを書かなきゃいけない……というか欲しい場面というのはあるのです。

で、私がよく使う設定ファイルの形式というのがまずあります。
こういうの。

title: wakaruwa
rssuri: http://localhost/rss.xml
keywords: kaede, anzu, kirari

コロン区切りのkey-value形式で、値の部分は文字列だったり、URIだったり、コンマ区切りのリストとかも書けるといいなぁみたいな。

RubyだったらちゃんとYAMLにして{:title => "wakaruwa", :rssuri => "http://localhost/rss.xml", :keywords => ["kaede", "anzu", "kirari"]}というHashを作ってあげればいい。これは容易い。容易い、けどURIのvalidationとか、リストのYAML表現とか、数値は文字列じゃなくてちゃんと数値型で欲しいとか、そういう細かいことを言い出すと面倒くさい。面倒くさいけどほしいものはほしい。

今回はHaskellで書くわけだし、そうなると上記のコードはData.Mapになりそうなもんだけど、Valueの型がバラバラだとまた面倒くさい。こういうの定義する?

data ConfigValue = ConfString String | ConfURI URI | ConfList ConfigValue

これでもまあいけるんだけど、keyの名前を文字列で渡すというのはちょっと、実行時エラーが出そうでいかにもHaskell的ではない。

やっぱりrecordを使いたいですよね。
つまりこういうのを自動生成したい。

data Config = Config {title :: String, rssuri :: URI, keywords :: [String]}

さらにこのレコードを構築する設定ファイルのパーサも自動生成したい。
いや、パーサは何度も書き直して、どうにか楽に書けるようにならないかと模索していたんですが、どうにもならなくて、どうしても上記の例で言うところの"title"とか"rssuri"とかいう文字列がソースコード中に複数回出てくるようになってしまって、エントリを一つ追加するのも本当に苦痛で仕方なくて。

ただ、レコードの名前とかフィールド名とか型とかはコンパイル時に決まるものだし、レコードって不完全な状態というか一部の値が入ってない状態とかはありえないし、とかそういう制約があって、型を含めた柔軟な自動生成とか初期化というのは非常に難易度が高い。
んですが、Template Haskellを使えばできそうな気がする。なんせHaskell構文木構築をHaskellで書けるわけだから、そりゃ文法的にできることは材料さえ揃っていれば全部できるんだろう。そこから先、コンパイルできるかどうかとかは知らないけども。

というわけで材料集め。
だいたいこのくらいの情報があれば自動生成できる気がする。

Config
    title String
    rssuri URI
    keywords [String]

レコード名があって、key名とvalueの型がある。

この情報をConfTmpという型にまとめる。

type ConfTmp = (String, [ConfLine])
type ConfLine = (String, ConfType)
data ConfType = ConfString | ConfURI | ConfList ConfType

レコードの名前があって、それにエントリの名前と型情報の組のリストがくっついてる。

これを使ってレコード型の定義を作る関数がmkRecord。

mkRecord :: Name -> [ConfLine] -> DecQ
mkRecord recName confLines =
    dataD (cxt []) recName [] [rec] [''Show]
  where
    rec = recC recName $ map confVSType confLines

    confVSType :: ConfLine -> VarStrictTypeQ
    confVSType (name, ctype) = confVSType' name $ confTypeQ ctype

    confVSType' :: String -> TypeQ -> VarStrictTypeQ
    confVSType' name typeq =
        varStrictType (mkName name) $ strictType notStrict typeq

    confTypeQ :: ConfType -> TypeQ
    confTypeQ ConfString = [t|String|]
    confTypeQ ConfURI = [t|String|]
    confTypeQ (ConfList ctype) = [t|[$(confTypeQ ctype)]|]

まあこの辺はTemplate Haskell(TH)を知ると結構自然に書けるようになります。

この辺はどうでもいいんですが、そんなこんなでできた設定ファイルパーサジェネレータがこちらになります。
呼び出し部分: nicodicbot/src/Config.hs at blog20120902 · yunomu/nicodicbot · GitHub
本体: nicodicbot/src/Config at blog20120902 · yunomu/nicodicbot · GitHub

やってることは以下のとおり。

  1. 必要なパーサの部品を作る(Config/Lib.hs)
  2. 設定のvalueの型ごとのパーサを作る(cv_string, cv_uri, cv_list,…)
  3. 以下TH
    1. recordを生成する
    2. recordをData.Defaultのインスタンスにする(後のStateのため)
    3. recordのvalue毎のパーサを生成する
    4. 今まで作ってきたものを合わせてrecord全体のパーサを生成する

まあだいたいこのまんまですが、3-3,3-4あたりがちょっと面倒くさかった。

何が面倒くさいって、Haskellでは値の蓄積とか逐次処理とかがそれなりに機能として分離されてるからrecordを構築するのが面倒くさくて、結局デフォルト値のrecordを用意して、そのrecordを状態として持つStateT Parserを使ってパースした値を蓄積する処理を繰り返すという形でなんとか頑張った。というかこれ自体は別に難しくないんですが、これTHを書きながら構築していったので、TH上で仕様変更するのが難しかった。
つまり要らんところで引っかかっていたということです。

THを書く時は必ず生成対象のHaskellコードを用意しておいて、それに向かってそのコードを生成するTHを書いていった方がいいです。あたりまえですけど。テストもあるとなお良い。TDDにまで落とし込めると更に良い。THのまま仕様変更とかは案外うまくいくけども余計に時間がかかるからオススメできない。
私のコードの中に「こういうのを作る」ってコメントがたくさんあるのはそういう感じです。

あと最初のうちはTHに慣れてなかったもんだからあんまりクォートを使ってなかったんですけど、THは基本的にクォートで書いて、一部分だけクォートの中で構文木を接合する形で書いたほうが圧倒的に楽だしわかりやすいですね。
このあたりは、なんかいろんな定義や式の構文木を見ていると、だんだんどこが部分木でどういうものが接合できるのかというのがわかるようになってきます。っていうかghciで確認しながらやれば一発です。
Haskellの構文もはっきりと把握できるようになるのでこういう作業は楽しい。

あと、やっぱり結構クリティカルな部分を触っているせいか、GHCがパニクったりBug reportを要求してきたりとかいうことがよくありました。
これなんかは現在進行形で発生しているんですが
#7092 (Spurious shadowing warnings for names generated with newName) – GHC

Linux版GHCだと、newNameで作ったのに名前が被ってるって文句言われるというのはバグっぽいらしいです。
これが一番目立つんですけど、他にも結構ありました。これ以外は再現条件はわかりませんが。

まとめ

HTMLパーサ

HTMLパーサに今まではTagSoupを使っていたんですけど、IOまわりをConduitで書き直してるついでになんか別のConduit対応パーサを使ってみようかなと思ってちょっと調べてみました。

とりあえず一番簡単というか最初に目についたのはhtml-conduitのsinkDocを使う方法。

import Data.Conduit
import qualified Data.Conduit.Binary as CB
import qualified Text.HTML.DOM as DOM

main :: IO ()
main = do
    doc <- runResourceT $ CB.sourceFile "test.html" $$ DOM.sinkDoc
    print doc

Text.XML.Documentが返ってくるので、あとは頑張りましょう。これほとんどxml-conduitじゃないか。

それと、同じモジュールのeventConduitを使う方法。

import Data.Conduit
import qualified Data.Conduit.Binary as CB
import qualified Data.Conduit.List as CL
import qualified Text.HTML.DOM as DOM

main :: IO ()
main = do
    doc <- runResourceT $ CB.sourceFile "test.html" $= DOM.eventConduit $$ CL.consume
    print doc

これはConduitなので、なんか適当にconsumeしてもいいし、ちょっと使い勝手がいい気がする。

html-conduitを入れた時に入ったっぽいTagStreamでもだいたい似たようなことができるけど、こっちの方が使いやすそうな気がする。いや同じか。まあどっちでもいいかんじです。

import Data.Conduit
import qualified Data.Conduit.Binary as CB
import qualified Data.Conduit.List as CL
import Text.HTML.TagStream

main :: IO ()
main = do
    doc <- runResourceT $ CB.sourceFile "test.html" $= tokenStream $$ CL.consume
    print doc

あとは、Text.HTML.TagStream.Parserを使って自分でパーサを組み立てる手もある。

このあたりは、Attoparsec用のHTMLパーサ関数ライブラリのようで、Data.Conduit.Attoparsec.sinkParserとかと組み合わせて使うんじゃないでしょうか。たぶん。

sinkDoc以外のやつらが結構使えるんじゃないかと思ってるんですが、別に今回は実装したわけでもないし、評価したわけでもないし、あーでもtagstreamが一番プリミティブっぽいからこの場合は無難かもなぁと思ったくらいで、あんまし意味は無いです。評価順を考えると実はどっちも大して変わらないのかもしれない。結局みんなTagStream.Parserにいきつくっぽいし、eventConduitかtokenStreamあたりがちょうどいいんじゃないでしょうか。
それはそうと今更ですがConduit.Listってかなり便利ですね。

Wai+WarpのWebサーバを拡張する

頂いたラムを飲みながらお送りしております。

で、前の続きです。
前の:Wai + WarpでWebサーバを作る - yunomuのブログ

Waiのマニュアルを見てるとちょっと面白い型があって
Network.Wai

type Application = Request -> ResourceT IO Response
type Middleware = Application -> Application

Applicationは前回出てきたとおりで、今回は下のMiddleware型。
Applicationを取ってApplicationを返すということで、多分引数として受け取ったApplicationに機能追加したり入力や出力を加工したりするために使うんでしょう。マニュアルにもGZIP encodingに使うとか書いてあるし。
でも役割的にMiddlewareっていうよりこれWrapperだよねぇと思わなくもない。

ということで、今回は前回作った超適当なWebサーバに機能を追加して遊んでみようと思います。
基本は前回のこれ。

{-# LANGUAGE OverloadedStrings #-}

import Network.Wai
import Network.Wai.Handler.Warp
import Network.HTTP.Types
import Blaze.ByteString.Builder.Char.Utf8

server :: Application
server _ = return $ ResponseBuilder status200 [] $ fromString "hello"

main :: IO ()
main = do
    run 8080 server

これに新機能を追加する。入出力の加工の方がわかりやすいかもしれないんだけど、ここではとりあえずHTTPの新しいメソッドを定義してみます。

{-# LANGUAGE OverloadedStrings #-}

import Network.Wai
import Network.Wai.Handler.Warp
import Network.HTTP.Types
import Blaze.ByteString.Builder.Char.Utf8

server :: Application
server _ = return $ ResponseBuilder status200 [] $ fromString "hello"

middleware :: Middleware
middleware app request = do
    if requestMethod request == "MOGE"
      then return $ ResponseBuilder status200 [] $ fromString res
      else app request
  where
    res = show $ pathInfo request

main :: IO ()
main = run 8080 $ middleware server

MOGEメソッドを作った。
機能は、ようはECHOです。パスをそのまま返す。メソッド名もECHOにすればよかった……。

Applicationの時もそうだったけど、今回のMiddlewareもやっぱりなんか型がわかりづらくて、

middleware :: Middleware

というのはtypeを展開すると要するに

middleware :: Application -> Request -> ResourceT IO Response

という事になります。

やってることは見ての通り、メソッドがMOGEだったらechoして、そうじゃなかったらWAIに任せるという感じです。
動作確認は、curlではできないのでtelnetでやります。

% telnet localhost 8080
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
MOGE harukasanmoe HTTP/1.0

HTTP/1.0 200 OK
Server: Warp/1.3.0

["harukasanmoe"]Connection closed by foreign host.

つつがなく成功しているようですね。

ちなみにWarpはHTTPのバージョン指定をしないとちゃんと動かないみたいです。勝手に0.9だと解釈してくれたりはしない。律儀な奴だ。
けどバージョン番号の部分は"3.0"でも"aaa"でも”^D"でもいいみたいで、なんか適当に0.9だか1.0だかだと判断して返してくれます。ただ1.1にするとちゃんとKeepAliveが働く。なんとなくパーサ書くのを中途半端に面倒臭がった形跡が見え隠れする。HTTPの仕様をそんなに詳しく知らないのでなんとも言えないけども。
あ、MOGEメソッドもバージョン番号を1.1にするとちゃんと1.1相当に動きます。話のわかる奴だ。
ということで、これでWebDAVの実装も余裕ですね。いや全然余裕じゃないっていうか、Warpの半分を書き換えた上でそれ以上の機能を追加する羽目になるような気がしますけども。

あとは、Network.Wai.Testを使ったりなんたり、というのは面倒くさいのでコードの参照で。
exercises/wai-example at master · yunomu/exercises · GitHub
あくまでもWaiのテストであって、Warpのテストではない点は注意です。Wai的にオッケーなら未知の形式のメソッドでも普通に通っちゃいますので。

HaskellからCライブラリを呼ぶ(FFI)

Haskellのなんでもアリ具合。

ライブラリをビルドしてるとちょいちょい見えるFFIという文字列、気になって調べてみると、ForeignFunctionInterfaceというものがあって、これはHaskellからCの関数を呼び出すためのモジュールというか拡張らしい。
Haskellのものすごいライブラリの充実度の影にはこういうものがあったりするんですね。

ということでちょっと遊んでみました。

だいたいこの記事のトレスです。
本物のプログラマはHaskellを使う - 第22回 FFIを使って他の言語の関数を呼び出す:ITpro

既存のライブラリを呼ぶ

上の記事に習って、libmのsinを呼び出してみます。

まず、何はなくともmanです。sin(3)のmanを見ると、

NAME
    sin, sinf, sinl - sine function

SYNOPSIS
    #include 

    double sin(double x);
    float sinf(float x);
    long double sinl(long double x);

    Link with -lm.

だいたいこんな感じに書かれていると思われる。

これをCで使う時はこんな感じ。

#include <stdio.h>
#include <math.h>

int main()
{
    printf("%f\n", sin(0));
    printf("%f\n", sin(M_PI / 2));
    return 0;
}

コンパイルして実行する。

% gcc -lm sin.c
% ./a.out
0.000000
1.000000

コンパイルの時の"-lm"は、さっきのmanにも書いてありましたが、libm(mathライブラリ)をリンクしろという意味で、実は今のgccはlibmは標準でリンクしてくれるっぽいので省略可能です。

これを割とそのまんまHaskellでも書ける。

{-# LANGUAGE ForeignFunctionInterface #-}
{-# INCLUDE <math.h> #-}

module Main where

foeign import ccall "sin" c_sin :: Double -> Double

main :: IO ()
main = do
    print $ c_sin 0
    print $ c_sin (pi / 2)

今私が使っているversionのghc(7.4.1)だと、言語拡張のForeignFunctionInterface指定は必要ありませんでした。
あと、"INCLUDE "も、

Warning: -#include and INCLUDE pragmas are deprecated: They no longer have any effect

って言われたので、これも要らないみたいです。多分、標準でリンクされてるライブラリの分は要らないってことなんでしょう。
ということで、最初の2行は削っていいみたいです。少なくともINCLUDEの方は警告出るし、削ったほうが無難でしょう。

コンパイル&実行。

% ghc -lm Sample.hs
[1 of 1] Compiling Main ( Sample.hs, Sample.o )
Linking Sample ...
% ./Sample
0.0
1.0

これもgccと同様に"-lm"を付けるんですが、この場合も省略していいみたいです。

CとHaskellの数値型変換

c_sinを定義してるこの行、

foeign import ccall "sin" c_sin :: Double -> Double

「Cのsin関数をc_sinという名前の"Double -> Double"型としてimportしろ」というような感じだと思われます。

ただ、じゃあここでDouble->Double型として書いているけども、そのHaskellのDouble型とCのdouble型って一致してるの? 精度とかバイト数とか符号とか違うんじゃない? という事なんですけど、
そのためにForeign.C.Typesってモジュールがあって、Cの型と対応する型とHaskellの型への変換が定義されているらしい。
Foreign.C.Types
いやここには型定義だけで、変換は正確にはPreludeモジュールにあるんですけど。
fromIntegralとrealToFracです。なんかここ命名規則統一してほしいですよね。

これを使って真面目に書き直すとこうなります。

module Main where

import Foreign.C.Types

foreign import ccall "sin" c_sin :: CDouble -> CDouble

cSin :: Double -> Double
cSin = realToFrac . c_sin . realToFrac

main :: IO ()
main = do
    print $ cSin 0
    print $ cSin (pi / 2)

入力と出力をそれぞれrealToFracで変換する。これでオッケー。

自作のライブラリを呼ぶ

次、自分で作ったライブラリをリンクしてみる。このへんはHaskellというよりC言語っていうかGCCとかダイナミックリンクライブラリの話かもしれませんが。
Cで書いたライブラリをHaskellから呼び出してみます。

自作ライブラリの例としてとりあえず足し算でもしてみましょう。

int add(int a, int b)
{
    return a + b;
}

コンパイルしてダイナミックリンクライブラリを作る。

% gcc -shared add.c -o libadd.so

そしてこれを使う側のHaskellコードを書く。

module Main where

import Foreign.C.Types

foreign import ccall "add" c_add :: CInt -> CInt -> CInt

cAdd :: Int -> Int -> Int
cAdd a b = fromIntegral $ c_add (fromIntegral a) (fromIntegral b)

main :: IO ()
main = do
    print $ cAdd 0 1
    print $ cAdd 3 5

コンパイルして実行する。

% ghc -L. -ladd Add.hs
[1 of 1] Compiling Main ( Add.hs, Add.o )
Linking Add ...
% ./Add
1
8

できた。

今回は自作ライブラリなのでコンパイル時にパスの指定が必要になります。(-LPATH)
あとはまあ、fromIntegralって書くのがめんどくさいとかそのくらいでしょうか。

副作用とか

ここまで見ればわかるとおり、なんの制約もなくHaskellからCの関数を呼び出せてしまいます。
ということはつまり、Cの中でexitしようがIOしようがやりたい放題です。というかむしろmathみたいにIOが無い方が珍しいんですが。

逆に、Haskell側がCの関数をどういう仕様にするかも比較的自由なようで、
たとえばさっきのadd関数が実はIOを伴っているとわかった、もしくはその恐れがある場合は、foreign importの定義をこんなふうにすればいい。

foreign import ccall "add" c_add :: CInt -> CInt -> IO CInt

勝手にいきなりIOって書いても何も問題がない。
というか、これでHaskellの純粋さは保たれます。なんだそれって感じですが多分結構重要です。

まあ普通は副作用の無い関数なんてそうそう呼びだそうと思わないだろうから、FFIやるときはだいたいIOって付ければいいんだと思います。数学系関数でも中でmalloc/freeとかやってるかもしれないしね。
というかたぶんやってます。カーネルコールはすべからくIOだ、油断するな!

ポインタとか

関数呼び出しのパターンにはいくつかあって、これまでみたいに値をやり取りするというのは実は少数派で、だいたいは引数を格納した構造体のポインタを渡したり、バッファのポインタを渡したり、バッファのポインタを受け取ったりする。
readとかwriteとか、connectとかbindとか、fopenやexecみたいな文字列渡しも系もそうですね。

そういうのはじゃあどうするのということで、
さしあたり簡単そうなmalloc/freeを試してみます。

まず、malloc/freeの定義ですが、こんなかんじです。

void *malloc(size_t size);
void free(void *ptr);

freeの戻り値のvoidはいいとして、mallocの戻り値とfreeの引数のvoid*型は何がしか作ってあげる必要がある。

というか説明しきれないので答えを書くとこうなる。

import Foreign.C.Types
import Foreign.Ptr

data CVoid

foreign import ccall "malloc" c_malloc :: CSize -> IO (Ptr CVoid)
foreign import ccall "free" c_free :: Ptr CVoid -> IO ()

malloc :: Int -> IO (Ptr CVoid)
malloc size = c_malloc $ fromIntegral size

free :: Ptr CVoid -> IO ()
free ptr = c_free ptr

CVoidというテキトウな型を定義して、Foreign.Ptrで定義されているPtrで包む、「Ptr CVoid = void *」という感じです。

Ptrで包まれた型は、Ptrで包んでいる限りは何の制約もなしに自由にキャストしたりできます。

cast :: Ptr CVoid -> Ptr Int
cast ptr = castPtr ptr

ただし、それ以外の操作は全くできません。

Ptr型はその名の通りポインタなんですが、「ポインタを辿る」という操作がそもそもできない。そこは許されていない。だからCVoidはデータコンストラクタが要らないわけです。あっても使えないからね。
まあこれだけだと全く意味はないけども、PtrはShowのインスタンスで、メモリのアドレスが返ってくるので、こういうことはできます。

main :: IO ()
main = do
    p <- malloc 0x1000
    print p
    free p

じゃあ何の意味があるんだという感じですけど、Cのライブラリでは最初に初期化関数が構造体のポインタを返して、その後は関数にポインタの値を渡しながら処理をすすめる、構造体の中身はユーザは見ない、というタイプのものが結構あって、これはこれでいい。ファイル読み書きとかがまさにそうですね。

もうちょっとちゃんとメモリを扱いたかったらForeign.Marshal.AllocモジュールとかでStorableのインスタンスになったポインタを確保して、peek/pokeで読み書きするという手もあります。っていうかStorableのインスタンスなら別に問題なく読み書きできるわけです。
そのあたりはマニュアル見りゃわかるような話です。

構造体の扱い(がわからない)

これでだいたいのC関数を呼び出すことはできるようになりました。
もう何も怖くありません。

と言いたいところですが、
結局構造体のポインタ渡しとかはどうやるのっていう話には微妙に答えていない。Cの構造体とHaskellのデータ型のマッピング方法がよくわからない。
ビットフィールド指定とかどうするのかな。いやそこまでいかなくても、普通にどうするんだろう。アラインメントとかどうなるんだろう。
といったあたりはなんかテストプログラム書けば解決しそうな気もしますけど。
そこまではまだ調べていません。

次はData.Bitsの使い方でも調べればいいのかな。
何を書こうとしてるんだ私は。

ソース

今回遊んだソースはこちらです。念のため。
exercises/ffi at master · yunomu/exercises · GitHub

recv: invalid argument (Bad file descriptor)

この前(Conduit + Attoparsec (+ Concurrent) - yunomuのブログ)の記事に書いてあるコードを実行すると6〜8割くらいの確率でエラーが出ます。結構な確率で。

recv: invalid argument (Bad file descriptor)

こんなの。

recvとかfile descriptorとかそういうカーネルコールレベルの事を言われても困るというか。
なんとなく、リソース管理まわりの処理がうまくいってないのはわかるけども。

で、ググったらSnoymanさんが説明してくれていた。
sendWaiResponse with ResponseSource constructor causes "recv: invalid argument (Bad file descriptor)" error: Google Groups

要するに、前回はcurlをこんな感じで実装していました。

curl :: (MonadUnsafeIO m,
         MonadThrow m,
         MonadIO m,
         MonadBaseControl IO m,
         Failure HttpException m) =>
    String -> m (ResumableSource (ResourceT m) ByteString)
curl url = do
    request <- parseUrl url
    withManager $ \manager ->
        responseBody <$> http request manager

で、これをこんな風に使ってた。

main = runResourceT $ do
    liftIO $ curl "http://localhost/"

これの、どうも"withManager"がいけなかったらしい。

withManagerはManagerの確保と開放とあと例外処理とかなんとかめんどくさい部分をうまくやってくれるんですが、この場合は同じくリソースの確保開放例外処理などをうまくやってくれるはずのResourceTとかrunResourceTが絡んでいるのでどうもややこしい話になっているようで。おおかた既に閉じたファイルディスクリプタを律儀にというか厳密に閉じようとして失敗したとかそういう話なんでしょう。いや間違えて変なタイミングで閉じてるという線もあるけど。

つまりwithManager(とかcloseManager)を使わずに、ResourceTを使うなら開放処理は全部ResourceTに任せてしまえばいいという話です。
その方針で書き直すとこんな感じになります。

curl :: (MonadBaseControl IO m, MonadResource m) =>
    String -> m (ResumableSource m ByteString)
curl url = do
    request <- liftIO $ parseUrl url
    manager <- liftIO $ newManager def
    responseBody <$> http request manager

main :: IO ()
main = runResourceT $ do
    str <- curl "http://localhost/"
    ...

なんか副次的(?)にクラス制約と型定義が短くなった。
というか前から型定義のResourceTは気に食わなかったのよねぇ。消えてよかった。

教訓は、ResourceTを使う時はwith系の扱いに注意しましょうという感じでしょうか。
Yesod本(Developing Web Applications with Haskell and Yesod - O'Reilly Media: conduit-0.2準拠)には順を追って書いてあるのでwithとの関係はわかるけど競合関係とかはわかんないんですよね。というか今や既にconduit-0.5ですし。

Wai + WarpでWebサーバを作る

WarpってWebサーバらしいですよ。
いやまあ今回はそういう前置きはいいんですけど、

Yesodの人ことSnoymanさんが作っているWaiとWarpでWebサーバを書いてみようと思います。

まずは調査。
Waiとはなんぞや。
Network.Wai
Web Application Interfaceらしいです。単体だとナンノコッチャという感じですが、ドキュメントを見るとHTTPのRequestとResponseっぽい型の定義があって、それらを扱うApplicationという型があります。

定義:

type Application = Request -> ResourceT IO Response

RequestをもらってResponseを返す関数がApplicationです。まあそのまんまですね。
RequestはHTTPの定義通りなので省略、Responseはなんか若干めんどくさいけど後で少し。

それとは別にWarpというのがあります。
こいつは、いわゆるWebサーバの本体っていうか、HTTPのコネクションを管理したり、ソケットを管理したりするやつみたいです。
Network.Wai.Handler.Warp

run :: Port -> Application -> IO ()

こいつを使います。

PortはIntなのでいいんですが、このためにはApplicationを作らなければいけません。

ということで、簡単に作りました。

import Network.Wai
import Network.Wai.Handler.Warp
import Network.HTTP.Types
import Blaze.ByteString.Builder.Char.Utf8

server :: Application
server _ = return $ ResponseBuilder status200 [] $ fromString "hello"

main :: IO ()
main = run 8080 server

動かす。

% ghci Main.hs
...略
*Main> main

クライアント側。

% curl -i http://localhost:8080/
HTTP/1.1 200 OK
Server: Warp/1.3.0
Transfer-Encoding: chunked

hello

おわり。

ここで、Responseの型がこんななので、

import qualified Network.HTTP.Types as H
import qualified Data.Conduit as C

data Response
    = ResponseFile H.Status H.ResponseHeaders FilePath (Maybe FilePart)
    | ResponseBuilder H.Status H.ResponseHeaders Builder
    | ResponseSource H.Status H.ResponseHeaders (C.Source (C.ResourceT IO) (C.Flush Builder))
  deriving Typeable

今回はとりあえず文字列を返すということでResponseBuilderを使ってみましたが、ファイルをそのまま返したりもできるみたいです。

server :: Application
server _ = return $ ResponseFile status200 [] "index.html" Nothing

さっきと同様に、データを準備して動かす。

% echo 明日は晴れるでしょう > index.html
% curl -i http://localhost:8080/
HTTP/1.1 200 OK
Server: Warp/1.3.0
Content-Length: 31

明日は晴れるでしょう

ファイル用意せずにやるとちゃんと404 NotFoundが返ってきます。賢いですね。
レスポンスヘッダにContent-typeくらい書けよという気もしますが。

あとは定義の通り、ConduitのSourceを返すこともできる。

それと、わざわざWarpでサーバ立てなくてもいいように、Waiだけをテストするためのパッケージもあります。
Network.Wai.Test
さすが気が利くというか、ありがたいことです。