2012-01-18 3 views
2

私はいくつかの呼び出しが追加の情報を返すためのフラグを取るWebサービスへのバインディングに取り組んでいます。例えば、IDを介してアーティストを取得することは、アーティストのレコーディングを返すために 'レコーディング'フラグを取ることができ、IDによるリリースを取得することは、そのリリースのトラックのすべてのレコーディングを取得するためにレコーディングフラグを取ることもできる。ただし、アーティストを取得する際には、そのアーティストによるすべてのリリースを取得できますが、実際に特定のリリースを取得した場合は無効です。どのようにフラグを持つ型システムを、時には共有され、時には排他的な関数に使うことができますか?

だから、Haskellのにそれをエンコードするために、次は有効なプログラムでなければなりません:

getArtistById 5678 [ WithRecordings ] 
getReleaseById 37837 [ WithRecordings ] 

し、次は、無効なプログラムであっても、およびビルドに失敗する必要があります。

getReleaseById 739 [ WithReleases ] 

私はいくつかの解決策を考えましたが、どちらを取るべきかわかりません。最初に考えたのは、タイプクラスArtistFlagReleaseFlagを使用することでしたが、これはいくつかの理由で意味をなさないものです。まず、ArtistFlag f => [f]は、脆弱な同じフラグのリストを意味します。しかし、型クラスは将来的に余分なフラグを追加することを意味します。これはあまり意味がありません。有限個のフラグがあります。

私の次のオプションは、各エンドポイントフラッグのための独立したデータ宣言した:

data ArtistFlag = ArtistWithRecordings | ArtistWithReleases 
data ReleaseFlag = ReleaseWithRecordings 

これはで動作するようにちょっと不格好です - プログラマのためのAPIを簡素化するために、理想的*WithRecordingsは常に同じ名前を持つ必要があります。

最後に、これは私が知識が不足しているために調査していない唯一のオプションですが、これはHListによって解決できる可能性があります。 getArtistByIdは異種のアーティストフラグセットを取るべきです。私はまだHListでこれを表現する方法はまだ分かっていません。

達人は私が実装してるAPIの:)

実際の部品がhttp://musicbrainz.org/doc/XML_Web_Service/Version_2#Subqueriesである言っているかのタイピングを聞くのが大好きだ - 完全にあなたの質問を取るなど、recordingsフラグに気づく

+0

"無効なプログラム"の例で 'getReleaseById 739 [WithReleases]'行しか持たないと思います。 – ehird

+0

@ええ、ええ、それをキャッチするために感謝します。 – ocharles

+0

[Type Directed Name Resolution](http://hackage.haskell.org/trac/haskell-prime/wiki/TypeDirectedNameResolution)は、この問題に対する(まだ実装されていない)解決策です。 –

答えて

6

を文字通り、これは簡単なリストでは達成できません。リストの各要素はまったく同じ型であり、他のすべての要素とは独立しているため、このような「遠くの行為」の可能性はありません。

しかし、それを非常に小さくするために必要な変更(実際には、HListのようなもので動作させるためにはるかに必要です)。あなたのAPIを基本的に同じに保つアイデアはありますが、プレフィックスを一切必要としません。複数の型がサポートするフラグを表現するために型クラスを使用してください。

data ArtistFlag = ArtistWithRecordings | ArtistWithReleases 
data ReleaseFlag = ReleaseWithRecordings 

withReleases :: ArtistFlag 
withReleases = ArtistWithReleases 

class HasRecordingsFlag flag where 
    withRecordings :: flag 

instance HasRecordingsFlag ArtistFlag where 
    withRecordings = ArtistWithRecordings 

instance HasRecordingsFlag ReleaseFlag where 
    withRecordings = ReleaseWithRecordings 

getArtistById :: Int -> [ArtistFlag] -> IO (Maybe Artist) 
getReleaseById :: Int -> [ReleaseFlag] -> IO (Maybe Release) 

ユーザコードの唯一の変更点は、Withが小文字になります。これは簡単な解決策であり、おそらく最も簡単で最も慣用的な方法で問題を解決することができます。特に、接続している外部APIの制約があります。

ただし、状況によっては、これがAPIの再構築を行う良い機会になりそうです。例えば、getArtistByIdgetReleaseByIdの機能は、私が心配していることを示しています。getArtistById 42 [withReleases]のタイプがgetArtistById 42 []の場合、以前の呼び出しから実際にリリースを取得するという静的な保証はありません。おそらくMaybe [Release]フィールドやそれに類する結果があり、リリースでアーティストをリクエストしているプログラムは、Maybe(例えば、fromJust)の値をunsafely unwrapする必要があります。これは、type-システム。

これに対する最適な解決策は、おそらく、ここに入るためにはローカライズされ詳細なさまざまな要因に依存しますが、それについて考えることがあります。もう一つのことは、おそらく「アーティストによるすべての録音」は、「アーティストによるすべての録音のすべての録音」と同じことです。つまり、本質的にアーティストと一緒にリリースを取得するのは、の最適化です。より「原始的な」形式を表現し、それが最も効率的な方法で自動的に検索できるようにするのが最もよいでしょう。

もちろん、これは必ずしも達成可能ではないかもしれません。直接APIを作成しようとしている場合、それは望ましくないかもしれません。しかし、これは理想的なことであり、可能な限りすばらしいAPIを得るために型システムで喜んで使えるなら、それは焦点の変更を検討する価値があるかもしれないと私に示唆しています:)

1

私は別の"flags"引数は常に静的な定数であるという前提に基づいて(おそらくは失敗!その前提が真実であれば、代わりにタイプ・レベルでそれを渡すことによって多くのレバレッジを得ることができます。ここでは非常に単純な例です:

class GetArtist a where getArtistById :: Int -> a 
class GetRelease a where getReleaseById :: Int -> a 

data Artist = Artist {- whatever data you store about an artist goes here -} 
data Recording = Recording {- data about recordings -} 
data Release = Release {- data about releases -} 

その後、あなたは、単に(もちろんの実装と一緒に)有効な戻り値の型を列挙することができます

instance GetArtist Artist where -- ... 
instance GetArtist [Recording] where -- ... 
instance GetArtist [Release] where -- ... 
instance (GetArtist a, GetArtist b) => GetArtist (a, b) where 
    getArtistById n = (getArtistById n, getArtistById n) 
-- maybe a similar instance for triples 
instance GetRelease Release where -- ... 
instance GetRelease [Recording] where -- ... 
-- no instance GetRelease [Release] 
instance (GetRelease a, GetRelease b) => GetRelease (a, b) where 
    getReleaseById n = (getReleaseById n, getReleaseById n) 

例えば、getArtistByIdを使用するには、単にgetArtistById 5678とし、必要なタイプ(Artistまたは(Artist, [Recording])など)で結果を使用します。

+0

これらのインスタンスのいくつかは '{ - #LANGUAGE FlexibleInstances# - }'を必要とします。代わりに、 'newtype Recordings = Recordings [Recording]'と 'instance Recordings where ...'の代わりに 'instance [Recording] where ...'などの代わりに使うことができます。 – dave4420

関連する問題