You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: vignettes/hotpatchR-intro.Rmd
+41-35Lines changed: 41 additions & 35 deletions
Original file line number
Diff line number
Diff line change
@@ -20,53 +20,57 @@ This vignette explains the `hotpatchR` design and why runtime namespace patching
20
20
21
21
## The legacy package lockdown problem
22
22
23
-
A loaded R package lives in a locked namespace. That means internal functions are resolved from inside the package bubble.
24
-
When one internal function calls another, R does not look in the global environment.
23
+
A loaded R package lives in a locked namespace. Internal functions are resolved from inside the package bubble, so calling a hidden helper from the global environment does not change the package's internal behavior.
25
24
26
-
So if you fix `a_freq_j()` in the global environment, `tt_to_tlgrtf()` inside the package still calls the original broken version.
27
-
28
-
This is the namespace trap that makes legacy hotfixing so difficult.
25
+
This is the namespace trap that makes legacy hotfixing difficult: a visible exported function may still invoke a broken internal helper even after you have sourced a fixed version elsewhere.
29
26
30
27
## Why the global workaround fails
31
28
32
29
The usual workaround is:
33
30
34
-
- identify the broken function
35
-
- find all parent callers in the package
36
-
- copy the broken function and every dependent caller into a hotfix script
31
+
- identify the broken internal function
32
+
- find every package caller that depends on it
33
+
- copy the broken internals into a hotfix script
37
34
- source the script into the global environment
38
-
- run tests to confirm the patched path
35
+
- run tests and hope the package resolves the new bindings
39
36
40
-
This is painful because a bug in one internal function can require copying many internal callers, even when only one implementation actually needs to change.
37
+
That workflow is brittle because a bug in one hidden helper can force you to patch many callers, even when only one implementation needs to change.
41
38
42
39
## hotpatchR philosophy
43
40
44
41
Instead of pulling functions out into the global environment, `hotpatchR` performs surgical edits inside the package namespace.
45
42
That means:
46
43
47
-
- fix only the broken function
48
-
-internal callers automatically resolve to the updated implementation
44
+
- fix only the broken internal function
45
+
-exported callers automatically resolve to the updated implementation
49
46
- the package remains loaded and otherwise unchanged
50
47
51
48
## Basic workflow
52
49
50
+
The package includes a real example of this pattern with an exported parent function and an internal child helper.
#> "Parent output -> I am the BROKEN child. Input: test"
58
+
59
+
inject_patch(
60
+
pkg = "hotpatchR",
61
+
patch_list = list(
62
+
dummy_child_func = function(x) {
63
+
paste("I am the FIXED child! Input:", x)
64
+
}
65
+
)
66
+
)
67
+
68
+
patched_result <- dummy_parent_func("test")
69
+
print(patched_result)
70
+
#> "Parent output -> I am the FIXED child! Input: test"
67
71
```
68
72
69
-
If `pkg_env` were a real package namespace, the same internal caller would now use the patched `broken_child()`.
73
+
This shows the real package behavior: `dummy_parent_func()` is exported, while `dummy_child_func()` is a hidden internal helper that gets replaced inside the `hotpatchR` namespace.
70
74
71
75
## How inject_patch works
72
76
@@ -77,39 +81,41 @@ If `pkg_env` were a real package namespace, the same internal caller would now u
77
81
3. assigns the replacement function into that environment
78
82
4. re-locks the binding
79
83
80
-
Because the replacement function's parent environment is set to the target namespace, it can still call other internal objects from the same package.
84
+
Because the replacement function can be defined with the package namespace as its parent, it still has access to the package's internal helpers.
81
85
82
86
## Rolling back a patch
83
87
84
88
If you need to restore the original binding, `undo_patch()` reverses the previous change.
0 commit comments