Typically in Erlang you write ..
(fun(X) -> X * 2 end)(6)
to get 12 as the result. With string lambda, you write ..
(utils:lambda("X * 2"))(6)
to get the same result. Ok, counting the characters of the module and the function, you grin at the fact that it is really more verbose than the original one. But we have less boilerplates, with the explicit function declaration being engineered within the lambda.
A couple of more examples of using the string lambda :
(utils:lambda("X+2*Y+5*X"))(2,5) => 22
(utils:lambda("Y+2*X+5*Y"))(5,2) => 34
Less noisy ? Let's proceed ..
lists:map(fun(X) -> X * X end, [1,2,3]).
lists:filter(fun(X) -> X > 2 end, [1,2,3,4]).
lists:any(fun(X) -> length(X) < 3 end, string:tokens("are there any short words?", " ")).
and with string lambdas, you have the same result with ..
utils:any("length(_) < 3", string:tokens("are there any short words?", " ")).
In the last example,
_is the parameter to a unary function and provides a very handy notation in the lambda. Here are some more examples with unary parameter ..
%% multiply by 5
%% length of the input list > 2 and every element of the list > 3
(utils:all_and(["length(_) > 2", "utils:all(\">3\", _)"]))([1,2,3,4]).
As per original convention,
->separates the body of the lambda from the parameters, when stated explicitly and the parameters are matched based on their first occurence in the string lambda ..
If not specified explicitly, parameters can be implicit and can be effectively deduced ..
%% left section implicit match
%% right section implicit match
%% both sections implicit match
Chaining for Curry
->can be chained to implement curried functions.
or deferred invocation ..
Higher Order Functions
String lambdas allow creating higher order functions with much less noise and much more impact than native boilerplates in the language.
%% compose allows composition of sequences of functions backwards
%% higher order functions
utils:map("+1", utils:map("*2", [1,2,3])).
lists:map(utils:compose(["+1", "*2"]), [1,2,3]).
And here are some catamorphisms ..
lists:reverse()is typically defined by Erlang in the usual recursive style :
reverse( = L) ->
reverse([_] = L) ->
reverse([A, B]) ->
reverse([A, B | L]) ->
lists:reverse(L, [B, A]).
reverse()using catamorphism and delayed invocation through string lambdas ..
Reverse = utils:lambda("utils:foldl(\"[E] ++ S\", , _)").
and later ..
Or the classic factorial ..
Factorial = utils:lambda("utils:foldr(\"E*S\", 1, lists:seq(1,_))").
and later ..
Motivation to learn another programming language
I am no expert in functional programming. I do not use functional programming in my day job. But that is why I am trying to learn functional programming. Also it is not that I will be using functional programming to write production code in the near future. We all know how the enterprise machinery works and how a typical application developer has to struggle through the drudgery of boilerplates in his bread-earner job. Learning newer paradigms of programming will teach me how to think differently and how to move gradually towards writing side-effect free programs. The principal take-away from such learning is to be able to write a piece of code that encapsulates my intention completely within the specific block, without having to look around for those unintentional impacts in other areas of the codebase.
I am a newbie in Erlang and the attempt to port string lambdas to Erlang is entirely driven by my desire to learn the programming language. The source code is still a bit rusty, may not use many of the idioms and best practices of the language, and may not cover some of the corner cases. Go ahead, download the source code, and do whatever you feel like. But drop in a few comments in case you have any suggestions for improvement.
- lib_lambda.erl (the string lambda implementation)
- lib_misc.erl (miscellaneous utilities)
- lib_lambda_utilities.erl (utility wrappers)