This answer is largely based on your comment, which I quote below so it doesn't get lost. (And many apologies for the length of it: I really didn't intend for this to be so mammoth, but there was a lot I felt it would be helpful to say. Hopefully it is, but don't hesitate to tell me if it isn't.)
Read INI file, read section list off it, print it into stdout, ask for user to choose one of them(simple getline is fine) and then set the selected section as [default]. Pretty much. My main struggle is constant type error when I store results of functions perfroming what I want in main.
And here is the code you linked to:
module Main where
import Data.Ini
main :: IO ()
main = do
tehfile <- readIniFile "/Users/g.reshetniak/.aws/credentials" -- ~/.aws/credentials: openFile: does not exist --> why shell expansion doesn't work?
s <- case tehfile of
Left a -> print "woot"
Right b -> head (map (\x -> print x) (sections b)) --TODO why this prints one element instead of mapping print over all section
--TODO selected_profile <- getLine
--TODO why `selected_profile <- getLine` is different from `let selected_profile = getLine` ?
p <- case tehfile of
Left c -> print "oops" --TODO why this does not print "oops" on lacking profile, instead prints Left "Couldn't find section: selected_profile"
Right d -> print (lookupValue "selected_profile" "aws_access_key_id" d)
return ()
Now I must admit that I've never come across an "INI file" before, so I'm not familiar with the format. So some of this is based on what I can glean from the documentation of the Data.Ini module you're using (which, in all fairness, seems very straightforward). So I can't promise to show you how to do exactly what you want (in particular I really don't know what you mean by "set the selected section as [default]") - but I hope to answer some of the questions you've asked in your code comments, and perhaps correct some misconceptions you may have about performing I/O in Haskell.
One of the first things I notice from this file, as in the code in your original question, is that you're binding variables to values, with the <- operator, but not then making use of them. Doing s <- someValue in a do block in Haskell is quite a bit like doing s = someValue in an imperiative language. It's pointless doing that in an imperative language if you don't then ever use the s variable afterwards, and it's exactly as pointless in Haskell, for exactly the same reason. In other words, this:
main = do
....
s <- someValue
....
is, if the second .... doesn't mention s anywhere, completely equivalent to this:
main = do
....
someValue
....
And note that someValue has been kept in there, because it may have some side effects that are important to the program - but whatever "result" the action returns, if any, isn't needed.
For example, here's just about the simplest possible console application in Haskell, a program that asks you for your name and then greets you using it:
main = do
putStrLn "Please tell me your name"
name <- getLine
putStrLn $ "Welcome, " ++ name ++ "!
The way this works is that getLine is an "IO action" of type IO String. It is basically equivalent to Python's input function (more accurately, name <- getLine is equivalent to name = input() in Python): it is an "action" that, when executed, reads a line of input from the console, and then "returns" the string that the user entered. If you want to access that string, you have to do as above and bind it to a variable with the <- "operator". (It's not really an operator - it's actually a special kind of syntactic sugar, but don't worry about that for now, you can think of it as an operator if you want.) But, if you just wanted to greet the user the same way every time, then you wouldn't need to bind the result, and could just do:
main = do
putStrLn "Please tell me your name"
getLine
putStrLn "Welcome!"
which still gives the user the pleasure of entering their name, even though the program then totally ignores it.
In general, when you have a do block inside main - or any other IO action - each line has to be an expression of type IO a, where a can be any type whatsoever (only the final line has to have the "correct" type for a - usually IO () in the case of main). And when you do x <- something, then again something has type IO a, and then x will be of type a: it's whatever you get as the result of the IO action something, which is guaranteed to be of type a since something has type IO a.
So when you do, at the start of your program:
tehfile <- readIniFile "somefile"
and we note from the documentation that readIniFile has type FilePath -> IO (Either String Ini), we see that readIniFile "somefile" has type IO (Either String Ini), and therefore tehFile has type Either String Ini.
I think you already understand that, from the fact that you then pattern match on tehfile using Left and Right, which is exactly right given its type. However, what you then go on to do doesn't really make sense, and is causing compiler errors.
Basically, if the file was successfully opened and parsed, tehFile will be a Right value holding a value of type Ini, which seems to be a representation of the data in the file. If anything went wrong, you will get a Left value, holding a String, which I assume will be an error message of some kind explaining what went wrong.
You can certainly then do:
case tehfile of
Left e -> print $ "an error occurred: " ++ e
....
in order to show the user the details of anything that went wrong. But note that a case expression is exactly that, an expression, and therefore must have a type. In particular, the results of all branches - here the Left branch and the Right branch - must have the same type. print takes a value of some printable type and outputs an IO (). So the Right b branch must also output a value of type IO () - in other words it must, like print "woot", be an action which possibly performs some IO, but has no meaningful "result".
You have actually done this, with:
head (map (\x -> print x) (sections b))
which I will write more succinctly and readably (but completely equivalently), as
head $ map print (sections b)
This is perfectly type-correct: sections b is a list of Text values (which are essentially strings), print transforms each of those to a value of type IO () (with the side effect of printing the value), and head takes the first element. So the above expression has type IO (), as you need - it's an IO action which simply prints the first of the "section" values. Your comment indicates you are puzzled that only the first element was printed: well this is a consequence of Haskell's lazy evaluation. In an "eager" language, map print (sections b) would print them all, but here you only ask for the first of those values, so only the first of the sections is actually printed.
Of course, as you probably noticed, it's not type correct to simply do
Right b -> map print (sections b)
because the expression on the right has type [IO ()] and you need a single IO (). This is what the mapM_ function is for:
Right b -> mapM_ print (sections b)
will do exactly the same in terms of observable effect, but the type is now the IO () that you need. [It is a variant of mapM, which runs over a list of values with a function that makes an IO action from each of them, and, rather than returning a list of actions as map would, returns an action whose result is a list of the corresponding results of each individual action. mapM_ is identical but "throws away" the results, so rather than the result being of the form IO [a] it's simply IO (), as you need here. And note also that both these functions are more general than I've explained here, but that's how they work with lists and IO, which is a pretty common use-case for them.]
Putting it together, for this bit of code you will have:
case tehfile of
Left e -> print $ "an error occurred: " ++ e
Right b -> mapM_ print (sections b)
and note that I've quite deliberately left out the s <- before it, which is found in your code. It's perfectly valid to do so, but not only do you not use the s anywhere in your later code (making the <- useless as I explained above), there's absolutely no way you even could use it. The case expression has type IO (), so anything bound to the result will have type () - and () is a type with only one value. This is why we use IO () for actions like main which have no sensible result value, because the type system demands we use some type here, and using a type that essentially tells us nothing makes sense if there is nothing sensible to go there.
Right, I've gone on for ages already. Hopefully you've got something from the above. Before I sign off, I'll try to briefly do a couple of things. Firstly, answer the other questions you raise in your comments:
why selected_profile <- getLine` is different from `let selected_profile = getLine` ?
let selected_profile = getLine is just basically a local variable assignment. In this case it makes selected_profile equal to getLine, which is an action of type IO String. It doesn't actually "perform" any IO, but just allows you to use the getLine IO action under a new name - to go back to Python, it's just like doing selected_profile = input in that language; that line in itself doesn't ask the user for anything, that doesn't happen till you execute the function. getLine in Haskell isn't a function, it's an "action" (executed by the runtime, rather than in Haskell code), but the principle is the same. Whereas selected_profile <- getLine is what actually runs the getLine action, to ask the user for input, which is then assigned to the local variable selected_profile.
why this does not print "oops" on lacking profile, instead prints Left "Couldn't find section: selected_profile"
That's because, in the wider context of your code, you succeeded in opening and parsing the INI file, so tehfile holds a Right value. It seems you want to print "ooops" if the user's input for the selected profile can't be found in the file, but in order to do that, you need to run the lookUpValue function first, and pattern match on its result, to print "oops" in case of a Left (error) value.
So to round off, here is a very simple version of a main which will do, very roughly at least, as you seem to intend. Note that you will need to import pack from Data.Text in order for this to compile (this is just a conversion from String to Text, don't get me started on the ridiculous number of string types in Haskell and why the default one, String, is so terrible that no serious application uses it but still most of the standard library functions force you to):
main :: IO ()
main = do
tehfile <- readIniFile "/Users/g.reshetniak/.aws/credentials"
case tehfile of
Left e -> print $ "Failed to parse file: " ++ e
Right i -> do
mapM_ print (sections b)
putStrLn "Please select which section you would like"
selected_profile <- getLine
case lookupValue (pack selected_profile) "aws_access_key_id" i of
Left e -> putStrLn $ "oops, an error: " ++ e
Right v -> print v
(Note that I haven't tried to compile or run the above, so please let me know if anything unexpected happens. I always seem to have some stupid but easily-fixed errors whenever I write more than about 4 lines of Haskell code at once...)
caseexpression, each case needs the same type.print "bar"has typeIO (), but42can't have this type (unless you make aNuminstance forIO ()which would be strange and almost certainly meaningless).doblock in theIOmonad (since the overall type isIO ()), so anything on the right of a "binding arrow"<-must have typeIO afor somea. So it's not valid for a simple numeric literal to appear here (it's not anIOvalue). You could have usedreturn 42to get round this (except that42does not have()type).returnfunction. This is simply a function which "boxes" a value inside a monad - it has almost nothing in common with the identically-named keyword in imperative languages, and in particular has nothing to do with control flow. The "return value" of a function, as you might understand it, is simply the expression that the function body evaluates to.IO ais essentially a "program" that, when run (by the Haskell runtime), will have some side effects and yield a value of typea.zandxare the values output by certain intermediate computations (which themselves can involve IO, eg reading a file), which you can then use in the rest of theMaincomputation (but which you appear not to do).