Haxl中的代码重用 - 避免每个请求类型的GADT构造函数 [英] Code reuse in Haxl - avoiding GADT constructor-per-request-type

查看:190
本文介绍了Haxl中的代码重用 - 避免每个请求类型的GADT构造函数的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

  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屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆