MSYS2 上で stack upgrade (Haskell)

使っていたstackのバージョンが1.7.0.1だったのでバージョンを上げようと思った.そこで MSYS2 上で $stack upgrade したがパーミッションの問題でうまくいかない.
「管理者として実行」でMSYS2シェルを立ち上げて$stack upgradeしてもうまくいかない.

$stack upgrade を実行したときの最後のメッセージが

Should I try to perform the file copy using sudo? This may fail
Try using sudo? (y/n) y
Going to run the following commands:

-  sudo cp C:\Users\Username\AppData\Roaming\local\bin\stack.exe C:\stack-1.7.0.1-windows-x86_64\stack.exe.tmp
-  sudo mv D:\stack-1.7.0.1-windows-x86_64\stack.exe.tmp D:\stack-1.7.0.1-windows-x86_64\stack.exe

だったので,「管理者として実行」でMSYS2シェルを立ち上げてから

$ cp "C:\Users\Username\AppData\Roaming\local\bin\stack.exe" "C:\stack-1.7.0.1-windows-x86_64\stack.exe.tmp"
$ mv "D:\stack-1.7.0.1-windows-x86_64\stack.exe.tmp" "D:\stack-1.7.0.1-windows-x86_64\stack.exe"

したらうまくいったようだ:

$ stack --version
Version 1.7.1, Git revision 681c800873816c022739ca7ed14755e85a579565 (5807 commits) x86_64 hpack-0.28.2
広告

HaskellからFFIでCの配列を扱う(マーシャリング)

HaskellのFFIについてはこのブログでも例えばこんな記事で扱っている.

今回はその記事では触れなかったマーシャリングについて扱う.まずは次のようなCの関数があったとしよう:

#include <stdlib.h>
#include <stdio.h>
#include "c_header.h"

/*
int sum(int* p);

int fib(int** pp, int n);
*/

int sum(int* p, size_t n)
{
  if(n == 0){ return 0; }
  size_t i;
  int retval = 0;
  for(i = 0; i < n; ++i)
  {
    retval += p[i];
  }
  return retval;
}

int fib(int** pp, int n)
{
  if(n<=0){ return 1;}
  *pp = malloc(n * sizeof(int));
  if(n>=1){ (*pp)[0] = 1;}
  if(n>=2){ (*pp)[1] = 1;}
  if(n>=3){
    int i;
    for(i=2; i < n; ++i)
    {
      (*pp)[i] = (*pp)[i-1] + (*pp)[i-2];
    }
  }
  return 0;
}

これらのコードは次のように使われる:

#include <stdio.h>
#include <stdlib.h>
#include "src/c_header.h"

#define N 10
int main(void)
{
  int array[] = {1,2,3,4,5,6,7,8,9,10};

  int s = sum(array, sizeof(array)/sizeof(array[0]));
  printf("sum=%d\n",s);

  int* p = 0;
  int r = fib(&p,N);
  if(r==1){ printf("failed"); }
  else{
    int i;
    for(i=0; i < N; ++i)
    {
      printf("fib(%d)=%d\n",i,p[i]);
    }
  }
  free(p);
  return 0;
}

sumやfibは次のようにHaskellから扱える:

module Lib
    where

import Foreign.C.Types
import Foreign.Ptr
import Foreign.Marshal.Array
import Foreign

-- int sum(int* p, size_t n);
foreign import ccall "c_header.h sum" cSum
  :: Ptr CInt -> CSize -> IO CInt

hSum :: [Int] -> IO Int
hSum xs =
  let
    xs' = fromIntegral <$> xs :: [CInt]
    len   = fromIntegral $ length xs :: CSize
  in
  do
    arr <- newArray xs' :: IO (Ptr CInt)
    l <- return len  :: IO CSize
    ret <- cSum arr l :: IO CInt
    return $ fromIntegral ret

-- int fib(int** pp, int n);
foreign import ccall "c_header.h fib" c_fib
  :: Ptr (Ptr CInt) -> CInt -> IO CInt

hFib :: Int -> IO [Int]
hFib n =
    do
      ptrOut <- malloc  :: IO (Ptr (Ptr CInt))
      n' <- return $ fromIntegral n :: IO CInt
      ret <- c_fib  ptrOut  n' :: IO CInt
      out <- peekArray n =<< peek ptrOut :: IO [CInt]
      free =<< peek ptrOut
      free ptrOut
      return (fromIntegral <$> out) :: IO [Int]

cSum と hSum, c_fibとhFibのシグネチャは結構ずれている.
関数cSumは sum と同様にポインタとサイズを受け取る.hSum ではサイズを配列から計算しているので長さを引数にする必要がない.
また,c_fib では二重ポインタと長さを引数にしているが,hFibでは長さだけが引数となっている.c_fib の引数となっている二重ポインタは出力用の変数だからである.

次のコードはhSum,hFibの使用例である.

module Main where

import Lib

main :: IO ()
main =
  do
    a <- hSum [1,2,3,4]
    print a
    xs <- hFib 15
    print xs

stack でこのコードをビルドする場合,stack.yaml に次のような行を加えねばならない:

c-sources:  src/c_impl.c

optparse-generic (Haskell)

自分用メモ.

必要なことはすべて https://github.com/Gabriel439/Haskell-Optparse-Generic-Library から辿れるhttp://hackage.haskell.org/package/optparse-generic/docs/Options-Generic.htmlに載っている.

■optparse-genericとは何ですか?:コマンドライン引数を処理するライブラリの一つです.

■インストール:stack install optparse-generic

■サンプル:

{-# LANGUAGE DataKinds          #-}
{-# LANGUAGE DeriveGeneric      #-}
{-# LANGUAGE FlexibleInstances  #-}  -- One more extension.
{-# LANGUAGE OverloadedStrings  #-}
{-# LANGUAGE StandaloneDeriving #-}  -- To derive Show
{-# LANGUAGE TypeOperators      #-}

import Options.Generic --stack install optparse-generic

data OptionInput  w = OptionInput
  { foo :: w ::: Int            <?>"Documentation for the foo flag"
  , bar :: w ::: Double         <?>"Documentation for the bar flag"
  , baz :: w ::: String         <?>"Documentation for the baz flag"
  , qux :: w ::: Maybe String   <?>"Documentation for the qux flag"
  , quux :: w ::: Bool          <?>"Documentation for the quux flag"
  } deriving (Generic)

instance ParseRecord (OptionInput Wrapped)
deriving instance Show (OptionInput Unwrapped)

main = do
    x <- unwrapRecord "program short description"
    let i = foo x :: Int
    let d = bar x :: Double
    let s = baz x :: String
    let s' = qux x :: Maybe String
    let b = quux x :: Bool
    print (x :: OptionInput Unwrapped)

■サンプルのビルド:stack ghc optiontest.hs
必要に応じて,package.yaml で
dependencies:
– optparse-generic

■サンプルを動かす:optiontest.exe --foo 2 --bar 3.478 --baz "hoge" --qux "fuga"

Haskell の QuickCheck を自動化する

ライブラリを開発していると、複数のテストを一挙に回したくなるかもしれませんね。そんなときはこうします。

{-# LANGUAGE TemplateHaskell #-}

import Data.List
import Test.QuickCheck

-- 与えられた2つのリストを連結する
cat :: (Eq a) => [a] -> [a] -> [a]
cat [] ys = ys
cat (x:xs) ys = x : (xs `cat` ys)

-- cat が結合律を満たすかどうかのテスト
prop_cat xs ys zs = (xs `cat` ys) `cat` zs == xs `cat` (ys `cat` zs)

-- 最初のリストから二番目のリストの要素を除去したリストを作る
sub :: (Eq a) => [a] -> [a] -> [a]
sub [] ys = []
sub (x:xs) ys | x `elem` ys = xs `sub` ys
| otherwise = x : (xs `sub` ys)

-- sub が結合律を満たすかどうかのテスト
prop_sub xs ys zs = (xs `sub` ys) `sub` zs == xs `sub` (ys `sub` zs)
--to run this test, > quickCheck prop_cap

--最初のリストに含まれている要素を除外しつつ2つの与えられたリストをマージする
ucat :: (Eq a) => [a] -> [a] -> [a]
ucat [] ys = ys
ucat (x:xs) ys | x `elem` ys = x : (xs `ucat` (ys `sub` [x]))
| otherwise = x : (xs `ucat` ys)

-- ucat が結合律を満たすかどうかのテスト
prop_ucat xs ys zs = (xs `ucat` ys) `ucat` zs == xs `ucat` (ys `ucat` zs)

return []
main = $forAllProperties (quickCheckWithResult stdArgs { maxSuccess = 2000 })

■動かし方/その出力例

$ stack runghc qc.hs
Run from outside a project, using implicit global project config
=== prop_cat from qc.hs:12 ===
+++ OK, passed 2000 tests.

=== prop_sub from qc.hs:20 ===
*** Failed! Falsifiable (after 9 tests and 7 shrinks):
[0]
[]
[0]

=== prop_ucat from qc.hs:29 ===
+++ OK, passed 2000 tests.

■コードの解説
cat, sub, ucat というリスト演算を定義し、それらが結合法則を満たすかどうかチェックしています。テストの結果、cat は合格、subは失格(つまり結合法則の反例が見つかった)、ucatは合格となりました。

■ポイント
1. TemplateHaskell を使う。
2. テストの名前は prop_ で始まる。 (TemplateHaskellを使っていることからの制約)
3. return[] とかいうキモいやつは我慢。 (TemplateHaskellを使っていることからの制約)
4. main は一番最後に来る。 (TemplateHaskellを使っていることからの制約)

QuickCheckで100回以上テストを回す(メモ)

HaskellにはQuickCheckという便利なライブラリがあります。これは、自分で作った関数が特定の性質を満たしているかどうか手早くテストするときに役に立ちます。

たとえば、あなたが(多分自分の勉強のために)2つのリストを結合する関数myconcatを次のように書いたとしましょう:

import Test.QuickCheck
-- $ stack install QuickCheck

myconcat :: (Eq a) => [a] -> [a] -> [a]
myconcat [] ys = ys
myconcat (x:xs) ys = x : (xs `myconcat` ys)

実際これは多くのHaskell教科書に(++)の参照実装として挙げられているものをそのまま真似ただけですから、当然

(xs `myconcat` ys) `myconcat` zs == xs `myconcat` (ys `myconcat` zs)

という性質は「常に」成り立つと期待したいところです。このような事を保証するためには、結局なんらかの証明を与える必要があります。
例えば Richard Bird 著/山下伸夫 訳『関数プログラミング入門 — Haskellで学ぶ原理と技法』では、そのような証明を「手で」つける例が随所で扱われています。あるいは、Coqのような言語を使って、成り立っていることが期待される性質が実際に成り立つことの形式的な証明を与えるという手段もあります。

いずれにせよ、何かのしっかりとした証明をつけるというのはなかなか面倒なことではあります。そこで、乱数で生成した例で手っ取り早くテストしたいという場合があります。特に、「期待される性質」が実際に成り立っていないかもしれないという疑念があるときにはこのような検査は有効です。間違った命題—その命題が本当に間違っているかどうかを事前に知ることができない場合が多いことが厄介なわけですが—を証明しようとあれこれ悩みたくないですからね。

今の場合ならソースコードの末尾にこんな関数を付け加えておけば良いです:

mytest xs ys zs = (xs `myconcat` ys) `myconcat` zs == xs `myconcat` (ys `myconcat` zs)

そしてghciから

*Main> quickCheck mytest
+++ OK, passed 100 tests.

と試せば良いわけです。

ところで、100回以上テストしたい場合はどうすればいいのでしょうか? ここ(stackoverflow)で答を見つけました。答から先に書くと、たとえば5000回テストしたい場合には

*Main>quickCheckWith stdArgs { maxSuccess = 5000 } mytest

とすればいいです。

引用した stackoverflow のあるコメントの後半に「どうやってこの答をみつけたか」の丁寧な解説があったので補足しつつ翻訳します。

1.API documentation を見に行く

API documentation のページはこんなふうなリンクから飛べる

2. quickCheck を見てその次に見たのは maxSuccess フィールドを持つ Args 型だった。

Args型

3. 全部のフィールドを書くのは嫌だったので、Args 型の値を探したら stdArgs が見つかった。(ブラウザの検索機能 Ctrl+F を使いましょう)。または、hoogle を使っても良かったかもしれない。

4. 自分の Args 型変数を使いたいので検索を続行した。次の行に quickCheckWith があった—これだ! または、hoogleを使うという手もあった。

img3

「使ったことがないライブラリをどう使うか」というのは非常に重要です。上に書いてあることはHoogleを使い慣れている人が当たり前にやっていることでしょうが、きちんと段階化し言語化してあるところが素晴らしいと思って訳してしまいました。

更に補足すると、stdArgs { maxSuccess = 5000 }の箇所は、stdArgsが返すArgs型の値の maxSuccessフィールドを 5000 に書き換えた値を生成しているのでした。

Ubuntu14.04 LTS に Haskell Platform7.10.2 を入れる(メモ)

メモ書き

$ sudo su -
# cd /
# wget https://downloads.haskell.org/~platform/7.10.2/haskell-platform-7.10.2-a-unknown-linux-deb7.tar.gz
# tar xvf haskell-platform-7.10.2-a-unknown-linux-deb7.tar.gz
# rm haskell-platform-7.10.2-a-unknown-linux-deb7.tar.gz
# ./install-haskell-platform.sh
# exit
$

■重要■
最後に which ghc して、今入れたものが表示されるか確認しておく。

必要に応じて ~/.bashrc に
GHC_HOME=/usr/local/haskell/ghc-7.10.2-x86_64
PATH=$GHC_HOME/bin:$PATH

メモ:Haskellの二項演算子風型構成子

元ネタ:http://stackoverflow.com/questions/19197339/haskell-data-constructor-meaning

(:->)という外国の顔文字みたいな型構成子が気になってHaskell2010を調べたがよくわからず、上のStackoverflowでようやくわかった。

そのまんまの引用だが

data Rose a = a :> [Rose a]
deriving (Eq, Show)

というサンプルが挙げられている。(この例の元ネタは”A rose is a rose is a rose” だろうか)。
最初が:で始まる記号列を型構成子に使えることは知らなかった。忘れそうなのでメモしておく。

HaskellからFFIでOpenCVを使ってみよう

この記事は Haskell Advent Calendar 2013 の二日目のために慌てて書いたものです.

FFIを使って,画像ライブラリOpenCVを使ってみましょう.OpenCVはBSDライセンスで公開されているフリーの画像処理ライブラリで,物体認識などの比較的高度な機能が実装されてます.今回はそのような高度な機能は使わず,画像の読み込みと書き出しと表示,さらに線を引いたり円を描いたりする初歩的な描画関数だけを使います.

Haskellでは,Foreign Function Interface(FFI,外部関数インタフェース)を通じて,Haskell以外の言語とやりとりが出来ます.FFIには二つの方向があります.

  • 他の言語が持つ機能をHaskellから利用するためのインタフェースを記述する.
  • Haskellで書かれた関数を他の言語から利用する.

本記事では一番目について,とくにC/C++コンパイラで作成されたライブラリの使用方法に限定して解説します.HaskellのFFIの概要は Haskell 2010 Language Reportの Chapter 8 で知ることが出来ますが,FFI と深い関わりのあるライブラリの章(Chapter 25 – Chapter 37)まで含めれば,Haskell 2010 Language Report のページの20%を超える部分がFFIの解説に割かれています.私自身が Chapter 8 をざっと読んだ程度の知識なので,以下の解説でも深いことは触れられません.ご了承ください.

実のところ,一番目の事項,つまりC/C++コンパイラで作成されたライブラリを使う話に限ったとしても,わざわざFFIを学ぶ必要はないかもしれません.というのも,多くの有用なライブラリに対してFFIを用いたHaskellバインディングがすでに作られているからです.しかしながら,自作のライブラリや世間であまり注目されていないライブラリをHaskellから使いたくなる可能性があるならばFFIを学ぶ理由はあるでしょう.もったいぶった前置きをしましたが,本記事で例として取り上げるOpenCVについても,実のところすでにある程度Haskellバインディングが用意されているので,この記事を読まなければHaskellからOpenCVを使えない訳じゃないですよということをかっこよく言いたかっただけです.

さて,ライブラリの実体は,ABIに従うように生成され,そのABIにしたがって解釈したり実行できるバイト列でした.Haskellからライブラリを使うためにはどのようなABIに従うのかを指定する必要があります.現状では ccall, stdcall, cplusplus ぐらいしか選択肢はないようです.以下では ccall を前提とします.

たとえば,Cの標準ライブラリ math.h で宣言されている
double erfc(double x);
をHaskellからFFIで使うコードの例は次のようになります:

import Foreign.C.Types

foreign import ccall "math.h erfc" c_erfc :: CDouble -> CDouble
erfc :: Double -> Double
erfc x = realToFrac $ c_erfc $ realToFrac x
main = putStrLn ("erfc(1.0)= " ++ show (erfc 1.0) )

上のコードはコマンドプロンプトから $ runghc erfc_from_libc.hs としてやれば実行できます.
上のコードの三行目の「ccall "math.h erfc"」の部分は,「C呼び出し規約に従って,math.h で宣言されている erfc を頼む」と読みましょう.さて,その行の残りのc_erfc :: CDouble -> CDouble に出てくる「CDouble」という型は,最初の行のimport Foregn.C.Typesでインポートされています.一般に,Cの double と Haskellの Double は同じ範囲を表せる保証がありません.そこで,Cにおける基本型のほとんどに対して大文字のCで始まる対応物がForegn.C.Typesで定義されています.Doubleの場合は殆どのコンパイラではCのdoubleと同じ範囲を表すんじゃないかと思いますが,とにかく違う型という扱いなので,これをHaskellの関数にラップしたerfc :: Double -> Double の定義では,引数の変形と返り値の変形の合計2回にわたってrealToFracを使っています.

実数や整数の変換については

fromIntegral :: (Integral a, Num b) => a -> b
truncate :: (Integral b, RealFrac a) => a -> b
realToFrac :: (Fractional b, Real a) => a -> b

が便利だったことをこの辺で思い出しましょう.

さて,Cライブラリの関数が副作用を伴わない場合は,話が比較的簡単です.では副作用を持つ関数を考えてみましょう.副作用というのはやや曖昧な言葉ですが,参照透明な関数とは異なった挙動をするもの,例えばI/O関係の関数は副作用を持つと言えます.Haskellでは副作用をアクションとして切り出してモナドとして扱うことによって,参照透明性を保ったままIOを行う処理が(ずばりIOという名前のモナドで)書けるのでした.

ではここでOpenCVを使って副作用を行うC++関数を書いてみましょう.(ちょっと流れが強引ですかね).

extern "C"
{
	void show_madoka();
}

extern "C"を指定しておくことで,生成されたオブジェクトコード中のシンボルがC呼び出し規約で読み込めることが保証されるのでした.)

#include "imshow.h"
#include  //opencv
#include //opencv
#include //opencv
#include 

void show_madoka(){
  using namespace std;
  ::cvNamedWindow("madoka");
  IplImage* pImg = cvLoadImage("madoka.jpg");
  if (!pImg) {
    cerr << "You need madoka.jpg" << endl;
    return;
  }
  cout << "#### 'ESC' to exit" << endl;
  ::cvShowImage("madoka", pImg);
  // ユーザが ESC キーを入力するまで待つ
  enum { Wait_msec = 100, ESC_key = 27,};
  while (true) {
    if (cvWaitKey(Wait_msec) == ESC_key) {break;}
  }
  ::cvDestroyWindow("madoka");
  ::cvReleaseImage(&pImg);
  return;
}

関数void show_madoka() はHaskellからインポートするときにどうなるでしょうか. () -> ()でしょうか?いいえ,()の値などありませんから,型シグネチャが縮退して()になる気がします.ではやってみましょう:

import Foreign.C.Types
foreign import ccall "imshow.h show_madoka" c'show_madoka_bozo :: ()

main = do
  return c'show_madoka_bozo
  putStrLn "madoka"

do式の中で c'show_madoka を置きたかったけれどIOモナドの分だけずれてるのでreturnしてやって型を合わせています.これでばっちり動くはず…なのに何も起きません!これが現実…!

待ってください.画像を表示するためにウィンドウを開いたりするのは副作用だからHaskellの側で受けるときの型シグネチャをIO () にしたら上手くいくのでは? やってみましょう:

import Foreign.C.Types
foreign import ccall "imshow.h show_madoka" c'show_madoka :: IO()

main = do
  c'show_madoka
  putStrLn "madoka"

今度はちゃんと上手く表示されました.(なお,画像にマウスカーソルを合わせてESCを押さないと正常に終了できません.ご了承ください.)

(ちなみに,imshow-bozo.hs や imshow-right.hs を動かすためにはリンクの指定などをする必要がありますが,この辺はいちいちコマンドラインで入力するのが面倒なのでMakefileで処理しています.記事の末尾にMakefileを載せることにします.)

いまとりあげた例は非常に小さいものですが,HaskellのFFIを使う上で気をつけなければならない事の例示となっています.とにかくヤバイということが伝われば幸いです.

さて,もうすこし丁寧に諸々のことを書いておきたい気持ちもありますが,表紙だとかスライドだとか年賀状だとか色々大変なので,今回カージオイド(という曲線)をHaskellからOpenCVの関数をFFI呼び出しして描いたコードをご紹介して記事を終えることにします.

次のヘッダファイルは,今回カージオイドを描くために使った関数をHaskellから import するために,必要に応じてラップしなおした関数の宣言を載せたものです.元々のOpenCVの関数では基本型の直積タイプの構造体を引数とする関数があり,Haskellで import すると扱いが面倒なので「全部基本型に展開して」しまっている箇所があります.

#include 

extern "C" {
	IplImage* load_image ( const char* filename);

	IplImage* create_image (
	int width
	, int height
	, const CvScalar color);

	void save_image (const char* filename, IplImage* pImg);

	void make_window (const char* winname);

	void show_image (const char* winname, IplImage* pImg);

	void destroy_window (const char* winname);

	void release_image ( IplImage* pImg );

	//crunched
	IplImage* create_image__crunched (
    int width
	, int height
	, double v0, double v1, double v2);

  //crunched
  void draw_line__crunched (
    IplImage* pImg,
    int pt1x, int pt1y,
    int pt2x, int pt2y,
    double v0, double v1, double v2,
    int thickness);

  void draw_circle__crunched (
  IplImage* pImg,
  int cx, int cy,
  int r,
  double v0, double v1, double v2,
  int thickness);
}

実装側は次のようにしました:

#include "ffi_opencv.h"
#include 
#include 
#include 

IplImage* load_image( const char* filename)
{
  return ::cvLoadImage(filename, CV_LOAD_IMAGE_ANYCOLOR);
}

IplImage* create_image( int width, int height, CvScalar color)
{
  IplImage* pImg = ::cvCreateImage(cvSize(width, height), IPL_DEPTH_8U, 3);
  ::cvSet(pImg, color);
  return pImg;
}

IplImage* create_image__crunched(
    int width,
    int height,
    double v0, double v1, double v2)
{
  return create_image(width, height, cvScalar(v0, v1, v2, 0));
}

void save_image(const char* filename, IplImage* pImg)
{
  ::cvSaveImage(filename, pImg);
  return;
}

void make_window(const char* winname)
{
  ::cvNamedWindow(winname);
  return;
}

void show_image(const char* winname, IplImage* pImg)
{
  ::cvShowImage(winname, pImg);
  enum {  Wait_msec = 100, ESC_key = 27,};
  while(true){ if (::cvWaitKey(Wait_msec) == ESC_key) {break;}}
  return;
}

void destroy_window(const char* winname)
{
  ::cvDestroyWindow(winname);
  return;
}

void release_image(IplImage* pImg)
{
  ::cvReleaseImage(&pImg);
  return;
}

void draw_line__crunched(
    IplImage* pImg,
    int pt1x, int pt1y,
    int pt2x, int pt2y,
    double v0, double v1, double v2,
    int thickness)
{
  ::cvLine(
    pImg
    , ::cvPoint(pt1x, pt1y)
    , ::cvPoint(pt2x, pt2y)
    , ::cvScalar(v0, v1, v2, 0)
    , thickness
    , CV_AA  );
  return;
}

void draw_circle__crunched(
  IplImage* pImg,
  int cx, int cy,
  int r,
  double v0, double v1, double v2,
  int thickness)
{
  ::cvCircle(
    pImg
    , ::cvPoint(cx, cy)
    , r
    , ::cvScalar(v0, v1, v2, 0)
    , thickness
    , CV_AA );
  return;
}

これらのコードを利用してカージオイドを描くHaskellプログラムは次のようになります:

import Foreign.C.Types
import Foreign.C.String
import Foreign.Ptr
import Control.Monad

data Point=Point{x::Double, y::Double}
data Scalar=Scalar{v0::Double, v1::Double, v2::Double}

toCInt :: Int -> CInt
toCInt = fromIntegral

toCDouble :: Double -> CDouble
toCDouble = realToFrac

data Image = Image
type ImagePtr = Ptr Image

type CV_Thickness = Int

foreign import ccall "ffi_opencv.h  load_image"
	c'load_image :: CString -> IO ImagePtr

foreign import ccall "ffi_opencv.h  create_image__crunched"
	c'create_image__crunched :: CInt -> CInt ->
				CDouble -> CDouble -> CDouble -> IO ImagePtr

foreign import ccall "ffi_opencv.h  save_image"
	c'save_image :: CString -> ImagePtr -> IO ()

foreign import ccall "ffi_opencv.h make_window"
	c'make_window :: CString -> IO ()

foreign import ccall "ffi_opencv.h show_image"
	c'show_image :: CString -> ImagePtr -> IO ()

foreign import ccall "ffi_opencv.h destroy_window"
	c'destroy_window :: CString -> IO ()

foreign import ccall "ffi_opencv.h release_image"
	c'release_image :: ImagePtr -> IO ()

foreign import ccall "ffi_opencv.h draw_line__crunched"
	c'draw_line__crunched :: ImagePtr -> CInt -> CInt -> CInt -> CInt -> CDouble -> CDouble -> CDouble -> CInt -> IO ()

foreign import ccall "ffi_opencv.h draw_circle__crunched"
	c'draw_circle__crunched :: ImagePtr -> CInt -> CInt -> CInt -> CDouble -> CDouble -> CDouble -> CInt -> IO ()

load_image :: String -> IO ImagePtr
load_image s = join $ fmap c'load_image $ newCString s

create_image :: Int -> Int -> Scalar -> IO ImagePtr
create_image w h (Scalar d0 d1 d2)
 = c'create_image__crunched (toCInt w) (toCInt h) (toCDouble d0) (toCDouble d1) (toCDouble d2)

save_image :: String -> ImagePtr -> IO ()
save_image s pImg =  join $ (liftM2  c'save_image) (newCString s) (return pImg)

make_window :: String -> IO ()
make_window s = join $ (liftM c'make_window) (newCString s)

show_image :: String -> ImagePtr -> IO ()
show_image s pImg = join $ (liftM2 c'show_image) (newCString s) (return pImg)

destroy_window :: String -> IO ()
destroy_window  s = join $ (liftM c'destroy_window) (newCString s)

release_image :: ImagePtr -> IO ()
release_image = c'release_image

draw_line__crunched :: ImagePtr -> Int -> Int -> Int -> Int -> Double -> Double -> Double -> Int -> IO ()
draw_line__crunched  pImg x1 y1 x2 y2 d0 d1 d2 c
  = c'draw_line__crunched pImg (toCInt x1) (toCInt y1) (toCInt x2) (toCInt y2) (toCDouble d0) (toCDouble d1) (toCDouble d2) (toCInt c)

draw_circle__crunched :: ImagePtr -> Double -> Double -> Double -> Double -> Double -> Double -> Int -> IO ()
draw_circle__crunched pImg cx cy r d0 d1 d2 t
  = c'draw_circle__crunched pImg cx' cy' r' d0' d1' d2' t'
    where
	  cx' = truncate cx
	  cy' = truncate cy
	  r' = truncate r
	  d0' = realToFrac d0
	  d1' = realToFrac d1
	  d2' = realToFrac d2
	  t' = fromIntegral t

draw_line ::  ImagePtr -> Point -> Point -> Scalar -> CV_Thickness -> IO ()
draw_line  pImg  Point{x=p1x,y=p1y}  Point{x=p2x,y=p2y}  Scalar{v0=d0,v1=d1,v2=d2} c = draw_line__crunched pImg p1x' p1y' p2x' p2y' d0 d1 d2  c
  where
    p1x' = truncate p1x
    p1y' = truncate p1y
    p2x' = truncate p2x
    p2y' = truncate p2y

draw_line_from_point_pair :: ImagePtr -> (Point,Point) -> IO ()
draw_line_from_point_pair  pImg  (p1,p2) = draw_line pImg p1 p2 (Scalar 0 0 0) 1

deg_to_rad :: Int -> Double
deg_to_rad t = (fromIntegral t) * pi / 180.0

shifted_sin :: Double -> Double
shifted_sin t =  c + r * sin t

shifted_cos :: Double -> Double
shifted_cos t =  c + r * cos t

main = do
  img >) nopIO (segments_cardioid img)
  fringe_circle img
  putStrLn "enter image save name : "
  savename  (Point, Point)
segment_info t = (
  Point{x=shifted_cos $ deg_to_rad t, y=shifted_sin $ deg_to_rad t},
  Point{x=shifted_cos $ deg_to_rad (2*t+180),  y=shifted_sin $ deg_to_rad (2*t+180)}
  )

segments_cardioid :: ImagePtr -> [IO()]
segments_cardioid  pImg = fmap ( (draw_line_from_point_pair pImg) . segment_info) deglist

fringe_circle :: ImagePtr -> IO()
fringe_circle pImg = draw_circle__crunched pImg c c r 128 128 128 1

実行すると保存するファイル名を聞いてくるので cardioid.jpg などと答えると画像を表示してくれます.画像にマウスカーソルを合わせてESCを押すと終了します.

こんな絵が生成されると思います:
cardioid

この絵は,円周上の対蹠する二点P,Qが,Pは角速度ωで,Qが角速度2ωで運動したときの線分PQの軌跡を描いたものです.屈折した三日月のような図形が線の中から浮かび上がっていますが,円から「折れた三日月」を取り除いたずんぐりとした形がなにやら動物の心臓を思わせるので「カージオイド(心臓形, cardioid)」と呼ばれています.円筒形の陶器のカップを日に晒すと円筒の内側で反射した光が屈曲して似たような図形を作りますが,あちらは腎臓形(nephroid)と呼ばれ,Pは角速度ωで,Qが角速度3ωで運動した場合に線分PQが作り出す包絡線です.

draw_cardioid.hs の説明をしておきましょう.今回描こうとしているものは円を基本とした図形なので,キャンバスの座標系に合わせたカスタム版の三角関数
shifted_sinshifted_cos
を用意しました.
deglist = [0, 3 .. 360-3]で3度刻みのリストを作っています.segments_cardioid :: ImagePtr -> [IO()]は定義部でこのdeglistを利用しており,「画像(へのポインタ)を受け取って線分を描くアクションのリストを返す」関数になっています.全部で120個のIO()(>>)でつないでmainを書いても良かったのですが面倒なのでfoldr (>>) nopIO (segments_cardioid img)としてやって合成したアクションを得ています.save_imageのあたりは,プログラムとしては別にイラナイのですが,Cの文字列が関与する関数の扱いを紹介したくてサンプルに入れた記憶があります.

この記事で使ったMinGW+MSYS用のMakefileです.環境に合わせて適宜修正して使ってください.

#####################################################
# Makefile for Haskell Advent Calendar 2013, Dec 2
#####################################################
.PHONY : madoka erfc_from_libc imshow_right imshow_bozo cardioid

GHC = ghc
LD_LIBRARY_PATH = $(PATH)
OPENCV_32_TRUNC = /c/opencv2.4.6.0-MSYS32
OPENCV_VERSION_SUFFIX = 246
OPENCV_HEADERS_INCLUDE32 = -I$(OPENCV_32_TRUNC)/include/opencv  \
-I$(OPENCV_32_TRUNC)/include
OPENCV_LIB_32_LOADPATH = -L$(OPENCV_32_TRUNC)/lib
CXXFLAGS = $(OPENCV_HEADERS_INCLUDE32)

LINK_OPENCV_LIBS = \
-lopencv_highgui$(OPENCV_VERSION_SUFFIX) \
-lopencv_core$(OPENCV_VERSION_SUFFIX) \
#-lopencv_objdetect$(OPENCV_VERSION_SUFFIX)  \
#-lopencv_nonfree$(OPENCV_VERSION_SUFFIX) \
#-lopencv_imgproc$(OPENCV_VERSION_SUFFIX) \
#-lopencv_legacy$(OPENCV_VERSION_SUFFIX)  \
#-lopencv_ml$(OPENCV_VERSION_SUFFIX)  \
#-lopencv_features2d$(OPENCV_VERSION_SUFFIX)  \

LDFLAGS = -lm -lstdc++ $(OPENCV_LIB_32_LOADPATH) $(LINK_OPENCV_LIBS)

madoka : show_madoka.o
	$(LINK.o)  -o madoka.exe  $(filter  %.o,   $^)  $(LDFLAGS)
	./madoka.exe

cardioid : draw_cardioid.hs ffi_opencv.o
	$(GHC) draw_cardioid.hs -o cardioid.exe ffi_opencv.o $(LDFLAGS)
	./cardioid.exe

imshow_bozo : imshow-bozo.hs  imshow.o
	$(GHC) imshow-bozo.hs -o imshow_bozo.exe imshow.o $(LDFLAGS)
	./imshow_bozo.exe

imshow_right : imshow-right.hs  imshow.o
	$(GHC) imshow-right.hs -o imshow-right.exe imshow.o $(LDFLAGS)
	./imshow-right.exe

erfc_from_libc : erfc_from_libc.hs
	$(GHC) erfc_from_libc.hs -o    erfctest.exe
	./erfctest.exe

clean :
	$(RM) ./*.o
	$(RM) ./*.exe

それでは皆様,良いお年を!

■2018/06/19 追記
FFIによるマーシャリングをこの記事で扱っている.

Windowsにajhcをインストールする

■ajhcとは
ajhc はmaster_qさんが最近力を入れている Haskell コンパイラです。色々な情報はMetasepiにあります。GHCよりも吐くバイナリが小さい jhc から fork されたHaskell コンパイラであり、現在は kernel レベルでのプログラミングを目標に開発が活発に行われています。

■現状、cabal からはインストールできない
「ニコニコ動画 ajhc」あたりでヒットする ajhc のデモでは小さなマイコンで動かす例が取り上げられる事が多いですが、もちろんWindowsで動かすことが出来ます。ただし、MinGWシェルから

$ cabal update
$ export LANG=en_US.UTF-8
$ cabal install ajhc

としても多分失敗します

■ajhcをzipからコンパイル
cabalからインストールできないのは、どちらかと言うとMinGWのツールの問題なのですが最近 master_q さんが workaround を施してくださったので、github/ajhcにある最新版は簡単にインストールできます。インストール方法のcabal じゃない方に書かれてる通りにやればいいのですが git を MinGWに入れてないとちゃんとコンパイルできないようです。git は触るのも嫌なので、github/ajhcにある最新版を zip でダウンロードしてMinGWのホーム(例えば C:\MinGW\msys\1.0\home\UserName あたり)で解凍しておきます。多分 ajhc-arafura なんて名前のフォルダができるはずです。あとは MinGWシェルから

$ cd ajhc-arafura
$ export LANG=en_US.UTF-8
$ autoreconf -i
$ ./configure
$ make
$ make install

とやれば良さそうですが、僕は MinGW に git 入れてないのでコンパイルが途中でこけてしまいました。エラーメッセージを読むと Makefile の

ChangeLog:
        git log fork-jhc-0.8.0-darcs20120314.. > $@

でコケてるらしいけどgit を入れるのも面倒なので、Makefile のその部分を

ChangeLog:
        touch $@

と書き換えてみたら make が通るようになりました。あとは make install するだけですね。問題なくインストールできると思います。

■簡単なテスト
動作確認してみましょう:

$ echo 'main = print "Hello, Ajhc"' > Hello.hs; ajhc Hello.hs -o Hello;./Hello.exe
......
......
"Hello, Ajhc"

ajhcには今のところ Haskell コンパイラとしての機能が足りておらず、現状ではGHCの代わりに使えるようなレベルではありません。しかし、この記事で紹介したように手軽にインストールできるものですから、触ってみたら面白いんじゃないかなと思います。あと、MinGW にはどうせなら git 入れといたほうがいいかもしれません。嫌いだし面倒なので僕は git を MinGW には入れませんが。

C82に参加してきました/関数型イカ娘本#4を書かなイカ!!

 2012/08/12のことですから少し前の話なのですが C82(コミックマーケット82)に,サークル「参照透明な海を守る会」のメンバーとして参加してきました (https://ikmsm.pastillage-research.org/ikmsm/books/c82.html). 今回は表紙だけでなく,記事を一本書きました. (上記リンクページにて本のサンプル PDF を見ることができます.)

想定した読者
 理工系の学部教養で習ったであろう集合と写像の簡単な知識を持つ人です. 関数型言語,特に Haskell への関心がある方を読者として想定しましたが,それ以上の知識を想定はしないようにしました. (何より,私自信が言語の深い理論を知らず, Haskell についても初心者なので書こうとしてもそんなに難しい話は書けません).

記事「蓮物語」の概要と目的
 記事冒頭からそのまま引用します.

 集合と写像について一応のことを知ってはいるけれど関数型言語に触れたことがない主人公が Haskell を学びます。同時に主人公は圏論の初歩を学び、圏論を応用すると Haskell の幾つかの重要な型クラスの挙動が理解しやすくなることを知ります。物語を通し、主人公は最小限の圏論の知識を手がかりに Haskell の型を理解しようと試行錯誤することになります。

 このような話題をきちんと論じるならば、集合と写像のなす圏を圏論の立場で丁寧に抽象化してから論じることになるでしょう。とはいうものの、圏論をゼロから学び cartesian closed category のあたりまで進めるのはなかなか大変です。そこまで徹底した抽象化を前提としなくてもHaskellと圏論の関わりを説明することはある程度可能だと筆者は考えました。物語を通して、圏論についての初歩的な知識を紹介し、 Haskell の幾つかの重要な型クラスが従うルールを圏論の立場から説明します。

感想
 久々に院生時代の論文執筆なみに呻吟することになったのですが,こういう機会でも無ければ深く掘り下げて勉強することもできないので,参加して良かったと思います. サークルメンバーの多くとは Twitter でも会話できるのですが, Skype のグループでのチャットは文字制限も緩いし場外からのトビゲリ・アンブッシュの心配もなく和気あいあいと話をすることができたので非常に楽しかったです. 

関数型イカ娘本#4を書かなイカ!!
 さてどちらかというとこちらが本題なのですが,C83(当落はまだわかりません)を前提として関数型イカ娘の四冊目の本を企画中です.  最終原稿作成や印刷依頼などの都合上2012/11/18(日)深夜が締め切りです. 

 サークルメンバーの皆さんは(私をのぞいて)皆スゴイ級の人たちですが,元々こういうノリで始まったサークルということもあり,内容の高度さよりは妄想の濃さを歓迎するようなサークルなので,是非お気軽に参加ください. 「すごい Haskell たのしく学ぼう ! 」の訳者のお二人(@tanakh さん,@nushio さん)とも Skype でチャットできますよ!