2

Haxl 是一个了不起的库,但我发现的主要痛点之一是由于对数据源的每种请求都需要在 Request GADT 中使用自己的构造函数。例如,以教程中的示例为例:

data BlogRequest a where
  FetchPosts       :: BlogRequest [PostId]
  FetchPostContent :: PostId -> BlogRequest PostContent

然后,这些构造函数中的每一个都在 DataSource 实例的 match 函数中进行模式匹配和单独处理。这种风格会导致大量的样板文件用于非平凡的应用程序。以使用关系数据库的应用程序为例,其中每个表都有一个主键。可能有数百个表,所以我不想为每个表定义一个构造函数(更不用说跨表的所有可能连接......)。我真正想要的是:

data DBRequest a where
  RequestById :: PersistEntity a => Key a -> DBRequest (Maybe a)

我正在使用持久性从我的表中创建类型,但这不是关键细节——我只想对多个可能的返回类型使用单个构造函数。

尝试编写 fetch 函数时出现问题。使用 Haxl 的通常过程是在构造函数上进行模式匹配以分离出各种类型的 BlockedFetch 请求,在上面的示例中对应于如下内容:

        resVars :: [ResultVar (Maybe a)]
        args :: [Key a]
        (psArgs, psResVars) = unzip
            [(key, r) | BlockedFetch (RequestById key) r <- blockedFetches]

...然后我会(以某种方式)按其键类型对参数进行分组,并为每个组发送一个 SQL 查询。但这种方法不会,因为这里可能是对多种PersistentEntity类型(即数据库表)的请求,每个类型都是不同的类型a,因此构建列表是不可能的。我曾考虑过使用存在量化类型来解决这个问题(类似于单例库中的 SomeSing),但是如果不对每个可能的表/类型进行模式匹配,我就无法根据需要对请求进行分组。

有没有办法实现这种代码重用?

4

1 回答 1

2

我看到两种方法:

Typeable

data DBRequest a where
  RequestById :: (Typeable a, PersistEntity a) => Key a -> DBRequest (Maybe a)

或 GADT“标签”类型:

data Tag a where
    TagValue1 :: Tag Value1
    TagValue2 :: Tag Value2
    TagValue3 :: Tag Value3
    TagValue4 :: Tag Value4
    TagValue5 :: Tag Value5

data DBRequest a where
  RequestById :: PersistEntity a => Tag a => Key a -> DBRequest (Maybe a)

这些是非常相似的模式,尤其是如果您使用 GHC-8.2 和https://hackage.haskell.org/package/base-4.10.1.0/docs/Type-Reflection.html (替换Tag aTypeRep a)。

无论哪种方式,您都可以Key a使用标签进行分组。我没试过,但 dependent-map可能很方便:http ://hackage.haskell.org/package/dependent-map

于 2018-01-20T01:19:25.377 回答