使用 Haskell 的简单 where 子句键入错误

Type error with simple where-clause with Haskell's beam

我正在尝试使用 Haskell 的 beam 创建一个带有简单 where 子句的 select 查询。来自 https://haskell-beam.github.io/beam/user-guide/queries/select/#where-clause,我相信这会奏效:

{-# LANGUAGE DeriveAnyClass       #-}
{-# LANGUAGE DeriveAnyClass       #-}
{-# LANGUAGE DeriveGeneric        #-}
{-# LANGUAGE FlexibleInstances    #-}
{-# LANGUAGE StandaloneDeriving   #-}
{-# LANGUAGE TypeFamilies         #-}
{-# LANGUAGE TypeSynonymInstances #-}

module Lib where

import Data.Int ( Int32 )
import Data.Word ( Word32 )
import Database.Beam

data FooT f
    = Foo
    { _fooId :: Columnar f Int32
    , _fooBar :: Columnar f Word32
    }
    deriving (Generic, Beamable)

instance Table FooT where
    data PrimaryKey FooT f =
        FooId (Columnar f Int32) deriving (Generic, Beamable)
    primaryKey = FooId . _fooId

type Foo = FooT Identity
type FooId = PrimaryKey FooT Identity

deriving instance Show Foo
deriving instance Eq Foo

data BazDb f = BazDb
    { _bazFoos :: f (TableEntity FooT)
    }
    deriving (Generic, Database be)

bazDb :: DatabaseSettings be BazDb
bazDb = defaultDbSettings

selectFoosByBar :: HasQBuilder be => Word32 -> SqlSelect be Foo
selectFoosByBar bar = select $
    filter_ (\foo -> _fooBar foo ==. bar) $
        all_ $ _bazFoos bazDb

但是我遗漏了一些重要的细节,所以我得到以下编译错误:

<SNIP>/Lib.hs:42:22: error:
    • Couldn't match type ‘QGenExpr QValueContext be QBaseScope Word32’
                     with ‘Word32’
       Expected type: Word32
         Actual type: Columnar (QExpr be QBaseScope) Word32
    • In the first argument of ‘(==.)’, namely ‘_fooBar foo’
      In the expression: _fooBar foo ==. bar
      In the first argument of ‘filter_’, namely
        ‘(\ foo -> _fooBar foo ==. bar)’
    • Relevant bindings include
        foo :: FooT (QExpr be QBaseScope) (bound at src/Lib.hs:42:15)
        selectFoosByBar :: Word32 -> SqlSelect be Foo
          (bound at src/Lib.hs:41:1)
   |
42 |     filter_ (\foo -> _fooBar foo ==. bar) $
   |  

现在,错误信息本身已经很清楚了,但我不太明白 ==. 的哪一边需要修改,也不知道如何修改。或者,如果它是缺少某些扩展名或类型注释的问题。

在违规行中,bar :: Word32(根据 selectFoosByBar 的签名)。

觉得_fooBar foo是一个Columnar (something) Word32.

错误消息说问题出在 ==. 的第一个 arg,但看看 type of ==.,我想你可以改变任何一方以获得一致意见。

为什么是bar :: Word32?这具有直觉意义;您正在尝试按单词过滤,因此 arg 应该是一个单词。这表明您可能想对 _fooBar foo 做一些事情以从中获得 Word32。这可能是一个简单的函数,但更有可能是相反的:以某种方式将您的 ==. bar 操作提升到“查询表达式”space.

代码的相关部分

selectFoosByBar bar = select $
    filter_ (\foo -> _fooBar foo ==. bar) $
        all_ $ _bazFoos bazDb

如果我们查看 ==.,您会看到 (==.) :: SqlEq expr a => a -> a -> expr Bool,因此 ==. 的两边需要具有相同的类型。

现在看看 (==.) 左边的内容,我们看到 _fooBar foo :: Columnar f Word32。我们无法摆脱 Columnar,但我们可以使用 val_:

将 bar 变成 beam 可以使用的东西
selectFoosByBar bar = select $
    filter_ (\foo -> _fooBar foo ==. val_ bar) $
        all_ $ _bazFoos bazDb

注意这只有在我们删除类型注释时才有效。使用类型注释,它看起来像:

selectFoosByBar
  :: (HasQBuilder be, HasSqlEqualityCheck be Word32,
      HasSqlValueSyntax
        (Sql92ExpressionValueSyntax
           (Sql92SelectTableExpressionSyntax
              (Sql92SelectSelectTableSyntax
                 (Sql92SelectSyntax (BeamSqlBackendSyntax be)))))
        Word32) =>
     Word32 -> SqlSelect be (FooT Identity)
selectFoosByBar bar = select $
    filter_ (\foo -> _fooBar foo ==. val_ bar) $
        all_ $ _bazFoos bazDb

我认为它需要这个巨大的注释以便 ghc 可以跟踪 be 抽象出的后端。

编辑: 如果我们启用 ConstraintKinds 我们可以简化注释:

type MagicSql be = 
      HasSqlValueSyntax
        (Sql92ExpressionValueSyntax
           (Sql92SelectTableExpressionSyntax
              (Sql92SelectSelectTableSyntax
                 (Sql92SelectSyntax (BeamSqlBackendSyntax be))))) 

selectFoosByBar
  :: (HasQBuilder be, HasSqlEqualityCheck be Word32, MagicSql be Word32) =>
     Word32 -> SqlSelect be (FooT Identity)