带有 Haskell 梁的简单 where 子句的类型错误

Posted

技术标签:

【中文标题】带有 Haskell 梁的简单 where 子句的类型错误【英文标题】:Type error with simple where-clause with Haskell's beam 【发布时间】:2021-12-30 12:31:48 【问题描述】:

我正在尝试使用 Haskell 的梁创建一个带有简单 where 子句的选择查询。从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) $
   |  

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

【问题讨论】:

【参考方案1】:

代码的相关部分

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

如果我们查看==.,您将看到(==.) :: SqlEq expr a =&gt; a -&gt; a -&gt; 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)
      

【讨论】:

太棒了!我试过val_,但类型约束愚弄了我。 “我认为它需要这个巨大的注释,以便 ghc 可以跟踪被抽象出的后端。”事实证明确实如此,所以只要我可以将自己限制在一个后端,例如selectFoosByBar :: Word32 -&gt; SqlSelect Postgres Foo 也可以。 是的,我会尝试这样做,但我无法安装任何后端。【参考方案2】:

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

认为_fooBar fooColumnar (something) Word32

错误消息说问题出在==. 的第一个参数上,但是查看type of ==.,我认为您可以更改任何一方以达成一致。

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

【讨论】:

以上是关于带有 Haskell 梁的简单 where 子句的类型错误的主要内容,如果未能解决你的问题,请参考以下文章

在 Haskell 中使用嵌套的 `where` 子句

`where` 子句在 Haskell 中在哪里派上用场

Haskell - 在“where”中定义一个带有守卫的函数

带有 WHERE 子句的 SQL COUNT

Linq-to-Entities:带有 WHERE 子句和投影的 LEFT OUTER JOIN

带有 WHERE 子句的 UNION