Reputation: 5410
The following function clearly has duplication between the two list comprehensions, but how can it be eliminated without increasing the total length of the code? I've got a sneaky feeling there's a nice abstraction lurking here but I just can't see it...
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]
Some background:
The function is taken from a crossword program. The crossword is represented as [Word], where
data Word = Word { startX :: Int,
startY :: Int,
text :: String,
sense :: Sense }
data Sense = Across | Down
The words where sense == Across start at position (startX, startY) and continue in the positive x direction, and those where sense == Down continue in the positive y direction. The aim of the function is to get the character at location (x, y) in a Just, or Nothing if there isn't a character there.
Feel free to point out any other sins against Haskell I've committed here, I've just started with the language and still trying to get to grips with it!
Upvotes: 2
Views: 175
Reputation: 5410
Satvik, thanks for your answer which got me thinking along the right lines. I separated out the condition and transformation functions as you suggested, then realized that it would be simpler to transform the data rather than the functions and put everything back into a list comprehension for readability:
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 ]
You pointed out that it's better to use Data.find in this case.. is this for readability or efficiency? I'm guessing that because lists are lazy in Haskell, the above code would stop after the first item in the list comprehension was evaluated, is this right?
Upvotes: 1
Reputation: 11208
Here are some points about your code:
filter
when want to select certain elements of a list based on the predicate. Data.List.find
Your conditions looks symmetric so you can define a transform
function as
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
Now writing the code will require writing conditions only once
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)
Upvotes: 4