简体   繁体   English

实现“mixins”时GADT,TypeFamilies类型推断失败

[英]GADTs, TypeFamilies type inference failure when implementing “mixins”

I am trying to create complex data structures with composable logic. 我试图用可组合逻辑创建复杂的数据结构。 That is, the data structure has a generic format (essentially, a record with some fields whose type can change) and some generic functions. 也就是说,数据结构具有通用格式(实质上是具有某些类型可以更改的字段的记录)和一些通用函数。 Specific structures have specific implementation of the generic functions. 具体结构具有通用功能的具体实现。

There are two approaches I tried. 我试过两种方法。 One is to use the type system (with type classes, type families, functional dependencies etc.). 一种是使用类型系统(具有类型类,类型族,功能依赖性等)。 The other is creating my own "vtable" and using GADTs. 另一个是创建我自己的“vtable”并使用GADT。 Both methods fail in a similar way - there seems to be something basic I'm missing here. 两种方法都以类似的方式失败 - 这里似乎有一些基本的东西。 Or, perhaps, there a better more Haskell-ish way to do this? 或者,或许,有更好的Haskell-ish方法来做到这一点?

Here is the failing "typed" code: 这是失败的“打字”代码:

{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE FunctionalDependencies #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeSynonymInstances #-}

module Typed 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.
class LogicBlock block incoming outgoing | block -> incoming, block -> outgoing where
  logicState :: block ~ Block state ports => Lens state LogicState
  logicPorts :: block ~ Block state ports => Lens ports (LogicPorts incoming outgoing)
  convert :: block ~ Block state ports => incoming -> State block outgoing
  runLogic :: State block outgoing
  runLogic = 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

instance LogicBlock MyBlock Int Bool where
  logicState = myLogicState
  logicPorts = myLogicPorts
  convert x = return $ x > 0

-- All this work to write:
testMyBlock :: State MyBlock Bool
testMyBlock = runLogic

It results in the following error: 它会导致以下错误:

Typed.hs:39:7:
    Could not deduce (block ~ Block state1 ports1)
    from the context (LogicBlock block incoming outgoing)
      bound by the class declaration for `LogicBlock'
      at Typed.hs:(27,1)-(41,19)
      `block' is a rigid type variable bound by
              the class declaration for `LogicBlock' at Typed.hs:26:18
    Expected type: StateT block Data.Functor.Identity.Identity outgoing
      Actual type: State (Block state1 ports1) outgoing
    In the return type of a call of `convert'
    In a stmt of a 'do' block: convert inputMessage

And here is the failing "vtable" code: 这是失败的“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
                         }

It results in the following error: 它会导致以下错误:

VTable.hs:44:5:
    Could not deduce (block1 ~ Block state1 ports1)
    from the context (block ~ Block state ports)
      bound by the type signature for
                 runLogic :: block ~ Block state ports =>
                             BlockLogic block incoming outgoing -> State block outgoing
      at VTable.hs:(37,1)-(46,17)
    or from (block ~ Block state1 ports1)
      bound by a pattern with constructor
                 BlockLogic :: forall incoming outgoing state ports block.
                               Lens state LogicState
                               -> Lens ports (LogicPorts incoming outgoing)
                               -> (incoming -> State block outgoing)
                               -> BlockLogic (Block state ports) incoming outgoing,
               in an equation for `runLogic'
      at VTable.hs:37:10-26
      `block1' is a rigid type variable bound by
               a pattern with constructor
                 BlockLogic :: forall incoming outgoing state ports block.
                               Lens state LogicState
                               -> Lens ports (LogicPorts incoming outgoing)
                               -> (incoming -> State block outgoing)
                               -> BlockLogic (Block state ports) incoming outgoing,
               in an equation for `runLogic'
               at VTable.hs:37:10
    Expected type: block1
      Actual type: block
    Expected type: StateT
                     block1 Data.Functor.Identity.Identity outgoing
      Actual type: State block outgoing
    In the return type of a call of `convert'
    In a stmt of a 'do' block: convert inputMessage

I don't get why GHC is going for "block1" when the whole thing is explicitly under ScopedTypeVariables and "forall block". 当整个事情明确地在ScopedTypeVariables和“forall block”下时,我不明白为什么GHC会选择“block1”。

Edit #1: Added functional dependencies, thanks to Chris Kuklewicz for pointing this out. 编辑#1:添加了功能依赖,感谢Chris Kuklewicz指出这一点。 The problem remains though. 问题仍然存在。

Edit #2: As Chris pointed out, in the VTable solution, getting rid of all the "block ~ Block state ports" and instead writing "Block state ports" everywhere solves the problem. 编辑#2:正如克里斯指出的那样,在VTable解决方案中,摆脱所有“阻塞〜阻塞状态端口”,而不是在任何地方编写“阻塞状态端口”解决问题。

Edit #3: Ok, so the problem seems to be that for each and every separate function, GHC requires sufficient type information in the parameters to deduce all the types, even for types that aren't used at all. 编辑#3:好的,所以问题似乎是对于每个单独的函数,GHC在参数中需要足够的类型信息来推断所有类型,即使对于根本没有使用的类型。 So in the case of (for example) logicState above, the parameters only give us the state, which isn't enough to know what the ports and incoming and outgoing types are. 因此,在(例如)上面的logicState的情况下,参数只给我们状态,这不足以知道端口以及传入和传出类型是什么。 Never mind it doesn't really matter to the logicState function; 没关系,它对logicState函数并不重要; GHC wants to know, and can't, so compilation fails. GHC想知道,但不能,所以编译失败。 If this is indeed the core reason, it would have been better if GHC complained directly when compiling the logicState decleration - it seems it has enough information to detect a problem there; 如果这确实是核心原因,那么如果GHC在编译logicState decleration时直接抱怨会更好 - 它似乎有足够的信息来检测那里的问题; if I had seen a problem saying "ports type is not used/determined" at that location, it would have been much clearer. 如果我在该位置看到“端口类型未被使用/确定”的问题,那就更清楚了。

Edit #4: It still isn't clear to me why (block ~ Block state ports) doesn't work; 编辑#4:我仍然不清楚为什么(阻塞〜阻塞状态端口)不起作用; I guess I'm using it for an unintended purpose? 我想我是出于意想不到的目的使用它? It seems like it should have worked. 看起来应该有用。 I agree with Chris that using CPP to work around it is an abomination; 我同意克里斯的观点,即使用CPP来解决这个问题是令人厌恶的; but writing "B trpe" (in my real code that has more paraneters) isn't a good solution either. 但写“B trpe”(在我的真实代码中有更多的paraneters)也不是一个好的解决方案。

I have a one line fix for your VTable code: 我有一个针对您的VTable代码的一行修复:

            , convert :: incoming -> State block outgoing

becomes

            , convert :: incoming -> State (Block state ports) outgoing

Then you should simplify the type of runLogic to 然后你应该简化runLogic的类型

runLogic :: BlockLogic (Block state ports) incoming outgoing
         -> State (Block state ports) outgoing

PS: More detail to answer comments below. PS:更多细节回答以下评论。

Eliminating "block ~" was not part of the fix. 消除“阻止〜”不是解决方案的一部分。 Usually "~" is only needed in instance a~b => ... where situations. 通常只有在instance a~b => ... where情况下才需要“〜”。

Previously if I give a function a xxx :: BlockLogic (Block state ports) incoming outgoing then it can unpack convert xxx :: State block outgoing . 以前如果我给一个函数一个xxx :: BlockLogic (Block state ports) incoming outgoing那么它可以解包convert xxx :: State block outgoing But the new block is not at all related to (Block state ports) , it is a new unknowable type. 但是新block(Block state ports)完全没有关系,它是一种新的不可知类型。 The compiler append a digit to the end of the name to make block1 which then appears in the error messages. 编译器在名称的末尾附加一个数字以生成block1 ,然后出现在错误消息中。

The original code (both versions) have problems with what types the compiler can infer from a given context. 原始代码(两个版本)都存在编译器可以从给定上下文推断出哪些类型的问题。

As for verbosity, try type . 至于详细程度,请尝试type Do not use CPP and DEFINE. 不要使用CPP和DEFINE。

type B s p = BlockLogic (Block s p)

runLogic :: B s p i o -> State (Block s p) o

PPS: Further explanation of problems with class version. PPS:进一步解释类版本的问题。 If I substitute (Block sp) for block and add the functional dependencies you mentioned: 如果我替换(阻止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

Using logicState nails down state but leaves ports unknown, making ports# 使用logicState钉住state但留下ports未知,使ports#

Using logicPorts nails down ports but leaves state unknown, making ports# 使用logicPorts钉在ports但留下state未知,使ports#

Compiling runLogic runs into lots of type mismatch errors between ports, ports0, ports1 and state, state0, state1. 编译runLogic在端口,端口0,端口1和状态,状态0,状态1之间运行许多类型不匹配错误。

These operations do not seem to fit together into the same type class. 这些操作似乎不适合同一类型类。 You could break them out into separate type classes, or perhaps add ", state->ports, ports->state" functional dependencies to the class declaration. 您可以将它们分解为单独的类型类,或者可以将“,state-> ports,ports-> state”函数依赖项添加到类声明中。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM