In a file I am saving a file path. I want to read the file path from a file and then convert it to a variable, so everytime I run a function it gets the current file path from the file and then I can use this String in another function. I know variables are immutable but is there a way I can do it?
Here as you can see I am trying to read the file path from the file and then check if it is correct:
checksForCdCommand dir = checkIfPathExists constructedPath $ concat' $ paths sample2 where
constructedPath = do
let file = "abc.txt"
contents <- readFile file
return (if contents /= "/" then contents "/" dir else contents dir)
Here if the contents is "/" it means that its my root dir so thats why I have this if else clause. But this way the code wont compile due to the following:
* Couldn't match expected type `[Char]'
with actual type `IO [Char]'
* In the first argument of `checkIfPathExists', namely
`constructedPath'
In the expression: checkIfPathExists constructedPath
In the expression:
checkIfPathExists constructedPath $ concat' $ paths sample2
I want to do something like this:
checksForCdCommand dir = checkIfPathExists constructedPath $ concat' $ paths sample2 where
constructedPath = if currDir /= "/" then currDir "/" dir else currDir dir
where currDir
always has the value from the file.
The idea is that I have a tree that represents a file system and with the func paths I get all of the paths to the children, with checkIfPathExists
I pass a String as an argument to see if it's part of all the paths and I am doing a command that is like the cd unix command and when I change directories I save the current dir in the file.
I need this current directory for other things and that's why I need to read it from a file and then change the variable currDir
so I can use it for other functions.
CodePudding user response:
change the variable currDir
This is the thing you can't do. All the rest is distracting window-dressing. If your approach relies fundamentally on doing this, you need a new approach. At some point, in an IO action, you need to read the contents of this abc.txt
file, and save that result into a variable. Then you pass that variable (whose value will not change!) to other functions. If at some point you want to "refresh" that variable, you will need to re-read it and use that new variable.
For example:
addOneToFileContents :: String -> Maybe (Int, Int)
addOneToFileContents c = case reads c of
[(x, _)] -> Just $ (x, x 1)
_ -> Nothing
main = do
num <- readFile "abc.txt"
case addOneToFileContents num of
Nothing -> putStrLn "abc.txt should contain a number" *> main
Just (x, y) -> putStrLn $ show x " 1 = " show y
This program is not very well written, but it demonstrates the basic idea. It assumes abc.txt exists; if not, you get some error. But assuming it does, it prints error messages very quickly until you (in another process) modify abc.txt to contain a number.
You will have to structure your program in a similar way: some IO action, probably in main
or close to it, reads the relevant file. Then it's passed to some other function, which makes decisions about what to do based on the file contents. Later, you re-read the file in main
and continue the loop.
There are tools that make this process simpler; for example, you can use Reader to plumb the contents of abc.txt through the pure parts of your program. It's not really much simpler in isolation, but it combines more cleanly with other bookkeeping you may have to do, like plumbing the results of additional IO actions through. But fundamentally it's no different: you have to do IO in IO actions, and variables don't change.
CodePudding user response:
"I know variables are immutable but is there a way I can do it?"
Haskell is very opinionated on the subject of mutable variables - they don't exist, only immutable values exist. Like dominoes, the output of one function is plumbed into the input of the next function. The only changeable things are the values input at the start, and the values that come out of the end, like a sausage machine. It is possible to save values using the state monad, or by using a database, but if you are using a file as an improvised mutable variable then this savours strongly of attempting to subvert the principles of immutability.
But what about this 'Python' code?
let file = "abc.txt"
contents <- readFile file
It looks like there are mutable variables happening here, but this is an illusion. This is syntactic sugar which Haskell turns into a sequence of linked lambda expressions.
The error message is caused because you are using the do
syntactical sugar:
* Couldn't match expected type `[Char]'
with actual type `IO [Char]'
In the absence of your type declaration, which you should really provide, Haskell believes two things:
- The use of
do
andreturn
means that you are doing anIO action
, typeIO [Char]
, where[Char]
means a string of text - The definition says that you are doing text operations, like
concat'
hence expected type is[Char]
, a string
So, it is confused.
There are two things that you need to know about the IO action.
- An IO action is like The Golden Goose. Just as the Golden Goose, when cut open, did not contain any gold, so an IO action, when cut open, does not contain the data that you are looking for.
- The IO tag taints anything it comes into contact with. So, the correct thing to do is to write functions which don't used IO actions at all, just transforming numbers into strings, strings into lists, and so on. You concentrate the tainted IO actions into the main function.
Hence my recommendations are:
- Write a main function, which includes IO actions
- Write as much of the remainder of the code without IO actions
- Include the type declarations for every function (it stops Haskell getting confused, and allows you to check that you're doing the right thing)