【问题标题】:In Haskell how can I match a type class with an instance of that type class?在 Haskell 中,如何将类型类与该类型类的实例匹配?
【发布时间】:2017-01-02 00:23:42
【问题描述】:

我定义了以下类:

class Configuration c where
  input :: (PortIn pin, Show pin) => c -> pin
  startSt :: (SysState st, Show st) => c -> st

class (Eq pin, Show pin) => PortIn pin

class (Eq st, Show st) => SysState st

以下类型和实例:

--inputs
data PIn = PIn { _clk   :: Bit
               , _reset :: Bool
               , _start :: Bool
               , _stop  :: Bool
               } deriving (Eq)
instance PortIn PIn
instance Show PIn where
  show PIn {..} =
    "PIn\n\t _clk = " P.++ show _clk
    P.++ "\n\t _reset = " P.++ show _reset
    P.++ "\n\t _start = " P.++ show _start
    P.++ "\n\t _stop = " P.++ show _stop

--Outputs and state data
data St = St { _cnt_en   :: Bool
             , _count_us :: BitVector 4
             , _stop_d1  :: Bool
             , _stop_d2  :: Bool
             , _count    :: BitVector 4
             } deriving (Eq)

instance SysState St
instance Show St where
 show St {..} =
        "St\n\t _cnt_en = " P.++ show _cnt_en

   P.++ "\n\t _count_us = " P.++ show _count_us
   P.++ "\n\t _stop_d1 = " P.++ show _stop_d1
   P.++ "\n\t _stop_d2 = " P.++ show _stop_d2
   P.++ "\n\t _count = " P.++ show _count

为什么我无法在此处创建配置实例:

data Config = Config { input'  :: PIn
                     , startSt' :: St
                     } deriving (Eq)

instance Show Config where
 show Config {..} =
        "Config:\n input = " P.++ show input'
   P.++ "\n startSt = " P.++ show startSt'

instance Configuration Config where
  input = input'
  startSt = startSt'

我收到以下错误:

Couldn't match type ‘pin’ with ‘PIn’
  ‘pin’ is a rigid type variable bound by
        the type signature for
          input :: (PortIn pin, Show pin) => Config -> pin
        at Clks_n_regs_4.hs:101:3
Expected type: Config -> pin
  Actual type: Config -> PIn
Relevant bindings include
  input :: Config -> pin (bound at Clks_n_regs_4.hs:101:3)
In the expression: input'
In an equation for ‘input’: input = input'

我认为我可以使用 input',因为它会生成一个 PIn,它是 pin 的一个实例。

我在某处有误解,希望有人能解释我所缺少的。

【问题讨论】:

  • 这看起来很像您试图将 OO 类压缩到 Haskell 中。你为什么需要这个(或认为你需要它)?
  • 我有几个版本的类型:Config、PIn、St 和一个名为runOneTest 的函数。使用类型和函数的代码是相同的。 runOneTest 的类型签名将始终为 runOneTest :: Config -> Signal TestResult 我希望能够重用所有其他调用 runOneTest 函数的代码。我昨天在 CodeReview 上发布了这个,但还没有结果。 [codereview.stackexchange.com/questions/151278/…

标签: haskell


【解决方案1】:

要了解您的代码为什么不进行类型检查,请参阅:existential vs. universally quantified types。要完成这项工作,一种可能性是使用Functional Dependencies。那么你的类型类将是:

{-# LANGUAGE MultiParamTypeClasses, FunctionalDependencies #-}

class (PortIn pin, Show pin, SysState st, Show st) => Configuration c pin st
    | c -> pin, c -> st where
  input   :: c -> pin
  startSt :: c -> st

实例定义为:

instance Configuration Config PIn St where
  input   = input'
  startSt = startSt'

这将使您的代码类型检查,但可能不是您所追求的。

另一个方向是使用existential types。在这种情况下:

{-# LANGUAGE ExistentialQuantification #-}

data PortInShow   = forall a . (PortIn a,   Show a) => PortInShow a
data SysStateShow = forall a . (SysState a, Show a) => SysStateShow a

class Configuration c where
  input   :: c -> PortInShow
  startSt :: c -> SysStateShow

instance Configuration Config where
  input   = PortInShow   . input'
  startSt = SysStateShow . startSt'

【讨论】:

    【解决方案2】:

    behzad.nouri 的答案中链接问题的适用性对于尚未了解这里发生了什么的人来说可能不清楚。要更详细地回答实际问题:

    class Configuration c where
      input :: (PortIn pin, Show pin) => c -> pin
      startSt :: (SysState st, Show st) => c -> st
    

    表示input的类型将是

    input :: (Configuration c, PortIn pin, Show pin) => c -> pin
    

    这反过来意味着input调用者可以在input的类型中为cpin选择任何他们喜欢的类型,只要c是一个Configurationpin 的实例是 PortInShow 的实例。 (这是所有多态函数的工作方式:even :: Integral a => a -> Bool 意味着调用者可以将even 应用于作为Integral 实例的任何类型的值,而fromInteger :: Num a => Integer -> a 意味着调用者可以处理fromInteger 具有作为 Num 实例的任何类型。)

    因此,作为Configuration Configinput 方法的实现者,您必须提供适用于任何类型pin 的实现,即PortIn 的实例和Show。但是您的实现input = input' 可能只适用于单一类型PIn。这就是编译器拒绝它的原因。 (它告诉你结果的类型pin 是一个“严格类型变量”,这意味着你不能选择它作为实现者,但必须假设它已经被调用者“严格”选择了。)

    至于该怎么做,您已经在 Gurkenglas 对 codereview 问题的评论中得到了一个很好的答案:您可以简单地参数化 Config 和其他必要的端口类型和状态类型。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-07-18
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-04-07
      相关资源
      最近更新 更多