Functions and procedures in VHDL, which are collectively known as subprograms, are directly analogous to functions and procedures in a high-level software programming language such as C or Pascal. A procedure is a subprogram that has an argument list consisting of inputs and outputs, and no return value. A function is a subprogram that has only inputs in its argument list, and has a return value.
Subprograms are useful for isolating commonly-used segments of VHDL source code. They can either be defined locally (within an architecture, for example), or they can be placed in a package and used globally throughout the design description or project.
Statements within a subprogram are sequential (like a process), regardless of where the subprogram is invoked. Subprograms can be invoked from within the concurrent area of an architecture or from within a sequential process or higher-level subprogram. They can also be invoked from within other subprograms.
Subprograms are very much like processes in VHDL. In fact, any statement that you can enter in a VHDL process can also be entered in a function or procedure, with the exception of a wait statement (since a subprogram executes once each time it is invoked and cannot be suspended while it is executing). It is therefore useful to think of subprograms as processes that (1) have been located outside the body of an architecture, and (2) operate only on their input and (in the case of procedures) their output parameters.
Nesting of functions and procedures is allowed to any level of complexity, and recursion is also supported in the language. (Of course, if you expect to generate actual hardware from your VHDL descriptions using synthesis tools, then you will need to avoid writing recursive functions and procedures, as such descriptions are not synthesizable).