我正在研究绑定到Web服务,其中一些调用采用标志来返回其他信息。例如,通过ID获取艺术家可以使用“录音”标志返回艺术家的录音,并且通过ID获取发行也可以获取录音标记以获得该发行的所有音轨录音。但是,在获取艺术家时,您可以获得该艺术家的所有版本,但是当您实际获得特定版本时,此标志无效。
因此要将其编码到Haskell中,以下内容应该是有效的程序:
getArtistById 5678 [ WithRecordings ]
getReleaseById 37837 [ WithRecordings ]
以下应该是一个无效的程序,并且无法构建:
getReleaseById 739 [ WithReleases ]
我想到了一些解决方案,但我不确定应该采取哪些措施。想到的第一个想法是使用类型类ArtistFlag
和ReleaseFlag
,但由于一些原因,这没有意义。首先,ArtistFlag f => [f]
表示相同标志的列表,这是有缺陷的。但是,类型类意味着将来添加额外标志的能力,这也没有多大意义 - 有一定数量的标志。
我的下一个选项是每个终点标志的单独数据声明:
data ArtistFlag = ArtistWithRecordings | ArtistWithReleases
data ReleaseFlag = ReleaseWithRecordings
这有点笨拙 - 理想情况下*WithRecordings
应始终具有相同的名称,以简化程序员的API。
最后,由于缺乏知识,这是我没有探索过的唯一选项,这可能是HList可以解决的。 getArtistById应该采用一组异类的艺术家旗帜。我不知道如何在HList中表达这一点,或者即使它可以做到这一点。
很想知道打字大师要说些什么:)
我正在实施的API的实际部分位于http://musicbrainz.org/doc/XML_Web_Service/Version_2#Subqueries - 注意recordings
标志等。
答案 0 :(得分:6)
完全按照字面意思提出问题,这不能通过一个简单的列表来实现;列表中的每个元素都具有完全相同的类型,并且独立于所有其他元素,因此不可能像这样“远距离行动”。
然而,要使它在非常小的情况下工作所需的更改(实际上,需要更多它才能使它与HList一样工作)。这是一个让您的API保持基本相同但不需要任何前缀的想法:使用type-classes来表示多种类型支持的标志。
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的好机会;例如,您展示的getArtistById
和getReleaseById
函数让我担心。如果getArtistById 42 [withReleases]
与getArtistById 42 []
具有相同的类型,则无法保证您实际从前一个调用中获取了版本;在结果中可能有一个Maybe [Release]
字段或类似字段,并且要求发布的艺术家的程序必须不安全地展开此Maybe
(例如,使用fromJust
)因为有一个不变量在未在类型系统中编码的API中。
对此最好的解决方案可能取决于各种因素过于局部化和详细,但需要考虑。对我来说另外一件事是,据推测,“艺术家的所有录音”与“艺术家所有发行的所有录音”都是一样的。因此,实质上,与艺术家一起检索版本是优化;如果我们能够表达更“原始”的形式,并以最有效的方式自动检索,那将是最好的。
当然,这可能并不总是可以实现,如果您正在尝试创建直接API,它甚至可能都不可取。但这是理想的,并向我建议,如果你愿意使用类型系统来尽可能地获得最好的API,那么可能值得考虑改变焦点:)
答案 1 :(得分: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])
,或其他任何类型