Since side-effects break referential transparency, don't they go against the point of functional languages?
Functional languages such as Standard ML, Scheme and Scala do not restrict side effects, but it is customary for programmers to avoid them. Assembly language programmers must be aware of hidden side effects—instructions that modify parts of the processor state which are not mentioned in the instruction's mnemonic.
A side effect is when a function relies on, or modifies, something outside its parameters to do something. For example, a function which reads or writes from a variable outside its own arguments, a database, a file, or the console can be described as having side effects.
What I keep as conclusions, are that side effects do not affect the function value, due to equational reasoning, this is why "functional languages do not allow/minimize side effects". Effects embedded in the function values affect and change the state that is ever saved -or saved outside the core of the program.
There are two techniques that are used by purely functional programming languages to model side effects:
1) A world type that represents external state, where each value of that type is guaranteed by the type system to be used only once.
In a language that uses this approach the function print
and read
might have the types (string, world) -> world
and world -> (string, world)
respectively.
They might be used like this:
let main w = let w1 = print ("What's your name?", w) in let (name, w2) = read w1 in let w3 = print ("Your name is " ^ name, w2) in w3
But not like this:
let main w = let w1 = print ("What's your name?", w) in let (name, w2) = read w in let w3 = print ("Your name is " ^ name, w2) in w3
(because w is used twice)
All built-in functions with side-effects would take and return a world value. Since all functions with side-effects are either built-ins or call other functions with side-effects, this means that all functions with side-effects need to take and return a world.
This way it is not possible to call a function with side-effects twice with the same arguments and referential transparency is not violated.
2) An IO monad where all operations with side effects have to be executed inside that monad.
With this approach all operations with side effects would have type io something
. For example print
would be a function with type string -> io unit
and read
would have type io string
.
The only way to access the value of performing operation would be to use the "monadic bind" operation (called >>= in haskell for example) with the IO operation as one argument and a function describing what to do with the result as the other operand.
The example from above would look like this with monadic IO:
let main = (print "What's your name?") >>= (lambda () -> read >>= (lambda name -> print ("Your name is " ^ name)))
There are several options available to handle I/O in a functional language.
There's a research dissertation that exhaustively analyses these.
Functional I/O is an ongoing field of research and there are other languages which address this issue in interesting and mind-mangling ways. Hoare logic is put to use in some research languages. Others (like Mercury) use uniqueness typing. Still others (like Clean) use effect systems. Of these I have a very, very limited exposure to Mercury only, so I can't really comment on details. There's a paper that details Clean's I/O system in depth, however, if you're interested in that direction.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With