Reputation: 7729
I would like to know all possible means of side effects in functional languages, even theoretical and not used in practice.
I know about Monads (Haskell) and Uniqueness types (Clean). Are there any other possibilities?
Upvotes: 2
Views: 258
Reputation: 495
I would like to know all possible means of side effects in functional languages, even theoretical and not used in practice. [...] Are there any other possibilities?
Definitely - here's what I've dug up here and there, mostly from cyberspace. Some initial points:
Most of what follows involves extending or altering Haskell as it is non-strict, which conflicts with the traditional approach to side-effects; see sections 2.1 (page 12-2), 3.1 and 3.2 (page 12-8) of A History of Haskell for more details. (Hint: the full document also makes for a good read.)
Likewise, I/O dominates the early research into side-effects for Haskell (monadic I/O only arrived officially in Haskell 1.3). With fresh insight and some work, long-dormant ideas can often be reapplied elsewhere.
As some of these approaches don't seem to have one, there are a few "improvised names".
These are just the approaches that I know of which can be used in functional languages. You can find other approaches if you're willing to look at languages which support other programming paradigms (e.g. logic in Mercury or Prolog). For inspiration, see Lambda Calculus for Engineers by Pieter Hartel and Willem Vree.
So, in addition to the approaches mentioned in the other answers, there are:
Continuations - For a quick introduction, see Continuations Made Simple and Illustrated by Denys Duchier. Early versions of Haskell used this approach as an alternative to stream-based I/O - see section 7.1 (page 12-22) of A History of Haskell. Philip Wadler relates it to some other approaches in How to Declare an Imperative (section 3.2 on page 18).
A more recent application can be seen in Peng Li and Steve Zdancewic's A Language-based Approach to Unifying Events and Threads (section 3.3 on page 6).
There's also half an Internet's worth of information on how it's done in languages ranging from Common Lisp to Visual Basic - just crank up your favourite search engine.
"Orthogonal directives" - see An alternative approach to I/O by Maarten Fokkinga and Jan Kuper.
Pseudodata - see Nondeterminism with Referential Transparency in Functional Programming Languages by F. Warren Burton. The approach is used by Dave Harrison to implement clocks in his thesis Functional Real-Time Programming: The Language Ruth And Its Semantics, and name supplies in the functional pearl On generating unique names by Lennart Augustsson, Mikael Rittri and Dan Synek; Hackage also reveals a few library implementations.
System tokens - very few details beyond the original memo:
L. Augustsson. Functional I/O Using System Tokens. PMG Memo 72, Dept Computer Science, Chalmers University of Technology, S-412 96 Göteborg, 1989.
Witnesses - see Witnessing Side Effects by Tachio Terauchi and Alex Aiken.
Observers - see Assignments for Applicative Languages by Vipin Swarup, Uday S. Reddy and Evan Ireland.
"Runtime-binding of free variables" - see Functional Programming with Side Effects by Mark B. Josephs.
"Effect trees" - not much information besides the actual paper:
Rebelsky S.A. (1992) I/O trees and interactive lazy functional programming. In: Bruynooghe M., Wirsing M. (eds) Programming Language Implementation and Logic Programming. PLILP 1992. Lecture Notes in Computer Science, vol 631. Springer, Berlin, Heidelberg.
Upvotes: 1
Reputation: 2806
Haskell used to treat the I/O state as a pair of infinite streams. As an example (greatly simplified), your main function took one argument which was a stream of responses, and produced one result which was a stream of requests. After "emitting" a request in the output stream, the corresponding response would then be available in the input stream at the same position in the stream. This remained functional because streams in Haskell are just data structures, so you could always access old responses. This paper gives a better description.
Another paradigm which exists is that of Functional Reactive Programming. FRP introduces a special type of value called a behavior, whose value reflects the current value of some object in the "real world" (be it a file on disk, position of a mouse, etc.). Input behaviors can be combined with each other in a functional way to produce output behaviors, which are used by the implementation to update things in the "real world" (e.g. contents of the display). Most FRP implementations also introduce a second special type of value called an event, which is equivalent to a behavior which has a value only at discrete points in time (e.g. key presses or mouse clicks), and provide library functions for combining behaviors and events in useful ways (most importantly "remembering" the value of a behavior at some point in the past when some event occurred).
Upvotes: 2
Reputation: 71495
In Mercury everything is pure by default (even IO), and it's impossible to to write impure code without using rarely needed features.
Pure IO is handled in a similar way to Clean (I believe), by using uniqueness and a state-of-the-world parameter. Mercury considers uniqueness to be a mode property rather than a type property though. (A "mode" in this context is more or less a data-flow direction)
But Mercury also has a static purity system. Some code is recognised by the compiler as impure (calls to the foreign language interface, or to known impure functions/predicates, accessing mutable variables, and a few other cases). Such code must be explicitly declared impure
or it is a compiler error. Since the compiler is aware of the impurity, it doesn't perform reorderings or other optimisations that could affect the impure code. If at some level you can provide a pure interface around the impure operations, you can promise to the compiler that a function/predicate is in fact pure. Otherwise the required impurity declarations propagate all the way up to the main
predicate; if you're prepared to do this you could essentially program imperatively in Mercury (it wouldn't be fun though).
Mercury also has a concept of semipure code. This is code that does not have a side effect the operation of any other semipure or impure code (pure code by definition is unaffected by side effects from any other code), but may be affected by side effects from other impure code. This extra level of information means that calls that are only not pure because they 'see' side effects but doesn't have any themselves can still be optimised much more freely by the compiler; they can be optimised away if their results are not needed, and they can be reordered so long as they aren't moved "over" an impure
call.
Upvotes: 6
Reputation: 6835
The D programming language has strongly pure functions and weakly pure functions. Strongly pure functions are similar in many ways to what you get in Haskell. Weakly pure function, on the other hand, can alter their arguments, but they cannot mutate global state. The idea is that pure is as pure does: the private state of a function is expanded to allow functions which can alter its private state without altering global state.
Upvotes: 2