this post was submitted on 09 Mar 2024
21 points (95.7% liked)

Programming Languages

1186 readers
1 users here now

Hello!

This is the current Lemmy equivalent of https://www.reddit.com/r/ProgrammingLanguages/.

The content and rules are the same here as they are over there. Taken directly from the /r/ProgrammingLanguages overview:

This community is dedicated to the theory, design and implementation of programming languages.

Be nice to each other. Flame wars and rants are not welcomed. Please also put some effort into your post.

This isn't the right place to ask questions such as "What language should I use for X", "what language should I learn", and "what's your favorite language". Such questions should be posted in /c/learn_programming or /c/programming.

This is the right place for posts like the following:

See /r/ProgrammingLanguages for specific examples

Related online communities

founded 1 year ago
MODERATORS
 

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.

top 8 comments
sorted by: hot top controversial new old
[–] tatterdemalion 7 points 8 months ago (4 children)

Isn't computation equivalent to passing a function by value?

[–] armchair_progamer 8 points 8 months ago* (last edited 8 months ago) (1 children)

I believe the answer is yes, except that we’re talking about languages with currying, and those can’t represent a zero argument function without the “computation” kind (remember: all functions are Arg -> Ret, and a multi-argument function is just Arg1 -> (Arg2 -> Ret)). In the linked article, all functions are in fact “computations” (the two variants of CompType are Thunk ValType and Fun ValType CompType). The author also describes computations as “a way to add side-effects to values”, and the equivalent in an imperative language to “a value which produces side-effects when read” is either a zero-argument function (getXYZ()), or a “getter” which is just syntax sugar for a zero-argument function.

The other reason may be that it’s easier in an IR to represent computations as intrinsic types vs. zero-argument closures. Except if all functions are computations, then your “computation” type is already your closure type. So the difference is again only if you’re writing an IR for a language with currying: without CBPV you could just represent closures as things that take one argument, but CBPV permits zero-argument closures.

[–] codemonk 1 points 8 months ago* (last edited 8 months ago)

Thank you so much! For years I was carrying around the thought: Why do we have to decide between eager and lazy evaluation? Your explanations are so clear and motivate me to finally dig deeper into that topic. So approachable. Thanks!

[–] [email protected] 1 points 8 months ago* (last edited 8 months ago)
[–] [email protected] 1 points 8 months ago

Kinda, except if you pass a function pointer the compiler automatically inserts parentheses to call the function after pasting it in every required place. But the compiler does not do that when you pass an expression, non-function-pointer variable or literal.

[–] [email protected] 1 points 8 months ago (1 children)

I was thinking something similar as well, can someone explain the difference to me?

[–] [email protected] 2 points 8 months ago* (last edited 8 months ago)

I think it has to do with how it can be evaluated.

func1( 1, ()=>console.log("hi")||10 )

In JS the second argument might be treated as both a computation and a value.

  • console.log(m.toString()) as a value (will print out ()=>console.log("hi")||10)
  • console.log(m()) as a computation

I think CBPV forces one or the other, which helps the compiler know how to optimize it. If it were just a computation, it wouldn't need as much overhead as a full function definition I suppose.

[–] [email protected] 2 points 8 months ago* (last edited 8 months ago)

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.