mrfr
mrfr

Reputation: 1794

Assign the return value of two functions, to two variables, in a do block

Sorry for the title gore (if you can suggest a better, please do). But my problem is that I dont quite understand how to get this do block to work. I have a code that returns the position of 5 in a list of lists. Like such:

findFive :: [[Int]] -> (Int, Int)
findFive rs = do
    x <- xPos rs 0
    y <- yPos rs 0
    return ( (x,y) )

xPos :: [[Int]] -> Int -> Int
xPos (rs:[[]]) n             = n
xPos (rs:rss)  n | elem 5 rs = n
                 | otherwise = xPos rss (n+1)

yPos :: [[Int]] -> Int -> Int
yPos (rs:[[]]) n              = n
yPos (rs:rss)  n | elem 5 rs  = n
                 | otherwise  = yPos rss (n+1)

I

But I cant use my do block this way. I can get it to work by doing

findFive :: [[Int]] -> (Int, Int)
findFive xs = ( (xPos xs 0), (yPos (transpose (xs)) 0) )

But that looks kinda ugly.

Also, is there a way to get this to work without sending in 0 to xPos and yPos ?

Upvotes: 2

Views: 186

Answers (3)

chepner
chepner

Reputation: 531245

Let's say pos is defined this way:

pos :: Eq =>    a          -- A value to find
             -> [[a]]      -- A 2d matrix
             -> Maybe Int  -- Index of first row containing the value, if any
pos k rows = pos' rows 0
             where pos' [] _ = Nothing
                   pos' (x:xs) n | elem k x = n
                                 | otherwise = pos' xs (n+1)

There are several changes here:

  1. It will work for lists of any type on which equality is defined, not just Int.
  2. It is generalized to find any value k :: a, not just 5.
  3. It can deal with failure to find any row containing k.

With this definition, we could define findFive as

findFive :: [[Int]] -> (Maybe Int, Maybe Int)
findFive xs = (pos 5 xs, pos 5 (transpose xs))

Using Control.Lens, you can factor out the function pos 5 so that it only needs to be written once. Think of over both as a version of map for pairs instead of lists.

import Control.Lens
findFive xs = over both (pos 5) (xs, transpose xs)

Using Control.Arrow, you can factor out the argument xs so that it only needs to be written once.

import Control.Lens
import Control.Arrow
findFive xs = over both (pos 5) ((id &&& transpose) xs)
-- id &&& transpose = \x -> (id x, transpose x)

Once you've done that, you can easily write findFive in point-free style, by composing over both (pos 5) and id &&& transpose:

findFive = over both (pos 5) . (id &&& transpose)

Upvotes: 0

Yawar
Yawar

Reputation: 11607

You can't use a do block this way because in do blocks you have to (1) bind names to the 'contents' of monadic values, and (2) return a value wrapped in the same monadic type as used in (1). In this case the monadic type would be the list. It's appropriate to return a list of (row, column) pairs because that automatically handles both the cases of not finding the number, or finding it multiple times. So we could do something like

import Control.Monad

findFive ::
  [[Int]] ->   -- ^ a matrix of numbers.
  [(Int, Int)] -- ^ the (zero-indexed) rows and columns of the number
               -- ^ @5@, if any (otherwise empty list).
findFive xss =
  do
    (xs, rowIdx) <- xss `zip` [0 ..]
    (x, colIdx) <- xs `zip` [0 ..]
    guard $ x == 5

    return (rowIdx, colIdx)

input :: [[Int]]
input = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]

-- Should print "row: 1, col: 1".
main :: IO ()
main =
  forM_ (findFive input) $ \(rowIdx, colIdx) ->
    do
      putStr "row: "
      putStr $ show rowIdx
      putStr ", col: "
      print colIdx

Upvotes: 0

chi
chi

Reputation: 116139

Why do? There are no monads there. A let..in suffices:

findFive :: [[Int]] -> (Int, Int)
findFive rs = let
    x = xPos rs 0
    y = yPos rs 0
    in (x,y)

Alternatively, use where:

findFive :: [[Int]] -> (Int, Int)
findFive rs = (x, y)
    where
    x = xPos rs 0
    y = yPos rs 0

Upvotes: 8

Related Questions