我们知道,fmap
“签名是(a -> b) -> f a -> f b
其中f
的一个Functor
。
为了尽可能地通用和更好地分解因子代码,人们可能希望将“事物列表”映射到另一个可能不同的“事物列表”。凭直觉,我不明白为什么它不应该或不可能。
我正在寻找的功能gmap
与该功能具有相同的功能,fmap
但具有该签名gmap :: (a -> b) -> (f a) -> (g b)
,在该功能中,我允许到达和离开容器有所不同。
我不确定这在一般情况下where f
和g
are 是否有意义Functors
,但是Traversable
假设我最感兴趣的是遍历数据,那么“事物列表”的想法在本质上听起来更像是课堂上所捕获的。
所以也许签名应该是gmap :: (Traversable f, Traversable g) => (a -> b) -> (f a) -> (g b)
。
即使g
具有不同的性质f
,它仍然可以从左到右遍历,因此仍然觉得应该能够将的第k个被访问元素映射f
到的第k个被访问元素g
。
假设我的想法没有错,Haskell中是否有这样的功能?
从本质上讲,我的问题是,您将如何以最简洁,最优雅的方式从Haskell中的一个类似列表的事物转换为另一个?
我们经常在Haskell中使用一种技巧来证明事情不可能发生,那就是尝试使用它来产生“假”,也就是产生一个type值
data Void
没有构造函数的类型。如果可能的话,使用您的类型签名来产生type的值Void
,那么就不可能实现您的类型签名。这也被称为“荒谬的还原”,或“矛盾相抵触”。如果您的类型签名允许我们产生类型的值Void
...那么,“显然”您的类型签名是双层的,无法实现。
在这种情况下,我们正在“返回”一个Traversable
实例,所以我们使用Traversable
like (,) Void
:
instance Traversable ((,) w) where traverse f (x, y) = (x,) <$> f y
现在,让我们f
用作任何旧的仿函数。它可以是任何东西...让我们使用,Maybe
因为似乎每个人都已经理解了。
然后,您可以编写:
gmap :: (a -> b) -> Maybe a -> (Void, b)
哦,不,那是不对的...看来,使用gmap可以Void
通过传入任何旧内容来创建一个:
gmap :: (() -> ()) -> Maybe () -> (Void, ())
所以现在我的创建策略是Void
:
bad :: Void bad = fst (gmap id Nothing)
因为Void
没有构造函数,所以不应该bad :: Void
存在类型的值(忽略诸如无限循环或部分函数之类的东西)。因此,如果仅存在的话gmap
就可以允许我们创建类型的值Void
...,则必须意味着它gmap
不能以您给定的形式存在。
对于更笼统的问题,Traversable
工作原理的“原因” 是它只能修改结构。它无法创建它们。在这里,您要创建的值g b
,但Traversable
不能“创建”它,而只能“转换”它。您的误解可能是因为您认为这Traversable
是“列表式”类型类:事实并非如此。使用[]
作为原型可能会误入歧途导致你。
我“典型” Traversable
的想象类型类的属性是Map k
,来自容器的Data.Map
:a Map
不是a,而是与键关联的值。对它的任何操作都必须能够尊重此关联属性...并且不能将其视为没有额外结构的大列表。
那么,什么会是可能是这样的:
replace :: (Foldable f, Traversable g) => f a -> g b -> g a
其中的所有值g b
都由的所有值代替f a
。如果您正在寻找一种练习,那么写这个实际上很有趣。基本上,replace
将保持相同的结构,该g a
有,但只是更换了值。所以,你可以“创造”一个g a
从f a
只要你有一个“榜样g b
”,可以这么说。如果您使用了类似的方法:
replace :: [a] -> Map k b -> Map k a
然后replace
将第二张地图中的所有值替换为列表中的项目,并以适当的键值替换它们。
然后您可以编写:
gmap :: (Traversable a, Traversable g) => (a -> b) -> f a -> g c -> g b
在此处以g a
您要复制的结构的“示例”为例。
能够“构建” Haskell常见类型类中的结构的最接近的东西是IsList
,来自https://hackage.haskell.org/package/base-4.12.0.0/docs/GHC-Exts.html#t:IsList
此类型类为您提供了fromList
和的两个函数toList
,因此您可以编写:
throughIsList :: (IsList l, IsList m, Item l ~ Item m) => l -> m throughIsList = fromList . toList
并使其在Functor
s上工作:
gmap :: (IsList (f a), IsList (g b), Item (f a) ~ a, Item (g b) ~ b) => (a -> b) -> f a -> g b gmap f = fromList . map f . toList
现在的问题是,大多数Functor
s都不是IsList
...的实例,而且许多实际实例也不是总数。因此,对于大多数Functor
s 来说,它并不是很有用。
因此,最后我认为没有令人满意的答案。如果您正在做的事情依赖于一个很好的答案(而不是“否”的答案)的事实……也许我可以问一下您的“最终目标”是什么?您打算将这种类型用于什么?
(例如,在那里的人问这样的问题的情况下90%“是有办法,我可以转换单子”或类似的东西,通常他们不想要做的东西一般,而是他们有特定的类型,他们中有心神。)