在Haskell中推导Read:为什么我必须使用构造函数的参数名称?

4

我一直在尝试使用 deriving,但是理解 deriving (Read) 的工作方式有些困难。

让我们看一下以下数据定义:

data Data         = D Int deriving (Eq, Read, Show)
data DataWithName = DWN { val :: Int } deriving (Eq, Read, Show)

这里没有什么特别的,只有两个数据类型,都包含一个Int,但第二个引入了一个名为val的名称,表示这个Int

在交互式控制台中,下面的指令可以正常工作:

*Main> D 5
D 5
*Main> DWN 5
DWN {val = 5}
*Main> DWN { val = 5 }
DWN {val = 5}

以下的代码不起作用(编辑:我预计这个代码不会起作用)

*Main> D { val = 5 }

<interactive>:11:1:
    Constructor `D' does not have field `val'
    In the expression: D {val = 5}
    In an equation for `it': it = D {val = 5}

现在让我们来谈谈这个问题:
我以为派生Read会给我相同的输入数据类型的方式,但是在下面的代码中,第一行和第二行可以工作,而第三行不起作用,因为没有给出参数名称:
d1 = read "D 1" :: Data -- Works
d2 = read "DWN { value = 1 }" :: DataWithName -- Works
d3 = read "DWN 1" :: DataWithName -- This does not work because parameter is not given.

有没有可能使derving (Read)能够推导出“非参数名称构造函数”,以便read "DWN 5" :: DataWithName能够额外工作,而不仅仅是“参数名称构造函数”?
或者你能否提供关于如何处理数据读取/输入的信息?
谢谢!

3
总的来说,“Read”相当糟糕。 “Read”最大的优点是被广泛实现,(几乎?)所有通用解析库都可以在其可用时使用其实例。但对于任何严肃的工作,您真的应该使用像“parsec”,“attoparsec”这样的东西,或者(如果性能确实很关键并且您愿意处理解析器生成器)Happy。 - dfeuer
谢谢,我会记住的。我在大学里只学了Haskell的基础知识,对于在Haskell中开发“严肃”的应用程序一无所知。如果您能为我提供有关阅读/编写数据时需要注意的事项以及相关链接/信息,我将不胜感激。如果您能再回答一下,我会给您点赞的 ;) - Markus Weninger
2
这是一个非常泛泛的问题。我也不会说我对“严肃应用程序”有很多了解。attoparsec 往往很容易使用,但它没有很好的报告解析错误的功能。parsec 更加复杂,而且在我看来不太直观,但它具有相当不错的错误处理能力。trifecta 以出色的错误处理而闻名,但性能却很差。 - dfeuer
好的,感谢所有的建议!我认为在使用Haskell方面,我还有很长的路要走才能变得更加高效 :D 我想我必须找到一个真正的项目,然后尝试用Haskell来实现它。 实践出真知。:P - Markus Weninger
1
这是学习更多关于解析器的好起点:https://github.com/JakeWheat/intro_to_parsing - Sibi
1个回答

2
我认为派生Read会给我相同的方式来输入数据类型,但是在下面的代码中,第一行和第二行正常工作,而第三行不起作用...
这并不是Read或deriving所做的,事实上表达式D { val = 5 }根本不正确。val只是从记录构造中得到的另一个普通函数。甚至可以检查其类型:
ghci| > :t val
val :: DataWithName -> Int

数据构造器D只接受Int作为其参数,这可以从它的类型中看出:
ghci| > :t D
D :: Int -> Data

无论你做什么,都不能将D { val = 5 }这样的内容赋给它,因为这是一种类型错误。
引用块:

是否有可能启用派生(Read)来派生“非参数名称构造函数”,以便read "DWN 5" :: DataWithName也能正常工作,而不仅仅是“参数名称构造函数”?

没有。但是你可以对String进行一些解析,并手动将其转换为所需的数据结构。

1
谢谢您的解释,但我认为是我翻译错了。我本来以为 D { val = 5 } 不会起作用。总的来说,这都是关于是否必须手动转换字符串的问题,而您的回答是“是的,我必须这样做”。 - Markus Weninger
"你可以对字符串进行一些解析,并手动将其转换为所需的数据结构。" 或者 "如果你喜欢冒险,你可以使用 instance Read Data where read = unsafeCoerce . (read :: String -> DataWithName)。" - user2407038
@user2407038 这在 7.10.3 版本中会导致编译器错误,因为 read 实际上不是 Read 类型类的方法:http://haddock.stackage.org/lts-4.2/base-4.8.2.0/Text-Read.html#g:1 - Sibi

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