wkoikingのブログ

内容は主にhaskellやxyzzy関連です。

【Haskell】diagramsでgtkのDrawingAreaにお絵かき

HaskellのdiagramsパッケージでgtkのDrawingAreaに描画してみたので、 minimalなサンプルコードを以下に記しておきます。

手順

cabal update
cabal install diagrams-gtk

ソースコード

{-# LANGUAGE NoMonomorphismRestriction #-}
module Main where

import Graphics.UI.Gtk
import Graphics.Rendering.Cairo (liftIO)
import Diagrams.Prelude
import Diagrams.Backend.Gtk
import Diagrams.Backend.Cairo (Cairo)

main :: IO ()
main = do
  initGUI
  window <- windowNew
  canvas <- drawingAreaNew
  set window [windowDefaultWidth   := 1000,
              windowDefaultHeight  := 1000,
              containerBorderWidth := 10,
              containerChild       := canvas]
  widgetShowAll window
  -- Event
  window `on` unrealize $ mainQuit
  canvas `on` exposeEvent $ tryEvent $ do
    liftIO $ defaultRender canvas signalImg
  mainGUI

signalImg :: Diagram Cairo R2
signalImg = pad 1.1 $ hinge ||| housing
 where housing = lights <> roundedRect 2 0.8 0.2 # fc gray
       lights = centerXY $ hcat' (with & sep .~ 0.1) $ zipWith fc [red, yellow, green] (replicate 3 $ circle 0.2)
       hinge = vrule 0.8 ||| hrule 0.4

実行結果

当たり判定をつける (7/19加筆)

diagramは、以下のように簡単に当たり判定をつけることができます。

{-# LANGUAGE NoMonomorphismRestriction #-}
module Main where

import Graphics.UI.Gtk
import Graphics.Rendering.Cairo (liftIO)
import Diagrams.Prelude hiding (Renderable)
import Diagrams.Backend.Gtk
import Diagrams.Backend.Cairo (Cairo)
import Control.Monad (when)

main :: IO ()
main = do
    initGUI
    window <- windowNew    
    canvas <- drawingAreaNew
    widgetSetSizeRequest canvas 500 500
    set window [windowDefaultWidth   := 500,
                windowDefaultHeight  := 500,
                containerBorderWidth := 10,
                containerChild       := canvas]
    widgetShowAll window
    -- Event
    window `on` unrealize $ mainQuit
    canvas `on` exposeEvent $ tryEvent $ do
        drawin <- eventWindow
        liftIO $ renderToGtk drawin c1
    canvas `on` buttonPressEvent $ tryEvent $ do
        button <- eventButton
        (x,y)  <- eventCoordinates
        case sample c1 (p2 (x,y)) of
            Any True -> do
                liftIO $ putStrLn "inside"
                liftIO $ putStrLn $ show (x, y)
                return ()
            Any False -> do
                liftIO $ putStrLn "outside"
                liftIO $ putStrLn $ show (x, y)
                return ()
    mainGUI

c1 :: Diagram Cairo R2
c1 = toGtkCoords $ (circle 100 ||| circle 50) # fc gray

実行結果

円の内側をクリックするとinside、 外側をクリックするとoutsideと表示されます。

gtk2hsを使ってみる

Haskellで、gtkを(久しぶりに)使ってみました。 とりあえず、昔書いたコードが動くところまで行ったので、手順と注意点を記録に残しておきます。

gtkとgtk3というパッケージがあるのですが、gtk3は上手く行かなかったので、そこらへんの記録も。

開発環境

手順

gtkパッケージの場合

  1. Haskell Platformをインストール
  2. ここからGTK+ 2.xのall-in-one bundleをダウンロード
  3. 中身をパスにスペースを含まないディレクトリに解凍
    • 例:C:\gtk\
  4. その中のbinにパスを通す
    • 例:C:\gtk\bin
  5. 以下のコマンドを実行
cabal update
cabal install gtk2hs-buildtools
cabal install gtk
  1. glade3.8をここからインストール
    • 3.14はgtk3用なので注意
  2. ここチュートリアルにしたがって、.gladeファイルを作る(以下のコードと同じディレクトリに置く)

  3. 以下のコードをコンパイルして実行
    • ghciからは実行できなかったので注意
module Main where

import Graphics.UI.Gtk
import Control.Monad.Trans ( liftIO )

main = do
    initGUI
    xml    <- builderNew
    builderAddFromFile xml "hellogtk2hs.glade"

    window      <- builderGetObject xml castToWindow "window1"
    window `on` unrealize $ mainQuit

    closeButton <- builderGetObject xml castToButton "button2"
    closeButton `on` buttonPressEvent $ tryEvent $ do
        liftIO $ widgetDestroy window

    label       <- builderGetObject xml castToLabel "label1"
    entry       <- builderGetObject xml castToEntry "entry1"
    applyButton <- builderGetObject xml castToButton "button1"
    applyButton `on` buttonPressEvent $ tryEvent $ do
        name <- liftIO $ get entry entryText
        liftIO $ set label [ labelText := "Hello " ++ name ]

    widgetShowAll window
    mainGUI

gtk3パッケージの場合

  1. Haskell Platformをインストール
  2. ここからGTK+ 3.xのall-in-one bundleをダウンロード
  3. 中身をパスにスペースを含まないディレクトリに解凍
    • 例:C:\gtk3\
  4. その中のbinにパスを通す
    • 例:C:\gtk3\bin
  5. 以下のコマンドを実行
cabal update
cabal install gtk2hs-buildtools
cabal install gtk3
  1. glade3.14をここからインストール
    • 3.8はgtk2用なので注意
  2. 以下同様

注意点

<イベント>と<イベントモナド>

onClickedとかを使う以下の書き方はもう古いようです:

module Main where

import Graphics.UI.Gtk
import Graphics.UI.Gtk.Glade

main = do
    initGUI
    Just xml    <- xmlNew "hellogtk2hs.glade"
    window      <- xmlGetWidget xml castToWindow "window1"
    onDestroy window mainQuit
    closeButton <- xmlGetWidget xml castToButton "button2"
    onClicked closeButton $ do
        widgetDestroy window
    label       <- xmlGetWidget xml castToLabel "label1"
    entry       <- xmlGetWidget xml castToEntry "entry1"
    applyButton <- xmlGetWidget xml castToButton "button1"
    onClicked applyButton $ do
        name <- get entry entryText
        set label [ labelText := "Hello " ++ name ]
    widgetShowAll window
    mainGUI

今どきの書き方は最初のコードを参考にしてください。 新しい書き方の方が、多少書くのが面倒くさくなってくれている分、より厳しい型によるチェックをしてくれます。

基本的な書き方は、

<オブジェクト> `on` <イベント> $ tryEvent $ do <イベントモナド>

と言った感じです。

主要な<イベント>はここのEventsの節にまとめられています。

<イベントモナド>はパラメタライズされており、各種イベントに対応するイベントモナドを設けることで、 チェックを厳格にしているようです。

例えば、<イベント>buttonPressEvent :: WidgetClass self => Signal self (EventM EButton Bool)に対応する<イベントモナド>はEventM EButtonです。

このモナドの専用の関数としてeventButton :: EventM EButton MouseButtonなどがあり、型によって、このコンテキストで使える関数を限定しています。

hoogleでEventM EButton a +gtkなどと検索すると、そのモナドで使える関数の一覧が出てくるので、 その点でも非常に便利です。

ちなみに、<イベントモナド> EventM tはMonadIOのインスタンスなので、liftIOを使えば、任意のIOアクションが行えまます(行わざる負えない場合がほとんどです)。

gladeを別にインストールする必要がない件

現在はgtkにgladeがデフォルトで入っているので、gladeパッケージを入れる必要がありません。

gladeパッケージを使うときと使わない時の変更点はだいたいこんな感じです:

gladeパッケージを使うとき(以前の書き方)

import Graphics.UI.Gtk
import Graphics.UI.Gtk.Glade

main = do
    initGUI
    Just xml    <- xmlNew "hellogtk2hs.glade"
    window      <- xmlGetWidget xml castToWindow "window1"

gladeパッケージ使わないとき(今どきの書き方)

import Graphics.UI.Gtk

main = do
    initGUI
    xml    <- builderNew
    builderAddFromFile xml "hellogtk2hs.glade"
    window      <- builderGetObject xml castToWindow "window1"

gtk3

gtk3は試してみましたが、 どうやらexpose-event(gtk2hsではexposeEvent)がdeplicatedのようで、代用としてdraw eventというのがあるようです。

gtk3パッケージでexposeEvent :: WidgetClass self => Signal self (EventM EExpose Bool)を使うと実行時エラーがでるので、注意です。

今のところ、私は、

  • gtk2hsにおいてdraw eventに対応する関数を見つけることができない

  • hoogleのAPI searchにgtk3パッケージが対応していない

などの理由でgtkパッケージの方(つまりgtk2へのバインディング)を使っています。

glade

gladeも、

  • gtk2 -> glade3.8
  • gtk3 -> glade 3.14

と使い分けなければ、上手く行きませんでした。

gtk2ではVBoxとかHBoxとWidgetが分かれていましたが、gtk3ではBoxの属性として、VとHがあるようで(?)、gtk3でglade3.8で作ったVBoxとかHBoxを読み込むとエラーがでました。

おわりに

今後、diagramsパッケージを使用してgtkアプリケーションのDrawingArea上にお絵かきをすることに挑戦する予定なので、 次回の記事はそのレポートの予定です。

xyzzy haskell-mode 改良

ghci-shell-modeに少し改良を加えました。

また、hs-modeからhaskell-modeへ名前を変更しました。

変更点

  • ghciへのコマンド出力時のエンコーディングをutf8に変更
    • 日本語のディレクトリでもghci-load-fileできるようになりました
  • ghci-shell-first-errorの追加
    • ghci-load-fileした後にF10ですぐにエラー行に飛べるようになりました

ghci-shell-mode

前回していなかったので少し説明を。

ghci-mode(マイナーモード)で、.hsファイルを編集中にC-c C-iで、 xyzzy上でghciを起動することができます。 (ghci起動と同時に現在編集中のファイルをロードします。)

ghci-shell-modeはghciとやりとりするためにghciと関連付けられたバッファで用いるモードです。

前回書き忘れていましたが、ghci-shell-modeのキーバインドは以下のとおりです。

だいたいcmdとかでghciを起動した時と同じように使えると思います。

キー関数名説明
RET ghci-shell-send-input ghciにコマンドを送信
C-c C-c ghci-shell-send-interrupt ghciにinterruptシグナルを送る
Up ghci-shell-up ヒストリー前
Down ghci-shell-down ヒストリー後
Home ghci-shell-home カーソルをプロンプトの手前に移動
S-Home ghci-shell-selection-beginning-of-line カーソルをプロンプトの手前に移動して選択
TAB ghci-dabbrev dabbrev
F10 ghci-shell-first-error 最初のエラー行へジャンプ

TAB補完がdabbrevを使っておりショボイため、ゆくゆくは改良したいと考えています。 (ちゃんとgithubとか使って公開したほうがいいかなぁ)

xyzzy haskell-mode

windowsのhaskellユーザーが少ないこともあって、 今まで、haskellのプログラミング環境の構築には苦労して 来ました。

一時期、haskellerの中ではメインストリーム1linux + vimに乗り換えようとも思いましたが、敷居が高すぎて、 断念したヘタレです。

結局、現時点ではwindows + xyzzyに落ち着いています:

OS Windows 7
エディタ xyzzy 0.2.2 系列 + haskell mode
コンパイラ ghc

 

拡大

lispの文法すら把握できていないヘタレですが、 他人のコードのコピペで適当なhaskell modeを作ってみたので、 公開します:

ファイル名コピペ元(参考程度だったり、ほぼそのままだったり)
hs-mode.l http://www.shido.info/xyzzy/hs-mode.html
ghci.l http://d.hatena.ne.jp/bonotake/20060429/1146338542
hasktags.l https://github.com/matsuoka/ctags.l-for-xyzzy
キーワードファイル http://www.shido.info/xyzzy/hs-mode.html

インストール方法 2

  1. hs-mode.lghci.lhasktags.l\sitelispにいれて、キーワードファイル Haskell\etcにいれます。

  2. .xyzzy(もしくはsite-init.l)に以下を追加します。

(require "hasktags")
(require "hs-mode")
(require "ghci")
(push '("\\.hs$" . (lambda ()
                     (hs-mode)
                     (ghci-mode)
                     )) *auto-mode-alist*)
(push '("\\.lhs$" . (lambda ()
                      (hs-mode)
                      (ghci-mode)
                      )) *auto-mode-alist*)

使い方

hs-mode.l

キー関数名説明
TAB hs-tab-indent-toggle 適当にインデントの調整(トグル)
RET hs-newline-and-indent 改行して手前の行にインデントをあわせる

ghci.l

キー関数名説明
C-c C-i ghci-load-file カレントバッファをghciバッファにロード

hasktags.l

関数名説明
hasktags-make-tags-file tagsファイルを生成
hasktags-jump-tag タグジャンプ
hasktags-back-tag-jump バックタグジャンプ

.xyzzyに適当にキーバインドを設定しておくとよいです3

(global-set-key #\F4 'hasktags-jump-tag)
(global-set-key #\S-F4 'hasktags-back-tag-jump)
(global-set-key #\M-/ 'hasktags-make-tags-file)

色設定(おまけ)

おまけに画面例で使った私のxyzzy色設定を晒しておきます。 (個人的にはなかなかイケてると思っている。)

[Colors]
textColor=#c6c6c6
backColor=#211816
ctlColor=#8080
selectionTextColor=#f000000
selectionBackColor=#e800000
kwdColor1=#6dc79c
kwdColor2=#cd987e
kwdColor3=#219fad
stringColor=#5263d6
commentColor=#808080
tagColor=#face87
cursorColor=#800080
caretColor=#ff00
imeCaretColor=#ffff
linenum=#c6c6c6
reverse=#fcf8
unselectedModeLineFg=#13000000
unselectedModeLineBg=#10e8e8e8
modeLineFg=#d0d0d0
modeLineBg=#13000000

おわりに

現時点では、ghci.lでxyzzyからghciを操作するとき、ghciのTAB補完機能が使えません。

最新のghciでは:completeというコマンドが実装され、(emacsなどの)テキストエディタから TAB補完機能が使えるようになったらしいです。

そのうちxyzzyでもこの:completeコマンドを利用して、TAB補完を実装したいと思っています。


  1. the State of Haskell, 2011 Survey?

  2. haskell platformとhasktags がインストールされており、ghci.exeとhasktags.exeの存在するディレクトリに パスが通っていることを前提としています。?

  3. (注意)このキーバインドだとhaskell以外の言語のタグジャンプができなくなります。?

Dell XPS 8700のHDDをSSDに換装

先日パソコンを買い換えました。

昔からDellのパソコンが好きなのですが、SSDを搭載したモデルがないので、最近は Dell PCの購入をためらっていましたが、これまで、使用していたDospara Prime Galleria JFのファンの騒音に耐え切れず、

  • Dell XPS 8700購入 → SSDに換装

という運びとなりました。 (本当はなるべく面倒くさいことはしたくなかったんですが)

構成

before

製品名Dospara Prime Galleria JF
OS Windows 7
CPU Core-i5 2400 3.10GHz
ドライブ Intel SSD 510
GPU NVIDIA GeForce GT440

after

製品名Dell XPS 8700
OS Windows 7
CPU Core i7-4770 3.40GHz
プライマリ ドライブ Intel SSD 510
セカンダリ ドライブ 1TB HDD
GPU NVIDIA GeForce GTX645

左がDell XPS 8700、右がDospara Prime Galleria JFです。

作業概要

実際は換装というよりは、

  1. Dospara PCからSSDを取り外す(データは入ったまま)
  2. Dell PCにSSDを装着
  3. リカバリーCDからDell PCを起動
  4. SSDにOSをインストール(HDDをセカンダリ、SSDをプライマリに設定)
  5. HDDをフォーマット

という作業を行っているので、HDDのほうもセカンダリドライブとして使えるような構成になっています。

準備するもの(パソコン以外で)

  • ドライバー(ネジ回す方の)

  • OSのリカバリーCD(最近はオプションになっているので、Dell PC購入時につけておく)

  • リソースCD(Dell PC買ったらついてくるドライバとかが入ってるやつ)

  • SSDのブラケット+取り付けネジ(HDDのベイにSSDを取り付けるのに必要なものです。別途手に入れる必要があります。1

作業詳細

とりあえず、はじめに電源を抜き、両方のパソコンのカバーを取り外しました。2

Dospara PC上のSSDのデータケーブルと電源ケーブルをマザーボードから取り外しました。

Dospara PCからSSDを取り外しました。

SSDをブラケットに固定(ネジ4箇所)し、Dell PCのシャドーベイに装着(ネジ4箇所)しました。3 (肝心なところの写真を撮っていませんでしたorz)

次に、PCの電源を入れ、以下を行って、無事、換装完了しました。

  1. CDドライブにOSのリカバリーCDを入れる

  2. Windwosが立ち上がる前に、F12キーを連打->画面に従って、リカバリーCDから起動

  3. 画面に従って、SSDにOSをインストール->SSDの方のWindows 7起動

  4. マイコンピュータでHDDの方を認識しているかチェック

  5. HDDをフォーマット(ここらへん参照)

  6. 必要に応じてリソースCDからドライバをインストール

ちなみに、Prime Galleriaくんは母が使うということで、実家に送り出しました。


  1. 今回は、Intel SSD 520 2.5inch Reseller BOX を別途購入した時に付属していたものを使いました。?

  2. XPS 8700の方はマニュアルがついていませんでしたが、「Dell XPS 8700 マニュアル」あたりでググるとpdfがありますので、外し方などは、そちらを参照しました。?

  3. ここで使用したネジもIntel SSD 520 2.5inch Reseller BOXを別途購入した時についていたものを使用しました。?

Haskellの小技集1

忘れがちだけど、覚えていると役に立つHaskell小技集です。

リストから数を引く

リストの要素に適当な数を足すのは(+ 1)のように部分適用を使えばよいですが、 (- 1)は部分適用と判定されないので、以下のように書きます。

ls = map (subtract 1) [1,2,3]

実行結果

ls ==> [0,1,2]

ついsubtract関数を忘れてしまいますよね。スペルも。。

特定のコンストラクタを持つ値だけ取り出す

data Temperature = C Int | F Int

ts = [C 1, F 13, C 12, F 43, F 12, C 11]

celsius = [a | C a <- ts]

実行結果

celsius ==> [1,12,11]

catMaybesとかもこれを使って定義されてるんですね。

(-> a) がApplicativeな件

(-> Bool)がApplicativeのインスタンスであることを利用して、リストls :: [a]から 述語p1, p2 :: a -> Boolの少なくとも一方を満たす要素だけ取り出したりできます。

例えば、Stringから“大文字または数値”である要素を取り出すコードは以下のように書けます:

import Control.Applicative
import Data.Char (isUpper, isDigit)

aa = filter (liftA2 (||) isUpper isDigit) "dsUOIJfa8eOIJee49r8u389f"

実行結果

aa ==> "UOIJ8OIJ498389"

もちろん、(||)(&&)に変えれば連言もいけます。

【Haskell】 Stateモナドで乱数生成用モジュールを作る

今日もHaskellネタ。

Haskellでは避けて通れないモナドの話をしてみようと思ったのですが

・・・やっぱり難しい。

結局のところ、

モナドを理解する ≒ モナドインスタンスをたくさん覚える

だよなぁと思ったので、モナドについて解説するのはやめにして、 実際にモナドを使ったコードを書いてみます。

Stateモナドを使った、簡単な乱数ライブラリ

使い方

拡大

ソースコード

module EasyRandom (
  Random(..),
  random,
  uniform,
  permutation,
  normal,
  runRandom
) where

import Control.Monad.State.Lazy
 (state, State(..), runState)
import qualified System.Random as R
 (random, randomR, getStdRandom, StdGen(..), Random)
import qualified Data.Random.Normal as N
 (normal)

type Random a = State R.StdGen a

random :: Random Int
random = state R.random 

normal :: Random Double
normal = state N.normal

constant :: a -> Random a
constant = return

uniform :: R.Random a => (a, a) -> Random a
uniform (x,y) = state $ R.randomR (x,y)

permutation :: [a] -> Random [a]
permutation ls = permutation' (length ls) ls

permutation' :: Int -> [a] -> Random [a]
permutation' _  = return 
permutation' _ [x] = return [x]
permutation' n (x:xs) = do
  ys <- permutation' (n - 1) xs
  i <- uniform (0, n - 2)
  return (ys !! i) : update i x ys
 where
  update :: Int -> a -> [a] -> [a]
  update i x xs 
   | i < 0     = xs
   | otherwise = let (ls, (r:rs)) = splitAt i xs in ls ++ (x:rs)

runRandom :: Random a -> IO a
runRandom = R.getStdRandom . runState

以上です(汗)

おまけ