我有一组函数可以在元组和列表之间进行转换:
pack2 :: [a] -> (a, a)
pack2 (a:b:_) = (a, b)
unpack2 :: (a, a) -> [a]
unpack2 (a, b) = [a, b]
pack3 :: [a] -> (a, a, a)
pack3 (a:b:c:_) = (a, b, c)
unpack3 :: (a, a, a) -> [a]
unpack3 (a, b, c) = [a, b, c]
-- and so on
我想稍微概括一下,以便可以实现以下目标:
packN [1, 2, 3, 4] :: (Int, Int) -- => (1, 2)
unpackN (1, 2, 3) -- => [1, 2, 3]
我试图像这样解决它:
class Pack a b where
packN :: a -> b
unpackN :: b -> a
instance Pack [a] (a, a) where
packN = pack2
unpackN = unpack2
instance Pack [a] (a, a, a) where
packN = pack3
unpackN = unpack3
它编译时没有任何错误(MultiParamTypeClasses, FlexibleInstances
已启用),但是,当尝试使用时,它会失败:
> unpackN (1, 2)
<interactive>:84:1:
Could not deduce (Pack a (t0, t1))
arising from the ambiguity check for ‘it’
from the context (Pack a (t, t2), Num t2, Num t)
bound by the inferred type for ‘it’:
(Pack a (t, t2), Num t2, Num t) => a
at <interactive>:84:1-14
The type variables ‘t0’, ‘t1’ are ambiguous
When checking that ‘it’
has the inferred type ‘forall a t t1.
(Pack a (t, t1), Num t1, Num t) =>
a’
Probable cause: the inferred type is ambiguous
我怎样才能让它发挥作用?
答案 0 :(得分:8)
unpack
必须以某种方式推断输入类型的返回类型,所以让我们看看如何做到这一点(pack
总是以[a]
作为输入,所以我们没有机会从中推断出任何东西)。
首先,我们可以声明一个功能依赖:
class Unpack tup a | tup -> a where
unpack :: tup -> [a]
在此我们承诺,我们只会编写Unpack
的实例,以便a
始终明确地从tup
推断出unpack (a, b)
。因此,当我们编写(a, b)
时,返回类型将从instance Unpack (a, a) a where
unpack (a, b) = [a, b]
的类型中显而易见,并且一切都会很好。
class Unpack tup where
type Elem tup
unpack :: tup -> [Elem tup]
instance Unpack (a, a) where
type Elem (a, a) = a
unpack (a, b) = [a, b]
其次,我们只能通过元组类型对类进行参数化,并使用类型族明确地计算它的元素类型:
unpack (1, 2) -- type error
然而,还有一件事需要解决。目前,如果元素类型是多态的,unpack在没有类型注释的情况下不起作用:
Num a => a
这是因为数字的类型为(1, 2)
,而(Int, Float)
的类型也可能为instance Unpack (a, a)
,这与我们在实例中指定的不同。只有当我们在元组中具有相同的具体单形类型时,我们的tup
才匹配。
通常,仅根据实例头的形式选择实例(此处实例头是instance Unpack tup
中的-- I assume we use the functional dependencies version here
instance a ~ b => Unpack (a, b) a where
unpack (a, b) = [a, b]
类型),只有在此之后GHC才会检查实例约束持有。我们可以利用这个优势:我们可以确保即使对于元组中的类型不同的情况我们也有一个实例,然后使用实例约束来强制类型相同:
unpack
现在instance (a ~ b, b ~ c, c ~ d) => Unpack (a, b, c, d) a where
unpack (a, b, c, d) = [a, b, c, d]
始终无需注释。
写出大元组的所有类型相等约束可能有点麻烦:
ConstraintKinds
幸运的是,我们可以使用{-# LANGUAGE
ConstraintKinds, DataKinds, TypeOperators, TypeFamilies,
MultiParamTypeClasses, FunctionalDependencies, UndecidableInstances #-}
import GHC.Exts
type family AllSame (xs :: [*]) :: Constraint where
AllSame '[] = ()
AllSame '[a] = ()
AllSame (a ': b ': xs) = (a ~ b, AllSame (b ': xs))
...
instance AllSame [a, b, c, d] => Unpack (a, b, c, d) a where
unpack (a, b, c, d) = [a, b, c, d]
和类型系列来编写等号的简写:
ConstraintKinds
这里要知道的是()
通过将元组类型视为约束的结合而工作,AllSame
作为始终满足的空约束。 {{1}}只是一个类型级函数,它扩展到一个等同于我们之前手写的约束。
答案 1 :(得分:2)
这是一个使用类型系列的版本(可能不是惯用的方式)来解决您的问题:
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE TypeFamilies #-}
module Test where
pack2 :: [a] -> (a, a)
pack2 (a:b:_) = (a, b)
unpack2 :: (a, a) -> [a]
unpack2 (a, b) = [a, b]
pack3 :: [a] -> (a, a, a)
pack3 (a:b:c:_) = (a, b, c)
unpack3 :: (a, a, a) -> [a]
unpack3 (a, b, c) = [a, b, c]
class Pack b where
type Element b :: *
packN :: [Element b] -> b
unpackN :: b -> [Element b]
instance Pack (a, a) where
type Element (a,a) = a
packN = pack2
unpackN = unpack2
instance Pack (a, a, a) where
type Element (a,a,a) = a
packN = pack3
unpackN = unpack3
所以这会起作用:
unpackN ((1,2) :: (Int, Int))
> [1,2]
也许这是一个更好的例子(因为它不共享通用数字缺陷 ^^):
*Test> unpackN ('a','b')
"ab"
*Test> unpackN ('a','b','c')
"abc"