将来課題

  • 簡潔に記述したい。TemplateHaskellとかつかう?
  • レコードに型変数持たせて多相にしたい
  • Maybeを意識しないといけない箇所を減らしたい(Control.Lens.Prismをつかえば良いらしい?)
  • Object型以外の型にも特異メソッドを追加できる型をいろいろ作りたい(Objectクラスにすればよい?)

その他、アイデア募集中です!

試み

https://github.com/nushio3/practice/tree/master/duck

{-# LANGUAGE DeriveDataTypeable #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE Rank2Types #-}
{-# LANGUAGE TypeFamilies #-}
module Data.Object where

import qualified Data.Map as Map
import           Data.Dynamic
import           Control.Lens

-- これが特異メソッドを持てるオブジェクトです。
newtype Object = Object (Map.Map TypeRep Dynamic)
  deriving (Show, Typeable)

-- レコード名(キー)としては文字列とかじゃなく型をつかいます。これは後述するように、
-- 名前衝突を回避するためです。このレコード(キー)に対応する値を型族として持たせます。
class Typeable a => KeyType a where
  type ValType a :: *

-- レコードはObjectから値型へのレンズとします。値をMaybeでくるんでおくことで、
-- 値が未定義の場合も扱えるようにしましょう。
type Record kt = Lens Object Object (Maybe (ValType kt)) (Maybe (ValType kt))

-- 空のオブジェクトです
empty :: Object
empty = Object $ Map.empty

-- レコードを作るヘルパ関数です。
mkRecord :: forall kt. (KeyType kt, Typeable (ValType kt)) => kt -> Record kt
mkRecord k1 = lens gettr settr
  where
    gettr :: Object -> Maybe (ValType kt)
    gettr (Object map0) = Map.lookup k map0 >>= fromDynamic
    settr :: Object -> (Maybe (ValType kt)) -> Object
    settr (Object map0) Nothing  = Object $ Map.delete k map0
    settr (Object map0) (Just x) = Object $ Map.insert k (toDyn x) map0
    k :: TypeRep
    k = typeOf k1

次に、飛行物体(Flying Objects)に関するレコードをいくつか定義します。ちょっと繰り返しが多いですが、試験版なので我慢してね・・・。

module Data.Object.Flying (speed, sound) where

import Data.Dynamic
import Data.Object


data Unidentified = Unidentified deriving Typeable
instance KeyType Unidentified where type ValType Unidentified = Bool
unidentified :: Record Unidentified
unidentified = mkRecord Unidentified

data Speed = Speed deriving Typeable
instance KeyType Speed where type ValType Speed = Double
speed :: Record Speed
speed = mkRecord Speed

data Sound = Sound deriving Typeable
instance KeyType Sound where type ValType Sound = String
sound :: Record Sound
sound = mkRecord Sound


さて、特異メソッドとDuck Typingの例です。

import           Control.Lens
import           Control.Monad
import           Data.List (isInfixOf)
import           Data.Object
import           Data.Object.Flying
import qualified Data.Object.Wav as Wav
import           Data.Maybe
import qualified Data.Vector as V

x1,x2,x3,x4,x5, santa :: Object
x1 = empty -- 空の物体

x2 = x1 & speed .~ Just 120 -- 飛行速度を追加

x3 = x2 & sound .~ Just "quack! quack. quack? quack..." -- 鳴き声追加

x4 = x3 & over speed (fmap (*2)) -- あひるを加速!

-- 音声データをWavフォーマットで追加。同じsoundという名前のレコードが複数ありますが、きちんと区別されます。
x5 = x2 & Wav.sound .~ Just (V.generate 44100 (\i -> floor (sin(2*pi*800 * fromIntegral i/44100))))

-- NOARDレーダーに反応あり!
santa = empty
 & speed .~ Just 64000
 & sound .~ Just "Merry Xmas! Hohohoho!"

main = do
  print x1
  print $ x1 ^. speed
  print x2
  print $ x2 ^. speed
  print $ x3 ^. sound
  let speeders :: [Object]
      speeders = do                       -- リストモナド
        x <- [x1, x2, x3, x4, x5, santa]  -- 飛行物体の一覧
        sp <- maybeToList $ x ^. speed    -- speedレコードを取り出す
        snd <- maybeToList $ x ^. sound   -- 音声データを取り出す
        -- レコードを持たないものはListモナドの機能により、
        -- 実行時エラーを出すことなく除外されます。
        guard $ sp < 200                  -- 速度が200以下
        guard $ "quack" `isInfixOf` snd   -- 鳴き声はガアガア
        return x
  print speeders

速度200以下で飛び、ガアガアと鳴く物体がいたら、それはあひるのはずです。あなたは無事あひるを見つけられましたか?

Haskellで特異メソッド

(この記事はHaskell Advent Calendar 2012の記事です。)

HaskellでReal World Problemを解いていると、オブジェクト指向でいうところのオブジェクトを作りたくなることはよくあります。Haskellでは、だいたい代数データ型とレコード構文がその役割を担ってきました。Haskellはレコードを自由に扱えなかったのですが、Lensの登場により、レコードは苦手科目から超得意科目へと変わりつつあります。

それでも、データ型は何だか融通が聞かない奴、という感じがします。開発途上で新しいレコードを追加したくなることはよくありますが、そもそもデータ型を定義しなおすのは面倒ですし、データ型を定義しなおしたら、その型の値を作っているところをすべて書き直さねばなりません。 あまつさえリアルワールドでは、全ての値についてレコードの値が得られないこともあるし、レコードを持っている値と持っていない値をいっしょのリストに入れて扱いたいときもあったりします(動物学をやっていて、技術の進歩により新たに動物の角の長さが測定できるようになったとします。でも、きりんは背が高すぎて未測定だったり、兎には角がなかったりしますよね。)

hasのアプローチのように、HasHornというインスタンスメソッドを持つ型クラスを作る、という手もあります。でも、これだと動物ごとに違う型を作ることになり、動物をぜんぶ同じリストに入れることはできません。

型クラスが、型に対するオープンなインターフェイスであるように、メソッドを持つ値に対するオープンなインターフェイスは作れないものでしょうか?具体的には、Duck Typingや、特異メソッドを、Haskellの型システムを活かす形で、実現できないでしょうか?

cabal大掃除

いろんなことがあった今年ももうすぐ終わりです。みなさんのcabalにもこの一年の勉強、チャレンジの成果が積もっているのではないでしょうか。反面、衝突を起こして新しいパッケージが入れられず壊れたままになってしまっているかもしれませんね。折しもマヤ歴によると今年は5126年に一度のリセットの年だそうです。以下のパッケージを使って、cabalの大掃除をしてみませんか?

https://github.com/nushio3/cabal-reset


(この記事はHaskell Advent Calendar 2012の時間稼ぎのための記事です。)

はじめてのFPGAプログラミング

「価格性能比と消費電力効率を極限まで追求した超並列計算機システムの実用化に関する研究」第一回シンポジウムというのに行って、JavaRockという言語でFPGAプログラミングを学び、Pongみたいなゲームを作ってきました。

ソース: https://github.com/nushio3/practice/tree/master/JavaRock

回路記述は大変だ、というのはよく聞きます。で、解決策は大別して

  1. 好みのプログラミング言語内の操作できるデータ構造として回路を表す
  2. 好みのプログラミング言語のプログラムが回路に翻訳されるようにする
  3. 抽象化力の高い新しい回路記述言語を作る

らしく、JavaRockはこのうちJavaで書かれたプログラムをVHDLに翻訳するという第二のアプローチです。Javaのマルチスレッドプログラムを書くと、回路上の並列処理になるというすごい発想です。
史上初のJavaRockからFPGAプログラムに入った人類という称号を頂きました。普通のプログラミング言語を書いて回路を作れるのは確かに入りやすい道でした。逆にじゃあそれは回路作成経験を積んだといえるのかと突っ込まれると心許ないですが、ボタンが「押された瞬間」を検出するのが難しい(Java1行が1クロックなので、押された瞬間に検出ロジックのところに居るとは限らない)とか、2つのスレッドでデータをやりとりできるよう余裕を見るとか、ループの内容を充実させるほど1周するのが遅くなるとか、ビット演算重要とか・・・。

何の気なしに2の冪じゃない数で割り算するプログラムを書いて「ええ、最近はそんなのがコンパイル通って動くの?」と経験者を驚かせてしまったり、「ああ、そこはまだ作ってないからこう書いてね・・・」とか教えてもらったのが印象的で、先入観なしに普通のプログラムを書くことでフィードバックができたのが一番の貢献かと思います。


三好さん&セミナー参加者の皆様、ありがとうございました。