惯用列表构造

Posted

技术标签:

【中文标题】惯用列表构造【英文标题】:Idiomatic list construction 【发布时间】:2015-09-02 19:00:37 【问题描述】:

我对 Haskell 和函数式编程很陌生,所以我真的不知道如何使这段代码符合地道:

type Coord = Double
data Point = Point Coord Coord Coord deriving Show

type Polyline = [Point]

-- Add a point to a polyline
addPoint :: Polyline -> Point -> Polyline
addPoint line p = p:line

line :: Polyline
line = []

constructLine :: Polyline -> Polyline
constructLine line =
    let 
        p1 = Point 2 4 87
        p2 = Point 3 7 2
        p3 = Point 23 4 8
    in addPoint (addPoint (addPoint line p1) p2) p3

main :: IO() 
main = do
    putStrLn ( show (constructLine line))

我的问题出在constructLine 函数中。如果我想添加很多点,嵌套的addPoint 函数将是一个问题。我该如何考虑这个?您还有其他可以改进的地方吗?

【问题讨论】:

提供一个给定点列表构造折线的函数。例如,您可以执行addPoints line ps = ps ++ line 或类似的操作。顺便说一句:你也可以用中缀表示法调用addPoint 惯用的方式可能是放弃addPoint,直接将列表构造为[p3, p2, p1],但如果您想保留该抽象,则只需创建自己的运算符,如infixr 5 #(#) :: Point -> Polyline -> Polyline(#) = addPoint(请注意,我在这里交换了参数顺序)。 infixr 行很重要,请确保保留它,但这意味着您可以改为将其写为 p3 # p2 # p1 # line。如果您真的想保持参数顺序,请执行infixl 5 #,它将改为line # p1 # p2 # p3 请注意,如果您将参数顺序更改为addPoint :: Point -> Polyline -> Polyline,那么您可以将其写为addPoint p3 $ addPoint p2 $ addPoint p1 line,这可能足以满足您的目的。 【参考方案1】:

对 addPoints 的多次调用可以替换为折叠。正如评论中所建议的,反转您的 addPoint 函数会使事情变得更容易:

addPoint' :: Point -> Polyline ->  Polyline
addPoint' p line = p:line

那么你的constructLine函数可以构建一个临时列表来添加添加使用折叠:

constructLine :: Polyline -> Polyline
constructLine line =
    let
        p1 = Point 2 4 87
        p2 = Point 3 7 2
        p3 = Point 23 4 8
    in foldr addPoint' line [p3,p2,p1]

这不会破坏封装(您可以将 Polyline 的实现替换为点列表以外的其他内容)并按照新点的结束顺序使用新点(p3 在 p2 前面,等等.)

【讨论】:

【参考方案2】:

您的 constructLine 示例让我觉得这是一个冗长的版本:

constructLine :: Polyline -> Polyline
constructLine line = [Point 23 4 8, Point 3 7 2, Point 2 4 87] ++ line

我不知道你是否遇到过这些,但请确定一下:

[Point 23 4 8, Point 3 7 2, Point 2 4 87] 只是一个列表文字。 ++ 是追加列表的函数。

通常,要将多个元素添加到列表中,您要做的就是拥有一个要添加的元素的列表,并将该列表与原始列表一起附加。

这种模式仍在继续。如果我们注意到line 是一个定义为[] 的常量,那么你的整个程序实际上只是一个冗长的版本:

type Coord = Double
data Point = Point Coord Coord Coord deriving Show

main :: IO () 
main = putStrLn (show points)
     where points = [Point 23 4 8, Point 3 7 2, Point 2 4 87]

基本上,如果在编译时知道点的值,您可以编写列表,无需经过所有这些间接操作。

【讨论】:

是的,我知道这一点,但我想抽象折线结构。无论如何感谢您的回答! @ElieGnrd:如果折线在概念上类似于列表,但您希望保护不变量,一种简单的方法是在不透明 PolyLine 类型和列表之间来回转换函数的点。然后可以根据列表操作(Haskell 很好地支持)来完成很多逻辑,并且要创建PolyLine,您只需提供一个列表。例如,Haskell 平台中的Data.Map.Map 类型就是这样工作的——创建一个最常见的方法之一是使用函数fromList :: Ord k => [(k, v)] -> Map k v

以上是关于惯用列表构造的主要内容,如果未能解决你的问题,请参考以下文章

附加到简短的 python 列表的惯用语法是啥?

在 F# 中“合并”多个相同长度的列表的惯用方式?

如何使用惯用的Scala替换(填充)来自另一个列表的选项列表中的None条目?

F# - 将 100 个对象创建到一个列表中 - 最实用和惯用的方式

在列表理解中拥有许多相同生成器的惯用方式

在 Kotlin 中处理可为空或空列表的惯用方式