如何解决Purescript 将 cons 定义为 typeclass operator 长答案旧答案 - 在问题发生重大变化之前写的
Cons operator is defined (:) 是为 Array (Array.cons) 和 List (Cons 类型构造函数) 定义的。所以要在代码中使用它,我们应该:
import Data.List ((:))
或
import Data.Array ((:))
我想知道是否可以定义 (:)
以便它可以导入到一个模块中并用于同一模块中的 Array 和 List。
我尝试这样做:
class Cons container element where
cons :: element -> container -> container
instance consArray :: Cons (Array a) a where
cons = Array.cons
instance constList :: Cons (List a) a where
cons = List.Cons
infixr 6 cons as :
它似乎适用于基本情况:
arr :: Array Int
arr = 1 : [2,3]
lst :: List Int
lst = 1 : (2 :3 : Nil)
但是一些高级案例,例如:
data X a = X a
getX :: ∀ msg. Int -> X msg
getX = unsafeCoerce
getCons :: ∀ msg.
Array (X msg) ->
Array (X msg)
getCons children = getX 1 : children
但它给出了错误:
No type class instance was found for
MyModule.Cons (Array (X msg2))
(X t3)
The instance head contains unkNown type variables. Consider adding a type annotation.
while applying a function cons
of type Cons t0 t1 => t1 -> t0 -> t0
to argument getX 1
while inferring the type of cons (getX 1)
in value declaration getCons
where msg2 is a rigid type variable
bound at (line 0,column 0 - line 0,column 0)
t0 is an unkNown type
t1 is an unkNown type
那么问题是,一般情况下是否有可能实现我所描述的?
UPD:
这个:
class Cons container where
cons :: forall element. element -> container element -> container element
解决了构造Array或List时(:)的统一使用问题。
因此,在导入此 (:)
后,我们可以在同一模块中执行:
arrFn :: ∀ a. a -> Array a -> Array a
arrFn el array = el : array
listFn :: ∀ a. a -> List a -> List a
listFn el list = el : el : list
-- and even this will work too:
data X a = X a
getX :: ∀ msg. Int -> X msg
getX = unsafeCoerce
getCons :: ∀ msg.
Array (X msg) ->
Array (X msg)
getCons children = getX 1 : children
问题是这样定义的运算符不能用于模式匹配(因为模式匹配只适用于类型构造函数),这会产生错误:
matchList :: List Int -> Int
matchList ls =
case ls of
Nil -> 0
(x : xs) -> x
而且似乎不可能实现 (:) 的完全通用用法,因此它适用于 Array/List
构造和 List
模式匹配。
解决方法
TL;DR:你需要一个函数依赖。
长答案
发生这种情况是因为编译器不知道它应该查找 Cons
的哪个实例。
在表达式 getX 1 : children
中,编译器知道 children :: Array (X msg)
,但是 getX 1
的类型是什么?函数 getX
可以返回任何类型的 X
,任何类型。那么它应该是X Int
吗?还是X String
?或者,也许,X Boolean
?没有办法告诉!因此,编译器只是为一些未知的类型 X t3
调用该类型 t3
,然后从那里继续,希望 t3
会及时为人所知。
但事实并非如此。编译器必须解决的下一件事是应用 (:)
运算符。要做到这一点,它需要找到一个实例 Cons (Array (X msg)) (X t3)
,但它不知道如何找到,因为它不知道 t3
是什么,并且现有实例中没有一个与该形状匹配。
顺便说一下,您可以在此时停下来验证一下。将您的实例更改为:
instance consArray :: Cons (Array a) b where
cons _ xs = xs
在那之后,getCons
突然编译。为什么?因为当替换 Cons (Array a) b
和 Cons (Array (X msg)) (X t3)
时,新的实例头 a ~ X msg
实际上与所需的 b ~ X t3
匹配。而且它从来没有真正出现过 t3
究竟是什么,所以它可以被忽略。
顺便说一句,您甚至不需要 X
来实现这一点。您可以通过以下方式重现问题:
getX :: forall a. Int -> a
getCons :: forall msg. Array msg -> Array msg
X
只会混淆视听。
但是“通常是否可以实现我所描述的”,您会问吗?
好吧,你还没有真正描述你想要的结果,所以很难确定。但是,如果我不得不猜测,在我看来,您真正想要的是让编译器弄清楚,因为 (:)
的第二个参数是 Array (X msg)
,那么第一个参数必须是该数组的一个元素 - 即 X msg
,- 然后使用该信息来推断此实例化中 getX
的预期类型。
如果这确实是您想要实现的目标,那么您需要的是一个functional dependency:
class Cons container element | container -> element where
^^^^^^^^^^^^^^^^^^^^^^
|
this bit here
这段语法告诉编译器,如果 container
以某种方式已知,那么 element
也必须是已知的。
在实践中它有两个作用:
- 您不能声明任何具有相同
container
但不同element
的实例。例如,Cons (Array Int) Int
和Cons (Array Int) String
不起作用。编译器会抱怨他们违反了函数依赖。 - 但作为回报,编译器现在可以通过知道
element
来推断container
。
因此,如果您只添加该位,getCons
将可以正常编译:编译器将首先理解 container ~ Array (X msg)
,因此它会选择匹配的实例 - Const (Array a) a
,- 并且来自将推断出 element ~ X msg
,因此 getX :: Int -> X msg
。
旧答案 - 在问题发生重大变化之前写的
这与在类型类中定义运算符无关。如果您只是从 Data.Array
导入运算符,则会发生完全相同的事情,只是错误消息会好一些。
问题在于您试图将 X a
类型的值与 Tuple String (X a)
类型的值数组相结合。类型不同。 Tuple String (X a)
与 X a
不同。
要使其工作,您必须修复类型签名以使类型相同:
xFn :: ∀ a. Tuple String (X a) -> Array (Tuple String (X a)) -> Array (Tuple String (X a))
xFn el list = el : list
或者你必须在尝试 cons 之前用 el
构造一个元组:
xFn :: ∀ a. X a -> Array (Tuple String (X a)) -> Array (Tuple String (X a))
xFn el list = Tuple "foo" el : list
无论哪种方式,元素都必须在某个时刻转换为元组。哪种方式“正确”取决于您的具体情况。
或者,您可以提供一个实例 Cons (Array (Tuple String a)) a
,并将元素转换为该实例内的元组。没有这样的实例是编译器在错误消息中抱怨的。它可能看起来像这样:
instance consTuple :: Cons (Array (Tuple String a)) a where
cons a xs = cons (Tuple "foo" a) xs
除非这样的实例不起作用,因为它会与 consArray
重叠。所以你可以通过将它们放在一个链中来解决那个:
instance consArray :: Cons (Array a) a where
cons = Array.cons
else instance consTuple :: Cons (Array (Tuple String a)) a where
cons a xs = cons (Tuple "foo" a) xs
虽然我无法想象您可以为此拥有什么可能的用例。
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。