section‹Logical relations for definability in PCF› (*<*)
theory PCF imports
Basis
Logical_Relations begin
(*>*) text‹
label{sec:directsem}
this machinery we can demonstrate some classical results about 🍋‹"Plotkin77"›. We diverge from the traditional treatment by
PCF as an untyped language and including both call-by-name
CBN) and call-by-value (CBV) abstractions following 🍋‹"DBLP:conf/icalp/Reynolds74"›. We also adopt some of the
of 🍋‹‹Chapter~11› in "Winskel:1993"›, in particular by
the fixed point operator a binding construct.
model the syntax of PCF as a HOL datatype, where variables have
drawn from the naturals:
›
type_synonym var = nat
datatype expr =
Var var
| App expr expr
| AbsN var expr (* non-strict fns *)
| AbsV var expr (* strict fns *)
| Diverge (‹Ω›)
| Fix var expr
| tt
| ff
| Cond expr expr expr
| Num nat
| Succ expr
| Pred expr
| IsZero expr
subsection‹Direct denotational semantics›
text‹
label{sec:densem}
give this language a direct denotational semantics by interpreting
into a domain of values.
abbreviation eval' :: "expr ==> ValD Env ==> ValD" (‹[_]_› [0,1000] 60) where "eval' M ρ ≡ evalD M⋅ρ"
subsection‹The Y Combinator›
text‹
can shown the Y combinator is the least fixed point operator using
the minimal invariant. In other words, @{term "fix"} is
in untyped PCF minus the @{term "Fix"} construct.
is Example~3.6 from 🍋‹"PittsAM:relpod"›. He attributes the
to Plotkin.
two functions are ‹Δ ≡ λf x. f (x x)› and ‹Y ≡
λf. (Δ f) (Δ f)›.
the numbers here are names, not de Bruijn indices.
classical result about PCF is that while the denotational semantics \emph{adequate}, as we show in \S\ref{sec:opsem}, it is not
emph{fully abstract}, i.e. it contains undefinable values (junk).
way of showing this is to reason operationally; see, for instance, 🍋‹‹\S4› in "Plotkin77"› and 🍋‹‹\S6.1› in "Gunter:1992"›.
is to use \emph{logical relations}, following 🍋‹"Plotkin:1973"›, and also 🍋‹"Mitchell:1996" and "Sieber:1992" and "DBLP:conf/mfps/Stoughton93"›.
this purpose we define a logical relation to be a set of vectors
@{typ "ValD"} that is closed under continuous functions of type
{typ "ValD → ValD"}. This is complicated by the @{term "ValF"} tag
having strict function abstraction.
›
definition
logical_relation :: "('i::type ==> ValD) set ==> bool" where "logical_relation R ≡ (∀fs ∈ R. ∀xs ∈ R. (λj. appF⋅(fs j)⋅(xs j)) ∈ R) ∧ (∀fs ∈ R. ∀xs ∈ R. (λj. strictify⋅(appF⋅(fs j))⋅(xs j)) ∈ R) ∧ (∀fs. (∀xs ∈ R. (λj. (fs j)⋅(xs j)) ∈ R) ⟶ (λj. ValF⋅(fs j)) ∈ R) ∧ (∀fs. (∀xs ∈ R. (λj. strictify⋅(fs j)⋅(xs j)) ∈ R) ⟶ (λj. ValF⋅(strictify⋅(fs j))) ∈ R) ∧ (∀xs ∈ R. (λj. fixD⋅(xs j)) ∈ R) ∧ (∀cs ∈ R. ∀ts ∈ R. ∀es ∈ R. (λj. cond⋅(cs j)⋅(ts j)⋅(es j)) ∈ R) ∧ (∀xs ∈ R. (λj. succ⋅(xs j)) ∈ R) ∧ (∀xs ∈ R. (λj. pred⋅(xs j)) ∈ R) ∧ (∀xs ∈ R. (λj. isZero⋅(xs j)) ∈ R)" (*<*)
abbreviation "PCF_lr R ≡ adm (λx. x ∈ R) ∧ logical_relation R ∧ PCF_consts_rel R"
text‹
fundamental property of logical relations states that all PCF
satisfy all PCF logical relations. This result is
due to 🍋‹"Plotkin:1973"›. The proof is by a
induction on the expression @{term "M"}.
›
lemma lr_fundamental: assumes lr: "PCF_lr R" assumes ρ: "∀v. (λi. ρ i⋅v) ∈ R" shows"(λi. [M](ρ i)) ∈ R" (*<*) using ρ proof(induct M arbitrary: ρ) case (Var v ρ) thenshow ?caseby simp next case (App e1 e2 ρ) with lr lr_l2r(1)[where fs="λj. [e1](ρ j)"and xs="λj. [e2](ρ j)"] show ?caseby simp next case (AbsN v e) with lr show ?case apply clarsimp apply (erule lr_r2l[where fs="λi. Λ x. [e](env_ext⋅v⋅x⋅(ρ i))"and R=R, simplified]) apply clarsimp apply (cut_tac ρ="λj. env_ext⋅v⋅(xs j)⋅(ρ j)"in AbsN.hyps) apply (simp add: env_ext_def) using AbsN(2) apply clarsimp apply (case_tac "v=va") apply (simp add: eta_cfun) apply simp apply simp done next case (AbsV v e) with lr show ?case apply clarsimp apply (rule lr_r2l_strict[where fs="λi. Λ x. [e](env_ext⋅v⋅x⋅(ρ i))", simplified]) apply simp apply clarsimp apply (cut_tac fs="λi. ValF⋅(Λ x. [e](env_ext⋅v⋅x⋅(ρ i)))"and xs=xs and R=R in lr_l2r(2)) apply simp_all apply (erule lr_r2l[where fs="λi. Λ x. [e](env_ext⋅v⋅x⋅(ρ i))"and R=R, simplified]) apply clarsimp apply (cut_tac ρ="λj. env_ext⋅v⋅(xsa j)⋅(ρ j)"in AbsV.hyps) apply (simp add: env_ext_def) using AbsV(2) apply clarsimp apply (case_tac "v=va") apply (simp add: eta_cfun) apply simp apply simp done next case (Fix v e ρ) show ?case apply simp apply (subst fix_argument_promote_fun) apply (rule fix_ind[where F="Λ f. (λx. (Λ xa. [e](env_ext⋅v⋅xa⋅(ρ x)))⋅(f x))"]) apply (simp add: lr) apply (simp add: lr inst_fun_pcpo[symmetric]) apply simp apply (cut_tac ρ="λi. env_ext⋅v⋅(x i)⋅(ρ i)"inFix.hyps) unfolding env_ext_def usingFix(2) apply clarsimp apply (case_tac "v=va") apply (simp add: eta_cfun) apply simp apply (simp add: cont_fun) done next case (Cond i t e ρ) with lr lr_l2r(4)[where cs="λj. [i](ρ j)"and ts="λj. [t](ρ j)"and es="λj. [e](ρ j)"] show ?caseby simp next case (Succ e ρ) with lr lr_l2r(5)[where xs="λj. [e](ρ j)"] show ?caseby simp next case (Pred e ρ) with lr lr_l2r(6)[where xs="λj. [e](ρ j)"] show ?caseby simp next case (IsZero e ρ) with lr lr_l2r(7)[where xs="λj. [e](ρ j)"] show ?caseby simp next case Diverge with lr show ?case apply simp apply (simp add: inst_fun_pcpo[symmetric]) done qed (insert lr, simp_all) (*>*)
text‹
can use this result to show that there is no PCF term that maps the
@{term "args ∈ R"} to @{term "result ∉ R"} for some logical
@{term "R"}. If we further show that there is a function
{term "f"} in @{term "ValD"} such that @{term "f args = result"} then
can conclude that @{term "f"} is not definable.
›
abbreviation
appFLv :: "ValD ==> ('i::type ==> ValD) list ==> ('i ==> ValD)" where "appFLv f args ≡ (λi. foldl (λf x. appF⋅f⋅(x i)) f args)"
lemma lr_appFLv: assumes lr: "logical_relation R" assumes f: "(λi::'i::type. f) ∈ R" assumes args: "set args ⊆ R" shows"appFLv f args ∈ R" (*<*) using args proof(induct args rule: rev_induct) case Nil with f show ?caseby simp next case (snoc x xs) thenshow ?case using lr_l2r(1)[OF _ _ lr, where fs="λi. (foldl (λf x. appF⋅f⋅(x i)) f xs)"and xs=x] by simp qed
(*>*) text‹›
corollary not_definable: fixes R :: "('i::type ==> ValD) set" fixes args :: "('i ==> ValD) list" fixes result :: "'i ==> ValD" assumes lr: "PCF_lr R" assumes args: "set args ⊆ R" assumes result: "result ∉ R" shows"¬(∃(f::ValD). definable f ∧ appFLv f args = result)" (*<*) proof assume"∃f. definable f ∧ appFLv f args = result" thenobtain f where df: "definable f" and f: "appFLv f args = result"by blast from df obtain M where Mf: "[M]env_empty = f" unfolding definable_def by blast with lr lr_fundamental[OF lr, where M=M and ρ="λi. env_empty"]
f args lr_appFLv[where f=f and R=R and args=args] have"result ∈ R"unfolding env_empty_def by (simp add: inst_fun_pcpo[symmetric]) with result show False .. qed (*>*)
subsection‹Parallel OR is not definable›
text‹
label{sec:por}
show that parallel-or is not ‹λ›-definable following 🍋‹"Sieber:1992"› and 🍋‹"DBLP:conf/mfps/Stoughton93"›.
-or is similar to the familiar short-circuting or except that
the first argument is @{term "⊥"} and the second one is
{term "ValTT"}, we get @{term "ValTT"} (and not @{term ⊥"}). It is continuous and then have included in the @{typ
ValD"} domain.
›
definition por :: "ValD ==> ValD ==> ValD" (‹_ por _› [31,30] 30) where "x por y ≡ if x = ValTT then ValTT else if y = ValTT then ValTT else if (x = ValFF ∧ y = ValFF) then ValFF else ⊥"
text‹The defining properties of parallel-or.›
lemma POR_simps [simp]: "(ValTT por y) = ValTT" "(x por ValTT) = ValTT" "(ValFF por ValFF) = ValFF" "(ValFF por ⊥) = ⊥" "(ValFF por ValN⋅n) = ⊥" "(ValFF por ValF⋅f) = ⊥" "(⊥ por ValFF) = ⊥" "(ValN⋅n por ValFF) = ⊥" "(ValF⋅f por ValFF) = ⊥" "(⊥ por ⊥) = ⊥" "(⊥ por ValN⋅n) = ⊥" "(⊥ por ValF⋅f) = ⊥" "(ValN⋅n por ⊥) = ⊥" "(ValF⋅f por ⊥) = ⊥" "(ValN⋅m por ValN⋅n) = ⊥" "(ValN⋅n por ValF⋅f) = ⊥" "(ValF⋅f por ValN⋅n) = ⊥" "(ValF⋅f por ValF⋅g) = ⊥" unfolding por_def by simp_all (*<*)
text‹
show that parallel-or is a continuous function.
›
lemma POR_sym: "(x por y) = (y por x)" unfolding por_def by simp
lemma ValTT_below_iff [simp]: "ValTT ⊑ x ⟷ x = ValTT" by (cases x) simp_all
lemma ValFF_below_iff [simp]: "ValFF ⊑ x ⟷ x = ValFF" by (cases x) simp_all
lemma monofun_por: "monofun (λx. x por y)" unfolding por_def by (rule monofunI) auto
lemma mic2mic: "max_in_chain i Y ==> max_in_chain i (λi. f (Y i))" unfolding max_in_chain_def by simp
lemma cont_por[cont2cont, simp]: assumes f: "cont (λx. f x)"and g: "cont (λx. g x)" shows"cont (λx. f x por g x)" proof - have A: "∧f y. cont f ==> cont (λx. f x por y)" by (rule cont_apply) (simp_all add: cont_por1) from A[OF f] A[OF g] show ?thesis by (auto simp: cont_por1 POR_sym intro: cont_apply[OF f]) qed
(*>*) text‹
need three-element vectors.
›
datatype Three = One | Two | Three
text‹
standard logical relation @{term "R"} that demonstrates POR is not
is:
[
(x, y, z) \in R\ \mbox{iff}\ x = y = z \lor (x = \bot\lor y = \bot)
]
POR satisfies this relation can be seen from its truth table (see
).
we restrict the ‹x = y = z› clause to non-function
. Adding functions breaks the ``logical relations'' property.
›
definition
POR_base_lf_rep :: "(Three ==> ValD) lf_rep" where "POR_base_lf_rep ≡ λ(mR, pR). { (λi. ValTT) } ∪ { (λi. ValFF) } ― ‹‹x = y = z› for bools› ∪ (∪n. { (λi. ValN⋅n) }) ― ‹‹x = y = z› for numerals› ∪ { f . f One = ⊥ } ― ‹‹x = ⊥›› ∪ { f . f Two = ⊥ } ― ‹‹y = ⊥››"
text‹
We close this relation with respect to continuous functions. This functor yields an admissible relation for all @{term " "} and is
.
lemma admS_POR_lf [intro, simp]: "POR_lf_rep r ∈ admS" proof show"⊥∈ POR_lf_rep r" unfolding POR_lf_rep_def POR_base_lf_rep_def by simp next show"adm (λx. x ∈ POR_lf_rep r)" unfolding POR_lf_rep_def using adm_POR_base_lf_rep[of r] adm_fn[of r] by simp qed
theorem POR_sat: "appFLv (ValF⋅(Λ x. ValF⋅(Λ y. x por y))) [POR_arg1_rel, POR_arg2_rel] = POR_result_rel" unfolding POR_arg1_rel_def POR_arg2_rel_def POR_result_rel_def by (simp add: fun_eq_iff split: Three.splits)
text‹
.. but is not PCF-definable:
›
theorem POR_is_not_definable: shows"¬(∃f. definable f ∧ appFLv f [POR_arg1_rel, POR_arg2_rel] = POR_result_rel)" apply (rule not_definable[where R="unlr POR.delta"]) using lr_POR_arg1_rel lr_POR_arg2_rel lr_POR_result_rel PCF_lr_POR_delta apply simp_all done
subsection‹Plotkin's existential quantifier›
text‹
can also show that the existential quantifier of 🍋‹‹\S5› in "Plotkin77"› is not PCF-definable using logical relations.
definition is quite loose; if the argument function @{term "f"}
any value to @{term "ValTT"} then @{term "plotkin_exists"} yields
{term "ValTT"}. It may be more plausible to test @{term "f"} on
only.
›
definition plotkin_exists :: "ValD ==> ValD"where "plotkin_exists f ≡ if (appF⋅f⋅⊥ = ValFF) then ValFF else if (∃n. appF⋅f⋅n = ValTT) then ValTT else ⊥" (*<*)
lemma plotkin_exists_tt [simp]: "appF⋅f⋅(ValN⋅n) = ValTT ==> plotkin_exists f = ValTT" unfolding plotkin_exists_def using monofun_cfun_arg[where f="appF⋅f"and x="⊥"] by auto
lemma monofun_pe: "monofun plotkin_exists" proof(rule monofunI) fix f g assume fg: "(f::ValD) ⊑ g" let ?goal = "plotkin_exists f ⊑ plotkin_exists g"
{ assume fbot: "appF⋅f⋅⊥ = ValFF" with fg have"appF⋅g⋅⊥ = ValFF" using monofun_cfun[where f="appF⋅f"and g="appF⋅g"and x="⊥"] by (simp add: monofun_cfun_arg) with fbot have ?goal by (simp add: plotkin_exists_def)
} moreover
{ assume efn: "∃n. appF⋅f⋅n = ValTT" thenobtain n where fn: "appF⋅f⋅n = ValTT"by blast thenhave fbot: "appF⋅f⋅⊥≠ ValFF" using monofun_cfun_arg[where f="appF⋅f"and x="⊥"and y="n"] by fastforce from fg have"appF⋅f⋅n ⊑ appF⋅g⋅n" using monofun_cfun_arg[OF fg, where f=appF] by (simp only: cfun_below_iff) with fn have gn: "appF⋅g⋅n = ValTT" using ValD.nchotomy[where y="appF⋅g⋅⊥"] by simp thenhave gbot: "appF⋅g⋅⊥≠ ValFF" using monofun_cfun_arg[where f="appF⋅g"and x="⊥"and y="n"] by fastforce from fn gn fbot gbot have ?goal apply (unfold plotkin_exists_def) by fastforce
} moreover
{ assume fbot: "appF⋅f⋅⊥≠ ValFF"and efn: "¬(∃n. appF⋅f⋅n = ValTT)" thenhave ?goal by (simp add: plotkin_exists_def)
} ultimatelyshow ?goal unfolding plotkin_exists_def by blast qed
(*>*) text‹
can show this function is continuous.
›
lemma cont_pe [cont2cont, simp]: "cont plotkin_exists" (*<*) proof (rule contI2[OF monofun_pe]) fix Y assume Y: "chain (Y :: nat ==> ValD)" let ?goal = "plotkin_exists (⊔ i. Y i) ⊑ (⊔ i. plotkin_exists (Y i))" have peY: "chain (λi. plotkin_exists (Y i))" by (rule chainI, simp add: monofunE[OF monofun_pe] chainE[OF Y])
{ assume"∃i. appF⋅(Y i)⋅⊥ = ValFF" thenobtain i where Yi: "appF⋅(Y i)⋅⊥ = ValFF"by blast have"Y i ⊑ (⊔ i. Y i)" using is_ub_thelub[OF Y, where x=i] by simp thenhave"appF⋅(Y i)⋅⊥⊑ appF⋅(⊔ i. Y i)⋅⊥"by (fastforce intro: monofun_cfun) with Yi have"ValFF ⊑ appF⋅(⊔ i. Y i)⋅⊥"by simp thenhave"appF⋅(⊔ i. Y i)⋅⊥ = ValFF"using ValD.nchotomy[where y="appF⋅(⊔ i. Y i)⋅⊥"] by simp moreover from Yi have"plotkin_exists (Y i) = ValFF"by (simp add: plotkin_exists_def) thenhave"ValFF ⊑ (⊔ i. plotkin_exists (Y i))"using is_ub_thelub[OF peY, where x=i] bysimp thenhave"ValFF = (⊔ i. plotkin_exists (Y i))"using ValD.nchotomy[where y="⊔ i. plotkin_exists (Y i)"] by simp ultimatelyhave ?goal by (simp add: plotkin_exists_def)
} moreover
{ assume"∃i j. appF⋅(Y i)⋅j = ValTT" thenobtain i j where Yij: "appF⋅(Y i)⋅j = ValTT"by blast from Yij have Yib: "appF⋅(Y i)⋅⊥≠ ValFF" using monofun_cfun_arg[where f="appF⋅(Y i)"and x="⊥"and y=j] by clarsimp moreover from Yij have"appF⋅(Y i)⋅j ⊑ appF⋅(⊔ i. Y i)⋅j" using is_ub_thelub[OF Y, where x=i] by (fastforce intro: monofun_cfun) with Yij have Yjlub: "appF⋅(⊔ i. Y i)⋅j = ValTT" using ValD.nchotomy[where y="appF⋅(⊔ i. Y i)⋅j"] by simp moreover from Yjlub have"appF⋅(⊔ i. Y i)⋅⊥≠ ValFF" using monofun_cfun_arg[where f="appF⋅(⊔ i. Y i)"and x="⊥"and y=j] by auto moreover from Yib Yij have"plotkin_exists (Y i) = ValTT"by (auto simp add: plotkin_exists_def) thenhave"ValTT ⊑ (⊔ i. plotkin_exists (Y i))"using is_ub_thelub[OF peY, where x=i] bysimp thenhave"ValTT = (⊔ i. plotkin_exists (Y i))"using ValD.nchotomy[where y="⊔ i. plotkin_exists (Y i)"] by simp ultimatelyhave ?goal by (simp add: plotkin_exists_def)
} moreover
{ assume nFF: "¬(∃i. appF⋅(Y i)⋅⊥ = ValFF)"and nTT: "¬(∃i j. appF⋅(Y i)⋅j = ValTT)" with Y have ?goal unfolding plotkin_exists_def using compact_below_lub_iff[OF ValD.compacts(2)]
compact_below_lub_iff[OF ValD.compacts(3)] by (simp add: contlub_cfun_arg contlub_cfun_fun)
} ultimatelyshow ?goal by blast qed
lemma cont_pe2[cont2cont, simp]: "cont f ==> cont (λx. plotkin_exists (f x))" by (rule cont_apply) simp_all
(*>*) text‹
we construct argument and result test vectors such that @{term
plotkin_exists"} satisfies these tests but no PCF-definable term
.
›
definition PE_arg_rel where "PE_arg_rel ≡ λi. ValF⋅(case i of 0 ==> (Λ _. ValFF) | Suc n ==> (Λ (ValN⋅x). if x = Suc n then ValTT else ⊥))"
definition PE_result_rel where "PE_result_rel ≡ λi. case i of 0 ==> ValFF | Suc n ==> ValTT"
text‹
that unlike the POR case the argument relation does not
PE: we don't treat functions that return @{term "ValTT"}s
@{term "ValFF"}s.
for POR, the difference between the two vectors is that the
can diverge but not the result.
›
definition PE_base_lf_rep :: "(nat ==> ValD) lf_rep"where "PE_base_lf_rep ≡ λ(mR, pR). { ⊥ } ∪ { (λi. ValTT) } ∪ { (λi. ValFF) } ― ‹‹x = y = z› for bools› ∪ (∪n. { (λi. ValN⋅n) }) ― ‹‹x = y = z› for numerals› ∪ { f . f 1 = ⊥∨ f 2 = ⊥ } ― ‹Vectors that diverge on one or two.›" (*<*)
lemma adm_PE_base_lf_rep: "adm (λx. x ∈ PE_base_lf_rep r)" unfolding PE_base_lf_rep_def using adm_below_monic_exists[OF _ below_monic_fun_K[where f=ValN], where P="λ_. True"] by (auto intro!: adm_disj simp: cont_fun)
lemma mono_PE_base_lf_rep: "mono PE_base_lf_rep" unfolding PE_base_lf_rep_def by (blast intro!: monoI)
(*>*) text‹
Again we close this under the function space, and show that it is admissible, monotonic and respects the minimal invariant.
\<close>
definition PE_lf_rep :: " nat ==> ValD) lf_rep" where
"PE_lf_rep R ≡ PE_base_lf_rep R ∪ fn_lf_rep R"
"PE_lf ≡ λr. mklr (PE_lf_rep r)"
(*<*)
lemma admS_PE_lf [intro, simp]: "PE_lf_rep r ∈ admS" proof show"⊥∈ PE_lf_rep r" unfolding PE_lf_rep_def PE_base_lf_rep_def by simp next show"adm (λx. x ∈ PE_lf_rep r)" unfolding PE_lf_rep_def using adm_PE_base_lf_rep[of r] adm_fn[of r] by simp qed
theorem PE_is_not_definable: "¬(∃f. definable f ∧ appFLv f [PE_arg_rel] = PE_result_rel)" (*<*) apply (rule not_definable[where R="unlr PE.delta"]) using lr_PE_arg_rel lr_PE_result_rel PCF_lr_PE_delta apply simp_all done (*>*)
subsection‹Concluding remarks›
text‹
techniques could be used to show that Haskell's ‹seq›
is not PCF-definable. (It is definable for each base
`type'' separately, and requires some care on function values.) If we
an (unlifted) product type then it should be provable that
evaluation is required to support ‹seq› on these
(given ‹seq› on all other objects). (See 🍋‹‹\S5.4› in "DBLP:conf/hopl/HudakHJW07"› and sundry posts to the
by Lennart Augustsson.) This may be difficult to do plausibly
adding a type system.
›
(*<*)
end (*>*)
Messung V0.5 in Prozent
¤ Dauer der Verarbeitung: 0.11 Sekunden
(vorverarbeitet am 2026-06-10)
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.