import Mathlib import Langlib.Grammars.Unrestricted.Toolbox import Langlib.Classes.RecursivelyEnumerable.Basics.Lifting import Langlib.Classes.RecursivelyEnumerable.Definition import Langlib.Utilities.LanguageOperations import Langlib.Utilities.Homomorphism import Langlib.Utilities.ClosurePredicates /-! # RE Closure Under String Homomorphism This file proves that the class of recursively enumerable languages is closed under ε-free string homomorphism. Given an unrestricted grammar `g` over terminals `α` generating `L`, and an ε-free string homomorphism `h : α → List β`, we construct an unrestricted grammar `hom_grammar g h` over terminals `β` whose language is `L.homomorphicImage h`. ## Main declarations - `hom_grammar` - `RE_closed_under_epsfree_homomorphism` ## Note on erasing homomorphisms The two-phase grammar construction `hom_grammar` is correct only for ε-free homomorphisms (where `h a ≠ []` for all `a`). For erasing homomorphisms (where some `h a = []`), the Phase 2 expansion can remove nonterminal placeholders, which may create new adjacencies that enable Phase 1 rules that have no corresponding rule application in the original grammar. A correct construction for the general case would require a more sophisticated mechanism (e.g., a scanning phase to ensure Phase 1 is complete before Phase 2 begins). -/ open List variable {α β : Type} -- ============================================================================ -- Grammar construction -- ============================================================================ section construction /-- Map a symbol by replacing terminals with nonterminal placeholders. -/ def homLiftSym {N : Type} : symbol α N → symbol β (N ⊕ α) | symbol.terminal a => symbol.nonterminal (Sum.inr a) | symbol.nonterminal n => symbol.nonterminal (Sum.inl n) /-- Lift an entire string to placeholder form. -/ def homLiftStr {N : Type} (s : List (symbol α N)) : List (symbol β (N ⊕ α)) := s.map homLiftSym /-- Lift a rule to phase 1 form. -/ def homLiftRule {N : Type} (r : grule α N) : grule β (N ⊕ α) := ⟨homLiftStr r.input_L, Sum.inl r.input_N, homLiftStr r.input_R, homLiftStr r.output_string⟩ /-- Create a phase 2 rule for terminal `a`. -/ def homExpandRule {N : Type} (h : α → List β) (a : α) : grule β (N ⊕ α) := ⟨[], Sum.inr a, [], (h a).map symbol.terminal⟩ /-- The two-phase grammar for the homomorphic image. -/ def hom_grammar (g : grammar α) (h : α → List β) : grammar β := ⟨g.nt ⊕ α, Sum.inl g.initial, (g.rules.map homLiftRule) ++ (all_used_terminals g).map (homExpandRule h)⟩ end construction -- ============================================================================ -- Helpers -- ============================================================================ section helpers variable {g : grammar α} lemma homLiftStr_map_terminal (w : List α) : @homLiftStr α β g.nt (w.map symbol.terminal) = w.map (fun a => (symbol.nonterminal (Sum.inr a) : symbol β (g.nt ⊕ α))) := by simp [homLiftStr, homLiftSym, List.map_map] lemma mem_prod_singletons_iff_flatMap (w : List α) (h : α → List β) (u : List β) : u ∈ (w.map (fun a => ({h a} : Language β))).prod ↔ u = w.flatMap h := by induction w generalizing u with | nil => simp | cons a w ih => simp only [List.map_cons, List.prod_cons, Language.mul_def] constructor · rintro ⟨u₁, hu₁, u₂, hu₂, rfl⟩ rw [Set.mem_singleton_iff] at hu₁ rw [hu₁, List.flatMap_cons] congr 1; exact (ih u₂).mp hu₂ · intro hu refine ⟨h a, Set.mem_singleton _, w.flatMap h, (ih _).mpr rfl, ?_⟩ simp [List.flatMap_cons, hu] end helpers -- ============================================================================ -- Forward direction -- ============================================================================ section forward_direction variable {g : grammar α} {h : α → List β} private lemma phase1_transforms {w₁ w₂ : List (symbol α g.nt)} (ht : grammar_transforms g w₁ w₂) : grammar_transforms (hom_grammar g h) (homLiftStr w₁) (homLiftStr w₂) := by obtain ⟨r, hr, u, v, hw₁, hw₂⟩ := ht use homLiftRule r, by unfold hom_grammar; aesop, homLiftStr u, homLiftStr v unfold homLiftStr homLiftRule; aesop private lemma phase1_derives {w₁ w₂ : List (symbol α g.nt)} (hd : grammar_derives g w₁ w₂) : grammar_derives (hom_grammar g h) (homLiftStr w₁) (homLiftStr w₂) := by induction hd with | refl => exact grammar_deri_self | tail _ hs ih => exact grammar_deri_of_deri_tran ih (phase1_transforms hs) private lemma terminals_in_derived_word {w : List α} (hw : w ∈ grammar_language g) : ∀ a ∈ w, a ∈ all_used_terminals g := by intro a ha contrapose! ha have h_not_derive : ∀ w : List (symbol α g.nt), a ∉ List.filterMap as_terminal w → ∀ v : List (symbol α g.nt), grammar_derives g w v → a ∉ List.filterMap as_terminal v := by intros w hw v hv induction' hv with w' v' hv' ih · assumption · obtain ⟨r, hr, u, v, hw', hv'⟩ := ih simp_all +decide [List.filterMap_append] intro x hx H exact ha <| List.mem_filterMap.mpr ⟨x, List.mem_flatten.mpr ⟨_, List.mem_map.mpr ⟨r, hr, rfl⟩, hx⟩, H⟩ convert h_not_derive [symbol.nonterminal g.initial] _ (List.map symbol.terminal w) hw using 1 · unfold as_terminal; aesop · simp +decide [as_terminal] private lemma phase2_step (a : α) (ha : a ∈ all_used_terminals g) (u v : List (symbol β (g.nt ⊕ α))) : grammar_transforms (hom_grammar g h) (u ++ [symbol.nonterminal (Sum.inr a)] ++ v) (u ++ (h a).map symbol.terminal ++ v) := ⟨homExpandRule h a, List.mem_append_right _ (List.mem_map.mpr ⟨a, ha, rfl⟩), u, v, by simp [homExpandRule], by simp [homExpandRule]⟩ private lemma phase2_expand_all (w : List α) (hw : ∀ a ∈ w, a ∈ all_used_terminals g) : grammar_derives (hom_grammar g h) (w.map (fun a => (symbol.nonterminal (Sum.inr a) : symbol β (g.nt ⊕ α)))) ((w.flatMap h).map symbol.terminal) := by induction w with | nil => exact grammar_deri_self | cons a w ih => simp only [List.map_cons, List.flatMap_cons, List.map_append] have ha := hw a List.mem_cons_self have hw' : ∀ b ∈ w, b ∈ all_used_terminals g := fun b hb => hw b (List.mem_cons_of_mem a hb) have step1 : grammar_transforms (hom_grammar g h) (symbol.nonterminal (Sum.inr a) :: w.map (fun a => (symbol.nonterminal (Sum.inr a) : symbol β (g.nt ⊕ α)))) ((h a).map symbol.terminal ++ w.map (fun a => (symbol.nonterminal (Sum.inr a) : symbol β (g.nt ⊕ α)))) := by simpa using phase2_step a ha [] _ exact grammar_deri_of_tran_deri step1 (grammar_deri_with_prefix ((h a).map symbol.terminal) (ih hw')) private lemma in_hom_of_in_original {w : List α} (hw : w ∈ grammar_language g) : w.flatMap h ∈ grammar_language (hom_grammar g h) := by unfold grammar_language grammar_generates at * have p1 := phase1_derives (h := h) hw rw [homLiftStr_map_terminal] at p1 exact grammar_deri_of_deri_deri p1 (phase2_expand_all w (terminals_in_derived_word hw)) end forward_direction -- ============================================================================ -- Backward direction (ε-free case) -- ============================================================================ section backward_direction variable {g : grammar α} {h : α → List β} -- Decorated symbols for tracking the invariant. private inductive DSym (N : Type) | nt : N → DSym N | unexpanded : α → DSym N | expanded : α → DSym N private def dToHom' {N : Type} (h : α → List β) : @DSym α N → List (symbol β (N ⊕ α)) | DSym.nt n => [symbol.nonterminal (Sum.inl n)] | DSym.unexpanded a => [symbol.nonterminal (Sum.inr a)] | DSym.expanded a => (h a).map symbol.terminal private def dToOrig' {N : Type} : @DSym α N → symbol α N | DSym.nt n => symbol.nonterminal n | DSym.unexpanded a => symbol.terminal a | DSym.expanded a => symbol.terminal a private def ValidForm' (g : grammar α) (h : α → List β) (s : List (symbol β (g.nt ⊕ α))) : Prop := ∃ ds : List (@DSym α g.nt), s = ds.flatMap (dToHom' h) ∧ grammar_derives g [symbol.nonterminal g.initial] (ds.map dToOrig') private lemma valid_initial' : ValidForm' g h [symbol.nonterminal (Sum.inl g.initial)] := ⟨[DSym.nt g.initial], rfl, grammar_deri_self⟩ -- ============================================================================ -- Helper: lifting DSym -- ============================================================================ private def liftDSym {N : Type} : symbol α N → @DSym α N | symbol.terminal a => DSym.unexpanded a | symbol.nonterminal n => DSym.nt n private lemma dToHom'_liftDSym {N : Type} (h : α → List β) (s : symbol α N) : dToHom' h (liftDSym s) = [homLiftSym s] := by cases s <;> simp [liftDSym, dToHom', homLiftSym] private lemma dToOrig'_liftDSym {N : Type} (s : symbol α N) : dToOrig' (@liftDSym α N s) = s := by cases s <;> simp [liftDSym, dToOrig'] private lemma flatMap_dToHom'_map_liftDSym {N : Type} (h : α → List β) (l : List (symbol α N)) : (l.map liftDSym).flatMap (dToHom' h) = homLiftStr l := by induction l with | nil => simp [homLiftStr] | cons s l ih => simp [List.map_cons, List.flatMap_cons, homLiftStr, dToHom'_liftDSym, ih] private lemma map_dToOrig'_map_liftDSym {N : Type} (l : List (symbol α N)) : (l.map liftDSym).map (@dToOrig' α N) = l := by induction l with | nil => simp | cons s l ih => simp [dToOrig'_liftDSym, ih] /- ============================================================================ Helper: splitting flatMap at a nonterminal (ε-free case) ============================================================================ In the ε-free case, every nonterminal in `ds.flatMap (dToHom' h)` comes from a unique DSym element, and we can split ds at that element. -/ private lemma dsym_flatMap_split_at_nonterminal (heps : ∀ a, h a ≠ []) (ds : List (@DSym α g.nt)) (u v : List (symbol β (g.nt ⊕ α))) (x : g.nt ⊕ α) : ds.flatMap (dToHom' h) = u ++ [symbol.nonterminal x] ++ v → ∃ ds₁ d ds₂, ds = ds₁ ++ [d] ++ ds₂ ∧ dToHom' h d = [symbol.nonterminal x] ∧ ds₁.flatMap (dToHom' h) = u ∧ ds₂.flatMap (dToHom' h) = v := by induction ds generalizing u v; · aesop; · rename_i d ds ih; rcases d with ( _ | _ | _ ) <;> simp_all +decide [ List.append_assoc ]; · rcases u with ( _ | ⟨ y, u ⟩ ) <;> simp_all +decide [ List.append_assoc ]; · intro h; cases h; exact ⟨ [ ], DSym.nt _, ds, rfl, rfl, by tauto ⟩; · grind +locals; · intro h; rcases u with ( _ | ⟨ u, u ⟩ ) <;> simp_all +decide [ List.append_assoc ]; · use [ ], DSym.unexpanded ‹_›, ds; cases h ; aesop; · grind +locals; · intro h_eq obtain ⟨u', hu'⟩ : ∃ u', u = (h ‹_›).map symbol.terminal ++ u' := by have h_prefix : (h ‹_›).map symbol.terminal ++ flatMap (dToHom' h) ds = u ++ symbol.nonterminal x :: v := by exact h_eq; have h_prefix : ∀ {l₁ l₂ l₃ : List (symbol β (g.nt ⊕ α))}, l₁ ++ l₂ = l₃ ++ symbol.nonterminal x :: v → (∀ y ∈ l₁, y ≠ symbol.nonterminal x) → ∃ u', l₃ = l₁ ++ u' := by intros l₁ l₂ l₃ h_eq h_no_x; induction' l₁ with a l₁ ih generalizing l₃ <;> simp_all +decide [ List.append_assoc ] ; rcases l₃ with ( _ | ⟨ b, l₃ ⟩ ) <;> simp_all +decide [ List.append_assoc ]; grind; specialize ih u' v; simp_all +decide [ List.append_assoc ]; obtain ⟨ ds₁, d, ds₂, rfl, hd, hu', hv ⟩ := ih ( by simpa [ dToHom' ] using h_eq ) ; use DSym.expanded ‹_› :: ds₁, d, ds₂; aesop; /- In the ε-free case, if `ds.flatMap (dToHom' h)` ends with `homLiftStr s` (which consists entirely of nonterminals), then `ds` ends with `s.map liftDSym`. -/ private lemma dsym_flatMap_match_suffix (heps : ∀ a, h a ≠ []) (ds : List (@DSym α g.nt)) (u : List (symbol β (g.nt ⊕ α))) (s : List (symbol α g.nt)) : ds.flatMap (dToHom' h) = u ++ homLiftStr s → ∃ ds₁ : List (@DSym α g.nt), ds = ds₁ ++ s.map liftDSym ∧ ds₁.flatMap (dToHom' h) = u := by intro hds; induction' s using List.reverseRecOn with s' sym ih generalizing ds u; · unfold homLiftStr at hds; aesop; · have h_split : ds.flatMap (dToHom' h) = u ++ (homLiftStr s' ++ [homLiftSym sym]) := by unfold homLiftStr at *; aesop; obtain ⟨ds₁, d, ds₂, hds₁, hd, hds₂⟩ : ∃ ds₁ d ds₂, ds = ds₁ ++ [d] ++ ds₂ ∧ dToHom' h d = [homLiftSym sym] ∧ ds₁.flatMap (dToHom' h) = u ++ homLiftStr s' ∧ ds₂.flatMap (dToHom' h) = [] := by have := dsym_flatMap_split_at_nonterminal heps ds ( u ++ homLiftStr s' ) [] ( match sym with | symbol.terminal a => Sum.inr a | symbol.nonterminal n => Sum.inl n ) ?_; · cases sym <;> tauto; · convert h_split using 1; cases sym <;> simp +decide [ homLiftSym ]; have h_ds₂_empty : ds₂ = [] := by induction ds₂ <;> simp_all +decide [ List.flatMap ]; cases ‹DSym g.nt› <;> simp_all +decide [ dToHom' ]; cases sym <;> simp_all +decide [ dToHom' ]; · rcases d with ( _ | _ | _ ) <;> simp_all +decide [ homLiftSym ]; obtain ⟨ ds₁, hds₁, hds₂ ⟩ := ih ds₁ u hds₂; use ds₁; aesop; · rcases d with ( _ | _ | _ ) <;> simp_all +decide [ homLiftSym ]; obtain ⟨ ds₁, hds₁, hds₂ ⟩ := ih ds₁ u hds₂; use ds₁; aesop; /- In the ε-free case, if `ds.flatMap (dToHom' h)` starts with `homLiftStr s` (which consists entirely of nonterminals), then `ds` starts with `s.map liftDSym`. -/ private lemma dsym_flatMap_match_prefix (heps : ∀ a, h a ≠ []) (ds : List (@DSym α g.nt)) (s : List (symbol α g.nt)) (v : List (symbol β (g.nt ⊕ α))) : ds.flatMap (dToHom' h) = homLiftStr s ++ v → ∃ ds₂ : List (@DSym α g.nt), ds = s.map liftDSym ++ ds₂ ∧ ds₂.flatMap (dToHom' h) = v := by intro h_eq induction' s with sym s' ih generalizing ds v; · aesop; · rcases ds with ( _ | ⟨ d, ds ⟩ ) <;> simp_all +decide [ homLiftStr ]; rcases d with ( _ | _ | _ ) <;> simp_all +decide [ dToHom' ]; · cases sym <;> simp_all +decide [ homLiftSym ]; exact Exists.elim ( ih ds v h_eq.2 ) fun ds₂ hds₂ => ⟨ ds₂, ⟨ rfl, hds₂.1 ⟩, hds₂.2 ⟩; · cases sym <;> simp_all +decide [ homLiftSym ]; exact ⟨ _, ⟨ rfl, ih _ _ h_eq.2 |> Classical.choose_spec |> And.left ⟩, ih _ _ h_eq.2 |> Classical.choose_spec |> And.right ⟩; · cases sym <;> cases h : h ‹_› <;> simp_all +decide [ homLiftSym ]; replace h_eq := congr_arg List.head? h_eq ; aesop /- ============================================================================ Main invariant lemma ============================================================================ Invariant preserved through steps (ε-free case). -/ private lemma valid_step' (heps : ∀ a, h a ≠ []) {s₁ s₂ : List (symbol β (g.nt ⊕ α))} (hv : ValidForm' g h s₁) (ht : grammar_transforms (hom_grammar g h) s₁ s₂) : ValidForm' g h s₂ := by rcases ht with ⟨ r, hr, u, v, rfl, rfl ⟩; unfold hom_grammar at hr; simp_all +decide [ List.mem_append, List.mem_map ] ; rcases hr with ( ⟨ r, hr, rfl ⟩ | ⟨ a, ha, rfl ⟩ ) <;> simp_all +decide [ homLiftRule, homExpandRule ]; · obtain ⟨ ds, hds₁, hds₂ ⟩ := hv; -- Use `dsym_flatMap_split_at_nonterminal` to split `ds` into `ds_L`, `d_mid`, and `ds_R`. obtain ⟨ds_L, d_mid, ds_R, hds_split⟩ : ∃ ds_L d_mid ds_R, ds = ds_L ++ [d_mid] ++ ds_R ∧ dToHom' h d_mid = [symbol.nonterminal (Sum.inl r.input_N)] ∧ ds_L.flatMap (dToHom' h) = u ++ homLiftStr r.input_L ∧ ds_R.flatMap (dToHom' h) = homLiftStr r.input_R ++ v := by have := dsym_flatMap_split_at_nonterminal heps ds ( u ++ homLiftStr r.input_L ) ( homLiftStr r.input_R ++ v ) ( Sum.inl r.input_N ) ?_; · exact this; · simpa [ List.append_assoc ] using hds₁.symm; -- Use `dsym_flatMap_match_suffix` to split `ds_L` into `ds_pre` and `r.input_L.map liftDSym`. obtain ⟨ds_pre, hds_pre⟩ : ∃ ds_pre, ds_L = ds_pre ++ r.input_L.map liftDSym ∧ ds_pre.flatMap (dToHom' h) = u := by have := dsym_flatMap_match_suffix heps ds_L u r.input_L; aesop; -- Use `dsym_flatMap_match_prefix` to split `ds_R` into `r.input_R.map liftDSym` and `ds_post`. obtain ⟨ds_post, hds_post⟩ : ∃ ds_post, ds_R = r.input_R.map liftDSym ++ ds_post ∧ ds_post.flatMap (dToHom' h) = v := by apply dsym_flatMap_match_prefix heps ds_R r.input_R v hds_split.right.right.right; refine' ⟨ ds_pre ++ r.output_string.map liftDSym ++ ds_post, _, _ ⟩ <;> simp_all +decide [ List.flatMap_append ]; · exact Eq.symm (flatMap_dToHom'_map_liftDSym h r.output_string); · convert grammar_deri_of_deri_tran hds₂ _ using 1; use r, hr, ds_pre.map dToOrig', ds_post.map dToOrig'; cases d_mid <;> simp_all +decide [ dToOrig' ]; · cases hds_split.2.1; exact ⟨ by rw [ show dToOrig' ∘ liftDSym = id from funext fun x => by cases x <;> rfl ] ; simp +decide, by rw [ show dToOrig' ∘ liftDSym = id from funext fun x => by cases x <;> rfl ] ; simp +decide ⟩; · cases hds_split.2.1; · cases h : h ‹_› <;> simp_all +decide [ dToHom' ]; · obtain ⟨ ds, hds₁, hds₂ ⟩ := hv; -- Use dsym_flatMap_split_at_nonterminal to split ds into ds₁, d, ds₂. obtain ⟨ds₁, d, ds₂, hds_split⟩ : ∃ ds₁ d ds₂, ds = ds₁ ++ [d] ++ ds₂ ∧ dToHom' h d = [symbol.nonterminal (Sum.inr a)] ∧ ds₁.flatMap (dToHom' h) = u ∧ ds₂.flatMap (dToHom' h) = v := by apply dsym_flatMap_split_at_nonterminal heps ds u v (Sum.inr a); simpa using hds₁.symm; use ds₁ ++ [DSym.expanded a] ++ ds₂; cases d <;> simp_all +decide [ dToHom', dToOrig' ]; cases hds₁ ; aesop private lemma valid_derives' (heps : ∀ a, h a ≠ []) {s : List (symbol β (g.nt ⊕ α))} (hd : grammar_derives (hom_grammar g h) [symbol.nonterminal (Sum.inl g.initial)] s) : ValidForm' g h s := by induction hd with | refl => exact valid_initial' | tail _ hs ih => exact valid_step' heps ih hs private lemma valid_terminal' {w : List β} (hv : ValidForm' g h (w.map symbol.terminal)) : ∃ w' ∈ grammar_language g, w = w'.flatMap h := by obtain ⟨ds, hds⟩ := hv obtain ⟨w', hw'⟩ : ∃ w' : List α, ds = w'.map DSym.expanded := by have h_ds_expanded : ∀ x ∈ ds, ∃ a : α, x = DSym.expanded a := by intro x hx; rcases x with (_ | _ | _) <;> simp_all +decide [List.flatMap] · have : symbol.nonterminal (Sum.inl ‹_›) ∈ (map (dToHom' h) ds).flatten := List.mem_flatten.mpr ⟨_, List.mem_map.mpr ⟨_, hx, rfl⟩, by simp [dToHom']⟩ grind · have : ∀ {l : List (symbol β (g.nt ⊕ α))}, l = (ds.map (dToHom' h)).flatten → (∃ x ∈ l, x = symbol.nonterminal (Sum.inr ‹_›)) → False := by grind apply this rfl simp +zetaDelta at * exact ⟨_, hx, by tauto⟩ have : ∀ {l : List (DSym g.nt)}, (∀ x ∈ l, ∃ a : α, x = DSym.expanded a) → ∃ w' : List α, l = w'.map DSym.expanded := by intro l hl; induction l with | nil => exact ⟨[], rfl⟩ | cons x l ih => obtain ⟨a, rfl⟩ := hl x (by simp) obtain ⟨w', hw'⟩ := ih fun x hx => hl x (by simp [hx]) exact ⟨a :: w', by simp [hw']⟩ exact this h_ds_expanded subst hw' refine ⟨w', ?_, ?_⟩ · have : (w'.map DSym.expanded).map (@dToOrig' α g.nt) = w'.map symbol.terminal := by simp [List.map_map, dToOrig'] rw [this] at hds exact hds.2 · have hinj : Function.Injective (symbol.terminal : β → symbol β (g.nt ⊕ α)) := fun x y hxy => by injection hxy apply hinj.list_map have hfm : (w'.map (DSym.expanded (N := g.nt))).flatMap (dToHom' h) = (w'.flatMap h).map (symbol.terminal (N := g.nt ⊕ α)) := by simp only [List.flatMap, List.map_map] simp only [Function.comp_def, dToHom'] rw [List.map_flatten, List.map_map]; rfl rw [hfm] at hds exact hds.1 /-- The backward direction (ε-free case). -/ private lemma backward_direction_epsfree (heps : ∀ a, h a ≠ []) {w : List β} (hd : w ∈ grammar_language (hom_grammar g h)) : ∃ w' ∈ grammar_language g, w = w'.flatMap h := valid_terminal' (valid_derives' heps hd) end backward_direction -- ============================================================================ -- Main theorems -- ============================================================================ /-- The class of RE languages is closed under ε-free string homomorphism. -/ theorem RE_closed_under_epsfree_homomorphism (L : Language α) (h : α → List β) (hL : is_RE L) (heps : IsEpsFreeHomomorphism h) : is_RE (L.homomorphicImage h) := by obtain ⟨g, rfl⟩ := hL refine ⟨hom_grammar g h, ?_⟩ ext w simp only [Language.homomorphicImage, Language.subst, grammar_language] constructor · intro hd obtain ⟨w', hw', rfl⟩ := backward_direction_epsfree heps hd exact ⟨w', hw', (mem_prod_singletons_iff_flatMap w' h _).mpr rfl⟩ · rintro ⟨w', hw', hu⟩ rw [(mem_prod_singletons_iff_flatMap w' h w).mp hu] exact in_hom_of_in_original hw' /-- The class of recursively enumerable languages is closed under ε-free string homomorphism. -/ theorem RE_closedUnderEpsFreeHomomorphism : ClosedUnderEpsFreeHomomorphism is_RE := fun L h heps hL => RE_closed_under_epsfree_homomorphism L h hL heps