有没有一种方法可以在Haskell中取消映射?

3

我正在编写一个Haskell程序。我创建了一个名为“measurement”的数据类型,它是一个double数组,看起来像这样:

data Measurement = Measurement [Double]  deriving (Show)

我有一个将数据转化为Measurement的函数,它接受一个双重列表并将其转化为一个Measurement的列表。具体实现如下:

castToMeasurement :: [[Double]] -> [Measurement]
castToMeasurement = map Measurement

但现在我想对双精度值进行一些操作。那么有没有一种方法可以将其映射到一个双精度数组?所以当我给出一个测量值(或测量值列表)时,它会将其转换为双精度列表(或双精度列表的列表)。谢谢!


每当你有 data MyType = SingleConstructor ExistingType 或者 data MyType parameter = SingleConstructor (ExistingTypeExpression parameter) 时,你应该使用一个新类型,比如 newtype MyType = SingleConstructor {getSingleConstructor :: ExistingType} 等等。newtype 声明的工作方式与 data 声明非常相似,只是因为只允许一个构造函数,编译器可以实现避免错误的编译时类型安全,但是 SingleConstructorgetSingleConstructor 实现为无操作,因此在运行时没有空间或时间影响。 - AndrewC
请注意,Measurement 是一个列表,而不是一个数组。在Haskell中,这些结构非常不同。此外,在这种情况下,您应该考虑从data切换到newtype,因为newtype可以在像castToMeasurement这样的情况下提供渐近更好的性能,特别是在最新的GHC中。 - dfeuer
4个回答

6
是的,有这样一个东西:
data Measurement = Measurement { getMeasurement :: [Double] } deriving Show

castToMeasurement :: [[Double]] -> [Measurement]
castToMeasurement = map Measurement

castFromMeasurement :: [Measurement] -> [[Double]]
castFromMeasurement = map getMeasurement

简单易懂,不是吗?


这真的很简单。谢谢! - Maarten Meeusen
哦,也许还有一个问题。getMeasurement是用来做什么的? - Maarten Meeusen
哦,我的回答太晚了:D - d12frosted
5
你应该阅读有关记录语法的内容:这里这里 - d12frosted
@arunasr 我认为这是完全合理的。只使用记录语法处理一个字段有什么问题?我在标准库中看到它被多次使用。例如:newtype Identity a = Identity { runIdentity :: a },可以在这里看到:http://hackage.haskell.org/package/transformers-0.4.1.0/docs/src/Data-Functor-Identity.html。为什么要使用模式匹配手动定义一个函数,而你可以免费使用记录语法来获得它?此外,你不能对`castFromMeasurement`进行模式匹配,因为输入是一个列表(即`[Measurement]`)。你无法定义`Measurement`的`Functor`实例。 - Aadit M Shah
@AaditMShah 同意。抱歉,那是匆忙评论,没有足够的思考。 - ArunasR

6

好的,有的和没有的。

按照你提出问题的方式,是否有一种“取消映射”的方法,答案通常是没有。假设我们有一个字符串列表:

example1 :: [String]
example1 = ["Hello", "cruel", "world"]

我们可以使用map length将其映射到字符串的长度:
example2 :: [Int]
example2 = map length example
-- value: [5, 5, 5]

但是没有办法“取消映射”example2的值以获取原始的example1。这需要一个函数,给定长度,找出原始列表字符串,但这显然是不够的信息!
但是这为我们提供了一些提示,可以让我们知道什么样的情况下可以执行您想要的“取消映射”。如果我们最初映射的函数具有反函数,那么我们可以使用该反函数进行映射,以“撤消”map的效果。在您的情况下,Measurement构造函数确实具有反函数:
-- | The inverse of the 'Measurement' constructor.  Laws:
--
-- > Measurement (getMeasurement x) == x
-- > getMeasurement (Measurement xs) == xs
getMeasurement :: Measurement -> [Double]
getMeasurement (Measurement xs) = xs

由于 MeasurementgetMeasurement 是相反的,因此可以得出 map Measurementmap getMeasurement 也是这样。因此有:

map getMeasurement (map Measurement xs) == xs
map Measurement (map getMeasurement xs) == xs

4
当然可以。在这里,您可以了解更多关于 拆解数据构造函数 的信息。
让我们考虑函数 f
f :: [Double] -> Measurement
f list = Measurement list

它只是封装了 Measurement 的构造函数。我使用 new 函数,因为对于我来说,思考函数要比思考构造函数容易得多。

现在你需要 f 的反函数:

g :: Measurement -> [Double]
g (Measurement list) = list

现在你可以构造函数:

castFromMeasurement :: [Measurement] -> [[Double]]
castFromMeasurement = map g

它看起来有点丑陋。因此,我们可以使用Lambda表达式进行修改:

castFromMeasurement :: [Measurement] -> [[Double]]
castFromMeasurement = map (\(Measurement list) -> list)

但请注意,这仅适用于您的数据类型不是抽象的情况(您可以完全访问构造函数)。另外,您可以按照以下方式重新定义您的数据:
data Measurement = Measurement { getMeasurement :: [Double] } deriving Show

在这种情况下,你已经有了一个名为g=getMeasurement的函数。因此,castFromMeasurement看起来像这样:
castFromMeasurement :: [Measurement] -> [[Double]]
castFromMeasurement = map getMeasurement

更一般地说,只有当你用于映射的函数 f 是可逆的时,才能执行unmap操作。

3
你得到了关于问题的答案,但让我们把数学带到桌面上,并学习何时以及如何取消映射。
我们是纯函数式编程人员;这意味着我们编写的函数非常数学化(出于许多原因,其中之一是:可以编写此答案)。在处理函数时,函数域是输入类型的每个可能值(对于装箱类型,还包括底部)。同样地,范围是输出类型的每个可能值。
你基本上在询问你示例中的函数 (fmap Measurement) 的反函数
函数的反函数会“撤销”该函数所做的操作。
如果我有值x和函数f,并且f的反函数是g,那么根据定义x = f(g(x))) = g(f(x)))。这可能是无意义的,因此请考虑函数f = (+1)g = subtract 1,并选择任何整数作为x。例如,让我们说x=5f(5) = 6,现在请注意,当您应用g时--g(6) = 5--您得到了您开始的数字。
通过将结果应用于g,您“撤销”了f,因为gf的反函数。
一些函数没有反函数(就像Luis Casillas在此处他的答案中所示)。当您的函数有反函数时,您需要自己找到它。如果确实可能,通常它和您试图求逆的函数一样难(例如在上述例子中,加号变为减号。就像您的例子一样 - 您的函数很简单,因此反函数也会很简单)。
判断是否存在反函数的一种简单方法是查看定义域和值域之间是否存在一对一映射。如果不存在,则在应用函数时丢失了数据,无法回溯。因此,如果不存在反函数但仍需要返回原始值,则应找到其他手段。例如,在先前对原始值进行压缩((x,fx)),要返回原始值,只需应用 fst

可汗学院:反函数


TheMathPage上的反函数


维基百科上的反函数


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接