Haxl中的代码重用 - 避免每个请求类型的GADT构造函数 [英] Code reuse in Haxl - avoiding GADT constructor-per-request-type
问题描述
data BlogRequest a where
FetchPosts :: BlogRequest [PostId]
FetchPostContent :: PostId - > BlogRequest PostContent
然后,每个这些构造函数都在DataSource的match函数中分别进行模式匹配和处理实例。这种风格导致了很多不平凡的应用程序的样板。举例来说,一个使用关系数据库的应用程序,其中每个表都有一个主键。可能有数百个表,所以我不想为每个表定义一个构造函数(更不用说跨表中所有可能的连接...)。我真正想要的是这样的:
data DBRequest a where
RequestById :: PersistEntity a =>关键a - > DBRequest(也许a)
我使用持久性从我的表中创建类型,但那不是'关键的细节 - 我只想使用一个构造函数来处理多种可能的返回类型。
尝试编写提取函数时出现问题。 Haxl通常的过程是在构造函数上进行模式匹配以分离出各种类型的BlockedFetch请求,这些请求在上面的例子中对应于类似这样的内容:
<$ p $ (key,r)
(psArgs,psResVars)= unzip
[(key,r) | BlockedFetch(RequestById key)r < - blockedFetches]
...然后我会(以某种方式)按键类型的参数,并为每个组分配一个SQL查询。但是这种方法并不是因为这里可能需要多个 PersistentEntity
类型(即数据库表),每个类型都是不同的类型 a
,所以建立这个列表是不可能的。我想过使用一种存在量化的类型来解决这个问题(类似于Singletons库中的SomeSing),但是我没有办法根据需要对请求进行分组,而没有在每个可能的表/类型上匹配模式。
有什么办法可以实现这种代码重用?
我看到两种方法:
$ b
Typeable
:
data DBRequest a where
RequestById ::(Typeable a,PersistEntity a)=>关键a - > DBRequest(也许a)
或GADT标记类型:
数据标记a其中
TagValue1 :: Tag Value1
TagValue2 :: Tag Value2
TagValue3 :: Tag Value3
TagValue4 :: Tag Value4
TagValue5 :: Tag Value5
data DBRequest a where
RequestById :: PersistEntity a =>标签a =>关键a - > DBRequest(也许a)
这些模式非常相似,特别是如果使用GHC-8.2,
with https://hackage.haskell.org /package/base-4.10.1.0/docs/Type-Reflection.html
(将标记为>
TypeRep a无论使用哪种方式,您都可以使用标签将
Key a
组合在一起。 我还没试过,但
dependent-map
可能会很方便: http://hackage.haskell.org/package/dependent-map
Haxl is an amazing library, but one of the major pain points I find is caused by the fact that each sort of request to the data source requires its own constructor in the Request GADT. For example, taking the example from the tutorial:
data BlogRequest a where
FetchPosts :: BlogRequest [PostId]
FetchPostContent :: PostId -> BlogRequest PostContent
Then each of these constructors are pattern matched on and processed separately in the match function of the DataSource instance. This style results in a lot of boilerplate for a non-trivial application. Take for example an application using a relational database, where each table has a primary key. There may be many hundreds of tables so I don't want to define a constructor for each table (let alone on all the possible joins across tables...). What I really want is something like:
data DBRequest a where
RequestById :: PersistEntity a => Key a -> DBRequest (Maybe a)
I'm using persistent to create types from my tables, but that isn't a critical detail -- I just want to use a single constructor for multiple possible return types.
The problem comes when trying to write the fetch function. The usual procedure with Haxl is to pattern match on the constructor to separate out the various types of BlockedFetch requests, which in the above example would correspond to something like this:
resVars :: [ResultVar (Maybe a)]
args :: [Key a]
(psArgs, psResVars) = unzip
[(key, r) | BlockedFetch (RequestById key) r <- blockedFetches]
...then I would (somehow) group the arguments by their key type, and dispatch a SQL query for each group. But that approach won't because here may be requests for multiple PersistentEntity
types (i.e. database tables), each of which is a different type a
, so building the list is impossible. I've thought of using an existentially quantified type to get around this issue (something like SomeSing in the singletons library), but then I see no way to group the requests as required without pattern matching on every possible table/type.
Is there any way to achieve this sort of code reuse?
I see two approaches:
Typeable
:
data DBRequest a where
RequestById :: (Typeable a, PersistEntity a) => Key a -> DBRequest (Maybe a)
or GADT "tag" type:
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)
These are very similar patterns, especially If you use GHC-8.2,
with https://hackage.haskell.org/package/base-4.10.1.0/docs/Type-Reflection.html
(replace Tag a
with TypeRep a
).
Either way, you can group Key a
using the tag. I haven't tried, but
dependent-map
might be handy: http://hackage.haskell.org/package/dependent-map
这篇关于Haxl中的代码重用 - 避免每个请求类型的GADT构造函数的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!