惯用列表构造
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
。以上是关于惯用列表构造的主要内容,如果未能解决你的问题,请参考以下文章
如何使用惯用的Scala替换(填充)来自另一个列表的选项列表中的None条目?