Call-by-push-value is an evaluation strategy that determines when arguments to functions are evaluated. Call-by-value is what every mainstream language does: arguments are evaluated before the function is called. Call-by-name substitutes arguments directly into a function, so they may be evaluated multiple times or not at all. For example, the following pseudocode:
function foo(n, m) {
sum = 0
for i in 1 to 4 {
sum = n + sum
}
if false {
print(m)
}
print(sum)
}
foo({print("1"); 2}, {print("3"); 4})
evaluated with Call-by-Value prints:
1
3
8
evaluated with Call-by-Name prints:
1
1
1
1
8
Call-by-push-value combines both by having two "kinds" of parameters: values which are evaluated immediately (call-by-value), and computations which are substituted (call-by-name). So the following code:
function foo(value n, computation m) {
sum = 0
for i in 1 to 4 {
sum = n + sum
}
if false {
print(m)
}
print(sum)
}
foo({print("1"); 2}, {print("3"); 4})
would print
1
8
The reason call-by-push-value may be useful is because both call-by-name and call-by-value have their advantages, especially with side-effects. Besides enabling programmers to write both traditional functions and custom loops/conditionals, CBPV is particularly useful for an IR to generate efficient code.
Currently, Scala has syntactic sugar for by-name parameters, and some languages like Kotlin and Swift make zero-argument closure syntax very simple (which does allow custom loops and conditionals, though it's debatable whether this is CBPV). Other languages like Rust and C have macros, which can emulate call-by-name, albeit not ideally (you have hygiene issues and duplicating syntax makes compilation slower). I don't know of any mainstream work on CBPV in the IR side.
I must say, your examples/explanation was much better than the link IMO.
I'd like to better understand the algebraic stuff the article was talking about. But clearly it requires brushing up on the Haskell kind system and I dont think I'm up for that today.