Rank2Types," ...将逃避其范围" (光束)

时间:2018-06-17 09:30:16

标签: haskell haskell-beam

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

到目前为止,我有了这个功能:

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的答案,因为它有一个很好的解释为什么这是不可能的。

2 个答案:

答案 0 :(得分:1)

基本上,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本身是res类型,它独立于s,这太过分了。

答案 1 :(得分:1)

在某些特定情况下,您可以按照自己的要求进行操作:

问题在于,对于一​​般的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)

查询。

但是不会接受更有趣的查询,例如不会按原样返回整个表的查询

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

我仍然认为有可能使其工作:

您需要定义自己的类型类,并在其后添加实例 QExprToIdentity(和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)