Haskell:如何用构造函数创建一个类?

时间:2017-08-23 13:27:18

标签: haskell

我想写一个有构造函数的类。例如:

class A a where

  -- `a`'s field
  type T a

  -- default value
  defT :: T a

  -- constructor
  mk :: T a -> a

我还需要一个默认构造函数:

mkDef :: A a => a
mkDef = mk defT

但是我收到了编译错误:

Couldn't match expected type ‘T a’ with actual type ‘T a1’
NB: ‘T’ is a type function, and may not be injective
The type variable ‘a1’ is ambiguous
Relevant bindings include
  mkDef :: a
In the first argument of ‘mk’, namely ‘defT’
In the expression: mk defT

我认为编译器不知道A a的第一个参数属于mk的哪个实例。如果我错了,请纠正我。

尝试使用ScopedTypeVariables在这里没有帮助,我得到的编译错误大致相同:

mkDef :: forall a . A a => a
mkDef = mk (defT :: T a)

另一个想法是使用FunctionalDependencies

class A' a t | a -> t where

  -- Get t from a
  getT' :: a -> t

  defT' :: t

  mk' :: t -> a

但是,我仍然无法使用默认构造函数:

mkDef' :: forall a t . A' a t => a
mkDef' = make def
  where

    def :: t
    def = defT' `asTypeOf` (getT' (undefined :: a))

    make :: t -> a
    make = mk'

有关a t所属的信息丢失了:

Could not deduce (A' a0 t) arising from a use of ‘defT'’
from the context (A' a t)
  bound by the type signature for mkDef' :: A' a t => a
The type variable ‘a0’ is ambiguous
Relevant bindings include
  def :: t
  make :: t -> a
In the first argument of ‘asTypeOf’, namely ‘defT'’
In the expression: defT' `asTypeOf` (getT' (undefined :: a))
In an equation for ‘def’:
    def = defT' `asTypeOf` (getT' (undefined :: a))

此外,我不喜欢这种方法,因为所有这些t都应该是类参数。

我想用TypeFamilies来解决这个问题,因为我已经有了一些以这种方式编写的代码。另外,请告诉我是否有这个问题的常见解决方案。

2 个答案:

答案 0 :(得分:4)

这里的问题是缺乏注入性,因为它通常发生在类型族中。考虑一下。

data T1 = ...
data T2 = ...

instance A T1 where
  type T T1 = Int
  ...
instance A T2 where
  type T T2 = Int
  ...

mk defT :: T1  -- error

最后一行需要defT :: Int。但是,两个实例都定义defT :: Int ~ T T1 ~ T T2,因此GHC无法决定选择哪一个。

更糟糕的是,GHC拒绝类型类中的defT :: T a类型,因为它含糊不清:即使我们知道我们想要defT :: SomeGivenType,GHC也无法理解a它应该选择。

在我看来,最简单的解决方案是:1)允许含糊不清的类型,以及2)明确选择实例。

{-# LANGUAGE AllowAmbiguousTypes, TypeApplications #-}
mk (defT @ T1)  -- after the @ we explicitly pass the type "a"

答案 1 :(得分:2)

我怀疑你想要一个新的数据类型,而不是一个类。例如:

data A a = Mk { get :: a }

这为你定义了一堆东西,但特别感兴趣的是这两个函数:

Mk :: a -> A a
get :: A a -> a

您可以拥有多个字段;例如,在:

data Counted a = Counted { value :: a, count :: Int }

现在存在这些功能:

Counted :: a -> Int -> Counted a
value :: Counted a -> a
count :: Counted a -> Int