O documento apresenta informações sobre Ana Luiza Bastos, uma desenvolvedora de software e cientista da computação, e sobre recursão e trampolines em programação funcional. É discutido como recursão pode causar overflow de pilha e como trampolines e estilo de passagem de continuação (CPS) resolvem este problema de forma segura à pilha.
33. Um estilo de programação em que
o controle é passado explicitamente
em forma de continuação
34. Continuação não chama a si mesma, ela só expressa
um fluxo de computação onde os resultados fluem em
uma direção.
Aplicar continuações não é chamar funções é passar
o controle do resultado.
35. Continuação é uma parte de
código que ainda vai ser executada
em algum ponto do programa
Callbacks por exemplo são continuations
45. ● O último parâmetro da função é sempre a
continuation.
● Todas as funções precisam acabar chamando
sua continuação com o resultado da execução
da função.
56. ● É um loop que chama funções repetidamente
● Cada função é chamada de thunk.
● O trampolim nunca chama mais de um thunk
● É como se quebrasse o programa em pequenos
thunks que saltam pra fora do trampolim, assim o
stack não cresce.
57. const trampolineFac = (n) {
const sum = (ac, n) => {
return n == 0
? ac
: thunk(sum, ac * n, n -
1)
}
return trampoline(thunk(sum, n, 0))
}
60. Trade off por stack safety
Trocamos o trabalho de criar stack frames com
o de criar binding de funções.
61. Em muitos casos o trade-off de
overhead por expressividade vale a
pena
62. Trampolines são mais apropriadas
para funções complexas em que não
existem soluções iterativas e não
conflitam com outras técnicas de
mediar controle de fluxo(Promises).
63. ● Kyle Simpson. Functional Light Programming. Cap
8. - github.com/getify/Functional-Light-JS
● Functional Programming Jargon -
github.com/hemanth/functional-programming-
jargon
● Structure and Interpretation of Computer
Programs - Javascript Adaptation
Antes de falar sobre recursão vamos falar sobre recursão
In most of the pure functional programming languages (Haskell, Clean, Erlang) there are no for or while loops, so iterating over lists needs to be done using recursive functions. Pure functional programming languages have language support and are optimized for list comprehension and list concatenation.Muitas linguagens de programação são em torno de recursão e apesar do JS ter muitos elementos funcionais não foi feita pra isso
In most of the pure functional programming languages (Haskell, Clean, Erlang) there are no for or while loops, so iterating over lists needs to be done using recursive functions. Pure functional programming languages have language support and are optimized for list comprehension and list concatenation.
Precisamos definir:
Defining an exit condition, an atomic definition that exists by itself (also called a “base case”).
Defining which part of the algorithm is recursive.(ou seja, quando a função deve chamar a si mesma)
A curried function takes one argument at a time and returns a function that takes the next argument. In Reason, functions can automatically be partially called:
Avoiding State Change (and mutable data) - one of the characteristics of functional programming is that functions do not change the state of the application, they rather create a new state from the old one.Declarations vs statements - in functional programming as in Mathematics a declarative approach is used for defining/describing functions.Idempotence - it means when invoking a function (any number of times) using the same arguments will always have the same result, this also goes hand in hand with avoiding state changeRecursion, however, is a natural match with pure functional programming - no state is needed to recurse, except for the (read-only) function arguments and a (write-only) return value.
if re-written using iterative loops would be much more complex, unweildy or harder to read/maintain.
Think about binary searching algorithms like tree-traversal.
However, not having side effects also means that recursion can be implemented more efficiently, and the compiler can optimize it more aggressively. I haven't studied any such compiler in depth myself, but as far as I can tell, most functional programming languages' compilers perform tail call optimization, and some may even compile certain kinds of recursive constructs into loops behind the scenes.
GCC does a much better job of TCO than GHC, because you can't do TCO across the creation of a thunk.
Javascript is single threaded, which means that only one task can be run at any given time. When the Javascript interpreter on the page starts executing code it is running in an environment that is referred to as the Global Execution Context.
factorial(0) // The factorial of 0 is 1 by definition (base case)factorial(1) // This call depends on factorial(0)factorial(2) // This call depends on factorial(1)factorial(3) // This first call depends on factorial(2)
factorial(0) // The factorial of 0 is 1 by definition (base case)factorial(1) // This call depends on factorial(0)factorial(2) // This call depends on factorial(1)factorial(3) // This first call depends on factorial(2)
So the way computers generally implement this idea of a function that could call another function that could call another. In memory they reserve an area of memory that's given the name a stack frame, generally. It's a small area of memory that keeps track of all the variables, sometimes referred to as the variables that are on the stack.
At some point, there's a physical limit to the system. At some point we've run out. Now when you weren't doing recursion. I
Various language features will trigger the creation of a new execution context (functions, eval, let blocks, closures, etc.).
https://www.datchley.name/content/images/2015/11/Execution-Stack---JS.png
Keep in mind that because of the scope chain, execution contexts will be able to access variables and functions delared in any parent (previous) scope.
function calls add to the stack.
. When the code in a given context is finished executing, that stack entry is destroyed and control is returned to the executable code from the previous stack execution context.
https://www.datchley.name/content/images/2015/11/Single-Function--Iterative-Stack---JS.png
In functional languages (like Elm, Elixir, Haskell, etc), it is impossible to do imperative loops, so the only option is recursion. não porque elas querem mas porque elas precisam
Programacao funcional é legal mas js não foi feito pra ser uma lang funcional o suporte js é poop
Since recursion is built into the language, the compiler will often make optimizations to guarantee that the call stack isn’t exceeded when processing large datasets
Clojure: Nao suporta TCO pois JVM
loop...recur: Note the similarity between this code and the Python fib_tail shown earlier. This is not a coincidence! Once the algorithm is expressed in tail form, it's pretty easy to convert it to an iteration pattern manually; if it wasn't easy, compilers wouldn't be able to do it automatically for the past 40 years!
. Since the tail call carries the whole state around in arguments, we just imitate this using an explicit loop and state variables.
Here, Javascript recognizes the tail-call and can then reuse the existing stack frame to make the recursive call, removing any previously local variables and state from the old call. As it turns out in the general sense, creating a new stack frame and throwing an old stack frame array.
, we could just reuse the same stack frame, we could just override it with. The memory, we could reuse that memory for the next stack frameDifferently to what happens with proper tail calls, tail call optimization actually improves the performance of tail recursive functions and makes running them faster.
Tail call optimization is a technique used by the compiler to transform your recursive calls into a loop using jumps.
Como fazer o tco funfar?
So it's kind of like PTC is the umbrella and TCO is the way that an engine might choose to do different sorts of optimizations, make things not slow.
A curried function takes one argument at a time and returns a function that takes the next argument. In Reason, functions can automatically be partially called:
hey’re always the last thing to be done and evaluated before the return and the return value of this called function is returned by the calling function.
Pra refatorar eu preciso fazer um acumulador
Retorna uma função com um argumento a mais chamado argumento
Assim o retorno sempre será a própria função
At the time of writing, Safari is the only browser to have shipped PTC. Node implemented tail calls in version 6.5, but it was hidden behind a flag (later they removed support for PTC altogether in Node 8).
The folks at Apple that drive Safari, they don't put features into their browser unless there is a really good reason why they want to. So my suspicion is that either something that's already shipped or something that's shipping soon on their road map, distinctly and directly requires them to have PTC support.he reason it's not in the other engines is not because it's too hard for them. They don't want to implement it. They've decided that implementing this feature, even though they voted to put it in the spec, they've decided that implementing this feature is gonna unnecessarily hamper other performance things that they wanna do.And so, they're pushing back on the TC39 committee saying, we wanna take that out of this spec. And the WebKit folks are like, well, we already shipped it like six months ago. So, we don't wanna take it out of the spec, we like it, we think it's good.
If the engine just started doing that, that would actually make every function in your program, every single time recursive or not slower. So just implementing that feature that they could run in the recursive case in fixed memory would make all functions, even non-recursive ones, run slower.
Conceito legal de programação
CPS desugars function return, exceptions and first-class continuations; function call turns into a single jump instruction
SCHEME?
A continuation is an object that captures the current state of a program
Easier: it is the work that remains to be done
A common representation is a function
I takes only one parameter with the result of previous computations
FORMA DE SIMULARAVALIACAO DEVAGAR
AVALIACAO PREGUICOSA
Call-by-ned: evaluation mechanism that delays the evaluation of an expression until its value is needed.
Continuations are often seen in asynchronous programming when the program needs to wait to receive data before it can continue. The response is often passed off to the rest of the program, which is the continuation, once it's been received.unbounded continuations can be treated as first-class values in a language, they become so powerful that they can be used to implement pretty much any control-flow feature you can imagine - exceptions, threads, coroutines and so on. This is precisely what continuations are sometimes used for in the implementation of functional languages, where CPS-transform is one of the compilation stages.
(lida com cps call/cc)
const printAsString = (num) => console.log(`Given ${num}`)
const addOneAndContinue = (num, cc) => {
const result = num + 1
cc(result)
}
addOneAndContinue(2, printAsString) // 'Given 3'
const continueProgramWith = (data) => {
// Continues program with data
}
readFileAsync('path/to/file', (err, response) => {
if (err) {
// handle error
return
}
continueProgramWith(response)
})
Even though most programming languages don't support real, unbounded continuations, bounded continuations is another deal. A bounded continuation is just a function that returns to its caller
Applying unbounded continuations is not just calling a function - it's passing control without hope of return. Just like coroutines, or longjmp in C.
Unbounded continuations do not return to their caller. They express a flow of computation where results flow in one direction, without ever returning
So, if I was in the very first call, that is I passed in only one number, then that would be calling the identity function with whatever I passed in, and just passing back the value directly. But if I was deeper into my stack of recursion, then I would just be passing sum into some function that had been passed to me, that had been passed to me, that had been passed.
[00:02:57] So, what does that non-base case look like? You notice again, instead of calling sum plus recur, I call recur with my list of nums, that's my running array list if you will, but my last position there is a function. It's another continuation, a continuation that takes a v and calls whatever the current continuation is with that plus the sum. And since it does not rely on stack value, any values will be captured inside of the continuation. We can make even more than one continuation to execute on a different execution path or even on the different CPU with no problem with mutable state.
Pra refatorar eu preciso fazer um acumulador
Funcao identdade, que é um termo pra uma função que torna tudo que você poe dentro dela
Para extrair o valor final
Junta alguns conceitos ja abordados
is common in functional programming and provides us a way to call our function in tail position without growing the stack.
dessa forma usamos esse thunk pra emular uma avaliação lazy, que linguagens como js nao suportam por padrão.
So the sole purpose of the trampoline function is to control the execution in an iterative way, and that ensures the stack to have only a single stack frame on the stack at any given time.
Remember how I said, when discussing unbounded continuations, that in "regular" languages like Python we're just cheating and simulating continuations with function calls? Trampolines is what make this viable without blowing the stack
def fact_cps_thunked(n, cont):
if n == 0:
return cont(1)
else:
return lambda: fact_cps_thunked(
n - 1,
lambda value: lambda: cont(n * value))
e o que é esse thunk? é uma expresão em uma função de argumetnos. Esse encapsulamento "atrasa" a avaliacao da expressao ate o ponto em que a funcao é chamada
algo como
> 2 * (3 + 4) // 14
> const f = () => 2 * (3 + 4)
> f() // 14
A curried function takes one argument at a time and returns a function that takes the next argument. In Reason, functions can automatically be partially called:
A curried function takes one argument at a time and returns a function that takes the next argument. In Reason, functions can automatically be partially called:
A curried function takes one argument at a time and returns a function that takes the next argument. In Reason, functions can automatically be partially called:
Here, Javascript recognizes the tail-call and can then reuse the existing stack frame to make the recursive call, removing any previously local variables and state from the old call. As it turns out in the general sense, creating a new stack frame and throwing an old stack frame array.
, we could just reuse the same stack frame, we could just override it with. The memory, we could reuse that memory for the next stack frameDifferently to what happens with proper tail calls, tail call optimization actually improves the performance of tail recursive functions and makes running them faster.
Tail call optimization is a technique used by the compiler to transform your recursive calls into a loop using jumps.
In my own performance profiling I found that the overhead from using the trampoline wasn’t nearly as large as I thought it would be. There’s no question about it — the trampoline is slower than an iterative loop. However, in many cases where a recursive solution can be cleaner and less error-prone, the performance overhead may be worth the readability benefits.