Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Haskell IO Passes to Another Function

This question here is related to Haskell Input Return Tuple

I wonder how we can passes the input from monad IO to another function in order to do some computation.

Actually what i want is something like

-- First Example
test = savefile investinput 
-- Second Example
maxinvest :: a
maxinvest = liftM maximuminvest maxinvestinput

maxinvestinput :: IO()
maxinvestinput = do
    str <- readFile "C:\\Invest.txt"
    let cont = words str
    let mytuple = converttuple cont
    let myint = getint mytuple

    putStrLn ""

-- Convert to Tuple
converttuple :: [String] -> [(String, Integer)]
converttuple [] = []
converttuple (x:y:z) = (x, read y):converttuple z

-- Get Integer
getint :: [(String, Integer)] -> [Integer]
getint [] = []
getint (x:xs) = snd (x) : getint xs

-- Search Maximum Invest
maximuminvest :: (Ord a) => [a] -> a
maximuminvest [] = error "Empty Invest Amount List"
maximuminvest [x] = x
maximuminvest (x:xs)   
     | x > maxTail = x  
     | otherwise = maxTail  
     where maxTail = maximuminvest xs 

In the second example, the maxinvestinput is read from file and convert the data to the type maximuminvest expected. Please help.

Thanks.

like image 861
nicholas Avatar asked Dec 17 '22 00:12

nicholas


1 Answers

First, I think you're having some basic issues with understanding Haskell, so let's go through building this step by step. Hopefully you'll find this helpful. Some of it will just arrive at the code you have, and some of it will not, but it is a slowed-down version of what I'd be thinking about as I wrote this code. After that, I'll try to answer your one particular question.


I'm not quite sure what you want your program to do. I understand that you want a program which reads as input a file containing a list of people and their investments. However, I'm not sure what you want to do with it. You seem to (a) want a sensible data structure ([(String,Integer)]), but then (b) only use the integers, so I'll suppose that you want to do something with the strings too. Let's go through this. First, you want a function that can, given a list of integers, return the maximum. You call this maximuminvest, but this function is more general that just investments, so why not call it maximum? As it turns out, this function already exists. How could you know this? I recommend Hoogle—it's a Haskell search engine which lets you search both function names and types. You want a function from lists of integers to a single integer, so let's search for that. As it turns out, the first result is maximum, which is the more general version of what you want. But for learning purposes, let's suppose you want to write it yourself; in that case, your implementation is just fine.

Alright, now we can compute the maximum. But first, we need to construct our list. We're going to need a function of type [String] -> [(String,Integer)] to convert our formattingless list into a sensible one. Well, to get an integer from a string, we'll need to use read. Long story short, your current implementation of this is also fine, though I would (a) add an error case for the one-item list (or, if I were feeling nice, just have it return an empty list to ignore the final item of odd-length lists), and (b) use a name with a capital letter, so I could tell the words apart (and probably a different name):

tupledInvestors :: [String] -> [(String, Integer)]
tupledInvestors []              = []
tupledInvestors [_]             = error "tupledInvestors: Odd-length list"
tupledInvestors (name:amt:rest) = (name, read amt) : tupledInvestors rest

Now that we have these, we can provide ourselves with a convenience function, maxInvestment :: [String] -> Integer. The only thing missing is the ability to go from the tupled list to a list of integers. There are several ways to solve this. One is the one you have, though that would be unusual in Haskell. A second would be to use map :: (a -> b) -> [a] -> [b]. This is a function which applies a function to every element of a list. Thus, your getint is equivalent to the simpler map snd. The nicest way would probably be to use Data.List.maximumBy :: :: (a -> a -> Ordering) -> [a] -> a. This is like maximum, but it allows you to use a comparison function of your own. And using Data.Ord.comparing :: Ord a => (b -> a) -> b -> b -> Ordering, things become nice. This function allows you to compare two arbitrary objects by converting them to something which can be compared. Thus, I would write

maxInvestment :: [String] -> Integer
maxInvestment = maximumBy (comparing snd) . tupledInvestors

Though you could also write maxInvestment = maximum . map snd . tupledInvestors.

Alright, now on to the IO. Your main function, then, wants to read from a specific file, compute the maximum investment, and print that out. One way to represent that is as a series of three distinct steps:

main :: IO ()
main = do dataStr <- readFile "C:\\Invest.txt"
          let maxInv = maxInvestment $ words dataStr
          print maxInv

(The $ operator, if you haven't seen it, is just function application, but with more convenient precedence; it has type (a -> b) -> a -> b, which should make sense.) But that let maxInv seems pretty pointless, so we can get rid of that:

main :: IO ()
main = do dataStr <- readFile "C:\\Invest.txt"
          print . maxInvestment $ words dataStr

The ., if you haven't seen it yet, is function composition; f . g is the same as \x -> f (g x). (It has type (b -> c) -> (a -> b) -> a -> c, which should, with some thought, make sense.) Thus, f . g $ h x is the same as f (g (h x)), only easier to read.

Now, we were able to get rid of the let. What about the <-? For that, we can use the =<< :: Monad m => (a -> m b) -> m a -> m b operator. Note that it's almost like $, but with an m tainting almost everything. This allows us to take a monadic value (here, the readFile "C:\\Invest.txt" :: IO String), pass it to a function which turns a plain value into a monadic value, and get that monadic value. Thus, we have

main :: IO ()
main = print . maxInvestment . words =<< readFile "C:\\Invest.txt"

That should be clear, I hope, especially if you think of =<< as a monadic $.

I'm not sure what's happening with testfile; if you edit your question to reflect that, I'll try to update my answer.


One more thing. You said

I wonder how we can passes the input from monad IO to another function in order to do some computation.

As with everything in Haskell, this is a question of types. So let's puzzle through the types here. You have some function f :: a -> b and some monadic value m :: IO a. You want to use f to get a value of type b. This is impossible, as I explained in my answer to your other question; however, you can get something of type IO b. Thus, you need a function which takes your f and gives you a monadic version. In other words, something with type Monad m => (a -> b) -> (m a -> m b). If we plug that into Hoogle, the first result is Control.Monad.liftM, which has precisely that type signature. Thus, you can treat liftM as a slightly different "monadic $" than =<<: f `liftM` m applies f to the pure result of m (in accordance with whichever monad you're using) and returns the monadic result. The difference is that liftM takes a pure function on the left, and =<< takes a partially-monadic one.

Another way to write the same thing is with do-notation:

do x <- m
   return $ f x

This says "get the x out of m, apply f to it, and lift the result back into the monad." This is the same as the statement return . f =<< m, which is precisely liftM again. First f performs a pure computation; its result is passed into return (via .), which lifts the pure value into the monad; and then this partially-monadic function is applied, via =<,, to m.

It's late, so I'm not sure how much sense that made. Let me try to sum it up. In short, there is no general way to leave a monad. When you want to perform computation on monadic values, you lift pure values (including functions) into the monad, and not the other way around; that could violate purity, which would be Very Bad™.


I hope that actually answered your question. Let me know if it didn't, so I can try to make it more helpful!

like image 157
Antal Spector-Zabusky Avatar answered Jan 08 '23 02:01

Antal Spector-Zabusky