私は人工生命を使った実験を実行するためのフレームワークを開発しており、関数従属性の代わりに型族を使用しようとしています。型族はHaskellersの間で好まれるアプローチのようですが、私は関数従属性がより適しているように見える状況に遭遇しました。私はトリックを逃していますか?これが型族を使ったデザインです。(このコードは正常にコンパイルされます。)
{-# LANGUAGE TypeFamilies, FlexibleContexts #-}
import Control.Monad.State (StateT)
class Agent a where
agentId :: a -> String
liveALittle :: Universe u => a -> StateT u IO a
-- plus other functions
class Universe u where
type MyAgent u :: *
withAgent :: (MyAgent u -> StateT u IO (MyAgent u)) ->
String -> StateT u IO ()
-- plus other functions
data SimpleUniverse = SimpleUniverse
{
mainDir :: FilePath
-- plus other fields
}
defaultWithAgent :: (MyAgent u -> StateT u IO (MyAgent u)) -> String ->
StateT u IO ()
defaultWithAgent = undefined -- stub
-- plus default implementations for other functions
--
-- In order to use my framework, the user will need to create a typeclass
-- that implements the Agent class...
--
data Bug = Bug String deriving (Show, Eq)
instance Agent Bug where
agentId (Bug s) = s
liveALittle bug = return bug -- stub
--
-- .. and they'll also need to make SimpleUniverse an instance of Universe
-- for their agent type.
--
instance Universe SimpleUniverse where
type MyAgent SimpleUniverse = Bug
withAgent = defaultWithAgent -- boilerplate
-- plus similar boilerplate for other functions
ユーザーに定型文の最後の2行を書かせないようにする方法はありますか?以下のfundepsを使用したバージョンと比較してください。これにより、ユーザーにとっては簡単になります。(UndecideableInstancesの使用は危険信号である可能性があります。)(このコードも正常にコンパイルされます。)
{-# LANGUAGE MultiParamTypeClasses, FunctionalDependencies, FlexibleInstances,
UndecidableInstances #-}
import Control.Monad.State (StateT)
class Agent a where
agentId :: a -> String
liveALittle :: Universe u a => a -> StateT u IO a
-- plus other functions
class Universe u a | u -> a where
withAgent :: Agent a => (a -> StateT u IO a) -> String -> StateT u IO ()
-- plus other functions
data SimpleUniverse = SimpleUniverse
{
mainDir :: FilePath
-- plus other fields
}
instance Universe SimpleUniverse a where
withAgent = undefined -- stub
-- plus implementations for other functions
--
-- In order to use my framework, the user will need to create a typeclass
-- that implements the Agent class...
--
data Bug = Bug String deriving (Show, Eq)
instance Agent Bug where
agentId (Bug s) = s
liveALittle bug = return bug -- stub
--
-- And now my users only have to write stuff like...
--
u :: SimpleUniverse
u = SimpleUniverse "mydir"
編集:簡単な例を提示しようとして、私は自分のデザインの動機の一部を省略しました。
ユニバースクラスが果たす最大の役割は、エージェントのシリアル化と逆シリアル化であるため、エージェントクラスにリンクする必要があると思います。またreadAgent
、writeAgent
機能します。ただし、変更後に誤ってエージェントを作成することを忘れないようにしたかったので、それらの関数をエクスポートする代わりに、withAgent
すべてを処理する関数を提供します。このwithAgent
関数は、エージェントで実行される関数と、プログラムを実行するエージェントの名前(一意のID)の2つのパラメーターを取ります。そのエージェントを含むファイルを読み取り、プログラムを実行し、更新されたエージェントをファイルに書き戻します。(代わりに、readAgent関数とwriteAgent関数をエクスポートすることもできます。)
Daemon
各エージェントにCPUの公平なシェアを与える責任があるクラスもあります。したがって、デーモンのメインループ内で、現在のエージェントのリストをユニバースに照会します。次に、エージェントごとに、そのエージェントのプログラムwithAgent
を実行する関数を呼び出します。liveAlittle
デーモンは、エージェントがどのタイプであるかを気にしません。
withAgent
この関数のもう1つのユーザーは、エージェント自体です。エージェントのliveALittle
関数内で、交配相手となる可能性のあるパートナーを探すために、エージェントのリストをユニバースに照会する場合があります。関数を呼び出して、withAgent
ある種の嵌合関数を実行します。明らかに、エージェントは同じ種(タイプクラス)の別のエージェントとのみ交配できます。
編集:これが私が使用すると思う解決策です。型族や関数従属性ではありませんが、コンパイラーがどちらliveALittle
を呼び出すかを認識できるように、何かを行う必要があります。私がそれを行った方法は、ユーザーliveALittle
にパラメーターとして正しいものを提供させることです。
{-# LANGUAGE DeriveGeneric #-}
import Control.Monad.State (StateT)
import Data.Serialize (Serialize)
import GHC.Generics (Generic)
class Agent a where
agentId :: a -> String
liveALittle :: Universe u => a -> StateT u IO a
-- plus other functions
class Universe u where
-- Given the name of an agent, read it from a file, and let it run.
withAgent :: (Agent a, Serialize a) =>
(a -> StateT u IO a) -> String -> StateT u IO ()
-- plus other functions
-- This method will be called by a daemon
daemonTask :: (Universe u, Agent a, Serialize a) =>
(a -> StateT u IO a) -> StateT u IO ()
daemonTask letAgentLiveALittle = do
-- do some stuff
withAgent letAgentLiveALittle "a"
-- do some other stuff
data SimpleUniverse = SimpleUniverse
{
mainDir :: FilePath
-- plus other fields
}
instance Universe SimpleUniverse where
withAgent = undefined -- stub
-- plus implementations for other functions
--
-- And now my users only have to write stuff like...
--
data Bug = Bug String deriving (Show, Eq, Generic)
instance Serialize Bug
instance Agent Bug where
agentId (Bug s) = s
liveALittle bug = return bug -- stub