Alloy newbie here.
I really like creating constraints in Alloy and having the analyzer check that the model is valid per the constraints.
But I ask myself, “Are these constraint definitions mere mental gymnastics, or will they help build better software?”
Let’s take a specific example. In a model of an email client’s address book one might define this constraint for adding a new entry into the address book:
The address mapping in the new book b’ is equal to the address
mapping in the old book b, with the addition of a link from the
name to the address. This constraint is expressed in Alloy as:
b'.addr = b.addr + n->a
That is beautiful.
But I am struggling to see its relevance when the add operation is implemented in code. For instance, I implemented the add operation in Common Lisp:
(defun add (b n a)
"Add a new entry, (n a), to address book b"
(adjoin (list n a) b :key #'first))
In words: “Here is a definition of a function named add which has three parameters: b, n, and a (book, name, address). Use the Common Lisp set function, adjoin, to add the list (n a) to b.”
That function seems to correctly implement a simple add function.
What do I do with the constraint that I defined in Alloy? Should I write additional code that expresses the constraint? In pseudo-code: [1]
(defun add (b n a)
"Add a new entry, (n a), to address book b"
(adjoin (list n a) b :key #'first)
Check that nothing has changed in the
address book except that it now has
a n->a mapping)
Writing such code seems like a lot of work, with no apparent benefit.
So my question is this: When implementing an Alloy model in code, what should I do with the constraints that are defined in Alloy?
Also, is there a tutorial which describes how to convert Alloy models to code, including a description of how Alloy constraint definitions are used in code?
Thank you.
[1] Note: I realize that there is a language extension called Screamer for constraint programming in Common Lisp
I believe the question stems from a minor misunderstanding of the goals and capabilities of some of the modeling languages like Alloy and touches some of the fundamental aspects of formal methods, verification and software modeling. Probably good resources to get a background story that would make things more clear include books like Calculus of Computation, as well as the book about Alloy you've mentioned.
Constraints are first-class citizens of the Alloy modeling language. The idea is that constraints reflect the semantics of the code one wants to model (and check properties about). Thus, from one perspective, Alloy program represents the code itself and there is no need for writing additional code (e.g. in a functional language) or mixing declared constraints with code. However, Alloy programs cannot be directly executed; rather, they can only be used to produce models according to such programs (i.e. sets of constraints).
More specifically, the Alloy constraint b'.addr = b.addr + n->a, might, under an appropriate interpretation, indeed capture the behavior of the add operation in Lisp, so that checking some properties that involve the constraint corresponds to checking whether those properties hold for the given operation in Lisp. This is the standard (and, arguably, intended) use of Alloy for modeling and verification of software. (In addition, Alloy is frequently used to model systems that don't have clear mappings into programs, like certain kinds of cyber-physical systems [1].) Note that this of course entails that the model one writes in Alloy must correctly model the program at hand (with respects to its semantics), in order for the checking of properties to make sense.
As mentioned, Alloy itself cannot produce executable code (such as the one you've given, written in Common Lisp). However, there are multiple approaches that use Alloy and models that Alloy generates to produce fragments of code or test cases. (Again, according to the specific program at hand.) In addition, an extension of Alloy, called Alloy* [2], which adds the possibility of solving higher order constraints within Alloy (quantification over relations) can be used to generate programs; actual code that performs the operation and accepts different inputs (similarly to the add function, according to the model). Again, such programs are represented with Alloy models and one needs to (perform additional effort to) translate those models into programs in some desired programming language.
Having that said, note that some (verification) systems indeed allow you to mix specifications with code, where specifications might be: written in the same way as the code that is to be executed (like in the verification/synthesis framework Leon [3]); or written in a distinct language (from the language of executable code), while the specifications are bound to code by some other means (like annotations in Dafny [4]).
[1] Kang, Eunsuk, et al. "Model-based security analysis of a water treatment system." Proceedings of the 2nd International Workshop on Software Engineering for Smart Cyber-Physical Systems. ACM, 2016.
[2] Milicevic, Aleksandar, et al. "Alloy*: A general-purpose higher-order relational constraint solver." Proceedings of the 37th International Conference on Software Engineering-Volume 1. IEEE Press, 2015.
[3] Blanc, Régis, et al. "An overview of the Leon verification system: Verification by translation to recursive functions." Proceedings of the 4th Workshop on Scala. ACM, 2013.
[4] Leino, K. Rustan M. "Dafny: An automatic program verifier for functional correctness." International Conference on Logic for Programming Artificial Intelligence and Reasoning. Springer Berlin Heidelberg, 2010.
Related
In the Alloy well-know book by Daniel Jakson he writes about three different styles in specifying constraints in Alloy: 1) predicate calculus 2) navigational style, and 3) relational calculus. (Section 3.1, page 34)
The theory behind the first and the last ones are somehow clear to to me as the first is the conventional first order logic when we take relations as predicates; and the last is relational calculus in which everything is taken as a relation and constraints are specified by employing the equation symbol and some algebraic operations (without quantifiers). In the book, it is said that the second one has the most expressive power (page 35).
My Question:
What is the theory behind the second one (i.e., navigational style)? Where should I look for (and under which terminology) to gain more information regarding this one in theory.
The motivation for the navigational style is that you want something between predicate calculus, which has no operations on sets or relations, so is very awkward and verbose to use in practice (and also less expressive due to the lack of transitive closure), and relational calculus, as pioneered by Tarski, which gives you relational operators but has no quantifiers, which is quite unnatural for most people (but which allows some beautifully succinct ways to express properties). The navigational style comes between the two, and gives you both set/relation operators and quantifiers, so in this sense -- informally at least, and probably formally too if you defined the three sublanguages carefully -- it's more expressive than either. At the same time, I had in mind that most people are comfortable with simple first order quantifiers but aren't so comfortable with relational join, and tend therefore to write formulas in which expressions denote sets. You can think of each of these expressions as starting at some atom, denoted by a quantified variable, and then "navigating" along relations to get larger and larger expressions that denote sets of atoms. This is how I think of an expression like me.parents.children : you start with me, navigate to my parents, then from them to their children -- and the result is the set containing me and my siblings. In my experience, this navigational style is what most people gravitate naturally towards.
It's true that if you had to pick one style of the three that matched OCL, it would be this navigational style, although it's true that OCL has a very much more complex semantics (due to various coercions and flattenings that happen automatically at each step), where in Alloy the semantics is just standard sets and relations. The syntax is probably more influenced by object oriented languages like Java, even though they don't have the kind of relational image that Alloy has (that is, if you have a field f and can write x.f, you can't write xs.f for the set of all objects that are mapped to by f from the objects in the set xs!). Note however that there is no special syntax here; these are just styles of usage of the same operators.
When I wrote this page in the book, I thought it would give readers some useful intuitions, but it seems to have just caused more confusion!
In Appendix E - Alternative Approaches, Daniel Jackson discusses OCL, which has a navigational style. I would assume he borrowed the syntax from it, as he discusses it as related work that precedes Alloy.
I've come across references to Haskell's Data.Typeable, but it's not clear to me why I would want to use it in my code.
What problem does it solve, and how?
Data.Typeable is an encoding of an well known approach (see e.g. Harper) to implementing delayed (dynamic) type checking in a statically typed language -- using a universal type.
Such a type wraps code for which type checking would not succeed until a later phase. Rather than reject the program as ill-typed, the compiler passes it on for runtime checking.
The style originated in Abadi et al., and developed for Haskell by Cheney and Hinze as a wrapper to represent all dynamic types, with the Typeable class appearing as part of the SYB work of SPJ and Lammel.
Reference
Martín Abadi, Luca Cardelli, Benjamin Pierce and Gordon Plotkin, "Dynamic Typing in a Statically Typed Language", ACM Transactions on Programming Languages and Systems (TOPLAS), 1991.
James Cheney and Ralf Hinze, "A lightweight implementation of generics and dynamics", Haskell '02: Proceedings of the 2002 ACM SIGPLAN Workshop on Haskell, 2002.
Lammel, Ralf and Jones, Simon Peyton, "Scrap your boilerplate: a practical design pattern for generic programming, TLDI '03: Proceedings of the 2003 ACM SIGPLAN International Workshop on Types in Languages Design and Implementation, 2003
Harper, 2011, Practical Foundations for Programming Languages.
Even in the text books: dynamic types (with typeable representations) are statically typed languages with only one type, Harper ch 20:
20.4 Untyped Means Uni-Typed
The untyped λ-calculus may be faithfully embedded in a
typed language with recursive types. This means that every
untyped λ-term has a representation as a typed expression
in such a way that execution of the representation of a
λ-term corresponds to execution of the term itself. This
embedding is not a matter of writing an interpreter for
the λ-calculus in ℒ{+×⇀µ} (which we could surely do), but
rather a direct representation of untyped λ-terms as typed
expressions in a language with recursive types.
The key observation is that the untyped λ-calculus is
really the uni-typed λ-calculus! It is not the absence
of types that gives it its power, but rather that it has
only one type, namely the recursive type
D = µt.t → t.
It's a library that allows, among other things, naming types. If a type a is declared Typeable, then you can get its name using show $ typeOf x where x is any value of type a. It also features limited type-casting.
(This is somewhat similar to C++'s RTTI or dynamic languages' reflection.)
One of the earliest descriptions I could find of a Data.Typeable-like library for Haskell is by John Peterson from 1992: http://www.cs.yale.edu/publications/techreports/tr1022.pdf
The earliest "official" paper I know of introducing the actual Data.Typeable library is the first Scrap Your Boilerplate paper from 2003: http://research.microsoft.com/en-us/um/people/simonpj/Papers/hmap/index.htm
I'm sure there's lots of intervening history that someone here can chime in with!
The Data.Typeable class is used primarily for generic programming in the Scrap Your Boilerplate (SYB) style. See also Data.Data
The idea is that SYB defines a collection combinators for performing operations such as printing, counting, searching, substiting, etc in a uniform manner over a variety of user-created types. The Typeable typeclass provides the necessary plumbing.
In modern GHC, you can just say deriving Data.Typeable when defining your own type in order to provide it with the necessary instances.
OK, for those who have never encountered the term, a quine is a "self-replicating" computer program. To be more specific, one which - upon execution - produces a copy of its own source code as its only output.
The quines can, of course, be developed in many programming languages (but not all); but some languages are obviously more suited to producing quines than others (to clearly understand the somewhat subjective-sounding "more suited", look at a Haskell example vs. C example in the Wiki page - and I provide my more-objective definition below).
The question I have is, from programming language perspective, what language features (either theoretical design ones or syntax sugar) make the language more suitable/helpful for writing quines?
My definition of "more suitable" is "quines are easier to write" and "are shorter/more readable/less obfuscated". But you're welcome to add more criteria that are at least somewhat objective.
Please note that this question explicitly excludes degenerate cases, like a language which is designed to contain "print_a_quine" primitive.
I am not entirely sure, so correct me if anyone of you knows better.
I agree with both other answers, going further by explaining, that a quine is this:
Y g
where Y is a Y fixed-point combinator (or any other fixed-point combinator), which means in lambda calculus:
Y g = g(Y g)
now, it is quite apparent, that we need the code to be data and g be a function which will print its arguments.
So to summarize we need for constructing such a quines functions, printing function, fixed-point combinator and call-by-name evaluation strategy.
The smallest language that satisfies this conditions is AFAIK Zot from the Iota and Jot family.
Languages like the Io Programming Language and others allow the treating of code as data. In tree walking systems, this typically allows the language implementer to expose the abstract syntax tree as a first class citizen. In the case of Io, this is what it does. Being object oriented, the AST is modelled around Message objects, and a special sentinel is created to represent the currently executing message; this sentinel is called thisMessage. thisMessage is a full Message like any other, and responds to the print message, which prints it to the screen. As a result, the shortest quine I've ever been able to produce in any language, has come from Io and looks like this:
thisMessage print
Anyway, I just couldn't help but sharing this with you on this subject. The above certainly makes writing quines easy, but not doing it this way certainly doesn't preclude easily creating a quine.
I'm not sure if this is useful answer from a practical point of view, but there is some useful theory in computability theory. In particular fixed points and Kleene's recursion theorem can be used for writing quines. Apparently, the theory can be used for writing quine in LISP (as the wikipedia page shows).
I recently started studying functional programming using Haskell and came upon this article on the official Haskell wiki: How to read Haskell.
The article claims that short variable names such as x, xs, and f are fitting for Haskell code, because of conciseness and abstraction. In essence, it claims that functional programming is such a distinct paradigm that the naming conventions from other paradigms don't apply.
What are your thoughts on this?
In a functional programming paradigm, people usually construct abstractions not only top-down, but also bottom-up. That means you basically enhance the host language. In this kind of situations I see terse naming as appropriate. The Haskell language is already terse and expressive, so you should be kind of used to it.
However, when trying to model a certain domain, I don't believe succinct names are good, even when the function bodies are small. Domain knowledge should reflect in naming.
Just my opinion.
In response to your comment
I'll take two code snippets from Real World Haskell, both from chapter 3.
In the section named "A more controlled approach", the authors present a function that returns the second element of a list. Their final version is this:
tidySecond :: [a] -> Maybe a
tidySecond (_:x:_) = Just x
tidySecond _ = Nothing
The function is generic enough, due to the type parameter a and the fact we're acting on a built in type, so that we don't really care what the second element actually is. I believe x is enough in this case. Just like in a little mathematical equation.
On the other hand, in the section named "Introducing local variables", they're writing an example function that tries to model a small piece of the banking domain:
lend amount balance = let reserve = 100
newBalance = balance - amount
in if balance < reserve
then Nothing
else Just newBalance
Using short variable name here is certainly not recommended. We actually do care what those amounts represent.
I think if the semantics of the arguments are clear within the context of the code then you can get away with short variable names. I often use these in C# lambdas for the same reason. However if it is ambiguous, you should be more explicit with naming.
map :: (a->b) -> [a] -> [b]
map f [] = []
map f (x:xs) = f x : map f xs
To someone who hasn't had any exposure to Haskell, that might seem like ugly, unmaintainable code. But most Haskell programmers will understand this right away. So it gets the job done.
var list = new int[] { 1, 2, 3, 4, 5 };
int countEven = list.Count(n => n % 2 == 0)
In that case, short variable name seems appropriate.
list.Aggregate(0, (total, value) => total += value);
But in this case it seems more appropriate to name the variables, because it isn't immediately apparent what the Aggregate is doing.
Basically, I believe not to worry too much about convention unless it's absolutely necessary to keep people from screwing up. If you have any choice in the matter, use what makes sense in the context (language, team, block of code) you are working, and will be understandable by someone else reading it hours, weeks or years later. Anything else is just time-wasting OCD.
I think scoping is the #1 reason for this. In imperative languages, dynamic variables, especially global ones need to be named properly, as they're used in several functions. With lexical scoping, it's clear what the symbol is bound to at compile time.
Immutability also contributes to this to some extent- in traditional languages like C/ C++/ Java, a variable can represent different data at different points in time. Therefore, it needs to be given a name to give the programmer an idea of its functionality.
Personally, I feel that features features like first-class functions make symbol names pretty redundant. In traditional languages, it's easier to relate to a symbol; based on its usage, we can tell if it's data or a function.
I'm studying Haskell now, but I don't feel that its naming conventions is so very different. Of course, in Java you're hardly to find a names like xs. But it is easy to find names like x in some mathematical functions, i, j for counters etc. I consider such names to be perfectly appropriate in right context. xs in Haskell is appropriate only generic functions over lists. There's a lot of them in Haskell, so this name is wide-spread. Java doesn't provide easy way to handle such a generic abstractions, that's why names for lists (and lists themselves) are usually much more specific, e.g. lists or users.
I just attended a number of talks on Haskell with lots of code samples. As longs as the code dealt with x, i and f the naming didn't bother me. However, as soon as we got into heavy duty list manipulation and the like I found the three letters or so names to be a lot less readable than I prefer.
To be fair a significant part of the naming followed a set of conventions, so I assume that once you get into the lingo it will be a little easier.
Fortunately, nothing prevents us from using meaningful names, but I don't agree that the language itself somehow makes three letter identifiers meaningful to the majority of people.
When in Rome, do as the Romans do
(Or as they say in my town: "Donde fueres, haz lo que vieres")
Anything that aids readability is a good thing - meaningful names are therefore a good thing in any language.
I use short variable names in many languages but they're reserved for things that aren't important in the overall meaning of the code or where the meaning is clear in the context.
I'd be careful how far I took the advice about Haskell names
My Haskell practice is only of mediocre level, thus, I dare to try to reply only the second, more general part of Your question:
"In essence, it claims that functional programming is such a distinct paradigm that the naming conventions from other paradigms don't apply."
I suspect, the answer is "yes", but my motivation behind this opinion is restricted only on experience in just one single functional language. Still, it may be interesting, because this is an extremely minimalistic one, thus, theoretically very "pure", and underlying a lot of practical functional languages.
I was curios how easy it is to write practical programs on such an "extremely" minimalistic functional programming language like combinatory logic.
Of course, functional programming languages lack mutable variables, but combinatory logic "goes further one step more" and it lacks even formal parameters. It lacks any syntactic sugar, it lacks any predefined datatypes, even booleans or numbers. Everything must be mimicked by combinators, and traced back to the applications of just two basic combinators.
Despite of such extreme minimalism, there are still practical methods for "programming" combinatory logic in a neat and pleasant way. I have written a quine in it in a modular and reusable way, and it would not be nasty even to bootstrap a self-interpreter on it.
For summary, I felt the following features in using this extremely minimalistic functional programming language:
There is a need to invent a lot of auxiliary functions. In Haskell, there is a lot of syntactic sugar (pattern matching, formal parameters). You can write quite complicated functions in few lines. But in combinatory logic, a task that could be expressed in Haskell by a single function, must be replaced with well-chosen auxiliary functions. The burden of replacing Haskell syntactic sugar is taken by cleverly chosen auxiliary functions in combinatory logic. As for replying Your original question: it is worth of inventing meaningful and catchy names for these legions of auxiliary functions, because they can be quite powerful and reusable in many further contexts, sometimes in an unexpected way.
Moreover, a programmer of combinatory logic is not only forced to find catchy names of a bunch of cleverly chosen auxiliary functions, but even more, he is forced to (re)invent whole new theories. For example, for mimicking lists, the programmer is forced to mimick them with their fold functions, basically, he has to (re)invent catamorphisms, deep algebraic and category theory concepts.
I conjecture, several differences can be traced back to the fact that functional languages have a powerful "glue".
In Haskell, meaning is conveyed less with variable names than with types. Being purely functional has the advantage of being able to ask for the type of any expression, regardless of context.
I agree with a lot of the points made here about argument naming but a quick 'find on page' shows that no one has mentioned Tacit programming (aka pointfree / pointless). Whether this is easier to read may be debatable so it's up to you & your team, but definitely worth a thorough consideration.
No named arguments = No argument naming conventions.
UML is a standard aimed at the modeling of software which will be written in OO languages, and goes hand in hand with Java. Still, could it possibly be used to model software meant to be written in the functional programming paradigm? Which diagrams would be rendered useful given the embedded visual elements?
Is there a modeling language aimed at functional programming, more specifically Haskell? What tools for putting together diagrams would you recommend?
Edited by OP Sept 02, 2009:
What I'm looking for is the most visual, lightest representation of what goes on in the code. Easy to follow diagrams, visual models not necessarily aimed at other programmers. I'll be developing a game in Haskell very soon but because this project is for my graduation conclusion work I need to introduce some sort of formalization of the proposed solution. I was wondering if there is an equivalent to the UML+Java standard, but for Haskell.
Should I just stick to storyboards, written descriptions, non-formalized diagrams (some shallow flow-chart-like images), non-formalized use case descriptions?
Edited by jcolebrand June 21, 2012:
Note that the asker originally wanted a visual metphor, and now that we've had three years, we're looking for more/better tools. None of the original answers really addressed the concept of "visual metaphor design tool" so ... that's what the new bounty is looking to provide for.
I believe the modeling language for Haskell is called "math". It's often taught in schools.
Yes, there are widely used modeling/specification languages/techniques for Haskell.
They're not visual.
In Haskell, types give a partial specification.
Sometimes, this specification fully determines the meaning/outcome while leaving various implementation choices.
Going beyond Haskell to languages with dependent types, as in Agda & Coq (among others), types are much more often useful as a complete specification.
Where types aren't enough, add formal specifications, which often take a simple functional form.
(Hence, I believe, the answers that the modeling language of choice for Haskell is Haskell itself or "math".)
In such a form, you give a functional definition that is optimized for clarity and simplicity and not all for efficiency.
The definition might even involve uncomputable operations such as function equality over infinite domains.
Then, step by step, you transform the specification into the form of an efficiently computable functional program.
Every step preserves the semantics (denotation), and so the final form ("implementation") is guaranteed to be semantically equivalent to the original form ("specification").
You'll see this process referred to by various names, including "program transformation", "program derivation", and "program calculation".
The steps in a typical derivation are mostly applications of "equational reasoning", augmented with a few applications of mathematical induction (and co-induction).
Being able to perform such simple and useful reasoning was a primary motivation for functional programming languages in the first place, and they owe their validity to the "denotative" nature of "genuinely functional programming".
(The terms "denotative" and "genuinely functional" are from Peter Landin's seminal paper The Next 700 Programming languages.)
Thus the rallying cry for pure functional programming used to be "good for equational reasoning", though I don't hear that description nearly as often these days.
In Haskell, denotative corresponds to types other than IO and types that rely on IO (such as STM).
While the denotative/non-IO types are good for correct equational reasoning, the IO/non-denotative types are designed to be bad for incorrect equational reasoning.
A specific version of derivation-from-specification that I use as often as possible in my Haskell work is what I call "semantic type class morphisms" (TCMs).
The idea there is to give a semantics/interpretation for a data type, and then use the TCM principle to determine (often uniquely) the meaning of most or all of the type's functionality via type class instances.
For instance, I say that the meaning of an Image type is as a function from 2D space.
The TCM principle then tells me the meaning of the Monoid, Functor, Applicative, Monad, Contrafunctor, and Comonad instances, as corresponding to those instances for functions.
That's a lot of useful functionality on images with very succinct and compelling specifications!
(The specification is the semantic function plus a list of standard type classes for which the semantic TCM principle must hold.)
And yet I have tremendous freedom of how to represent images, and the semantic TCM principle eliminates abstraction leaks.
If you're curious to see some examples of this principle in action, check out the paper Denotational design with type class morphisms.
We use theorem provers to do formal modelling (with verification), such as Isabelle or Coq. Sometimes we use domain specific languages (e.g. Cryptol) to do the high level design, before deriving the "low level" Haskell implementation.
Often we just use Haskell as the modelling language, and derive the actual implementation via rewriting.
QuickCheck properties also play a part in the design document, along with type and module decompositions.
Yes, Haskell.
I get the impression that programmers using functional languages don't feel the need to simplify their language of choice away when thinking about their design, which is one (rather glib) way of viewing what UML does for you.
I have watched a few video interviews, and read some interviews, with the likes of erik meijer and simon peyton-jones. It seems as when it comes to modelling and understanding ones problem domain, they use type signatures, especially function signatures.
Sequence diagrams (UML) could be related to the composition of functions.
A static class diagram (UML) could be related to type signatures.
In Haskell, you model by types.
Just begin with writing your function-, class- and data signatures without any implementation and try to make the types fit. Next step is QuickCheck.
E.g. to model a sort:
class Ord a where
compare :: a -> a -> Ordering
sort :: Ord a => [a] -> [a]
sort = undefined
then tests
prop_preservesLength l = (length l) == (length $ sort l)
...
and finally the implementation ...
Although not a recommendation to use (as it appears to be not available for download), but the HOPS system visualizes term graphs, which are often a convenient representation of functional programs.
It may be also considered a design tool as it supports documenting the programs as well as constructing them; I believe it can also step through the rewriting of the terms if you want it to so you can see them unfold.
Unfortunately, I believe it is no longer actively developed though.
I realize I'm late to the party, but I'll still give my answer in case someone would find it useful.
I think I'd go for systemic methodologies of the likes of SADT/IDEF0.
https://en.wikipedia.org/wiki/Function_model
https://en.wikipedia.org/wiki/Structured_Analysis_and_Design_Technique
Such diagrams can be made with the Dia program that is available on both Linux, Windows and MacOS.
You can a data flow process network model as described in Realtime Signal Processing: Dataflow, Visual, and Functional Programming by Hideki John Reekie
For example for code like (Haskell):
fact n | n == 0 = 1
| otherwise = n * fact (n - 1)
The visual representation would be:
What would be the point in modelling Haskell with Maths? I thought the whole point of Haskell was that it related so closely to Maths that Mathematicians could pick it up and run with it. Why would you translate a language into itself?
When working with another functional programming language (f#) I used diagrams on a white board describing the large blocks and then modelled the system in an OO way using UML, using classes. There was a slight miss match in the building blocks in f# (split the classes up into data structures and functions that acted upon them). But for the understanding from a business perspective it worked a treat. I would add mind that the problem was business/Web oriented and don't know how well the technique would work for something a bit more financial. I think I would probably capture the functions as objects without state and they should fit in nicely.
It all depends on the domain your working in.
I use USL - Universal Systems Language. I'm learning Erlang and I think it's a perfect fit.
Too bad the documentation is very limited and nobody uses it.
More information here.