如何从以下Haskell代码段中消除重复?

时间:2012-09-15 14:21:32

标签: haskell

以下功能显然在两个列表推导之间存在重复,但如何在不增加代码总长度的情况下消除它?我有一种偷偷摸摸的感觉,这里隐藏着一个很好的抽象,但我看不到它......

letterAt :: [Word] -> Int -> Int -> Maybe Char 
letterAt wrds x y = listToMaybe $ 
  [wtext !! (x - wx) | 
    Word wx wy wtext Across <- wrds, 
    wy == y && x >= wx && x < wx + length wtext] ++ 
  [wtext !! (y - wy) | 
    Word wx wy wtext Down <- wrds, 
    wx == x && y >= wy && y < wy + length wtext] 

一些背景知识:

该功能取自填字游戏程序。填字游戏表示为[Word],其中

data Word = Word { startX :: Int, 
                   startY :: Int, 
                   text :: String, 
                   sense :: Sense } 

data Sense = Across | Down

感觉的词= =在位置(startX,startY)开始并在正x方向继续,而感觉= =向下的那些在正y方向继续。该函数的目的是将位置(x,y)中的字符设置为Just,如果没有字符则为Nothing。

随意指出我在这里犯下的Haskell的任何其他罪行,我刚开始使用这种语言并且仍然试图掌握它!

2 个答案:

答案 0 :(得分:4)

以下是您的代码的一些要点:

  • 当想要根据谓词选择列表的某些元素时,最好使用filter
  • 由于您只想要满足某个谓词的第一个元素,因此您可以使用Data.List.find

您的条件看起来是对称的,因此您可以将transform函数定义为

transform f x y (Word wx wy wtext Across) = f wtext wy wx y x
transform f x y (Word wx wy wtext Down) = f wtext wx wy x y

现在编写代码只需要写入条件

letterAt :: [Word] -> Int -> Int -> Maybe Char
letterAt wrds x y = (transform charValue x y) <$> find (transform condition x y)  wrds
    where
        condition wtext wx wy x y = wx == x && y >= wy && y < wy + length wtext
        charValue wtext wx wy x y = wtext !! (y-wy)

答案 1 :(得分:1)

Satvik,谢谢你的回答让我思考正确的方向。我按照你的建议分离了条件和转换函数,然后意识到转换数据而不是函数会更简单,并将所有内容放回列表理解中以便于阅读:

letterAt :: [Word] -> Int -> Int -> Maybe Char 
letterAt wrds x y = listToMaybe 
  [wtext !! x' | Word wx wy wtext sens <- wrds,   
                 let (x', y') = case sens of 
                       Across -> (x - wx, y - wy) 
                       Down   -> (y - wy, x - wx), 
                 y' == 0, x' >= 0, x' < length wtext ] 

你指出在这种情况下最好使用Data.find ..这是为了可读性还是效率?我猜测因为列表在Haskell中是懒惰的,上面的代码会在列表推导中的第一项被评估后停止,这是对的吗?