3

私は人工生命を使った実験を実行するためのフレームワークを開発しており、関数従属性の代わりに型族を使用しようとしています。型族は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"

編集:簡単な例を提示しようとして、私は自分のデザインの動機の一部を省略しました。

ユニバースクラスが果たす最大の役割は、エージェントのシリアル化と逆シリアル化であるため、エージェントクラスにリンクする必要があると思います。またreadAgentwriteAgent機能します。ただし、変更後に誤ってエージェントを作成することを忘れないようにしたかったので、それらの関数をエクスポートする代わりに、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
4

2 に答える 2

7

関数従属性を持つ型族とマルチパラメータ型クラス

タイトルの質問に答えるために、関数従属性は直感に反する傾向があるため、それらを機能させるのは頭痛の種です。型族ははるかに使いやすく、関数型プログラマーにとってははるかに直感的であるため、常に最初に使用することをお勧めします(制御できない他のクラスのインスタンスを作成するために追加の型パラメーターが絶対に必要な場合を除く) )。

あなたの場合、どちらかが必要かどうかはわかりません。つまり、問題が発生している理由だと思います。

あなたのユニバースのクラスとインスタンスは何と言っていますか?

どちらのクラス定義も、ユーザーを各ユニバースの使用に1回結び付けますが、異なるエージェントタイプのユニバースを再利用したい場合があります。

ユニバースインスタンスで何が起こっているかを見てみましょう。

  • 型族:標準的な関数セットを大規模に再利用するだけで、多くの定型文を記述してインスタンスを作成します。これは、それに対処するために特定のタイプのMyAgentを知る必要がなかったことを示しています。どの関数にもエージェントコンテキストがないようです。うーん。
  • 機能従属性:インスタンスを使用するinstance Universe SimpleUniverse a where...と、魔法のようにAgent Bugインスタンスが機能するユニバースを提供します。これは、インスタンス宣言が型を使用したaため、方程式の一致する最後で、に関するファクトを使用しなかったためaです。

これは、あなたが宇宙とエージェントをそれほど強く結びつける必要がないのではないかと私に思わせます。提案1:2つの別々であるがリンクされたクラスを持つことは可能ですか?

class Universe u where
   withAgents :: Agents a => (a -> StateT u IO a) -> String -> StateT u IO ()

ここでは、ユニバースは特定のエージェントタイプではなく、任意のエージェントタイプを受け入れる必要があると言っています。ご指摘のとおり、エージェントの名前をAgentsに変更して、すべてのエージェントタイプを共用体タイプで表すために使用することをユーザーに提案しました。

class Agents a where
   agentId :: a -> String
   liveALittle :: Universe u => a -> StateT u IO a

ここでは、エージェントタイプは、あらゆる種類のユニバースと対話できる必要があると言っています。

宇宙の性質

次のようなデフォルトの宣言を記述できると感じるという事実

defaultWithAgent :: (MyAgent u -> StateT u IO (MyAgent u)) -> String -> StateT u IO ()

または、バグに関する情報を使用しないインスタンスを宣言します。

instance Universe SimpleUniverse a where
    withAgent = ...

タイプまたはwithAgentを参照せずに書き込むことができることを提案します。ua

提案2:TheUniverseタイプのいずれかを優先して、Universeクラスを完全に破棄して、定義することはできますか?

withAgent :: (Agents a => a -> StateT TheUniverse IO a) -> String -> StateT TheUniverse IO ()

私はあなたに合うとは確信していません、または...

提案3:ユニバースのクラス制限を完全に廃止し、任意のタイプwithAgentで機能するようにします。

withAgent :: (Agents a => a -> StateT u IO a) -> String -> StateT u IO ()

他にどのような機能が必要かを知らずに何が最善かを言うのは難しいですが、うまくいけば、これらの1つが役立つかもしれません。デフォルトの定義は常に機能すると言っているようだったので、私は提案2と3のみを行います。おそらく実際には、一部の関数はユニバースクラスに含まれている必要があります。これは、それらがユニバースの構造を使用しているが、エージェントの内部詳細は使用していないためです。ユニバースを使用しているにもかかわらず、内部の詳細ではなくクラス関数のみを使用しているため、他のユーザーはエージェントに属している可能性があります。いずれにせよ、私たちは以下を持っています:

包括的な提案:

機能に必要なエージェントまたはユニバースの詳細レベルについて慎重に検討してください。両方の場合は、2つの別々のヘルパー関数にリファクタリングできる可能性があるため、関数はユニバースとエージェントの両方の内部動作を知る必要はありません。そうすれば、両方の型を持つ型クラスは必要ありません。TypeFamiliesもFunDepsも必要ありません。

于 2012-10-18T18:15:26.547 に答える
5

あなたは物事を複雑にしすぎていると思います。宇宙のあらゆる種類のアクターをサポートすることはそれほど複雑ではなく、それほど複雑ではありません。

Universe次のようにクラスを記述してください。

class Universe u where
  withAgent :: Agent a => (a -> StateT u IO a) -> String -> StateT u IO ()

aクラスヘッドのスコープに含める必要がないため、関数従属性またはマルチパラメータ型クラスを使用する必要がないことに注意してください。によってスコープに入れられAgent a => ...ます。これは基本的に、機能依存バージョンで行っていることでもあります。これはu a | u -> a、を使用してもa、実際にはクラス本体で使用されないためです。代わりに、Agent a => ...外側の影を付けaます。

于 2012-10-18T16:58:28.137 に答える