Purescript 将 cons 定义为类型类运算符
Purescript define cons as typeclass operator
Cons operator is defined (:) is defined for Array (Array.cons) and List (Cons type constructor).所以要在代码中使用它,我们应该:
import Data.List ((:))
或
import Data.Array ((:))
我想知道是否可以定义(:)
,以便它可以在一个模块中导入并在同一个模块中用于数组和列表。
我试过这样做:
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
可以 return X
任何类型,任何类型。那么应该是X Int
?还是X String
?或者,也许 X Boolean
?没有办法告诉!因此,编译器只是为一些未知的 t3
调用该类型 X t3
,并从那里继续,希望 t3
会及时为人所知。
但事实并非如此。编译器必须解决的下一件事是应用 (:)
运算符。为此,它需要找到一个实例 Cons (Array (X msg)) (X t3)
,但它不知道如何找到,因为它不知道 t3
是什么,并且现有实例的 none 匹配那个形状。
顺带一提,你可以到此为止,验证一下。将您的实例更改为此:
instance consArray :: Cons (Array a) b where
cons _ xs = xs
之后,getCons
突然编译。为什么?因为在替换 a ~ X msg
和 b ~ X t3
时,新实例头 Cons (Array a) b
实际上确实匹配所需的 Cons (Array (X msg)) (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
将不起作用。编译器会抱怨它们违反了函数依赖性。
- 但是在 return 中,编译器现在可以通过知道
container
. 来推断 element
因此,如果您只添加该位,getCons
将编译正常:编译器将首先理解 container ~ Array (X msg)
,因此它会选择匹配的实例 - Const (Array a) a
,-并从那里推断出 element ~ X msg
,因此 getX :: Int -> X msg
.
旧答案 - 在问题发生实质性改变之前写的
这与在类型 class 中定义运算符无关。如果您只是从 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
或者你必须在尝试使用它之前从 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
重叠。所以你可以通过将它们放在一个链中来修复 that:
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
尽管我无法想象您对此有什么可能的用例。
Cons operator is defined (:) is defined for Array (Array.cons) and List (Cons type constructor).所以要在代码中使用它,我们应该:
import Data.List ((:))
或
import Data.Array ((:))
我想知道是否可以定义(:)
,以便它可以在一个模块中导入并在同一个模块中用于数组和列表。
我试过这样做:
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
可以 return X
任何类型,任何类型。那么应该是X Int
?还是X String
?或者,也许 X Boolean
?没有办法告诉!因此,编译器只是为一些未知的 t3
调用该类型 X t3
,并从那里继续,希望 t3
会及时为人所知。
但事实并非如此。编译器必须解决的下一件事是应用 (:)
运算符。为此,它需要找到一个实例 Cons (Array (X msg)) (X t3)
,但它不知道如何找到,因为它不知道 t3
是什么,并且现有实例的 none 匹配那个形状。
顺带一提,你可以到此为止,验证一下。将您的实例更改为此:
instance consArray :: Cons (Array a) b where
cons _ xs = xs
之后,getCons
突然编译。为什么?因为在替换 a ~ X msg
和 b ~ X t3
时,新实例头 Cons (Array a) b
实际上确实匹配所需的 Cons (Array (X msg)) (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
将不起作用。编译器会抱怨它们违反了函数依赖性。 - 但是在 return 中,编译器现在可以通过知道
container
. 来推断
element
因此,如果您只添加该位,getCons
将编译正常:编译器将首先理解 container ~ Array (X msg)
,因此它会选择匹配的实例 - Const (Array a) a
,-并从那里推断出 element ~ X msg
,因此 getX :: Int -> X msg
.
旧答案 - 在问题发生实质性改变之前写的
这与在类型 class 中定义运算符无关。如果您只是从 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
或者你必须在尝试使用它之前从 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
重叠。所以你可以通过将它们放在一个链中来修复 that:
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
尽管我无法想象您对此有什么可能的用例。