可能无法匹配类型变量

发布于 2025-02-10 09:52:39 字数 3859 浏览 2 评论 0原文

我正在尝试使用相当特定的体系结构(有限状态机)创建电报机器人。例如,可能会有(1)状态可以运行某些功能,该状态将返回其他状态或(2)状态,该状态将将bot移至其他具有不同状态的机器。

因此,我创建了类似的类别和两种类型:

{-# LANGUAGE DeriveAnyClass, GADTs #-}
module Main where

class (Eq a) => TeleClass a

type BotView a = String -> a

data TeleState a where
    TeleState :: TeleClass a => a -> BotView a -> TeleState a
    TeleMachineChange :: (TeleClass a, TeleClass b) => a -> TeleMachine b -> TeleState a

data TeleMachine a = TeleMachine
    { defaultState :: a
    , stateMachine :: [TeleState a]
    }

然后GHC允许我创建一些看起来完全像我想要的FSM:

data MainStates = MAIN_MENU | TASK_CHOOSING | CHANGE_VIEW | MODER_VIEW
    deriving (TeleClass, Eq)
data AdminStates = ADMIN_MENU | PUBLISH_TASK
    deriving (TeleClass, Eq)
data ModerStates = MODER_MENU | EDIT_TASK
    deriving (TeleClass, Eq)

fMainMenu _  = TASK_CHOOSING
fChooseTask _ = CHANGE_VIEW

fAdminMenu _ = PUBLISH_TASK
fPublishTask _ = ADMIN_MENU

fModerMenu _ = EDIT_TASK
fEditTask _ = MODER_MENU

moderMachine = TeleMachine
    { defaultState = MODER_MENU
    , stateMachine =
        [ TeleState MODER_MENU fModerMenu
        , TeleState EDIT_TASK fEditTask
        ]
    }

adminMachine = TeleMachine
    { defaultState = ADMIN_MENU
    , stateMachine =
        [ TeleState ADMIN_MENU fAdminMenu
        , TeleState PUBLISH_TASK fPublishTask
        ]
    }

mainMachine = TeleMachine
    { defaultState = MAIN_MENU
    , stateMachine =  
        [ TeleState MAIN_MENU fMainMenu
        , TeleState TASK_CHOOSING fChooseTask
        , TeleMachineChange CHANGE_VIEW adminMachine
        , TeleMachineChange MODER_VIEW moderMachine
        ] 
    }

然后将一些状态应用于FSM,我添加了此功能:

data MatchResult a b = Func a | Machine b | None

matchState :: (TeleClass a, TeleClass c) =>
    [TeleState a] -> a -> MatchResult (BotView a) (TeleMachine c)
matchState (TeleState mState fFunc : stateMachine) state
    | mState == state = Func fFunc
    | otherwise = matchState stateMachine state
matchState (TeleMachineChange mState mMachine : stateMachine) state
    | mState == state = Machine mMachine
    | otherwise = matchState stateMachine state
matchState [] _ = None

编译器抱怨它无法与“ C”类型匹配:

    • Couldn't match type ‘b’ with ‘c’
      ‘b’ is a rigid type variable bound by
        a pattern with constructor:
          TeleMachineChange :: forall a b.
                               (TeleClass a, TeleClass b) =>
                               a -> TeleMachine b -> TeleState a,
        in an equation for ‘matchState’
        at example.hs:70:13-45
      ‘c’ is a rigid type variable bound by
        the type signature for:
          matchState :: forall a c.
                        (TeleClass a, TeleClass c) =>
                        [TeleState a] -> a -> MatchResult (BotView a) (TeleMachine c)
        at example.hs:(65,1)-(66,65)
      Expected type: MatchResult (BotView a) (TeleMachine c)
        Actual type: MatchResult (BotView a) (TeleMachine b)
    • In the expression: Machine mMachine
      In an equation for ‘matchState’:
          matchState (TeleMachineChange mState mMachine : stateMachine) state
            | mState == state = Machine mMachine
            | otherwise = matchState stateMachine state
    • Relevant bindings include
        mMachine :: TeleMachine b (bound at example.hs:70:38)
        matchState :: [TeleState a]
                      -> a -> MatchResult (BotView a) (TeleMachine c)
          (bound at example.hs:67:1)
   |
71 |     | mState == state = Machine mMachine
   |                         ^^^^^^^^^^^^^^^^

我尝试使用存在量的量化和量化限制扩展,但无法解决此问题。现在,我不知道这是否可行,可以在Haskell之类的语言中实现类似的内容。

I'm trying to create telegram bot with quite specific architecture (Finite-state machines). For example, there can be (1) state that will run some function, that will return other state or (2) state that will move bot into some other machine with different states.

So I created class and two types that look something like that:

{-# LANGUAGE DeriveAnyClass, GADTs #-}
module Main where

class (Eq a) => TeleClass a

type BotView a = String -> a

data TeleState a where
    TeleState :: TeleClass a => a -> BotView a -> TeleState a
    TeleMachineChange :: (TeleClass a, TeleClass b) => a -> TeleMachine b -> TeleState a

data TeleMachine a = TeleMachine
    { defaultState :: a
    , stateMachine :: [TeleState a]
    }

And then GHC allows me to create some FSMs that look exactly like I want:

data MainStates = MAIN_MENU | TASK_CHOOSING | CHANGE_VIEW | MODER_VIEW
    deriving (TeleClass, Eq)
data AdminStates = ADMIN_MENU | PUBLISH_TASK
    deriving (TeleClass, Eq)
data ModerStates = MODER_MENU | EDIT_TASK
    deriving (TeleClass, Eq)

fMainMenu _  = TASK_CHOOSING
fChooseTask _ = CHANGE_VIEW

fAdminMenu _ = PUBLISH_TASK
fPublishTask _ = ADMIN_MENU

fModerMenu _ = EDIT_TASK
fEditTask _ = MODER_MENU

moderMachine = TeleMachine
    { defaultState = MODER_MENU
    , stateMachine =
        [ TeleState MODER_MENU fModerMenu
        , TeleState EDIT_TASK fEditTask
        ]
    }

adminMachine = TeleMachine
    { defaultState = ADMIN_MENU
    , stateMachine =
        [ TeleState ADMIN_MENU fAdminMenu
        , TeleState PUBLISH_TASK fPublishTask
        ]
    }

mainMachine = TeleMachine
    { defaultState = MAIN_MENU
    , stateMachine =  
        [ TeleState MAIN_MENU fMainMenu
        , TeleState TASK_CHOOSING fChooseTask
        , TeleMachineChange CHANGE_VIEW adminMachine
        , TeleMachineChange MODER_VIEW moderMachine
        ] 
    }

And then to apply some state to FSM I added this function:

data MatchResult a b = Func a | Machine b | None

matchState :: (TeleClass a, TeleClass c) =>
    [TeleState a] -> a -> MatchResult (BotView a) (TeleMachine c)
matchState (TeleState mState fFunc : stateMachine) state
    | mState == state = Func fFunc
    | otherwise = matchState stateMachine state
matchState (TeleMachineChange mState mMachine : stateMachine) state
    | mState == state = Machine mMachine
    | otherwise = matchState stateMachine state
matchState [] _ = None

And compiler complained that it couldn't match type 'b' with 'c':

    • Couldn't match type ‘b’ with ‘c’
      ‘b’ is a rigid type variable bound by
        a pattern with constructor:
          TeleMachineChange :: forall a b.
                               (TeleClass a, TeleClass b) =>
                               a -> TeleMachine b -> TeleState a,
        in an equation for ‘matchState’
        at example.hs:70:13-45
      ‘c’ is a rigid type variable bound by
        the type signature for:
          matchState :: forall a c.
                        (TeleClass a, TeleClass c) =>
                        [TeleState a] -> a -> MatchResult (BotView a) (TeleMachine c)
        at example.hs:(65,1)-(66,65)
      Expected type: MatchResult (BotView a) (TeleMachine c)
        Actual type: MatchResult (BotView a) (TeleMachine b)
    • In the expression: Machine mMachine
      In an equation for ‘matchState’:
          matchState (TeleMachineChange mState mMachine : stateMachine) state
            | mState == state = Machine mMachine
            | otherwise = matchState stateMachine state
    • Relevant bindings include
        mMachine :: TeleMachine b (bound at example.hs:70:38)
        matchState :: [TeleState a]
                      -> a -> MatchResult (BotView a) (TeleMachine c)
          (bound at example.hs:67:1)
   |
71 |     | mState == state = Machine mMachine
   |                         ^^^^^^^^^^^^^^^^

I tried to use ExistentialQuantification and QuantifiedConstraints extensions, but couldn't fix this problem with them. Right now I don't know if this is viable idea to implement something like this in language like haskell.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

蝶舞 2025-02-17 09:52:39

签名

matchState :: (TeleClass a, TeleClass c) =>
    [TeleState a] -> a -> MatchResult (BotView a) (TeleMachine c)

...这只是

matchState :: ∀ a c . (TeleClass a, TeleClass c) =>
    [TeleState a] -> a -> MatchResult (BotView a) (TeleMachine c)

表达的速记,即对于c呼叫者选择的任何类型,此功能将能够从输入列表中获取合适的telemachine。这似乎是一件艰难的事情。

我认为您要表达的是“好的,我会尝试在其中找到一台匹配的机器,但我不知道它的类型是什么”。确实,这确实是存在类型

matchState :: ∀ a . ∃ c . (TeleClass a, TeleClass c) =>
    [TeleState a] -> a -> MatchResult (BotView a) (TeleMachine c)

haskell不允许使用存在类型的功能,但是您可以使matchResult类型存在,与telemachinechange telemachine的构造函数是存在的:

data MatchResult a where
  Func :: a -> MatchResult a
  Machine :: TeleClass b => b -> MatchResult a
  None :: MatchResult a

matchState :: ∀ a . TeleClass a =>
    [TeleState a] -> a -> MatchResult (BotView a)

或者,您可以使用持续通话风格的matchstate伪造存在类型:

matchState :: ∀ a r . TeleClass a =>
     [TeleState a]
  -> a
  -> (∀ c . TeleClass a
       => MatchResult (BotView a) (TeleMachine c) -> r)
  -> r
matchState (TeleState mState fFunc : stateMachine) state φ
    | mState == state = φ (Func fFunc)
    | otherwise = matchState stateMachine state φ
matchState (TeleMachineChange mState mMachine : stateMachine) state
    | mState == state = φ (Machine mMachine)
    | otherwise = matchState stateMachine state φ
matchState [] _ = φ None

The signature

matchState :: (TeleClass a, TeleClass c) =>
    [TeleState a] -> a -> MatchResult (BotView a) (TeleMachine c)

...which is just shorthand for

matchState :: ∀ a c . (TeleClass a, TeleClass c) =>
    [TeleState a] -> a -> MatchResult (BotView a) (TeleMachine c)

expresses that for whatever type c the caller picks, this function will be able to procure a suitable TeleMachine from the input list. It seems a tough thing to do.

I think what you're trying to express is instead “ok, I'll try to find a matching machine in there, but I don't know what its type is going to be”. That is indeed an existential type

matchState :: ∀ a . ∃ c . (TeleClass a, TeleClass c) =>
    [TeleState a] -> a -> MatchResult (BotView a) (TeleMachine c)

Haskell doesn't allow functions with existential type, but you could make the MatchResult type existential, similary to how the TeleMachineChange constructor of TeleMachine is existential:

data MatchResult a where
  Func :: a -> MatchResult a
  Machine :: TeleClass b => b -> MatchResult a
  None :: MatchResult a

matchState :: ∀ a . TeleClass a =>
    [TeleState a] -> a -> MatchResult (BotView a)

Alternatively, you can fake an existential type for matchState with continuation-passing style:

matchState :: ∀ a r . TeleClass a =>
     [TeleState a]
  -> a
  -> (∀ c . TeleClass a
       => MatchResult (BotView a) (TeleMachine c) -> r)
  -> r
matchState (TeleState mState fFunc : stateMachine) state φ
    | mState == state = φ (Func fFunc)
    | otherwise = matchState stateMachine state φ
matchState (TeleMachineChange mState mMachine : stateMachine) state
    | mState == state = φ (Machine mMachine)
    | otherwise = matchState stateMachine state φ
matchState [] _ = φ None
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文