Rank2Types,“......会逃脱它的范围”(光束)

Rank2Types, "... would escape its scope" (beam)

目前我正在尝试使用更多 beam 的可组合性。不幸的是,我无法弄清楚我正在尝试做的事情是否可行。我想做的是编写一个函数,它接受一个 Q 查询,并从查询中选择两次,一次未修改,一次包装到 "count(*)"-聚合中。

到目前为止我得到了这个函数:

runPaginatedQuery :: ( QExprToIdentity res ~ a
                     , FromBackendRow Sqlite (QExprToIdentity res)
                     , ProjectibleWithPredicate ValueContext SqliteExpressionSyntax res
                     , ProjectibleWithPredicate AnyType SqliteExpressionSyntax res
                     )
                    => Connection -> Int -> Int
                    -> (forall s. Q SqliteSelectSyntax db s res)
                    -> IO ([a], Int)
runPaginatedQuery conn limit offset q = do
  (Just count) <- runBeamSqlite conn $ runSelectReturningOne $ select $ aggregate_ (const $ countAll_) q
  l <-  runBeamSqlite conn $ runSelectReturningList $ select q
  return (l,count)

函数类型检查,但如果我尝试使用它,例如

main = do
  conn <- open "test.db"
  (ss :: [Student], n) <- runPaginatedQuery conn 20 0 (all_ (_students schoolDb))
  print n

我收到以下类型错误:

src/Main.hs:57:56: error:
    • Couldn't match type ‘res0’
                     with ‘StudentT (QExpr SqliteExpressionSyntax s)’
        because type variable ‘s’ would escape its scope
      This (rigid, skolem) type variable is bound by
        a type expected by the context:
          forall s. Q SqliteSelectSyntax SchoolDb s res0
        at src/Main.hs:57:27-81
      Expected type: Q SqliteSelectSyntax SchoolDb s res0
        Actual type: Q SqliteSelectSyntax
                       SchoolDb
                       s
                       (StudentT
                          (QExpr
                             (Database.Beam.Backend.SQL.SQL92.Sql92SelectTableExpressionSyntax
                                (Database.Beam.Backend.SQL.SQL92.Sql92SelectSelectTableSyntax
                                   SqliteSelectSyntax))
                             s))
    • In the fourth argument of ‘runPaginatedQuery’, namely
        ‘(all_ (_students schoolDb))’
      In a stmt of a 'do' block:
        (ss :: [Student], n) <- runPaginatedQuery
                                  conn 20 0 (all_ (_students schoolDb))
      In the expression:
        do conn <- open "test.db"
           (ss :: [Student], n) <- runPaginatedQuery
                                     conn 20 0 (all_ (_students schoolDb))
           print "hi"
   |
57 |   (ss :: [Student], n) <- runPaginatedQuery conn 20 0 (all_ (_students schoolDb))
   |                                                        ^^^^^^^^^^^^^^^^^^^^^^^^^

我无法理解错误消息。

编辑: 看来我想要实现的目前是不可能的,所以我接受了@chi的回答,因为它有很好的解释为什么不可能。

基本上,runPaginatedQuery 要求查询的结果类型 res 不依赖于 s。但是你的。

 Expected type: Q SqliteSelectSyntax SchoolDb s res0
    Actual type: Q SqliteSelectSyntax
                   SchoolDb
                   s
                   (StudentT (QExpr
                   (Database.Beam.Backend.SQL.SQL92.Sql92SelectTableExpressionSyntax
                   (Database.Beam.Backend.SQL.SQL92.Sql92SelectSelectTableSyntax
                   SqliteSelectSyntax))
                   s))
               -- ^^^

我看不到任何简单的修复方法。 runPaginatedQuery 的类型看起来过于严格。

直觉上,它应该采用类似 (forall s. Q SqliteSelectSyntax db s (F s)) 的形式,其中 F 是某种类型级函数,需要使 QExprToIdentity (F s) 独立于 s。然而,我们无法在 Haskell.

中对类型级函数 F 进行普遍量化

您的代码几乎要求这样做,但隐式要求 F s 本身是一种独立于 s 的类型 res,这太多了。

在某些特定情况下,您可以按照您的要求去做:

问题是,对于一般人来说 res 很难做任何事情。 但实际上,您会聚合一些 table。所以如果你按摩类型, 它将进行类型检查并解决问题,因为我们可以将 s 放到您 res.

的位置
runPaginatedQuery
    :: (Beamable table, FromBackendRow Sqlite (table Identity))
    => Connection -> Int -> Int
    -> (forall s. Q Sqlite db s (table (QExpr Sqlite s)))
    -> IO ([table Identity], Int)

此 `runPaginatedQuery 将接受您的示例

all_ (_students schoolDb)

查询。

但不会接受更有趣的查询,例如 return 整个 table

do s <- all_ (_students schoolDb)
   return (studentFoo s)

我仍然认为有可能让它发挥作用:

您需要定义自己的类型-class,实例如下 QExprToIdentity (and ThreadRewritable等的结构)。

我并不是说这很容易,但我很确定这是可能的。


顺便说一句,下次请提供一个更完整的示例,以便尝试提供帮助的人可以快速开始:

这是您可以加载到 GHCi 中的完整示例:

{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeFamilies #-}

import Database.Beam
import Database.Beam.Sqlite
import Database.Beam.Sqlite.Connection
import Database.Beam.Sqlite.Syntax
import Database.SQLite.Simple


runPaginatedQuery
    :: (Beamable table, FromBackendRow Sqlite (table Identity))
    => Connection -> Int -> Int
    -> (forall s. Q Sqlite db s (table (QExpr Sqlite s)))
    -> IO ([table Identity], Int)
runPaginatedQuery conn limit offset q = do
  Just count <- runBeamSqlite conn $ runSelectReturningOne $ select $ aggregate_ (const $ countAll_) q
  l <-  runBeamSqlite conn $ runSelectReturningList $ select q
  return (l,count)

data StudentT f = Student
    { studentId :: C f Int
    }
  deriving (Generic, Beamable)

type Student = StudentT Identity

instance Table StudentT where
    newtype PrimaryKey StudentT f  = StudentId (C f Int)
      deriving (Generic, Beamable)

    primaryKey = StudentId . studentId

data SchoolDb f = SchoolDb
    { _students :: f (TableEntity StudentT) }
  deriving (Generic, Database be)

schoolDb :: DatabaseSettings be SchoolDb
schoolDb = defaultDbSettings

main = do
  conn <- open "test.db"
  (ss :: [Student], n) <- runPaginatedQuery conn 20 0 (all_ (_students schoolDb))
  print (n :: Int)