Haskell GADTs、TypeFamilies在实现“时类型推断失败”;“混合”;

Haskell GADTs、TypeFamilies在实现“时类型推断失败”;“混合”;,haskell,type-inference,gadt,Haskell,Type Inference,Gadt,我试图用可组合逻辑创建复杂的数据结构。也就是说,数据结构有一个通用格式(本质上是一个记录,其中一些字段的类型可以更改)和一些通用函数。特定结构具有通用功能的特定实现 我试过两种方法。一种是使用类型系统(带有类型类、类型族、函数依赖项等)。另一个是创建自己的“vtable”并使用GADTs。两种方法都以相似的方式失败——这里似乎缺少一些基本的东西。或者,也许有更好的哈斯凯尔式的方法 以下是失败的“键入”代码: 下面是失败的“vtable”代码: 我不明白为什么GHC会选择“block1”,而整个事

我试图用可组合逻辑创建复杂的数据结构。也就是说,数据结构有一个通用格式(本质上是一个记录,其中一些字段的类型可以更改)和一些通用函数。特定结构具有通用功能的特定实现

我试过两种方法。一种是使用类型系统(带有类型类、类型族、函数依赖项等)。另一个是创建自己的“vtable”并使用GADTs。两种方法都以相似的方式失败——这里似乎缺少一些基本的东西。或者,也许有更好的哈斯凯尔式的方法

以下是失败的“键入”代码:

下面是失败的“vtable”代码:

我不明白为什么GHC会选择“block1”,而整个事情都显式地在ScopedTypeVariables和“forall block”下

编辑#1:添加了函数依赖项,感谢Chris Kuklewicz指出这一点。但问题依然存在

编辑#2:正如Chris所指出的,在VTable解决方案中,去掉所有“块~块状态端口”,而是到处写“块状态端口”解决了问题

编辑#3:好的,所以问题似乎是,对于每个单独的函数,GHC在参数中都需要足够的类型信息来推断所有类型,即使对于根本没有使用的类型也是如此。因此,在上述(例如)logicState的情况下,参数只给出状态,这不足以知道端口以及传入和传出类型。不要紧,它对logicState函数实际上并不重要;GHC想知道,但不能,所以编译失败。如果这确实是核心原因,那么如果GHC在编译logicState Declaration时直接投诉会更好——它似乎有足够的信息来检测那里的问题;如果我在那个位置看到“端口类型未使用/已确定”的问题,它会更清楚


编辑#4:我仍然不清楚为什么(块~块状态端口)不工作;我想我用它是为了一个意外的目的?看来这应该奏效。我同意Chris的观点,使用CPP来解决这个问题是一件令人憎恶的事情;但是写“B t r p e”(在我的真实代码中,有更多的偏执者)也不是一个好的解决方案。

我有一个关于VTable代码的单行修复程序:

{-# LANGUAGE GADTs #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TemplateHaskell #-}

module VTable where

import Control.Monad.State
import Data.Lens.Lazy
import Data.Lens.Template

-- Generic Block.
data Block state ports = Block { _blockState :: state, _blockPorts :: ports }

-- For the logic we want to use, we need some state and ports.
data LogicState = LogicState { _field :: Bool }
data LogicPorts incoming outgoing =
  LogicPorts { _input :: incoming, _output :: outgoing }

makeLenses [ ''Block, ''LogicState, ''LogicPorts ]

-- We need to describe how to reach the needed state and ports,
-- and provide a piece of the logic.
data BlockLogic block incoming outgoing where
  BlockLogic :: { logicState :: Lens state LogicState
                , logicPorts :: Lens ports (LogicPorts incoming outgoing)
                , convert :: incoming -> State block outgoing
                }
             -> BlockLogic (Block state ports) incoming outgoing

-- | The generic piece of logic.
runLogic :: forall block state ports incoming outgoing
          . block ~ Block state ports
         => BlockLogic block incoming outgoing
         -> State block outgoing
runLogic BlockLogic { .. } = do
  state <- access $ blockState
  let myField = state ^. logicState ^. field
  if myField
  then do
    ports <- access blockPorts
    let inputMessage = ports ^. logicPorts ^. input
    convert inputMessage
  else
    error "Sorry"

-- My block uses the generic logic, and also maintains additional state and ports.
data MyState = MyState { _myLogicState :: LogicState, _myMoreState :: Bool }
data MyPorts = MyPorts { _myLogicPorts :: LogicPorts Int Bool, _myMorePorts :: Int }

makeLenses [ ''MyState, ''MyPorts ]

type MyBlock = Block MyState MyPorts

-- All this work to write:
testMyBlock :: State MyBlock Bool
testMyBlock = runLogic $ BlockLogic
                         { logicState = myLogicState
                         , logicPorts = myLogicPorts
                         , convert = \x -> return $ x > 0
                         }
            , convert :: incoming -> State block outgoing
变成

            , convert :: incoming -> State (Block state ports) outgoing
然后您应该将
runLogic
的类型简化为

runLogic :: BlockLogic (Block state ports) incoming outgoing
         -> State (Block state ports) outgoing
PS:更多细节请回答下面的评论

消除“block~”不是修复的一部分。通常,“~”只在
实例a~b=>中需要。。。其中
情况

以前,如果我给函数一个
xxx::BlockLogic(块状态端口)传入传出
,那么它可以解包
convert xxx::state Block传出
。但是新的
(块状态端口)
完全不相关,它是一种新的不可知类型。编译器在名称的末尾附加一个数字以生成
block1
,然后出现在错误消息中

原始代码(两个版本)在编译器可以从给定上下文推断哪些类型方面存在问题

至于冗长,请尝试键入
。不要使用CPP和DEFINE

type B s p = BlockLogic (Block s p)

runLogic :: B s p i o -> State (Block s p) o
PPS:关于类版本问题的进一步解释。如果我用(块SP)替换块并添加您提到的功能依赖项:

class LogicBlock state ports incoming outgoing | state ports -> incoming outgoing where
  logicState :: Lens state LogicState
  logicPorts :: Lens ports (LogicPorts incoming outgoing)
  convert :: incoming -> State (Block state ports) outgoing
使用logicState锁定
状态
,但将
端口
保留为未知,使
端口

使用logicPorts锁定
端口
,但保持
状态
未知,使
端口

编译
runLogic
会在端口、端口0、端口1和状态、状态0、状态1之间遇到大量类型不匹配错误


这些操作似乎不适合放在同一个类型类中。您可以将它们分解为单独的类型类,或者在类声明中添加“state->ports,ports->state”函数依赖项。

类型类解决方案已被分解:对runLogic的调用不提供传入类型,对logicState也是如此。我不理解您所说的“对runLogic的调用不提供传入类型”。我认为我在执行“instance MyBlock…”时指定了传入类型。在调用站点,“runLogic::State block outgoing”可能是从上下文推断出来的,或者是由类型注释指定的,但是如果存在“instance LogicBlock inAlpha outgoing”和“instance LogicBlock inBeta outgoing”呢??runLogic应该分派到哪个实例?对,我的错。这就是FunDeps的用武之地。块->传入,块->传出。尽管如此,编译错误仍然存在:-(我在回答中添加了一个PPS来讨论那些剩余的错误。是的,我发现了这一点;但这是我真实代码的简化版本。在我的真实代码中,它是“块结构状态端口事件”,而不是“块状态端口”因此,作为一种解决方法,这是一个不错的解决方案(我总是可以#定义块结构状态端口事件:-).但问题仍然存在。为什么这个函数不能按原样编译?为什么消除块~…会有不同?这是一个GHC错误,还是我做错了什么?我在上面的回答中添加了PS来回答您的问题。我有点明白您的意思;每个函数都需要有足够的具体参数来确定e推导所有“依赖”所需的“独立”类型为零类型。因此,在logicState的情况下,GHC仍然觉得有义务知道端口以及传入和传出端口是什么,即使函数根本不需要它们;而且由于没有足够的参数信息来确定它们,编译失败。我想这是有道理的,但我希望GHC在编译logicState时会抱怨-t这会更清楚。拆分为几个类型类可能会有所帮助。你说的是“elimi”
runLogic :: BlockLogic (Block state ports) incoming outgoing
         -> State (Block state ports) outgoing
type B s p = BlockLogic (Block s p)

runLogic :: B s p i o -> State (Block s p) o
class LogicBlock state ports incoming outgoing | state ports -> incoming outgoing where
  logicState :: Lens state LogicState
  logicPorts :: Lens ports (LogicPorts incoming outgoing)
  convert :: incoming -> State (Block state ports) outgoing