不同类型到不同实例构造函数的通用转换



我正在尝试定义类型类实例,以使用Num的1-4个分量的向量,表示为元组。这是我(从上一个问题中(找到的最好的解决方案

{-# LANGUAGE FlexibleInstances #-}
data OneTuple a = OneTuple a deriving Show
class MyClass a where
prints :: a -> String
instance (Show n, Num n) => MyClass (n, n) where
prints = show
instance (Show n, Num n) => MyClass (n, n, n) where
prints (a, b, c) = foldl (++) "" $ map (++" ") $ map show [a,b,c]
instance (Show n, Num n) => MyClass (OneTuple n) where
prints (OneTuple a) = show a

但是这伴随着要求CCD_ 2被写为CCD_。我尝试为此实现tuplify函数,但

wrap :: (MyClass a) => (String -> String) -> a -> String
wrap f = f . prints
tuplify :: (MyClass b) => a -> b
tuplify (a,b) = (a,b)
tuplify (a,b,c) = (a,b,c)
tuplify a = OneTuple a
stringFunction :: String -> String
stringFunction = id
vectorToString x = wrap stringFunction $ tuplify x

不编译。我正试图找到一个看起来像的东西

vectorToString (1,2,3)
vectorToString (1,2)
vectorToString 1

这可能吗?我记得我读过一些关于函数的行为不应该根据输入类型而改变的文章,这正是tuplify所做的。有什么不同的方法可以解决这个问题吗?这是来自ghci:的错误消息

question.hs:18:9: error:
• Couldn't match expected type ‘a’ with actual type ‘(a0, b0)’
‘a’ is a rigid type variable bound by
the type signature for:
tuplify :: forall b a. MyClass b => a -> b
at question.hs:17:1-32
• In the pattern: (a, b)
In an equation for ‘tuplify’: tuplify (a, b) = (a, b)
• Relevant bindings include
tuplify :: a -> b (bound at question.hs:18:1)
|
18 | tuplify (a,b) = (a,b)
|         ^^^^^
question.hs:18:17: error:
• Couldn't match expected type ‘b’ with actual type ‘(a0, b0)’
‘b’ is a rigid type variable bound by
the type signature for:
tuplify :: forall b a. MyClass b => a -> b
at question.hs:17:1-32
• In the expression: (a, b)
In an equation for ‘tuplify’: tuplify (a, b) = (a, b)
• Relevant bindings include
b :: b0 (bound at question.hs:18:12)
a :: a0 (bound at question.hs:18:10)
tuplify :: a -> b (bound at question.hs:18:1)
|
18 | tuplify (a,b) = (a,b)
|                 ^^^^^

我记得我读过一些关于函数的行为不应该根据输入类型而改变的文章

听起来你在考虑利斯科夫替代原理。但这是OO世界的东西,不适用于Haskell。您甚至不能真正地制定,因为Haskell没有子类型。

Haskell确实有一个相关的原理:参数多态函数不能根据使用的类型有不同的行为。但这与其说是程序员应该遵守的原则,不如说是语言强制执行的原则。这就是为什么tuplify的不同情况不可能编译的原因之一:在那里,你可以尝试在类型上进行模式匹配。这不是在Haskell函数声明中可以做到的。

然而,这几乎正是类型类所做的,事实上,您自己的类型类已经做到了。

tuplify还有另一个问题:签名MyClass b => a -> b意味着调用者可以选择ab,然后函数必须能够在它们之间进行转换。例如,它必须能够将2元组转换为3元组。您可能想要表达的是,每个输入类型都与一个结果类型相关联,即类型级函数。我们将这些类型的族称为,实际上您可以编写

{-# LANGUAGE TypeFamilies #-}
type family Tuplified t where
Tuplified (a,b) = (a,b)
Tuplified (a,b,c) = (a,b,c)
Tuplified t = t

然而,这并没有真正让你走得更远:现在你有了一个类型级别的函数,但没有针对个别情况的相应值级别的函数。该功能必须具有类型

tuplify :: t -> Tuplified t

但是定义它时不能不使用类型类。你可以编写这样一个类型的类,但你会遇到与你在其他问题中试图解决的问题相同的问题!

我看不出这有什么意义。也许,您的类实例实际上应该更像标准的Show类:

instance MyClass Integer where prints = show
instance MyClass Int where prints = show
instance (MyClass a, b~a) => MyClass (a,b) where
prints (x,y) = '(' : prints x ++ "," ++ prints y ++ ")"

最新更新