Just because a functional language is functional (Maybe even completely pure like Haskell!), it doesn't mean that programs written in that language must be pure when ran.
Haskell's approach, for example, when dealing with side-effects, can be explained rather simply: Let the whole program itself be pure (meaning that functions always return the same values for the same arguments and don't have any side effect), but let the return value of the main
function be an action that can be ran.
Trying to explain this with pseudocode, here is some program in an imperative, non-functional language:
main:
read contents of abc.txt into mystring
write contents of mystring to def.txt
The main
procedure above is just that: a series of steps describing how to perform a series of actions.
Compare this to a purely functional language like Haskell. In functional languages, everything is an expression, including the main function. One can thus read the equivalent of the above program like this:
main = the reading of abc.txt into mystring followed by
the writing of mystring to def.txt
So, main
is an expression that, when evaluated, will return an action describing what to do in order to execute the program. The actual execution of this action happens outside of the programmers world. And this is really how it works; the following is an actual Haskell program that can be compiled and ran:
main = readFile "abc.txt" >>= \ mystring ->
writeFile "def.txt" mystring
a >>= b
can be said to mean "the action a
followed by the result of a
given to action b
" in this situation, and the result of the operator is the combined actions a and b. The above program is of course not idiomatic Haskell; one can rewrite it as follows (removing the superfluous variable):
main = readFile "abc.txt" >>=
writeFile "def.txt"
...or, using syntactic sugar and the do-notation:
main = do
mystring <- readFile "abc.txt"
writeFile "def.txt" mystring
All of the above programs are not only equivalent, but they are identical as far as the compiler is concerned.
This is how files, database systems, and web servers can be written as purely functional programs: by threading action values through the program so that they are combined, and finally end up in the main
function. This gives the programmer enormous control over the program, and is why purely functional programming languages are so appealing in some situations.