Functional CoffeeScript programming with the f_context library
Those who are faced with functional programming languages are probably familiar with this design:
This is one of the classic examples of phase transitions - factorial calculation.
Now it can be done on CoffeeScript with the f_context library, just by wrapping the code in f_context -> , for example:
How it works
See more examples
Modules
Pattern Matching
Destructuring
Guards
Variable _ and Matching Same Arguments
Real-Life Examples
By default, f_context returns the generated module, i.e. it can be put in some kind of variable:
Using the module directive, you can immediately put a module in a global space,
such as window or global :
what is it and why is it needed
Example:
Result:
what is it and why is it needed
Examples:
Result:
what it is and why it is needed.
Guards are specified through the where directive (% condition%) .
In guards, you can specify a more flexible comparison. Example of calculating the Fibonacci series:
The _ variable is used to "reset" the arguments, if their value is not important, but the number of arguments is important.
Example of the implementation of the all function:
If you set the same names for the arguments, they will be automatically compared.
An example implementation of the range function:
Sometimes it is much more convenient to implement a particular algorithm using recursion.
For example, reduce and quick sort functions in a functional style:
In my opinion, it is simpler and clearer than their imperative counterparts.
Let's get back to the very first example in this article - factorial calculation.
In CoffeeScript, this is an absolutely valid construct.
An example is more complicated, counting the number of elements in a list in a functional style:
And this is also valid CoffeeScript code.
Thus, we can write in the usual style for functional languages directly on coffee, without the need to use a precompiler.
For clarity, I will continue to talk on the example of the same factorial. So
this code:
translates to js as follows:
It can even be executed, but with "ReferenceError" errors, that fact and N are not declared.
You can wrap this code in a wrapper function so that it is passed as an argument
in js we get the following:
Now function_wrapper can analyze the function passed to it in the
argument and pass all the missing variables to it. Something like this:
Now the code is executed without errors, but so far does nothing.
Next up is fact_stub , which can also parse its arguments and
generate its local branch of the current function. For a factorial, it will look something like this:

After the parsing tree is built, you can easily generate a module and create it through the same Function.apply.
The module body should look like this:
In fact, everything is a little more complicated, but here I tried to state the basic principles of work.
If the article is not clear - write, I will try to fix it.
The library itself is here: github.com/nogizhopaboroda/f_context
Any comments are welcome. Like feature requests.
fact(0) -> 1
fact(N) -> N * fact(N - 1)
This is one of the classic examples of phase transitions - factorial calculation.
Now it can be done on CoffeeScript with the f_context library, just by wrapping the code in f_context -> , for example:
f_context ->
fact(0) -> 1
fact(N) -> N * fact(N - 1)
How it works
See more examples
What the library can do
Modules
Pattern Matching
Destructuring
Guards
Variable _ and Matching Same Arguments
Real-Life Examples
Modularity:
By default, f_context returns the generated module, i.e. it can be put in some kind of variable:
examples = f_context ->
f_range(I) ->
f_range(I, 0, [])
f_range(I, I, Accum) -> Accum
f_range(I, Iterator, Accum) ->
f_range(I, Iterator + 1, [Accum..., Iterator])
examples.f_range(10) #=> [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
Using the module directive, you can immediately put a module in a global space,
such as window or global :
f_context ->
module("examples")
f_range(I) ->
f_range(I, 0, [])
f_range(I, I, Accum) -> Accum
f_range(I, Iterator, Accum) ->
f_range(I, Iterator + 1, [Accum..., Iterator])
examples.f_range(10) #=> [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
Pattern matching
what is it and why is it needed
Example:
matching_example_1("foo") -> "foo matches"
matching_example_1("bar") -> "bar matches"
matching_example_1(1) -> "1 matches"
matching_example_1(true) -> "true matches"
matching_example_1(Str) -> "nothing matches, argument: #{Str}"
Result:
matching_example_1("foo") #returns "foo matches"
matching_example_1("bar") #returns "bar matches"
matching_example_1(1) #returns "1 matches"
matching_example_1(true) #returns "true matches"
matching_example_1("baz") #returns "nothing matches, argument: baz"
Destructuring
what is it and why is it needed
Examples:
test_destruct_1([Head, Tail...]) -> {Head, Tail}
test_destruct_1_1([Head, Head1, Tail...]) -> {Head, Head1, Tail}
test_destruct_2([Head..., Last]) -> {Head, Last}
test_destruct_2_1([Head..., Last, Last1]) -> {Head, Last, Last1}
test_destruct_3([Head, Middle..., Last]) -> {Head, Middle, Last}
test_destruct_3_1([Head, Head2, Middle..., Last, Last2]) -> {Head, Head2, Middle, Last, Last2}
Result:
test_destruct_1([1,2,3]) #returns {Head: 1, Tail: [2,3]}
test_destruct_1_1([1,2,3,4]) #returns {Head: 1, Head1: 2, Tail: [3,4]}
test_destruct_2([1,2,3]) #returns {Head: [1,2], Last: 3}
test_destruct_2_1([1,2,3,4]) #returns {Head: [1,2], Last: 3, Last1: 4}
test_destruct_3([1,2,3,4]) #returns {Head: 1, Middle: [2,3], Last: 4}
test_destruct_3_1([1,2,3,4,5,6]) #returns {Head: 1, Head2: 2, Middle: [3,4], Last: 5, Last2: 6}
Guards
what it is and why it is needed.
Guards are specified through the where directive (% condition%) .
In guards, you can specify a more flexible comparison. Example of calculating the Fibonacci series:
#без гвардов
fibonacci_range(Count) ->
fibonacci_range(Count, 0, [])
fibonacci_range(Count, Count, Accum) -> Accum
fibonacci_range(Count, 0, Accum) ->
fibonacci_range(Count, 1, [Accum..., 0])
fibonacci_range(Count, 1, Accum) ->
fibonacci_range(Count, 2, [Accum..., 1])
fibonacci_range(Count, Iterator, [AccumHead..., A, B]) ->
fibonacci_range(Count, Iterator + 1, [AccumHead..., A, B, A + B])
#с гвардами
fibonacci_range(Count) ->
fibonacci_range(Count, 0, [])
fibonacci_range(Count, Count, Accum) -> Accum
fibonacci_range(Count, Iterator, Accum) where(Iterator is 0 or Iterator is 1) ->
fibonacci_range(Count, Iterator + 1, [Accum..., Iterator])
fibonacci_range(Count, Iterator, [AccumHead..., A, B]) ->
fibonacci_range(Count, Iterator + 1, [AccumHead..., A, B, A + B])
Variable _ and matching the same arguments
The _ variable is used to "reset" the arguments, if their value is not important, but the number of arguments is important.
Example of the implementation of the all function:
f_all([Head, List...], F) ->
f_all(List, F, F(Head))
f_all(_, _, false) -> false
f_all([], _, _) -> true
f_all([Head, List...], F, Memo) ->
f_all(List, F, F(Head))
If you set the same names for the arguments, they will be automatically compared.
An example implementation of the range function:
f_range(I) ->
f_range(I, 0, [])
f_range(I, I, Accum) -> Accum
f_range(I, Iterator, Accum) ->
f_range(I, Iterator + 1, [Accum..., Iterator])
Life examples
Sometimes it is much more convenient to implement a particular algorithm using recursion.
For example, reduce and quick sort functions in a functional style:
f_context ->
f_reduce(List, F) ->
f_reduce(List, F, 0)
f_reduce([], _, Memo) -> Memo
f_reduce([X, List...], F, Memo) ->
f_reduce(List, F, F(X, Memo))
f_context ->
f_qsort([]) -> []
f_qsort([Pivot, Rest...]) ->
[
f_qsort((X for X in Rest when X < Pivot))...,
Pivot,
f_qsort((Y for Y in Rest when Y >= Pivot))...
]
In my opinion, it is simpler and clearer than their imperative counterparts.
How it works
Let's get back to the very first example in this article - factorial calculation.
fact(0) -> 1
fact(N) -> N * fact(N - 1)
In CoffeeScript, this is an absolutely valid construct.
An example is more complicated, counting the number of elements in a list in a functional style:
count(List) ->
count(List, 0)
count([], Iterator) -> Iterator
count([Head, List...], Iterator) ->
count(List, Iterator + 1)
And this is also valid CoffeeScript code.
Thus, we can write in the usual style for functional languages directly on coffee, without the need to use a precompiler.
For clarity, I will continue to talk on the example of the same factorial. So
this code:
fact(0) -> 1
fact(N) -> N * fact(N - 1)
translates to js as follows:
fact(0)(function() {
return 1;
});
fact(N)(function() {
return N * fact(N - 1);
});
It can even be executed, but with "ReferenceError" errors, that fact and N are not declared.
You can wrap this code in a wrapper function so that it is passed as an argument
function_wrapper ->
fact(0) -> 1
fact(N) -> N * fact(N - 1)
in js we get the following:
function_wrapper(function() {
fact(0)(function() {
return 1;
});
fact(N)(function() {
return N * fact(N - 1);
});
});
Now function_wrapper can analyze the function passed to it in the
argument and pass all the missing variables to it. Something like this:
var function_wrapper = function(fn){
var fn_body = fn.toString().replace(/function.*\{([\s\S]+)\}$/ig, "$1");
var new_function = Function.apply(null, fn_body, /*именованные аргументы*/ 'fact', 'N');
var fact_stub = function(){
return function(){};
};
// маркируем N как переменную
var N_stub = function(){};
N_stub.type = "variable";
N_stub.name = "N";
new_function(fact_stub, N_stub)
}
Now the code is executed without errors, but so far does nothing.
Next up is fact_stub , which can also parse its arguments and
generate its local branch of the current function. For a factorial, it will look something like this:

After the parsing tree is built, you can easily generate a module and create it through the same Function.apply.
The module body should look like this:
var f_fact_local_0 = function(){
return 1;
};
var f_fact_local_1 = function(N){
return N * f_fact(N - 1);
};
var f_fact = function(){
if(arguments[0] === 0){
return f_fact_local_0();
}
return f_fact_local_1(arguments[0]);
};
return {
f_fact: f_fact
};
In fact, everything is a little more complicated, but here I tried to state the basic principles of work.
If the article is not clear - write, I will try to fix it.
The library itself is here: github.com/nogizhopaboroda/f_context
Any comments are welcome. Like feature requests.
Only registered users can participate in the survey. Please come in.
Is the article clear?
- 54.9% Understand 28
- 25.4% Don't understand 13
- 33.3% What is it all about? 17