SoldierKB
SoldierKB

Reputation: 53

Simple Haskell program not behaving correct

I'm new to Haskell and trying to write simple program to find maximal element and it's index from intput. I receive values to compare one by one. Maximal element I'm holding in maxi variable, it's index - in maxIdx. Here's my program:

loop = do
    let maxi = 0
    let maxIdx = 0
    let idx = 0
    let idxN = 0
    replicateM 5 $ do
        input_line <- getLine
        let element = read input_line :: Int

        if maxi < element
        then do 
            let maxi = element
            let maxIdx = idx
            hPutStrLn stderr "INNER CHECK"
        else
            hPutStrLn stderr "OUTER CHECK"

        let idx = idxN + 1
        let idxN = idx

        print maxIdx

    loop

Even though I know elements coming are starting from bigger to smaller (5, 4, 3, 2, 1) program enters INNER CHECK all the time (it should happen only for the first element!) and maxIdx is always 0. What am I doing wrong? Thanks in advance.

Upvotes: 1

Views: 173

Answers (2)

user2297560
user2297560

Reputation: 2983

I think alf's answer is very good, but for what it's worth, here's how I would interpret your intention.

{-# LANGUAGE FlexibleContexts #-}

module Main where

import System.IO
import Control.Monad.State

data S = S { maximum :: Int
           , maximumIndex :: Int
           , currentIndex :: Int }

update :: Int -> Int -> S -> S
update m mi (S _ _ ci) = S m mi ci

increment :: S -> S
increment (S m mi ci) = S m mi (ci+1)

next :: (MonadIO m, MonadState S m) => m ()
next =  do
  S maxi maxIdx currIdx <- get

  input <- liftIO $ getLine
  let element = read input :: Int

  if maxi < element
  then do
    modify (update element currIdx)
    liftIO $ hPutStrLn stderr "INNER CHECK"
  else
    liftIO $ hPutStrLn stderr "OUTER CHECK"

  modify increment

run :: Int -> IO S
run n = execStateT (replicateM_ n next) (S 0 0 0)

main :: IO ()
main = do
  S maxi maxIdx _ <- run 5    
  putStrLn $ "maxi: " ++ (show maxi) ++ " | maxIdx: " ++ (show maxIdx)

This uses a monad transformer to combine a stateful computation with IO. The get function retrieves the current state, and the modify function lets you change the state.

Upvotes: 0

alf
alf

Reputation: 8523

Anyway, let's have fun.

loop = do
    let maxi = 0
    let maxIdx = 0
    let idx = 0
    let idxN = 0
    replicateM 5 $ do
        input_line <- getLine
        let element = read input_line :: Int

        if maxi < element
        then do 
            let maxi = element
            let maxIdx = idx
            hPutStrLn stderr "INNER CHECK"
        else
            hPutStrLn stderr "OUTER CHECK"

        let idx = idxN + 1
        let idxN = idx

        print maxIdx

    loop

is not a particularly Haskelly code (and as you know is not particularly correct).

Let's make if Haskellier.

What do we do here? We've an infinite loop, which is reading a line 5 times, does something to it, and then calls itself again for no particular reason.

Let's split it:

import Control.Monad

readFiveLines :: IO [Int]
readFiveLines = replicateM 5 readLn

addIndex :: [Int] -> [(Int, Int)]
addIndex xs = zip xs [0..]

findMaxIndex :: [Int] -> Int
findMaxIndex xs = snd (maximum (addIndex xs))

loop :: ()
loop = loop

main :: IO ()
main = do xs <- readFiveLines
          putStrLn (show (findMaxIndex xs))

snd returns the second element from a tuple; readLn is essentially read . getLine; zip takes two lists and returns a list of pairs; maximum finds a maximum value.

I left loop intact in its original beauty.

You can be even Haskellier if you remember that something (huge expression) can be replaced with something $ huge expression ($ simply applies its left operand to its right operand), and the functions can be combined with .: f (g x) is the same as (f . g) x, or f . g $ x (see? it's working for the left side as well!). Additionally, zip x y can be rewritten as x `zip` y

import Control.Monad

readFiveLines :: IO [Int]
readFiveLines = replicateM 5 readLn

addIndex :: [Int] -> [(Int, Int)]
addIndex = (`zip` [0..])

findMaxIndex :: [Int] -> Int
findMaxIndex = snd . maximum . addIndex

main :: IO ()
main = do xs <- readFiveLines
          putStrLn . show . findMaxIndex $ xs

As for debug print, there's a package called Debug.Trace and a function traceShow which prints its first argument (formatted with show, hence the name) to stderr, and returns its second argument:

findMaxIndex :: [Int] -> Int
findMaxIndex = snd . (\xs -> traceShow xs (maximum xs)) . addIndex

That allows you to tap onto any expression and see what's coming in (and what are the values around — you can show tuples, lists, etc.)

Upvotes: 9

Related Questions