We have seen short-circuiting evaluation (Section 21.6), which is a particular instance of lazy evaluation where some sub-expression is conditionally evaluated.
In this chapter, we consider call-by-name, which is another of form of lazy evaluation in defining the semantics of function call \(e_1\texttt{(}e_2\texttt{)}\). In contrast to call-by-value, call-by-name semantics does not evaluate the function argument to a value before starting to evaluate the function body. Instead, it takes the unevaluated argument expression and substitutes it for the formal parameter.
It is one tiny difference on paper that is a substantively different semantically. The \(\TirName{DoCallByValue}\) rule requires that the argument be eagerly evaluated to a value before applying the substitution, while the \(\TirName{DoCallByName}\) rule does not. Call-by-name is lazy in that if \(e_1\) does not end up using the parameter \(x\) in the subsequent evaluation, then \(e_2\) will not be evaluated (i.e., like being “short-circuited”).
27.1 TypeScripty: Call-By-Name Parameters
Let us consider TypeScripty with functions (Section 26.5) and extend it with call-by-name parameters. Note that TypeScript does not have call-by-name parameters, so this is an extension beyond TypeScript.
27.1.1 Syntax
Let us consider the abstract syntax of TypeScripty with numbers and functions extended with parameter-passing modes:
Figure 27.1: Syntax of TypeScripty with number literals, function literals with parameter modes, and variable declarations, and function call expressions.
Function parameters are annotated with a mode\(d\) to be either call-by-value (\(\mathbf{const}\)) or call-by-name (\(\mathbf{name}\)). To emphasize that variable binding can be encoded as a function call and a function literal, we introduce the variable binding expression as declaring a variable with a mode (\(d\;x\;\texttt{=}\;e_1\texttt{;}\;e_2\)). That is, variables can be bound by value (\(\mathbf{const}\;x\;\texttt{=}\;e_1\texttt{;}\;e_2\)) or by name (\(\mathbf{name}\;x\;\texttt{=}\;e_1\texttt{;}\;e_2\)).
Note that most languages have a default parameter-passing mode (e.g., by value in the case of JavaScript/TypeScript), so the concrete syntax may leave the mode declaration optional.
We can represent the abstract syntax of TypeScripty with parameter-passing modes in Scala as follows:
trait Typ // τcaseobject TNumber extends Typ // τ ::= numbercaseclassTFun(xt:(String, MTyp), tret: Typ)extends Typ // τ ::= (x: m τ) => τ'caseclassMTyp(m: Mode, t: Typ)// m τ trait Mode // mcaseobject MConst extends Mode // m ::= constcaseobject MName extends Mode // m ::= nametrait Expr // ecaseclassN(n:Double)extends Expr // e ::= ncaseclassFun(xt:(String, MTyp), e1: Expr)extends Expr // e ::= (x: m τ) => e1caseclassVar(x:String)extends Expr // e ::= xcaseclassCall(e1: Expr, e2: Expr)extends Expr // e ::= e1(e2)caseclassDecl(m: Mode, x:String, e1: Expr, e2: Expr)extends Expr // e ::= m x = e1; e2defisValue(e: Expr):Boolean= e match{caseN(_)|Fun(_, _)=>truecase _ =>false}
defined traitTyp
defined objectTNumber
defined classTFun
defined classMTyp
defined traitMode
defined objectMConst
defined objectMName
defined traitExpr
defined classN
defined classFun
defined classVar
defined classCall
defined classDecl
defined functionisValue
We use the MTyp constructor for the pair of a mode \(d\) and a type \(\tau\) for clarity.
27.1.2 Small-Step Operational Semantics
Let us consider the small-step operational semantics of TypeScripty with parameter-passing modes:
Figure 27.2: Small-step operational semantics of TypeScripty with number literals, function literals with parameter modes, variable declarations, and function call expressions.
Depending on the declared parameter-passing mode, either \(\TirName{DoCallByValue}\) or \(\TirName{DoCallByName}\) applies. Only \(\TirName{SearchCall1}\) is needed for call-by-name, so \(\TirName{SearchCall2}\) only applies for \(\mathbf{const}\) parameters.
We see the parallel of \(\TirName{DoConstDecl}\) and \(\TirName{DoNameDecl}\) with \(\TirName{DoCallByValue}\) and \(\TirName{DoCallByName}\), respectively. Similarly, \(\TirName{SearchConstDecl}\) matches up with \(\TirName{SearchCall2}\).
defsubst(v: Expr, x:String, e: Expr)={defsubst(e: Expr): Expr = e match{caseN(_)=> ecaseFun(yt @ (y, _), e1)=>if(x == y) e elseFun(yt,subst(e1))caseVar(y)=>if(x == y) v else ecaseCall(e1, e2)=>Call(subst(e1),subst(e2))caseDecl(m, y, e1, e2)=>Decl(m, y,subst(e1),if(x == y) e2 elsesubst(e2))}subst(e)}
We see that we can generalize slightly by defining a judgment form \(d\;\vdash e\;\mathsf{redex}\) to determine whether an actual argument expression \(e\) is reducible for a given parameter-passing mode \(d\). This helper judgment is then used for both \(\TirName{SearchCall2}\) and \(\TirName{SearchDecl}\) to reduce the argument expression as needed before applying the substitution in \(\TirName{DoCall}\) and \(\TirName{DoDecl}\), respectively:
We write \(d\;\nvdash e\;\mathsf{redex}\) for the judgment form when an actual argument expression \(e\) is not reducible for a given parameter-passing mode \(d\):
\(\fbox{$d\;\vdash e\;\mathsf{redex}$}\)
\(\inferrule[ConstMode]{
e\neq v
}{
\mathbf{const}\;\vdash e\;\mathsf{redex}
}\)
We can the \(d\;\vdash e\;\mathsf{redex}\) and \(d\;\nvdash e\;\mathsf{redex}\) judgment forms by defining a helper function isRedex that we use in a slightly more generalized version of step:
We see that call-by-name is a small change in the run-time of the language and thus the changes manifest in the operational semantics. The static semantics essentially stay the same as before, ignoring the \(\mathbf{const}\) or \(\mathbf{name}\) modes.
Exercise 27.1 Define the typing judgment \(\Gamma \vdash e : \tau\) for the above language.