将来課題
- 簡潔に記述したい。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の時間稼ぎのための記事です。)
自動微分なるものをしてみんとてするなり
http://ja.pk.paraiso-lang.org/Hackage/ad/main
そうこうするうちに http://hackage.haskell.org/package/dvda なんてのも視界に入ってきたからこっちも試さんとなー
はじめてのFPGAプログラミング
「価格性能比と消費電力効率を極限まで追求した超並列計算機システムの実用化に関する研究」第一回シンポジウムというのに行って、JavaRockという言語でFPGAプログラミングを学び、Pongみたいなゲームを作ってきました。
ソース: https://github.com/nushio3/practice/tree/master/JavaRock
回路記述は大変だ、というのはよく聞きます。で、解決策は大別して
らしく、JavaRockはこのうちJavaで書かれたプログラムをVHDLに翻訳するという第二のアプローチです。Javaのマルチスレッドプログラムを書くと、回路上の並列処理になるというすごい発想です。
史上初のJavaRockからFPGAプログラムに入った人類という称号を頂きました。普通のプログラミング言語を書いて回路を作れるのは確かに入りやすい道でした。逆にじゃあそれは回路作成経験を積んだといえるのかと突っ込まれると心許ないですが、ボタンが「押された瞬間」を検出するのが難しい(Java1行が1クロックなので、押された瞬間に検出ロジックのところに居るとは限らない)とか、2つのスレッドでデータをやりとりできるよう余裕を見るとか、ループの内容を充実させるほど1周するのが遅くなるとか、ビット演算重要とか・・・。
何の気なしに2の冪じゃない数で割り算するプログラムを書いて「ええ、最近はそんなのがコンパイル通って動くの?」と経験者を驚かせてしまったり、「ああ、そこはまだ作ってないからこう書いてね・・・」とか教えてもらったのが印象的で、先入観なしに普通のプログラムを書くことでフィードバックができたのが一番の貢献かと思います。
三好さん&セミナー参加者の皆様、ありがとうございました。