A denotational semantics for needed narrowing

Michael Hanus^{∗}  Salvador Lucas^{†}

In Proc. of the 1996 Joint Conference on Declarative Programming (APPIA-GULP-PRODE’96), pp. 259–270, San Sebastian, 1996

Abstract

Needed narrowing [2] is currently the best (lazy) narrowing strategy for functional logic programs. In order to automatically improve compilation, it is essential to rely on an adequate semantic framework. The denotational semantics of a programming language is its standard semantics and can be used as a formal basis to improve implementations. In this work we introduce a denotational semantics which is adequate to express needed narrowing.^{1}

Keywords: denotational semantics, functional logic languages, lazy narrowing strategies.

1 Introduction

Lazy narrowing strategies in functional logic programming are important in avoiding unnecessary computations and enabling the use of infinite data structures. In order to have a good framework to implement lazy languages and automatically improve compilation, we need to rely on an adequate semantic definition. The denotational semantics of a programming language maps syntactic constructs in the program to the abstract values which they denote [15]. It is often considered as the standard semantics of programming languages and can be used as a suitable basis for analyses and implementations.

The denotational definition of a programming language may be more or less close to the operational principle of the language. In the setting of functional logic languages, this is to say that the particular narrowing strategy used in the operational semantics can be more or less reflected in the denotational description of the language. Some denotational approaches to the semantics of functional logic languages can be found in [11, 13]. However, these semantic definitions do not reflect the strategy used for narrowing and, therefore, they are not considered here.

In this paper we introduce a denotational semantics for term rewriting systems which allows us express the needed narrowing strategy. The needed narrowing strategy is considered as a combination of program transformation and specialization of the general narrowing mechanism.

∗Informatik II, RWTH Aachen, Germany, hanus@informatik.rwth-aachen.de.

†DSIC, U.P. de Valencia, Spain, slucas@dsic.upv.es. Work partially supported by Bancaixa (Bancaja-Europa grant) and CICYT (under grant TIC 95-0433-C03-03).

1This work is a short version of [6] which we refer to find out the missing details and proofs.

In the semantic description of the computation, we work with finite substitutions in representing the solutions of narrowing evaluations thus borrowing the treatment of [3] from the logic programming setting. The formalization here is slightly different. We define a suitable notion of finite substitution and use them to deal with renaming as well as for the solutions of evaluations.

We give the main properties of the semantic definition and prove the adequacy of this semantics to express needed narrowing.

In Section 2, we briefly recall the technical concepts and results used in the remainder of the paper. In Section 3, we describe the notion of finite substitution that we deal in the semantics. In Section 4, we summarize the notion of needed narrowing. In Section 5, the denotational semantics and its basic properties are given. We also give the adequacy result. Finally, Section 6 points to conclusions and future work.

2 Preliminaries

We introduce the most important notations used in the paper. For full definitions we refer to [4].

The set of terms T (Σ, V) is constructed w.r.t. a given many-sorted signature Σ and variables from V. We denote by ar(f) the number of argument positions of a symbol f ∈ Σ. We write Var(t) for the set of variables occurring in a term t, and \overline{t_{n}} for a tuple t_{1}, …, t_{n} of terms (where we sometimes omit the subscript n). Functional logic programs are generally constructor-based [5], i.e., the signature is the disjoint union Σ = C ⊎ F of two classes of symbols: constructors c ∈ C that construct data terms, and defined functions or operations f ∈ F that operate on data terms. We denote by HNF_{Σ} the set of terms in T (Σ, V) which are in head normal form (hnf ), i.e., terms where the root symbol is not a defined function.

Terms are viewed as labelled trees in the usual way. An occurrence or position p is a path identifying a subterm in a term. t|_{p} denotes the subterm of t at position p, and t[s]_{p} denotes the result of replacing t|_{p} with s in t.

A term rewriting system (TRS) is a pair R = (Σ, R) where R is a set of rewrite rules. Since functional logic programs are constructor-based, we assume that a program R is a constructor-based term rewriting system consisting of rewrite rules l → r, where l is a pattern, i.e., the root of l is an operation and the arguments of l do not contain any operation symbols.

A substitution is a mapping σ : V → T (Σ, V) which is the identity mapping at all but finitely many points. We denote by ϵ the identity substitution. We write substitutions in the form {x_{1} ↦ t_{1}, …, x_{n} ↦ t_{n}}. Terms t, s unify if there exists a substitution σ such that σ(t) = σ(s). In this case, σ is called a unifier of t and s. If W is a set of variables, we denote by σ =_{W} σ′ that σ(x) = σ′(x) for all x ∈ W. A term t is a variant of s if it is obtained from s by a unique replacement of variables by new variables.

Functional logic programs compute with partial information, i.e., a functional expression may contain logical variables. The goal is to compute values for these variables such that the expression is evaluable to a particular normal form, e.g., a constructor term [11]. This is done by narrowing. A term t is narrowable to a term s if there exist a non-variable position p in t (i.e., t|_{p} is not a variable), a variant α : l → r of a rewrite rule in R with Var(t) ∩ Var(l → r) = Ø and a unifier σ of t|_{p} and l such that s = σ(t[r]_{p}). In this case we write t ⇝_{p,α,σ} s. If σ is a most general unifier of t|_{p} and l, the narrowing step is called most general.^{2} We write t_{0} ⇝_{σ}^{∗} t_{n} if there is a narrowing derivation t_{0} ⇝_{p_{1},α_{1},σ_{1}} t_{1} ⇝_{p_{2},α_{2},σ_{2}} ⋯ ⇝_{p_{n},α_{n},σ_{n}} t_{n} with σ = σ_{n}∘⋯∘σ_{2}∘σ_{1}. The renamings in the i-th narrowing step must be also performed apart of all variables appearing in the previous narrowing steps j < i.

Narrowing solves equations, i.e., computes values for the variables in an equation such that the equation becomes true, where an equation is a pair t ≈ t′ of terms of the same sort. Since we do not require terminating term rewriting systems, normal forms may not exist. Hence, we define the validity of an equation as a strict equality on terms in the spirit of functional logic languages with a lazy operational semantics such as BABEL [11]. Thus, a substitution σ is a solution for an equation t ≈ t′ iff σ(t) and σ(t′) are reducible to a same ground constructor term. Equations can also be interpreted as terms by defining the symbol ≈ as a binary operation symbol, more precisely, one operation symbol for each sort. Therefore, all notions for terms, such as substitution, narrowing etc., will also be used for equations. The semantics of ≈ is defined by the following rules, where ∧ is assumed to be a right-associative infix symbol, and c is a constructor of arity 0 in the first rule and arity k > 0 in the second rule.

                              c ≈ c → true

c(x_{1}, …, x_{k}) ≈ c(y_{1}, …, y_{k}) → x_{1} ≈ y_{1} ∧ ⋯ ∧ x_{k} ≈ y_{k}

                           true ∧ x → x

These are the equality rules of a signature. By adding the equality rules to the rewrite system, equation solving can be done by narrowing equations to “true” [2, 11].

3 Finite substitutions

3.1 Formalization of finite substitutions

In this section we formalize the notion of finite substitution which we need in this work. The definition is very close to the one in [3]. Nevertheless, some differences arise in the detail and meaning of involved operations.

Definition 3.1 (Finite substitution) Let V be an infinite set of variables and D ⊂ V a finite subset of variables. A finite substitution (f.s.) θ is a mapping θ : D → T (Σ, V). The set of all finite substitutions is denoted as F Subst. Some basic functions in F Subst → V are used to describe the finite substitutions.

Dom(θ) = D  Rng(θ) = ∪_{x∈Dom(θ)}Var(θ(x))  Var(θ) = Dom(θ) ∪ Rng(θ)
Ide(θ) = {x ∈ Dom(θ) | θ(x) = x}  DOM(θ) = Dom(θ)\Ide(θ)

2Narrowing is often identified with most general narrowing. However, it is shown in [2] that dropping the requirement for most general narrowing steps is crucial for optimal evaluation strategies.

A f.s. θ such that Ide(θ) = Ø is called a strict finite substitution (s.f.s.).

In [3] is called Dom(θ) what we refer as Var(θ). In [3] Dom(θ) is actually the domain of the substitution, but it is (implicitly) also the range and contains (necessarily, to be consistent with the definition) identity bindings for some variables. Therefore, implicit assumptions on the identity bindings in the f.s. are taken. We feel that this does not aid the formal treatment of the f.s. Our definition is based on the standard intuitive notion of (partial) function, where domain and range are not necessarily overlapping.

We apply a finite substitution θ (which is not defined for some variables) to any term t ∈ T (Σ, V) by means of the lifting ¯ : F Subst → Subst which is defined as θ̄(x) = θ(x) if x ∈ Dom(θ) and θ̄(x) = x otherwise. Then θ(t) = θ̄(t). This is necessary if we use finite substitutions to ‘collect’ the computed answers in the narrowing process. The following example shows that the composition of finite substitutions based on the composition of partial functions is not adequate to express the composition of partial solutions.

Example 3.2 Let ϕ = {x′ ↦ 0} and θ = {x ↦ f(x′, y′)} be f.s’s. Then, if we consider the application of f.s.’s to terms in the standard treatment of partially defined functions, we can not obtain the composite substitution, since f(x′, y′) is not in T (Σ, {x′}) which is the (extended to terms) domain of ϕ. Therefore, the binding for x will not be correctly established. With the previous definition of application we would obtain the binding x ↦ f(0, y′), as expected.

The (infix) restriction operation (↓: F Subst × ℘(V) → F Subst) combines the projection and extension operations in [3] as follows.

Definition 3.3 (Restriction of finite substitutions) Let ϕ, θ be f.s’s. and W be a finite set. θ ↓_{W} is a f.s such that Dom(θ ↓_{W}) = W, θ ↓_{W} (x) = θ(x) if x ∈ Dom(θ) ∩ W and θ↓_{W} (x) = x if x ∈ W \Dom(θ).

The Example 3.2 points out that, in spite of the fact that the f.s.’s are partially defined functions w.r.t. the set of variables V, it is desirable to have a mechanism to compose two f.s.’s. We define the composition operation by lifting f.s.’s to the space of substitutions.

Definition 3.4 (Composition of finite substitutions) Let ϕ, θ be f.s’s. Define ϕ ∘ θ = (ϕ̄ ∘ θ̄)↓_{Dom(ϕ)∪Dom(θ)}. This amounts to say that Dom(ϕ ∘ θ) = Dom(ϕ) ∪ Dom(θ).

3.2 Narrowing and finite substitutions

Managing the renaming in the narrowing process leads to new considerations concerning finite substitutions. By following [3], the renaming process is guided in part by the ‘current’ computed f.s. The requirement of joining domains in the composition of f.s.’s (see Definition 3.4) is needed if we use f.s’s to propagate the renaming-apart restrictions in the narrowing process [14]. We rename terms apart of the set of variables appearing in a given f.s. Therefore, we must distinguish even between the f.s. θ and θ ∘ ϵ or θ ∘ ϵ′ provided that ϵ and ϵ′ are different identity f.s. (i.e. Dom(ϵ) ≠ Dom(ϵ′) and not contained in θ), since rename a term l′ out of Var(θ) is not the same that renaming it out of Var(θ) ∪ Var(ϵ). Thus, the domains of f.s.’s must be carefully managed in the operations concerning them.

The following proposition expresses an important property of partial solutions in a narrowing derivation.

Proposition 3.5 Let t ⇝_{σ′′}^{+} t′′ ⇝_{σ′}^{∗} t′ be a narrowing derivation. Then σ↓_{Var(t)}= (σ′ ∘ σ′′)↓_{Var(t)}= (σ′↓_{Var(t′′)} ∘σ′′)↓_{Var(t)}.

4 Needed narrowing

4.1 Definitional trees

Needed narrowing relies on the concept of definitional trees of [1]. A definitional tree can be used as a representation of the rules defining a given function symbol. T is a partial definitional tree (pdt) iff one of the following cases holds:^{3}

T = rule(π → r) where π → r is a variant of a rule in R.

T = branch(π, o, T_{1}, …, T_{n}) where π is a pattern, o is the occurrence of a variable of π, c_{1}, …, c_{n} are constructors of the sort of π|_{o} for some n > 0, and for all i in {1, …, n}, T_{i} is a pdt with pattern π[c_{i}(x_{1}, …, x_{ar(c_{i})})]_{o} where x_{1}, …, x_{ar(c_{i})} are new variables. Moreover, T_{i} must be of finite depth.

A definitional tree of a k-ary function f is a pdt T with pattern f(\overline{x}), where \overline{x} is an ar(f)-tuple of distinct variables. A function f with definitional tree T is called inductively sequential if T contains all and only the rules defining f in the term rewriting system R which we call the ‘program’.

Example 4.1 Let us consider the following program:

   from(N) → [N|from(s(N))]
   first(0, L) → [ ]
   first(s(N), [E|L]) → [E|first(N, L)]

Then

rule(from(N) → [N|from(s(N))]) is a definitional tree for the function from, and

branch(first(X, Y), 1, rule(first(0, Y) → [ ]),

branch(first(s(N), Y), 2,
rule(first(s(N), [E|L]) → [E|first(N, L)])))

is a definitional tree for the function first.

3We ignore the exempt nodes which are in the original definition of [1].

A TRS is called inductively sequential if all function symbols are inductively sequential. An inductively sequential TRS can be viewed as a set of definitional trees, each defining a function symbol.

A definitional tree determines a narrowing strategy, namely the needed narrowing strategy. Roughly speaking, given a term t = f(\overline{t}), the needed narrowing strategy looks in the definitional tree of f for a rule node which applies to t directly or after reducing some of its subterms to head normal form.

More formally, if T is a node rule(l → r), then we apply the rule l → r to t. If T is a node branch(π, o, T_{1}, …, T_{n}), then we consider the subterm t|_{o}. If t|_{o} has a function symbol at the top, this subterm is reduced to a head normal form by applying recursively the strategy to t|_{o}. If t|_{o} has a constructor symbol at the top, we narrow t with a subtree T_{i} whose pattern unifies with t, or fail otherwise. If t|_{o} is a variable, we (nondeterministically) select a subtree T_{i}, unify t with the pattern of T_{i}, and narrow this instance of t with T_{i}.

4.2 Term Rewriting Systems and case expressions

It is possible to integrate the definitional trees into the term rewriting system by using case expressions. In this way, each function symbol has just one associated rule which completely defines the meaning of the function. Using case expressions as a represention of the equations which define a function is a common transformation technique in functional programming. The syntax of a case expression is as follows:

case X of c_{1}(\overline{x}) : X_{1}
…
c_{n}(\overline{y}) : X_{n}

where X is a variable, c_{1} … c_{n} are different constructors of the sort of X, \overline{x}, …, \overline{y} are tuples of new variables, and X_{1} …, X_{n} are terms, possibly containing case expressions. We interpret such a case expression with n patterns and n actions as a 2n + 1-ary function case(X, c_{1}(\overline{x}), X_{1}, …, c_{n}(\overline{y}), X_{n}).

In this way, we can put together the rules defining a function f in the original program into a single (case) rule in a transformed TRS. A more complete treatment of case expressions in the setting of the definitional trees can be found in [7].

Example 4.2 The TRS of the Example 4.1 is transformed into the following TRS:

    from(N) → [N|from(s(N))]
    first(X,Y) → case(X,0,[ ],s(N),case'(Y,[E|L],[E|first(N,L)]))

The semantics of the functions case and case' can be simply defined by the following rewrite rules:

    case(0   ,0,X,s(N),Y) → X
    case(s(N),0,X,s(N),Y) → Y
    case'([E|L],[E|L],X) → X

In general, a case expression with patterns π_{1}, …, π_{n} can be viewed as a function case_{π_{1},...,π_{n}} which is defined by the following set of rules:

case_{π_{1},...,π_{n}}(π_{1}, π_{1}, X, , …, ) → X
…
case_{π_{1},...,π_{n}}(π_{n}, , …, , π_{n}, X) → X

We often omit the index π_{1}, …, π_{n} and abbreviate a ‘pattern/action’ pair (π, X) as Ξ and a tuple of these pairs as \overline{Ξ}.

4.3 Narrowing with case expressions

Given a term rewriting system R = (Σ, R), let R′ = (Σ ∪ Σ_{c}, R′ ∪ R_{c}) be the transformed TRS where Σ_{c} is the set of case function symbols, R′ contains the case version of rules in R, and R_{c} is the set of additional case rules which must be considered.

Remark 4.3 The transformation of R into R′ have some consequences:

1. For each defined function f there is only one rule f(\overline{x}) → r ∈ R′. The righthand side r of the rule is a (normal) term or a case expression.

2. Given a term t = f(\overline{t}), it is always possible to apply a rule. No evaluation of arguments in t is required for this application.

3. All rules in R_{c} have the form case(c_{i}(\overline{x}), X_{1}, X′_{1}, …, c_{i}(\overline{x}), X′_{i}, …, X_{n}, X′_{n}) → X′_{i}, where \overline{x}, X_{1}, X′_{1}, …, X_{n}, X′_{n} are distinct variables.

4. Given a term t = case(s, π_{1}, X_{1}, …, π_{n}, X_{n}), only the first argument of this case term must be evaluated up to a head normal form in order to apply some case rule. Evaluations in the remaining arguments of a case expressions are not necessary.

In expressing the definitional trees by means of case expressions, we can evaluate the goals by using a simpler narrowing strategy, namely leftmost-outermost narrowing.^{4} Evaluating goals by needed narrowing w.r.t. R is equivalent to evaluating goals by leftmost-outermost narrowing w.r.t. R′ in the following sense.

Theorem 4.4 ([7]) Let t be a term with a function symbol at the top and T a defin-

itional tree for this function symbol. For each needed narrowing derivation t ⇝^{∗}^{N}_{σ} c

w.r.t. R, there exists a leftmost-outermost narrowing derivation t⇝^{∗}^{L}_{σ′}c w.r.t. R′ with σ =_{V ar(t)} σ′ and vice versa.

4A position p is leftmost-outermost in a set P of positions if there is no p′ ∈ P with p′ prefix of p, or p′ = q · i · q′ and p = q · j · q′′ and i < j. A narrowing step is leftmost-outermost if the selected subterm is the leftmost-outermost one among all possible narrowing positions.

5 Lazy denotational semantics

5.1 Preliminary definitions

In functional logic programming, given a goal t, we are interested in two classes of observable information: the evaluated expression(s) corresponding to this goal and the solution (or set of solutions) leading to this computed expression. Therefore, the answer for a goal is a function ψ : T erm → ℘(F Subst) which maps (possible evaluated) results to sets of substitutions.^{5}

Example 5.1 Consider the rules of Example 4.1 and the term first(X,from(0)). Then the answer ψ representing all possible evaluations of this term has the property ψ([]) = {{X ↦ 0}}, ψ([0]) = {{X ↦ s(0)}}, ψ([0,s(0)]) = {{X ↦ s(s(0))}}, and so on. Thus, ψ has a non-empty denotation containing a single substitution on lists of ascending natural numbers starting from 0.

In the following, we use some operations on answers:

• The bottom answer ψ_{⊥} is defined by ψ_{⊥}(t) = Ø for all term t.

This is the bottom element in the domain of answers.

• The additive composition ⋄ : Answer × Answer → Answer is defined by

(ψ ⋄ ψ′)(t) = ψ(t) ∪ ψ′(t) .

Since ⋄ is a commutative and associative operation, we extend it to a set of answers Ψ in the obvious way, and write ⋄ Ψ. In particular, if Ψ = Ø we define ⋄ Ψ = ψ_{⊥}.

• The non-trivial domain Δ : Answer → ℘(T (Σ, V)), defined by

Δ(ψ) = {t ∈ T (Σ, V) | ψ(t) ≠ Ø} ,

denotes the set of all terms which are computable by applying some substitution.

• We denote by t ↦ Θ a unique-valued answer ψ which has the property ψ(t) = Θ and ψ(t′) = Ø for all t′ ≠ t.

• The restriction (infix operator) ↓: Answer × ℘(V) → Answer is defined by ψ↓_{W} (t) = ψ(t)↓_{W}.

• The right substitution composition ◁ : F Subst × Answer → Answer is defined by (θ ◁ ψ)(t) = (ψ(t)) ∘ θ. Note that (ϕ ∘ θ) ◁ ψ = θ ◁ (ϕ ◁ ψ).

5Other representations of answers, like sets of pairs of terms and substitutions sets, are also possible. However, our representation leads to a concise denotational semantics.

5.2 Denotational semantics

For a complete presentation of the denotational semantics, we introduce the denotational domains and the signatures of the semantic functions.

• Domains:

P rogram = Rule^{∗} (R) Rule = Rule (ρ)
Den = T erm → F Subst → Answer (δ)  F Subst = FSubst (ϵ, σ, θ)
Goal = T erm (t) T erm = T (Σ, V) (t)
Answer = T erm → ℘(F Subst)  (ψ)

• Semantic functions:

P[[ ]] : P rogram → Den → Den R[[ ]] : Rule → Den → Den G[[ ]] : Goal → Den → F Subst → Answer

• Definition of semantics functions:

P[[Ø]] δ = δ_{⊥}

P[[{ρ} ∪ R]] δ = R[[ρ]] δ ♢ P[[R]] δ

R[[l → r]] δ t θ = if σ = fail ∨ θ(t) ∈ V then ψ_{⊥} else (G[[σ(r′)]] δ (σ∘θ))↓_{Var(θ)} where l′ → r′ = ren(l → r, Var(θ) ∪ Var(t)) and σ = unif(θ(t), l′)

G[[x]] δ θ = x ↦ {θ} G[[c(\overline{t})]] δ θ = c(\overline{t}) ↦ \{θ\} G[[f(\overline{t})]] δ θ = δ(f(\overline{t})) θ G[[case(s, \overline{Ξ})]] δ θ =

let ψ_{s} = G[[s]] δ θ in ⋄ \begin{matrix}s′ ∈ Δ(ψ_{s}) \\ θ′ ∈ ψ_{s}(s′) \end{matrix} (δ(case(s′, \overline{Ξ})) (θ′ ∘ θ))↓_{Var(θ)}

A program is a sequence of rewrite rules (of the special form discussed in Section 4.2). The meaning of a program is a function which maps denotations into denotations. A denotation δ ∈ Den maps each term instantiated by some substitution into an answer. It describes the ‘transformation power’ of a program. Hence, δ(t) θ expresses the (direct) evaluation (by means of the program) of t in the context of the substitution θ.

The meaning of an empty program is the bottom denotation δ_{⊥}: P[[Ø]] δ = δ_{⊥} where δ_{⊥} is defined as follows: δ_{⊥}(t) θ = ψ_{⊥} for all t and θ. Thus, an empty program cannot compute any value for all possible input terms and substitutions.

The meaning of a non-empty program is the combination of all answers obtained by the different rules of the program. For this purpose, we have to define the composition of denotations which can be done by extending the additive composition ⋄ of answers to the corresponding operation on denotations. ♢ : Den × Den → Den is defined as (δ♢δ′)(t) θ = (δ(t) θ) ⋄ (δ′(t) θ). Now, the meaning of a (non-empty) program is obtained by adding the denotations assigned to the rules contained in the program: P[[{ρ} ∪ R]] δ = R[[ρ]] δ ♢ P[[R]] δ.

A rewrite rule is an evaluation component of the program. If a rule l → r applies (with unifier σ) to an (instantiated) term θ(t), then, to evaluate θ(t), just evaluate σ(r):^{6}

R[[l → r]] δ t θ = if σ = fail ∨ θ(t) ∈ V then ψ_{⊥} else (G[[σ(r′)]] δ (σ ∘ θ))↓_{Var(θ)} where l′ → r′ = ren(l → r, Var(θ) ∪ Var(t)) and σ = unif(θ(t), l′)

The meaning of a goal or term is given as a function that, for some denotation δ of the program and accumulated partial solution θ, yields the answer function for this goal. However, in lazy languages, we are not interested in a total evaluation of the arguments of a given function symbol. Since the arguments in the left-hand sides of rules are flat constructor terms or variables (see Section 4.3), we evaluate the expressions up to head normal form in order to apply some rule. Therefore, when a goal is a head normal form, we stop the (lazy) evaluation: G[[t]] δ θ = t ↦ {θ} if t is a variable or a constructor-rooted term.

If the goal is an operation-rooted term f(\overline{t}), then a rule directly applies and we use the denotation of the program to obtain the meaning of the goal: G[[f(\overline{t})]] δ θ = δ(f(\overline{t})) θ.

If the goal is a case expression case(s, \overline{Ξ}), we may need some evaluation of the selection argument s. In this case, we evaluate s up to a hnf, thus obtaining the answer ψ_{s} = G[[s]] δ θ. Next, we apply the denotation of the program to the case expression with s replaced by all its possible evaluations s′ ∈ Δ(ψ_{s}):

G[[case(s, \overline{Ξ})]] δ θ = ⋄ \begin{matrix}s′ ∈ Δ(ψ_{s}) \\ θ′ ∈ ψ_{s}(s′) \end{matrix} (δ(case(s′, \overline{Ξ})) (θ′ ∘ θ))↓_{var(θ)}

As usual, the denotation δ_{0} of a program is defined as the least fixpoint^{7} of the defining equations, i.e., δ_{0} = fix P[[R ∪ E]] where R contains the rewrite rules for operations and case functions and E is the set of equality rules of the given signature. Now, we can directly obtain the meaning of a given equation e by means of the function G[[ ]] as follows: ψ = G[[e]] δ_{0} ϵ, where ϵ is the identity substitution with Dom(ϵ) = Var(e). We can also obtain the result ψ of the complete evaluation of a given term t as follows: let ψ′ = G[[X ≈ t]] δ_{0} ϵ, where X is a fresh variable and ϵ is the identity substitution with Dom(ϵ) = Var(t) ∪ {X}. If ψ′ = ψ_{⊥}, then ψ = ψ_{⊥}. Otherwise, ψ′ must be a unique answer of the form true ↦ {θ_{1}{X ↦ t_{1}}, …, θ_{n}{X ↦ t_{n}}, …}, since equalities can only be derived to the constant true. Then ψ = t_{1} ↦ {θ_{1}} ⋄ ⋯ ⋄ t_{n} ↦ {θ_{n}} ⋄ · · ·.

5.3 Properties of the semantic functions

The following proposition allows us to express a solution θ obtained from the evaluation of either an operation rooted term or a case expression with evaluated selection argument, as a combination of the first mgu σ used to narrow the term and the answer which corresponds to the evaluation of the rhs instantiated by σ.

6ren(s, W) denotes the renaming of s so that the new variant of s has no variables in common with W. By introducing a total ordering on V, it is easy to define ren as a function.

7We consider the standard ordering in the domains, e.g., Ø is the least element in sets and functions are ordered pointwise on their domains.

Proposition 5.2 Let t be either an operation rooted term t = f(\overline{t}) or t = case(s, \overline{Ξ}) where s is a hnf such that there is a variant α_{σ} : l′ → r′ of a rule which can be applied to t with mgu σ. Let Θ be the set of all of such unifiers. Let ψ = G[[t]] δ ϵ with Var(ϵ) = Var(t) and ψ′_{σ} = G[[σ(r′)]] δ ϵ′ with Var(ϵ′) = Var(σ(r′)). Then ψ = ⋄_{σ∈Θ}(σ ◁ ψ′_{σ})↓_{Var(t)}.

Roughly speaking, the following proposition amounts to say that we can express a solution θ of a case expression case(s, \overline{Ξ}) with a non evaluated selection argument s as a combination of the solutions ϕ of the evaluation s′ of s and the answers corresponding to the partially evaluated goal case(s′, \overline{Ξ}).

Proposition 5.3 Let t = case(s, \overline{Ξ}), where s is not a hnf. Let ϵ_{s} such that Var(ϵ_{s}) = Var(s). Then

ψ = G[[case(s, \overline{Ξ})]] δ ϵ = ⋄ \begin{matrix}s′ ∈ Δ(ψ′_{s}) \\ ϕ ∈ ψ′_{s}(s′) \end{matrix} (ϕ ◁ ψ_{ϕ})↓_{Var(ϵ)}

where ψ′_{s} = G[[s]] δ ϵ_{s}, ψ_{ϕ} = G[[ϕ(case(s′, \overline{Ξ}))]] δ ϵ′, Var(ϵ′) = Var(ϕ(case(s′, \overline{Ξ}))).

These propositions express the compositional behavior of the semantic evaluations. Note the similarities with the result shown in Proposition 3.5 for the operational setting.

The main result of the paper is given now. It expresses that the semantic definition reflects the operational behavior of the needed narrowing strategy.

Theorem 5.4 (Adequacy of the semantics w.r.t. needed narrowing) Let R = (Σ, R) be an inductively sequential TRS and R′ = (Σ ∪ Σ_{c}, R′ ∪ R_{c}) be the case version of R. Let t ∈ T (Σ, V) and t′ ∈ HNF_{Σ}. Let ϵ be the identity substitution with Dom(ϵ) = Var(t). If ψ = G[[t]] δ ϵ, then

t ⇝^{∗}^{N}_{σ} t′ ⇔ σ↓_{Var(t)}∈ ψ(t′).

6 Conclusions and further work

We have presented a denotational semantics for lazy functional logic languages based on the needed narrowing strategy. Our semantics is the first denotational definition of needed narrowing.

Some abstract interpretation frameworks reinforce the use of denotational semantics as the formal basis for data-flow program analysis by giving an intermediate meta-language able to couch the program as a set of semantic equations [10, 12]. In this way, a given analysis can be defined as a particular interpretation of the language. Therefore, this is a reusable framework for program analysis. We plan to apply this kind of strategy for the analysis of functional logic programs based on needed narrowing strategy.

References

[1] S. Antoy. Definitional Trees. In H. Kirchner and G. Levi, editors, Proc. of Int’l Conf. on Algebraic and Logic Programming, ALP’92, LNCS 632:143-157, Springer-Verlag, Berlin, 1992.

[2] S. Antoy R. Echahed, and M. Hanus. A needed narrowing strategy. In Proc of the 21st ACM Symposium on Principles of Programming Languages, POPL’94, pages 268-279, ACM Press, 1994.

[3] S.K. Debray and P. Mishra. Denotational and operational semantics for Prolog. Journal of Logic Programming 5:61-91, 1988.

[4] N. Dershowitz and J.P. Jouannaud. Rewrite Systems. In J. van Leeuwen, editor, Handbook of Theoretical Computer Science, volume B: Formal Models and Semantics, pages 243-320. Elsevier, Amsterdam and The MIT Press, Cambridge, MA, 1990.

[5] M. Hanus. The integration of functions into logic programming: From theory to practice. Journal of Logic Programming 19&20:583-628, 1994.

[6] M. Hanus and S. Lucas. Definition and analysis of a denotational semantics for needed narrowing. Technical Report, DSIC II/2/96, Universidad Politécnica de Valencia, 1996. Also available by URL http://www.dsic.upv.es/users/elp/papers.html.

[7] M. Hanus and C. Prehofer. Higher-Order Narrowing with Definitional Trees. Proc. of RTA’96. To appear in Springer LNCS.

[8] G. Huet and J.J. Lévy. Computations in orthogonal term rewriting systems. In J.L. Lassez and G. Plotkin, editors, Computational logic: essays in honour of Alan Robinson, MIT Press, Cambridge, MA, 1991.

[9] N.D. Jones and A. Mycroft. Stepwise Development of Operational and Denotational Semantics for PROLOG. In Proc. of the 1984 International Symposium on Logic Programming, pages 289-298, IEEE Computer Soc., Atlantic City, N.J., 1988.

[10] K. Marriott. Frameworks for abstract interpretation. Acta Informatica 30:103-129, 1993.

[11] J.J. Moreno-Navarro and M. Rodríguez-Artalejo. Logic programming with functions and predicates: the language BABEL. Journal of Logic Programming, 12:191-223, 1992.

[12] F. Nielson. Towards a denotational theory of abstract interpretation. In S. Abramsky and C. Hankin, editors, Abstract Interpretation of Declarative Languages, Ellis Horwood Ltd., John Wiley and sons, pages 218-245, 1987.

[13] U.S. Reddy. Functional Logic Languages Part I (Preliminary Report). In J.H. Fasel and R.M. Keller, editors, Proc. of a Workshop on Graph Reduction, LNCS 279:401-425, Springer-Verlag, Berlin, 1987.

[14] J.C. Shepherdson. Mistakes in Logic Programming: the Role of Standarising Apart, manuscript, University of Bristol, 1991.

[15] J.E. Stoy. Denotational semantics: the Scott-Strachey approach to programming language theory. The MIT Press, Cambridge MA, 1977.

[16] R.D. Tennent. Denotational Semantics. In S. Abramsky, D.M. Gabbay and T.S.E. Maibaum, editors, Handbook of Logic in Computer Science, Volume 3, pages 169-322, Oxford University Press, Oxford, 1992.