[英]GADTs, TypeFamilies type inference failure when implementing “mixins”
我試圖用可組合邏輯創建復雜的數據結構。 也就是說,數據結構具有通用格式(實質上是具有某些類型可以更改的字段的記錄)和一些通用函數。 具體結構具有通用功能的具體實現。
我試過兩種方法。 一種是使用類型系統(具有類型類,類型族,功能依賴性等)。 另一個是創建我自己的“vtable”並使用GADT。 兩種方法都以類似的方式失敗 - 這里似乎有一些基本的東西。 或者,或許,有更好的Haskell-ish方法來做到這一點?
這是失敗的“打字”代碼:
{-# 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
它會導致以下錯誤:
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
這是失敗的“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
}
它會導致以下錯誤:
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
當整個事情明確地在ScopedTypeVariables和“forall block”下時,我不明白為什么GHC會選擇“block1”。
編輯#1:添加了功能依賴,感謝Chris Kuklewicz指出這一點。 問題仍然存在。
編輯#2:正如克里斯指出的那樣,在VTable解決方案中,擺脫所有“阻塞〜阻塞狀態端口”,而不是在任何地方編寫“阻塞狀態端口”解決問題。
編輯#3:好的,所以問題似乎是對於每個單獨的函數,GHC在參數中需要足夠的類型信息來推斷所有類型,即使對於根本沒有使用的類型。 因此,在(例如)上面的logicState的情況下,參數只給我們狀態,這不足以知道端口以及傳入和傳出類型是什么。 沒關系,它對logicState函數並不重要; GHC想知道,但不能,所以編譯失敗。 如果這確實是核心原因,那么如果GHC在編譯logicState decleration時直接抱怨會更好 - 它似乎有足夠的信息來檢測那里的問題; 如果我在該位置看到“端口類型未被使用/確定”的問題,那就更清楚了。
編輯#4:我仍然不清楚為什么(阻塞〜阻塞狀態端口)不起作用; 我想我是出於意想不到的目的使用它? 看起來應該有用。 我同意克里斯的觀點,即使用CPP來解決這個問題是令人厭惡的; 但寫“B trpe”(在我的真實代碼中有更多的paraneters)也不是一個好的解決方案。
我有一個針對您的VTable代碼的一行修復:
, 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:更多細節回答以下評論。
消除“阻止〜”不是解決方案的一部分。 通常只有在instance a~b => ... where
情況下才需要“〜”。
以前如果我給一個函數一個xxx :: BlockLogic (Block state ports) incoming outgoing
那么它可以解包convert xxx :: State block outgoing
。 但是新block
與(Block state ports)
完全沒有關系,它是一種新的不可知類型。 編譯器在名稱的末尾附加一個數字以生成block1
,然后出現在錯誤消息中。
原始代碼(兩個版本)都存在編譯器可以從給定上下文推斷出哪些類型的問題。
至於詳細程度,請嘗試type
。 不要使用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釘住state
但留下ports
未知,使ports#
使用logicPorts釘在ports
但留下state
未知,使ports#
編譯runLogic
在端口,端口0,端口1和狀態,狀態0,狀態1之間運行許多類型不匹配錯誤。
這些操作似乎不適合同一類型類。 您可以將它們分解為單獨的類型類,或者可以將“,state-> ports,ports-> state”函數依賴項添加到類聲明中。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.