6

编辑:我跟进了一个更具体的问题。谢谢这里的回答者,我认为后续问题可以更好地解释我在这里介绍的一些困惑。


TL; DR我正在努力将约束证明纳入表达式,同时在构造函数上使用具有存在约束的 GADT。(这是一个严重的嘴,对不起!)


我将一个问题提炼为以下内容。我有一个简单的 GADT,它表示点调用X和函数应用程序调用F。点X被限制为Objects

data GADT ix a where
  X :: Object ix a => a -> GADT ix a
  F :: (a -> b) -> GADT ix a -> GADT ix b

Constrained指的是其对象受某物约束的容器,Object某物。(编辑:我真正的问题涉及约束类别中Category类)Cartesian

-- | I can constrain the values within containers of kind `* -> *`
class Constrained (ix :: * -> *) where
  type Object ix a :: Constraint

-- | Here's a trivial constraint. A more interesting one might include `Typeable a`, for ex
instance Constrained (GADT ix) where
  type Object (GADT ix) a = (Constrained ix, Object ix a)

我想写一个表达式:

-- error: Could not deduce: Object ix Int arising from a use of ‘X’
ex0 :: GADT ix String
ex0 = F show (X (3 :: Int))

虽然显而易见的解决方案有效,但在构建更大的表达式时它很快就会变得冗长:

-- Typechecks, but eventually verbose
ex1 :: Object ix Int => GADT ix String
ex1 = F show (X (3 :: Int))

我认为正确的解决方案应该是这样的:

-- error: Could not deduce: Object ix Int arising from a use of ‘X’
ex2 :: Constrained ix => GADT ix String
ex2 = F show (X (3 :: Int))

但我仍然无法得到那个证明Object ix Int

我敢肯定它比我想象的要简单。我已经尝试向类实例Object中的约束族添加约束。GADT我尝试在表达式的签名中提供约束。我试过了QuantifiedConstraints,虽然,我不确定我是否完全掌握它。请有智慧的人帮助我!


可运行:

{-# LANGUAGE GADTs #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE QuantifiedConstraints #-}
{-# LANGUAGE KindSignatures #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeFamilyDependencies #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE UndecidableInstances #-}
{-# LANGUAGE AllowAmbiguousTypes #-}
{-# LANGUAGE InstanceSigs #-}

module Test where

import Data.Kind
import Data.Functor.Identity
import Data.Functor.Const

-- | I can constrain the values within containers of kind `* -> *`
class Constrained (ix :: * -> *) where
  type Object ix a :: Constraint

-- | Here's a trivial constraint. A more interesting one might include `Typeable a`, for instance
instance Constrained (GADT ix) where
  type Object (GADT ix) a = (Constrained ix, Object ix a)

-- | A demo GADT that has function application ('F'), and points ('X'). The
-- points are constrained.
data GADT ix a where
  X :: Object ix a => a -> GADT ix a
  F :: (a -> b) -> GADT ix a -> GADT ix b

-- -- Broken
-- -- error: Could not deduce: Object ix Int arising from a use of ‘X’
-- ex0 :: GADT ix String
-- ex0 = F show (X (3 :: Int))

-- Typechecks
-- but for larger programs becomes verbose, requiring many explicit constraints
ex1 :: Object ix Int => GADT ix String
ex1 = F show (X (3 :: Int))

-- -- What I want, but, it's broken
-- ex2 :: Constrained ix => GADT ix String
-- ex2 = F show (X (3 :: Int))
4

2 回答 2

1

我认为正确的解决方案应该是这样的:

-- error: Could not deduce: Object ix Int >arising from a use of ‘X’
ex2 :: Constrained ix => GADT ix String
ex2 = F show (X 3)

不幸的是,这个解决方案没有任何意义。编译器有理由指出它不知道Object ix Int此时满足,因为它所知道的只是Constrained ix可能通过.Object ix Int

量化解决方案

所以也许你想要的是一个约束,它说:“在这一点上,Object ix a我使用的所有约束都得到满足”——我们可以尝试通过量化来做到这一点:

{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE ConstraintKinds #-}

type Satisfied ix = forall a. Object ix a

ex2 :: Satisfied ix => GADT ix String
ex2 = F show (X 3)

不幸的是,这给了我们一个 GHC 错误:

• Quantified predicate must have a class or type variable head:
        forall a. Object ix a
• In the quantified constraint ‘forall a. Object ix a’
  In the type synonym declaration for ‘Satisfied’

因为Object是类型族而不是类或类型变量。

重新架构

但是……为什么是Object类型族?事实上,为什么会Constrained作为一个没有方法的无法无天的阶级存在呢?如果我们想对容器和类型的组合施加约束,Haskell 已经为我们提供了这样做的方法——只需使用实例约束!

{-# LANGUAGE MultiParamTypeClasses #-}

class Object ix a

type Constrained ix = forall a. Object ix a

因为如果我们有

instance (...<some stuff>...) => Constrained Foo where
  type Object ix a = (...<some def>...)

我们可以把它翻译成

instance (...<some stuff>..., ...<some def>...) 
  => Object ix a

这使得这个例子可以编译。

ex2 :: Constrained ix => GADT ix String
ex2 :: F show (X 3)
于 2021-11-23T10:52:50.463 回答
1

如果没有更多上下文,很难说最好的解决方案是什么,但这里有几种可能性:

完全避免约束

就目前而言,您的 GADT 似乎没有太多理由限制XObject. 也许这只是不需要?

data GADT ix a where
  X :: a -> GADT ix a
  F :: (a -> b) -> GADT ix a -> GADT ix b

相反,约束可能在需要时来自外部

咬紧牙关约束列表,但让它们更好

如果您的表达式中有许多不同的类型都需要满足相同的约束,则可以使用类似的帮助器All

ex2' :: All (Object ix) '[Int] => GADT ix String
ex2' = F show (X (3 :: Int))

除了Int;列表中还可以有更多类型 和/或您可以进行同义词约束,例如

type StdObjs ix = (Object ix Int, Object x Bool, ...)

ex2'' :: StdObjs ix => GADT ix String
ex2'' = F show (X (3 :: Int))

通过数据结构本身向后传播约束

如果您确实需要对X值进行约束,则可以在 GADT 中以另一种方式表达这一点。例如,如果该函数不是通用函数,而是已经被限制为仅接受Objects 的函数,则可以这样:

data YourFunc ix a b where
  YourFunc :: Object ix a => (a->b) -> YourFunc ix a b

show' :: Object ix Int => YourFunc ix Int String
show' = YourFunc show

这并不能直接帮助您解决您所询问的问题,但也许该功能是共享的或其他东西。你甚至可以有类似的东西

class Object ix a => InferrenceChain ix a where
  type PreElem ix a :: Type
  propInferrence :: (InferrenceChain ix (PreElem a) => r) -> r

进而

data YourFunc ix a b where
  YourFunc :: InferrenceChain ix a
                 => (PreElem a -> a) -> YourFunc (PreElem a) a

然后最后你可以证明X约束只是放在Object ix String外面并递归propInferrence。但这可能会变得非常繁琐。

于 2021-11-23T18:30:17.883 回答