Closed paulotorrens closed 7 years ago
This is a good question. There are not currently any plans to add lambda functions to Alf, however some thought has been given to it. But doing so would not be quite as easy as it might seem.
First of all, Alf does not currently have an "anonymous class" capability, like Java had before introducing lambda functions. So introducing lambda functions into Alf as just syntactic sugar would not be as straightforward to do in the way it was done in Java. Second, one can almost, but not quite (due to fUML limitations) achieve the effect of lambda functions using instances of activities, so this is another route to consider.
Finally, many of the paradigms for which lambda functions are most useful in a language that is otherwise not purely functional, particularly involving collections, are already handled in Alf using dataflow-oriented expressions (i.e., sequence operation expressions, sequence expansion expressions and sequence reduction expressions). I would consider it still to be an open research question on how best to include functional constructs in a language whose underlying behavior semantics are primarily dataflow-oriented. (Note also that the Alf sequence expression syntax already uses the "->" symbol Java uses in its lambda syntax.)
In any case, any such enhancement to the language would need to be considered by the OMG Revision Task Force for Alf. If you would like to submit this as an issue for their consideration, you can do so at http://issues.omg.org/issues/create-new-issue. The current specification to raise an issue against is OMG document formal/2013-09-01, "Action Language for Foundational UML Version 1.0.1". The RTF is just finishing up work on Alf Version 1.1, so it may actually be a while before any new issues are addressed -- but please put it on the list if you are interested in it being considered by the next RTF.
Thanks for your interest!
Thank you for your reply. In Java, actually, lambdas are not just sugar, but they could have been implemented as such, and it's actually easier to think of them as such. I consider really elegant how they made lambdas implicitly convertible to interfaces with a single method, I believe a similar approach could be taken; I hadn't considered activities, though, those might be a nice solution as well. Could you elaborate a little bit on that?
I'll take some time to study more deeply how to work with Alf and how it maps to fUML, then I'll create the issue there. I'd love to see lambda functions in Alf!
Activities in UML are actually kinds of classes. You can instantiate an activity as a class, and the object you get is an un-started execution for that activity. You can then start such an execution object using a start object behavior action. In fUML, you can only start the execution asynchronously (in Alf this actually happens automatically when you create the execution object), but the full UML standard allows you to use a start object behavior action to call an execution synchronously (see the UML 2.5 spec, subclause 16.3.3.1).
Now, since an activity is a class, it can also have generalization relationships. So, you could define an "abstract" activity "superclass" that specifies a certain signature of parameters, and then define a number of specialized activities that have the abstract activity as their generalization, each providing a different behavior meeting the same abstract signature. And you could type, say, the parameter of an operation by the abstract activity, but pass in an instance of any of the specialized activities, depending on the behavior you want. Then, the implementation of the operation would use a start object behavior action to call whichever execution was passed in.
But there is a problem with this, even in full UML. The UML 2.5 spec says that "if the identified [instantiated] Behavior is already executing, then the StartObjectBehaviorAction has no effect." However, it is unclear whether "already executing" applies to the case in which the execution object has been previously started, but has completed execution after being synchronously called. This makes it unclear if the execution object can be restarted multiple times in a loop, which is necessary to handle a lot of the collection-oriented functionality for which one might use lambda functions. And, in any case, one would probably really want to have each conceptual "execution" of a behavior to really correspond to a different execution object anyway.
So that's why I say that this "almost" works.
By the way, I just happened to see that DZone is starting a series on functional programming in Java 8. "Part 0" of this series (available here has some examples. It struck me that it might be instructive to show how a couple of these can already be done straightforwardly in Alf, even without lambda functions.
For example, one of the Java examples from DZone is
public String greet(List<String> names) {
String greeting = names
.stream()
.map(name -> name + " ")
.reduce("Welcome ",
(acc, name) -> acc + name);
return greeting + "!";
}
An Alf version would be
public greet(in names: String[] sequence): String {
greeting = "Welcome"->union(names)
->collect name (name + " ")
->reduce StringFunctions::'+';
return greeting + "!";
}
Another Java example is
public List<Integer> addOne(List<Integer> numbers) {
return numbers
.stream()
.map(number -> number + 1)
.collect(Collectors.toList());
}
And the Alf version is
public addOne(in numbers: Integer[] sequence): Integer[] sequence {
return numbers->collect number (number + 1);
}
None of the Alf code requires lambda functions. Instead, they all map to underlying fUML activity data flow functionality, with the collect
expressions mapping to expansion regions and the reduce
expression mapping to a reduce action. And I think the Alf code is actually rather cleaner than the Java code.
Of course, Alf only has a limited number of kinds of "sequence expansion expressions" (besides collect
, there are iterate
, select
, reduce
, forAll
, exists
and one
expressions), and the language does not provide any way to define others. So the capability is not as powerful or general as what you get with lambda expressions. But many of the examples people of give of how to use functional programming in Java 8, especially those involving collections, can really already be done similarly using the data-flow-oriented expressions in Alf.
P.S. I tested these. They work. π
Thank you very much for the details! Also, by the way, I just love Alf's sequence expansion expressions (my field of research is programming languages, and I don't believe I've seem anything like that before). But, though they seem to work here on those Java examples, they are really missing the point here. (I'm really sorry for the details, I don't know how used you are to functional programming.)
One important aspect in functional programming is that functions are first-class citizens, i.e., they can be passed as parameters, stored in variables, and be returned from methods. For example, in Haskell we got the dot operator, which creates a function by joining two different functions together.
-- Compose a function using dot
fun = (* 2) . read
main =
print (fun "10") -- prints 20
...and we pass functions around all the time. In Java, we could write it as such:
public <T, U, R> Function<T, R> dot(Function<U, R> f, Function<T, U> g) {
return (param) -> f.apply(g.apply(param));
};
On which one could call:
// Store as a variable somewhere...
Function<String, Integer> foo = dot((i) -> i * 2, Integer::valueOf);
// Call it when needed!
System.out.println(foo.apply("10")); // prints 20
This is actually a common usage. A lot of higher order functions exist, such as map
. Though function composition is not that common in the object-oriented world, (synchronous) callbacks are. For example, if I were to instantiate a AButton
class in Alf, it would be useful to declare the callback the button is supposed to execute at the same place where I'm creating the button instance. Compare to Java:
JButton button = new JButton("Click Me!");
button.addActionListener(e -> System.out.println("I was clicked!"));
...instead of having to write a whole separate activity to instantiate there. Of course these could be as simply as function pointers in C, but, for a functional programming approach, being able to construct them at runtime is desirable (where they are closures, they capture the variables in their environment); in this respect, they are pretty much like an anonymous inline class in Java. The first example could pretty much be written as:
public <T, U, R> Function<T, R> dot(Function<U, R> f, Function<T, U> g) {
return new Function<T, R>() {
@Override public R apply(T param) {
return f.apply(g.apply(param));
};
};
};
...or, way worse, I could have defined a ComposedFunction
class elsewhere. I think you see my point. You already seemed to pass an "inline function" on collect number (number + 1)
, but being able to assign that to a variable, take that as a parameter and etc would be great.
P.s.: I've already worked with dataflow languages (Lucid), sadly it doesn't have functions as first-class citizens.
As it turns out, I do know functional programming. I am an old Lisp hand from back in the days when Scheme was the new thing. I even understand monads. So I keep up with the field -- but I haven't implemented any real system using functional programming in a long time.
And, as I noted previously, the data-flow capabilities in Alf are not as powerful as full lambda expressions. On the other hand, in my experience, pure functional programming is just not very natural for most people when trying to model large domains. And, in hybrid languages, the functional capabilities are usually just used as a syntactically simpler way to do traditional computational tasks: things like operations over collections and callbacks being common examples.
But I am not convinced that this is really the best way to do such things from a modeling perspective. Collection operations do seem to me to make more sense when thought of as data flow processing, which may also be even more natural to implement on parallel hardware than functional expressions (for these sorts of computations, at least). And the whole callback architecture could be replaced with a better model of, for example, asynchronous front-end UI and back-end logic components that communicate using signals.
Now, a lot of what is in and out of Alf right now comes down to decisions about what was in and out of UML and fUML -- some of which were made a long time ago, and not all of which have proven to be good decisions. However, as we consider adding more features to Alf, I think it is important to consider them from a modeling perspective, not just a computational perspective.
Now, that's not to say that having some sort of lambda expressions and functional values in Alf is a bad idea. It is just that I don't feel that some of the arguments that are traditionally made for them in non-pure functional languages are actually as convincing as they may seem at first. But it is worth thinking deeper about.
Thank you for the insights. I'll take a few days to review Alf and fUML and make the issue there. π
I am closing this issue for now, since adding lambda expressions to Alf would be a functionality change that needs to be addressed as part of the OMG revision process for the Alf specification.
Are there plans to add lambda functions to Alf?
They are a really common idiom in most languages, and they are specially needed for a more functional programming approach (which makes heavy use of higher order functions). They are also used a lot in object oriented programming for implementing callbacks.
Java didn't have lambda functions until recently, which is actually surprising given that it's standard library expected callbacks in several places, where one would create a verbose anonymous class with an overriden method. Java 8 lambda expressions were an elegant solution to that, where lambda expressions are described as anonymous classes implementing an interface with a single method (usually annotated with
@FunctionalInterface
). This seems like a fit solution for Alf, given that it could be implemented as syntactic sugar and wouldn't need any changes to the fUML model but would make programming with it easier; also, it would be unambiguous as of now to use Java's lambda syntax in Alf's grammar.