doeff-hy 0.5.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- doeff_hy-0.5.0/.gitignore +142 -0
- doeff_hy-0.5.0/LICENSE +21 -0
- doeff_hy-0.5.0/PKG-INFO +395 -0
- doeff_hy-0.5.0/README.md +378 -0
- doeff_hy-0.5.0/architecture.hy +55 -0
- doeff_hy-0.5.0/docs/hy-upstream-rewriting-unparse.md +69 -0
- doeff_hy-0.5.0/docs/static-check.md +142 -0
- doeff_hy-0.5.0/pyproject.toml +56 -0
- doeff_hy-0.5.0/src/doeff_hy/__init__.py +46 -0
- doeff_hy-0.5.0/src/doeff_hy/ast_unparse.py +85 -0
- doeff_hy-0.5.0/src/doeff_hy/binding_forms.py +1369 -0
- doeff_hy-0.5.0/src/doeff_hy/check_slot.py +190 -0
- doeff_hy-0.5.0/src/doeff_hy/clause_endings.hy +230 -0
- doeff_hy-0.5.0/src/doeff_hy/clause_ops.hy +172 -0
- doeff_hy-0.5.0/src/doeff_hy/conductor.hy +166 -0
- doeff_hy-0.5.0/src/doeff_hy/declarations.hy +450 -0
- doeff_hy-0.5.0/src/doeff_hy/declarations.pyi +58 -0
- doeff_hy-0.5.0/src/doeff_hy/env_places.py +78 -0
- doeff_hy-0.5.0/src/doeff_hy/expansion_names.py +164 -0
- doeff_hy-0.5.0/src/doeff_hy/frozen.hy +202 -0
- doeff_hy-0.5.0/src/doeff_hy/frozen.pyi +62 -0
- doeff_hy-0.5.0/src/doeff_hy/handle.hy +1042 -0
- doeff_hy-0.5.0/src/doeff_hy/http.hy +99 -0
- doeff_hy-0.5.0/src/doeff_hy/http.pyi +57 -0
- doeff_hy-0.5.0/src/doeff_hy/json_value.py +167 -0
- doeff_hy-0.5.0/src/doeff_hy/lazy.py +139 -0
- doeff_hy-0.5.0/src/doeff_hy/light_stack.py +41 -0
- doeff_hy-0.5.0/src/doeff_hy/macros.hy +2826 -0
- doeff_hy-0.5.0/src/doeff_hy/macros.pyi +57 -0
- doeff_hy-0.5.0/src/doeff_hy/match_fields.py +155 -0
- doeff_hy-0.5.0/src/doeff_hy/outcome_forms.hy +224 -0
- doeff_hy-0.5.0/src/doeff_hy/positions.hy +80 -0
- doeff_hy-0.5.0/src/doeff_hy/py.typed +0 -0
- doeff_hy-0.5.0/src/doeff_hy/pytest_items.py +386 -0
- doeff_hy-0.5.0/src/doeff_hy/pytest_plugin.py +11 -0
- doeff_hy-0.5.0/src/doeff_hy/quoted_forms.py +65 -0
- doeff_hy-0.5.0/src/doeff_hy/record.hy +593 -0
- doeff_hy-0.5.0/src/doeff_hy/record.pyi +18 -0
- doeff_hy-0.5.0/src/doeff_hy/record_runtime.py +89 -0
- doeff_hy-0.5.0/src/doeff_hy/session.py +21 -0
- doeff_hy-0.5.0/src/doeff_hy/session.pyi +7 -0
- doeff_hy-0.5.0/src/doeff_hy/sexpr.hy +563 -0
- doeff_hy-0.5.0/src/doeff_hy/static_baseline.py +164 -0
- doeff_hy-0.5.0/src/doeff_hy/static_cache.py +175 -0
- doeff_hy-0.5.0/src/doeff_hy/static_check.py +1385 -0
- doeff_hy-0.5.0/src/doeff_hy/static_stub.py +868 -0
- doeff_hy-0.5.0/src/doeff_hy/static_types.py +1 -0
- doeff_hy-0.5.0/src/doeff_hy/static_types.pyi +131 -0
- doeff_hy-0.5.0/src/doeff_hy/static_view.py +222 -0
- doeff_hy-0.5.0/src/doeff_hy/system_form.hy +292 -0
- doeff_hy-0.5.0/src/doeff_hy/table.hy +284 -0
- doeff_hy-0.5.0/src/doeff_hy/table.pyi +53 -0
- doeff_hy-0.5.0/src/doeff_hy/type_forms.py +109 -0
- doeff_hy-0.5.0/src/doeff_hy/typecheck.hy +64 -0
- doeff_hy-0.5.0/src/doeff_hy/typecheck_handlers.hy +45 -0
- doeff_hy-0.5.0/src/doeff_hy/typecheck_handlers.pyi +11 -0
- doeff_hy-0.5.0/src/doeff_hy/wire.hy +169 -0
- doeff_hy-0.5.0/src/doeff_hy/wire.pyi +62 -0
- doeff_hy-0.5.0/src/doeff_hy_bytecode_guard/__init__.py +64 -0
- doeff_hy-0.5.0/src/doeff_hy_bytecode_guard/bound_source.py +962 -0
- doeff_hy-0.5.0/src/doeff_hy_bytecode_guard/called_code.py +125 -0
- doeff_hy-0.5.0/src/doeff_hy_bytecode_guard/code_store.py +312 -0
- doeff_hy-0.5.0/src/doeff_hy_bytecode_guard/dataclass_code.py +79 -0
- doeff_hy-0.5.0/src/doeff_hy_bytecode_guard/expansion.py +16 -0
- doeff_hy-0.5.0/src/doeff_hy_bytecode_guard/import_gc.py +98 -0
- doeff_hy-0.5.0/src/doeff_hy_bytecode_guard/import_scope.py +53 -0
- doeff_hy-0.5.0/src/doeff_hy_bytecode_guard/loader_hooks.py +759 -0
- doeff_hy-0.5.0/src/doeff_hy_bytecode_guard/macro_use.py +2296 -0
- doeff_hy-0.5.0/src/doeff_hy_bytecode_guard/records.py +540 -0
- doeff_hy-0.5.0/src/doeff_hy_bytecode_guard.pth +1 -0
- doeff_hy-0.5.0/src/hy-stubs/__init__.pyi +79 -0
- doeff_hy-0.5.0/src/hy-stubs/macros.pyi +29 -0
- doeff_hy-0.5.0/src/hy-stubs/models.pyi +134 -0
- doeff_hy-0.5.0/src/hy-stubs/py.typed +1 -0
- doeff_hy-0.5.0/tests/conductor_dsl_macro_script.hy +35 -0
- doeff_hy-0.5.0/tests/data/record_field_cases.json +14 -0
- doeff_hy-0.5.0/tests/defeffect_shape_deftests.hy +207 -0
- doeff_hy-0.5.0/tests/defrecord_check_deftests.hy +215 -0
- doeff_hy-0.5.0/tests/defrecord_shape_deftests.hy +183 -0
- doeff_hy-0.5.0/tests/defwire_deftests.hy +318 -0
- doeff_hy-0.5.0/tests/match_field_names_deftests.hy +125 -0
- doeff_hy-0.5.0/tests/multimod/__init__.py +0 -0
- doeff_hy-0.5.0/tests/multimod/data_fetch.hy +20 -0
- doeff_hy-0.5.0/tests/multimod/effects.hy +15 -0
- doeff_hy-0.5.0/tests/multimod/python_layer.py +16 -0
- doeff_hy-0.5.0/tests/multimod/signal.hy +17 -0
- doeff_hy-0.5.0/tests/test_aliased_generic_contract.py +110 -0
- doeff_hy-0.5.0/tests/test_ast_unparse_on_py314.py +124 -0
- doeff_hy-0.5.0/tests/test_bound_source_store.py +119 -0
- doeff_hy-0.5.0/tests/test_bytecode_guard.py +1133 -0
- doeff_hy-0.5.0/tests/test_check_slot_opacity.py +473 -0
- doeff_hy-0.5.0/tests/test_code_store_entries.py +204 -0
- doeff_hy-0.5.0/tests/test_conductor_dsl.py +17 -0
- doeff_hy-0.5.0/tests/test_contract_alias_static_types.py +80 -0
- doeff_hy-0.5.0/tests/test_contract_type_params.py +136 -0
- doeff_hy-0.5.0/tests/test_dataclass_code_store.py +93 -0
- doeff_hy-0.5.0/tests/test_declarations.py +314 -0
- doeff_hy-0.5.0/tests/test_declarations_static_types.py +109 -0
- doeff_hy-0.5.0/tests/test_defeffect_answer_static_types.py +142 -0
- doeff_hy-0.5.0/tests/test_defeffect_shape.py +41 -0
- doeff_hy-0.5.0/tests/test_defeffect_type_params.py +153 -0
- doeff_hy-0.5.0/tests/test_defenum.py +185 -0
- doeff_hy-0.5.0/tests/test_defhandler_effect_type_filter.py +132 -0
- doeff_hy-0.5.0/tests/test_defhandler_passed_effects.py +119 -0
- doeff_hy-0.5.0/tests/test_defpipeline.hy +106 -0
- doeff_hy-0.5.0/tests/test_defrecord_check.py +41 -0
- doeff_hy-0.5.0/tests/test_defrecord_shape.py +41 -0
- doeff_hy-0.5.0/tests/test_deftest_scans.py +77 -0
- doeff_hy-0.5.0/tests/test_defwire.py +39 -0
- doeff_hy-0.5.0/tests/test_duplicate_stages.hy +61 -0
- doeff_hy-0.5.0/tests/test_env_places_read.py +57 -0
- doeff_hy-0.5.0/tests/test_frozen_json_contract.py +92 -0
- doeff_hy-0.5.0/tests/test_frozen_refreeze.py +311 -0
- doeff_hy-0.5.0/tests/test_frozen_static_types.py +174 -0
- doeff_hy-0.5.0/tests/test_generic_type_contract.py +164 -0
- doeff_hy-0.5.0/tests/test_gensym_canonical.py +492 -0
- doeff_hy-0.5.0/tests/test_helper_imports_table.py +207 -0
- doeff_hy-0.5.0/tests/test_hy_internal_names_have_one_home.py +186 -0
- doeff_hy-0.5.0/tests/test_hy_stubs.py +115 -0
- doeff_hy-0.5.0/tests/test_import_gc.py +169 -0
- doeff_hy-0.5.0/tests/test_json_value.py +234 -0
- doeff_hy-0.5.0/tests/test_lazy_and_threading.py +678 -0
- doeff_hy-0.5.0/tests/test_light_stack.py +124 -0
- doeff_hy-0.5.0/tests/test_macro_file_ext_without_stack_walk.py +39 -0
- doeff_hy-0.5.0/tests/test_macro_use_called.py +239 -0
- doeff_hy-0.5.0/tests/test_macro_use_record.py +1901 -0
- doeff_hy-0.5.0/tests/test_match_field_names.py +38 -0
- doeff_hy-0.5.0/tests/test_multimod.hy +167 -0
- doeff_hy-0.5.0/tests/test_none_type_contract.py +142 -0
- doeff_hy-0.5.0/tests/test_pre_check_static_types.py +111 -0
- doeff_hy-0.5.0/tests/test_pytest_item_types.py +63 -0
- doeff_hy-0.5.0/tests/test_pytest_items.py +137 -0
- doeff_hy-0.5.0/tests/test_python_interop.hy +45 -0
- doeff_hy-0.5.0/tests/test_quoted_forms.py +76 -0
- doeff_hy-0.5.0/tests/test_record_static_types.py +143 -0
- doeff_hy-0.5.0/tests/test_runtime_do_spelling.py +56 -0
- doeff_hy-0.5.0/tests/test_sexpr.hy +75 -0
- doeff_hy-0.5.0/tests/test_source_positions.py +177 -0
- doeff_hy-0.5.0/tests/test_stage_of.hy +112 -0
- doeff_hy-0.5.0/tests/test_static_cache.py +585 -0
- doeff_hy-0.5.0/tests/test_static_check.py +518 -0
- doeff_hy-0.5.0/tests/test_static_check_baseline.py +190 -0
- doeff_hy-0.5.0/tests/test_static_check_defhandler.py +591 -0
- doeff_hy-0.5.0/tests/test_static_check_deftest.py +171 -0
- doeff_hy-0.5.0/tests/test_static_check_effects_reads.py +97 -0
- doeff_hy-0.5.0/tests/test_static_check_for_do.py +182 -0
- doeff_hy-0.5.0/tests/test_static_check_locate.py +125 -0
- doeff_hy-0.5.0/tests/test_static_check_named_paths.py +74 -0
- doeff_hy-0.5.0/tests/test_static_check_of_contract.py +92 -0
- doeff_hy-0.5.0/tests/test_static_check_parallel.py +196 -0
- doeff_hy-0.5.0/tests/test_static_check_post_early_return.py +230 -0
- doeff_hy-0.5.0/tests/test_static_check_pyright_python.py +46 -0
- doeff_hy-0.5.0/tests/test_static_check_pyright_threads.py +143 -0
- doeff_hy-0.5.0/tests/test_static_check_recursive_defk.py +142 -0
- doeff_hy-0.5.0/tests/test_static_check_session.py +151 -0
- doeff_hy-0.5.0/tests/test_static_check_sibling_imports.py +113 -0
- doeff_hy-0.5.0/tests/test_static_check_slot.py +452 -0
- doeff_hy-0.5.0/tests/test_static_stub.py +422 -0
- doeff_hy-0.5.0/tests/test_system_static_types.py +172 -0
- doeff_hy-0.5.0/tests/test_table.py +583 -0
- doeff_hy-0.5.0/tests/test_table_static_types.py +103 -0
- doeff_hy-0.5.0/tests/test_tail_resume_tco.py +230 -0
- doeff_hy-0.5.0/tests/test_type_forms.py +62 -0
- doeff_hy-0.5.0/tests/test_val_var_lazy.py +752 -0
- doeff_hy-0.5.0/tests/test_wire_static_types.py +193 -0
- doeff_hy-0.5.0/tests/test_with_handler_macro.py +49 -0
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
# Python
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*$py.class
|
|
5
|
+
*.so
|
|
6
|
+
# macOS debug symbols for the compiled extension above: build output that
|
|
7
|
+
# embeds the builder's absolute paths, so it must not be distributed either.
|
|
8
|
+
*.dSYM/
|
|
9
|
+
.Python
|
|
10
|
+
env/
|
|
11
|
+
venv/
|
|
12
|
+
.venv/
|
|
13
|
+
ENV/
|
|
14
|
+
env.bak/
|
|
15
|
+
venv.bak/
|
|
16
|
+
.env
|
|
17
|
+
.env.*
|
|
18
|
+
|
|
19
|
+
# Distribution / packaging
|
|
20
|
+
.Python
|
|
21
|
+
build/
|
|
22
|
+
develop-eggs/
|
|
23
|
+
dist/
|
|
24
|
+
downloads/
|
|
25
|
+
eggs/
|
|
26
|
+
.eggs/
|
|
27
|
+
lib/
|
|
28
|
+
lib64/
|
|
29
|
+
parts/
|
|
30
|
+
sdist/
|
|
31
|
+
var/
|
|
32
|
+
wheels/
|
|
33
|
+
*.egg-info/
|
|
34
|
+
.installed.cfg
|
|
35
|
+
*.egg
|
|
36
|
+
MANIFEST
|
|
37
|
+
|
|
38
|
+
# Testing
|
|
39
|
+
.pytest_cache/
|
|
40
|
+
.coverage
|
|
41
|
+
.coverage.*
|
|
42
|
+
coverage.xml
|
|
43
|
+
*.cover
|
|
44
|
+
.hypothesis/
|
|
45
|
+
htmlcov/
|
|
46
|
+
.tox/
|
|
47
|
+
|
|
48
|
+
# IDE
|
|
49
|
+
# Generated by Eclipse buildship and machine-specific: embeds the local Java home
|
|
50
|
+
# and toolchain paths. The gradle build does not read it.
|
|
51
|
+
ide-plugins/pycharm/.settings/org.eclipse.buildship.core.prefs
|
|
52
|
+
.idea/
|
|
53
|
+
.vscode/
|
|
54
|
+
*.swp
|
|
55
|
+
*.swo
|
|
56
|
+
*~
|
|
57
|
+
.DS_Store
|
|
58
|
+
|
|
59
|
+
# UV
|
|
60
|
+
!uv.lock
|
|
61
|
+
.venv/
|
|
62
|
+
|
|
63
|
+
# Ruff
|
|
64
|
+
.ruff_cache/
|
|
65
|
+
|
|
66
|
+
# MyPy
|
|
67
|
+
.mypy_cache/
|
|
68
|
+
.dmypy.json
|
|
69
|
+
dmypy.json
|
|
70
|
+
|
|
71
|
+
# Generated logs, traces, and local databases
|
|
72
|
+
.doeff-lint.jsonl
|
|
73
|
+
.doeff-flow/
|
|
74
|
+
*.jsonl
|
|
75
|
+
# 旧い版の coordinator の置き場の log(版上げの下見の検 test_deployed_state_upgrade.hy が読む fixture — 生成された log ではない)
|
|
76
|
+
!packages/doeff-cluster/tests/fixtures/coordinator_state_*/wal/wal.jsonl
|
|
77
|
+
# codex の app-server の実物の行を録った物(行の分類の検 packages/doeff-codex/tests/test_lines.hy が読む fixture — 生成された log ではない)
|
|
78
|
+
!packages/doeff-codex/tests/recorded/codex-*/*.jsonl
|
|
79
|
+
*.db
|
|
80
|
+
*.db-journal
|
|
81
|
+
*.db-shm
|
|
82
|
+
*.db-wal
|
|
83
|
+
*.sqlite
|
|
84
|
+
*.sqlite-journal
|
|
85
|
+
*.sqlite-shm
|
|
86
|
+
*.sqlite-wal
|
|
87
|
+
*.sqlite3
|
|
88
|
+
*.sqlite3-journal
|
|
89
|
+
*.sqlite3-shm
|
|
90
|
+
*.sqlite3-wal
|
|
91
|
+
None
|
|
92
|
+
Untitled
|
|
93
|
+
|
|
94
|
+
# Local agent and browser automation state
|
|
95
|
+
.agent-home/
|
|
96
|
+
.playwright-mcp/
|
|
97
|
+
.claude/settings.local.json
|
|
98
|
+
.claude/skills/install-vscode-plugin/
|
|
99
|
+
.agents/skills/install-vscode-plugin/
|
|
100
|
+
|
|
101
|
+
# Accidental desktop helper bundle captured at repository root
|
|
102
|
+
/server.js
|
|
103
|
+
|
|
104
|
+
# Rust build artifacts
|
|
105
|
+
rust/**/target/
|
|
106
|
+
packages/**/target/
|
|
107
|
+
|
|
108
|
+
# Local deploy keys
|
|
109
|
+
proboscis_doeff_deploy
|
|
110
|
+
proboscis_doeff_deploy.pub
|
|
111
|
+
|
|
112
|
+
# Worktrees and orchestration
|
|
113
|
+
.git-worktrees/
|
|
114
|
+
.orch/
|
|
115
|
+
.sisyphus/
|
|
116
|
+
VAULT
|
|
117
|
+
|
|
118
|
+
# Override global gitignore for examples
|
|
119
|
+
!examples/
|
|
120
|
+
ORCH_PROMPT.md
|
|
121
|
+
.worktrees/
|
|
122
|
+
|
|
123
|
+
# Local workspace artifacts
|
|
124
|
+
# Repo-root scratch directory only. A `python/` directory inside a package
|
|
125
|
+
# (e.g. packages/doeff-indexer/python/) is NOT covered by this rule.
|
|
126
|
+
/python/
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
# 設計記録は repo に入れる — global の除外(`artifacts` の dir・`*.html`・`*.svg`)を打ち消す
|
|
130
|
+
# (打ち消しは dir を先に戻さないと中身に効かない)
|
|
131
|
+
!docs/design/*/artifacts/
|
|
132
|
+
!docs/design/**
|
|
133
|
+
# 書き込みロックだけは入れない(道具が作る一時 file — 打ち消しの後に置く)
|
|
134
|
+
docs/design/*/.records.lock
|
|
135
|
+
|
|
136
|
+
# Repository-owned Zenn article visuals (override global image ignores)
|
|
137
|
+
!/images/zenn-use-cases-v0/*.png
|
|
138
|
+
# 記事が参照する採用画像は generated/ の下に在る(上の行は直下だけに効き、2026-09-16 の 47 枚は入らなかった)
|
|
139
|
+
!/images/zenn-use-cases-v0/generated/
|
|
140
|
+
!/images/zenn-use-cases-v0/generated/*.png
|
|
141
|
+
# 手で書いた図の正本(global の `*.svg` の除外を打ち消す)
|
|
142
|
+
!/publications/zenn-use-cases-v0/visuals/*.svg
|
doeff_hy-0.5.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 proboscis contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
doeff_hy-0.5.0/PKG-INFO
ADDED
|
@@ -0,0 +1,395 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: doeff-hy
|
|
3
|
+
Version: 0.5.0
|
|
4
|
+
Summary: Hy macros for writing doeff programs, effects, handlers and tests
|
|
5
|
+
Project-URL: Homepage, https://github.com/proboscis/doeff/tree/main/packages/doeff-hy
|
|
6
|
+
Project-URL: Repository, https://github.com/proboscis/doeff
|
|
7
|
+
Project-URL: Issues, https://github.com/proboscis/doeff/issues
|
|
8
|
+
License-Expression: MIT
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Requires-Python: >=3.11
|
|
11
|
+
Requires-Dist: doeff-core-effects<0.6,>=0.5.0
|
|
12
|
+
Requires-Dist: doeff-hy-pytest<0.6,>=0.5.0
|
|
13
|
+
Requires-Dist: doeff<0.6,>=0.5.0
|
|
14
|
+
Requires-Dist: hy<=1.3.1,>=1.3.0
|
|
15
|
+
Requires-Dist: pydantic>=2.11
|
|
16
|
+
Description-Content-Type: text/markdown
|
|
17
|
+
|
|
18
|
+
# doeff-hy
|
|
19
|
+
|
|
20
|
+
Macros for writing [doeff](https://github.com/proboscis/doeff) programs, effects,
|
|
21
|
+
handlers and tests in [Hy](https://hylang.org/), a Lisp that compiles to Python.
|
|
22
|
+
|
|
23
|
+
**This README is for** developers who want to write doeff code in Hy, or who read Hy
|
|
24
|
+
code that uses doeff. After reading it you can install doeff-hy, define an effect, a
|
|
25
|
+
function that performs it and a handler for it, call them from Python, test them with
|
|
26
|
+
pytest, and look up each macro.
|
|
27
|
+
|
|
28
|
+
## What it does
|
|
29
|
+
|
|
30
|
+
doeff-hy gives you `defk`, `defeffect`, `defhandler`, `deftest` and related macros.
|
|
31
|
+
They expand to ordinary doeff code (`@do` generators, `EffectBase` dataclasses,
|
|
32
|
+
handler installers, pytest functions) with runtime contracts (`:pre` / `:post`) on
|
|
33
|
+
arguments and results.
|
|
34
|
+
|
|
35
|
+
Parts of [doeff-core-effects](https://github.com/proboscis/doeff/tree/main/packages/doeff-core-effects)
|
|
36
|
+
are written with these macros, so doeff-hy is installed whenever doeff is.
|
|
37
|
+
|
|
38
|
+
## Installation
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
pip install doeff # installs doeff-hy
|
|
42
|
+
pip install doeff-hy # the same set of packages
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Requires Python 3.11 or newer and Hy 1.3 (doeff-hy pins `hy>=1.3.0,<=1.3.1`, the
|
|
46
|
+
versions its tests cover).
|
|
47
|
+
|
|
48
|
+
## Quick start
|
|
49
|
+
|
|
50
|
+
`greeting.hy` defines an effect, a function that performs it, and a handler:
|
|
51
|
+
|
|
52
|
+
```hy file=greeting.hy
|
|
53
|
+
(require doeff-hy.macros [defeffect defk defhandler <-])
|
|
54
|
+
|
|
55
|
+
(defeffect Greeting
|
|
56
|
+
"Ask which word to greet with."
|
|
57
|
+
{:fields [(: lang str)]
|
|
58
|
+
:answer str
|
|
59
|
+
:tags {:context "example" :role "intent"}})
|
|
60
|
+
|
|
61
|
+
(defk greet [name]
|
|
62
|
+
{:pre [(: name str)] :post [(: % str)]}
|
|
63
|
+
(<- word (Greeting :lang "en"))
|
|
64
|
+
(+ word ", " name))
|
|
65
|
+
|
|
66
|
+
(defhandler english
|
|
67
|
+
(Greeting [lang]
|
|
68
|
+
(resume "hello")))
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
`main.py` imports it from Python and runs it:
|
|
72
|
+
|
|
73
|
+
```python file=main.py
|
|
74
|
+
import doeff_hy # noqa: F401 - lets Python import .hy / .hyk / .hyp modules
|
|
75
|
+
from doeff import run
|
|
76
|
+
from doeff_core_effects.scheduler import scheduled
|
|
77
|
+
|
|
78
|
+
from greeting import english, greet
|
|
79
|
+
|
|
80
|
+
print(run(scheduled(english(greet("doeff"))))) # hello, doeff
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
- `(<- word effect)` performs the effect and binds its answer to `word`.
|
|
84
|
+
- `{:pre [(: name str)] :post [(: % str)]}` checks the argument and the result types
|
|
85
|
+
at run time (`%` is the result).
|
|
86
|
+
- `english` is a handler installer: it wraps a program and answers every `Greeting`
|
|
87
|
+
performed inside it with `(resume "hello")`.
|
|
88
|
+
|
|
89
|
+
## Testing with deftest
|
|
90
|
+
|
|
91
|
+
`deftest` defines a pytest test whose body performs effects. Put the tests in
|
|
92
|
+
`test_greeting.hy`:
|
|
93
|
+
|
|
94
|
+
```hy file=test_greeting.hy
|
|
95
|
+
(require doeff-hy.macros [deftest <-])
|
|
96
|
+
(import doeff_core_effects [Ask])
|
|
97
|
+
(import greeting [english greet])
|
|
98
|
+
|
|
99
|
+
(deftest test-greet
|
|
100
|
+
(<- text (english (greet "doeff")))
|
|
101
|
+
(assert (= text "hello, doeff")))
|
|
102
|
+
|
|
103
|
+
(deftest test-greet-from-env
|
|
104
|
+
{:env {"name" "world"}}
|
|
105
|
+
(<- name (Ask "name"))
|
|
106
|
+
(<- text (english (greet name)))
|
|
107
|
+
(assert (= text "hello, world")))
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
A `deftest` function asks pytest for a fixture named `doeff_interpreter` and calls it
|
|
111
|
+
with the test's program (and with `env=` when the test sets `:env`). You decide which
|
|
112
|
+
handlers the tests run under. pytest also needs to be told to collect `test_*.hy`
|
|
113
|
+
files. Both go in `conftest.py`:
|
|
114
|
+
|
|
115
|
+
```python file=conftest.py
|
|
116
|
+
import doeff_hy # noqa: F401 - lets Python import .hy / .hyk / .hyp modules
|
|
117
|
+
import pytest
|
|
118
|
+
from doeff import run
|
|
119
|
+
from doeff_core_effects.handlers import reader
|
|
120
|
+
from doeff_core_effects.scheduler import scheduled
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
@pytest.fixture
|
|
124
|
+
def doeff_interpreter():
|
|
125
|
+
"""How a deftest body runs: deftest calls this with the test's program."""
|
|
126
|
+
|
|
127
|
+
def interpret(program, env=None):
|
|
128
|
+
return run(scheduled(reader(env=env or {})(program)))
|
|
129
|
+
|
|
130
|
+
return interpret
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def pytest_collect_file(file_path, parent):
|
|
134
|
+
"""Collect deftest functions from test_*.hy files."""
|
|
135
|
+
if file_path.suffix == ".hy" and file_path.name.startswith("test_"):
|
|
136
|
+
return pytest.Module.from_parent(parent, path=file_path)
|
|
137
|
+
return None
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
```bash run
|
|
141
|
+
pytest # 2 passed
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
[doeff-hy-pytest](https://github.com/proboscis/doeff/tree/main/packages/doeff-hy-pytest),
|
|
145
|
+
installed with doeff-hy, is a pytest plugin that pytest loads by itself. It fixes a
|
|
146
|
+
Hy problem on Python 3.14 and offers optional time limits for these tests.
|
|
147
|
+
|
|
148
|
+
### deftest options
|
|
149
|
+
|
|
150
|
+
The optional map after the test name (or after its fixture list) takes these keys:
|
|
151
|
+
|
|
152
|
+
| Key | What it does | Example |
|
|
153
|
+
| --- | --- | --- |
|
|
154
|
+
| `:interpreters` | Runs the test once per name; the names are passed to a `doeff_interpreter_name` fixture | `{:interpreters ["fast" "traced"]}` |
|
|
155
|
+
| `:env` | Passed to `doeff_interpreter` as `env=` | `{:env {"name" "world"}}` |
|
|
156
|
+
| `:params` | `pytest.mark.parametrize` for each fixture parameter | `{:params {"day" ["2026-01-01" "2026-02-01"]}}` |
|
|
157
|
+
| `:marks` | Adds pytest marks (`pytest -m "not slow"`) | `{:marks ["slow"]}` |
|
|
158
|
+
| `:skip-if` / `:skip-reason` | Skips when the expression is true; it is evaluated when the module is imported | `{:skip-if (not (shutil.which "git")) :skip-reason "git not installed"}` |
|
|
159
|
+
|
|
160
|
+
With `:interpreters`, resolve the name in `conftest.py`:
|
|
161
|
+
|
|
162
|
+
```python excerpt
|
|
163
|
+
INTERPRETERS = {
|
|
164
|
+
"fast": fast_interpreter,
|
|
165
|
+
"traced": traced_interpreter,
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
@pytest.fixture
|
|
170
|
+
def doeff_interpreter(doeff_interpreter_name):
|
|
171
|
+
return INTERPRETERS[doeff_interpreter_name]
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
Fixture parameters go in a list after the name:
|
|
175
|
+
|
|
176
|
+
```hy excerpt
|
|
177
|
+
(deftest test-report-for-day [day]
|
|
178
|
+
{:params {"day" ["2026-01-01" "2026-02-01"]}}
|
|
179
|
+
(<- report (daily-report day))
|
|
180
|
+
(assert (> (len report.rows) 0)))
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
## File extensions
|
|
184
|
+
|
|
185
|
+
| Extension | Purpose | Allowed macros |
|
|
186
|
+
| --- | --- | --- |
|
|
187
|
+
| `.hy` | General Hy source (handlers, effects, utilities) | All |
|
|
188
|
+
| `.hyk` | Kleisli modules (reusable effectful functions) | `defk`, `deff`, `defhandler`, `deftest` |
|
|
189
|
+
| `.hyp` | Program modules (entry points) | `defp`, `defpp`, `deftest` |
|
|
190
|
+
|
|
191
|
+
`import doeff_hy` registers `.hyk` and `.hyp` as Hy source extensions.
|
|
192
|
+
|
|
193
|
+
## Macros
|
|
194
|
+
|
|
195
|
+
Import macros with `(require doeff-hy.macros [<names>])`.
|
|
196
|
+
|
|
197
|
+
| Macro | Purpose |
|
|
198
|
+
| --- | --- |
|
|
199
|
+
| `defeffect` | Define an effect type: a frozen dataclass inheriting `EffectBase`, with its fields, answer type and tags |
|
|
200
|
+
| `defk` | Define an effectful function (`@do` + `:pre` / `:post` contracts) |
|
|
201
|
+
| `fnk` | Anonymous effectful function |
|
|
202
|
+
| `deff` | Define a pure function with `:pre` / `:post` contracts |
|
|
203
|
+
| `defhandler` | Define a handler installer from one clause per effect type |
|
|
204
|
+
| `defp` | Define a program constant (`:post` required) |
|
|
205
|
+
| `defpp` | Define a `Program[Program[T]]` constant |
|
|
206
|
+
| `deftest` | Define an effectful pytest test |
|
|
207
|
+
| `do!` | Inline do block: perform effects in sequence |
|
|
208
|
+
| `<-` | Perform an effect and bind its answer: `(<- name effect)`; `(<- name Type effect)` also checks `isinstance` |
|
|
209
|
+
| `!` | Perform an effect in argument position: `(f (! (Ask "x")))` |
|
|
210
|
+
| `on-raise` | Map matching `Raise` reasons to answers: `(on-raise body (Conflict d) answer …)` |
|
|
211
|
+
| `absent-as` | Fold `Absent` into a default: `(absent-as 0 body)` |
|
|
212
|
+
| `defpipeline` | Named-stage pipeline composition |
|
|
213
|
+
| `traverse` | Applicative traverse over collections |
|
|
214
|
+
| `for/do` | Comprehension whose body performs effects |
|
|
215
|
+
|
|
216
|
+
### Handlers
|
|
217
|
+
|
|
218
|
+
A `defhandler` clause names an effect type and its fields, and must end every path
|
|
219
|
+
with one of `resume`, `transfer`, `finish`, `reperform` or `raise`. `(finish value)`
|
|
220
|
+
ends the handler's scope without resuming; finishing an ordinary effect needs
|
|
221
|
+
`:finish-reason "…"`. Clauses for `Raise` and `Absent` never resume.
|
|
222
|
+
|
|
223
|
+
### Typed binding
|
|
224
|
+
|
|
225
|
+
`(<- name Type effect)` expands to the bind plus `(assert (isinstance name Type) …)`.
|
|
226
|
+
The expansion has one definition point (`_bind-yield` in `macros.hy`), shared by the
|
|
227
|
+
`<-` macro and by every body that accepts `<-` (`do!`, `defp`, `deftest`, `for/do`,
|
|
228
|
+
`traverse`, `defhandler` clauses), so the type contract holds wherever the bind is
|
|
229
|
+
written. The 2- and 3-element forms add no check.
|
|
230
|
+
|
|
231
|
+
`None` is a type in these positions, as in a Python annotation: `(: % None)`,
|
|
232
|
+
`(<- x None effect)` and `#(int None)` check against `type(None)` at run time
|
|
233
|
+
(`runtime_type_form` in `doeff_hy/type_forms.py` is the one place that maps it).
|
|
234
|
+
`(| int None)` needs no mapping.
|
|
235
|
+
|
|
236
|
+
### Absence and failure
|
|
237
|
+
|
|
238
|
+
Expected absence and failure are two effects in `doeff_core_effects.effects`:
|
|
239
|
+
`Absent(why)` (the Maybe side) and `Raise(reason)` (the Result side). Code writes only
|
|
240
|
+
the success path; the places that want to *do* something about absence or failure
|
|
241
|
+
fold them into values: `maybe` (`Absent` → `Nothing`) and `result` (`Raise` → `Err`)
|
|
242
|
+
from `doeff_core_effects.outcomes`, and the `on-raise` / `absent-as` macros. The
|
|
243
|
+
nesting order picks the shape: `(result (maybe b))` is `Ok(Some v)` / `Ok(Nothing)` /
|
|
244
|
+
`Err(e)`.
|
|
245
|
+
|
|
246
|
+
`defeffect` may split its answer by meaning — `:absent [Missing] :failure [Unreachable]
|
|
247
|
+
:value [Conflict]`; the rest is success. For such an effect `(<- x (ReadRow …))` binds
|
|
248
|
+
only the success and performs `Absent` / `Raise(answer)` in the caller's scope
|
|
249
|
+
instead; `:absent F` at the end of a bind turns an absence inside that bind into
|
|
250
|
+
`Raise(F)`. `<-` also opens `Ok` / `Err` / `Some` / `Nothing` values. An effect
|
|
251
|
+
without the declaration binds exactly as before (the bind yields the same object).
|
|
252
|
+
|
|
253
|
+
### Where `!` may appear
|
|
254
|
+
|
|
255
|
+
`(! effect)` is replaced with a `yield` expression at the position where it is
|
|
256
|
+
written. Branches, `and` / `or` short-circuiting, argument order, `try` suites, and
|
|
257
|
+
literal containers therefore keep normal Python evaluation order.
|
|
258
|
+
|
|
259
|
+
Python-native `lfor` / `gfor` / `sfor` / `dfor` comprehensions and plain nested `fn` /
|
|
260
|
+
`defn` bodies cannot host a doeff `!` safely. Macro expansion raises an
|
|
261
|
+
`ADR-DOE-HY-003` `SyntaxError` with a rewrite: use `for/do` plus `<-` for effectful
|
|
262
|
+
iteration, and `fnk` or `defk` for an effectful function.
|
|
263
|
+
|
|
264
|
+
### Names: val, var, lazy val, lazy var, session val, session var
|
|
265
|
+
|
|
266
|
+
In the bodies of `defk`, `deftest` and `defhandler` clauses, bind names with these
|
|
267
|
+
forms (`doeff-hy-check` warns on `setv` and asks for `val` or `var`):
|
|
268
|
+
|
|
269
|
+
| Form | Meaning |
|
|
270
|
+
| --- | --- |
|
|
271
|
+
| `(val x expr)` | Bind once. Binding the same name again is an error at expansion time |
|
|
272
|
+
| `(var x expr)` | Rebindable. Rebind with `(:= x new-value)` |
|
|
273
|
+
| `(lazy val x expr)` | Evaluated the first time `x` is used in that call, then remembered. Never evaluated if unused. If the first evaluation raises, the next use evaluates again |
|
|
274
|
+
| `(lazy var x expr)` | Like `lazy val`, and rebindable with `:=`. If rebound before first use, `expr` is never evaluated |
|
|
275
|
+
| `(session val x expr)` | Directly under `defhandler` only. Created once, on first use, and shared for the handler's session |
|
|
276
|
+
| `(session var x expr)` | Directly under `defhandler` only. Rebindable state that lives for the handler's session. Rebind with `(:= x v)` |
|
|
277
|
+
|
|
278
|
+
In `prices.hy`:
|
|
279
|
+
|
|
280
|
+
```hy file=prices.hy
|
|
281
|
+
(require doeff-hy.macros [defeffect defk defhandler <-])
|
|
282
|
+
|
|
283
|
+
(defeffect LoadPrices
|
|
284
|
+
"Load the whole price table."
|
|
285
|
+
{:answer dict
|
|
286
|
+
:tags {:context "example" :role "intent"}})
|
|
287
|
+
|
|
288
|
+
(defk price-of [ticker]
|
|
289
|
+
{:pre [(: ticker str)] :post [(: % float)]}
|
|
290
|
+
(lazy val table (! (LoadPrices))) ; performed only if `table` is used
|
|
291
|
+
(var price 0.0)
|
|
292
|
+
(when (in ticker table)
|
|
293
|
+
(:= price (get table ticker)))
|
|
294
|
+
price)
|
|
295
|
+
|
|
296
|
+
(defhandler price-table
|
|
297
|
+
(session var loads 0) ; counts loads for the whole session
|
|
298
|
+
(LoadPrices []
|
|
299
|
+
(:= loads (+ loads 1))
|
|
300
|
+
(resume {"ACME" 12.5})))
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
Run from Python:
|
|
304
|
+
|
|
305
|
+
```python
|
|
306
|
+
import doeff_hy # noqa: F401 - lets Python import .hy / .hyk / .hyp modules
|
|
307
|
+
from doeff import run
|
|
308
|
+
from doeff_core_effects.handlers import state
|
|
309
|
+
from doeff_core_effects.scheduler import scheduled
|
|
310
|
+
|
|
311
|
+
from prices import price_of, price_table
|
|
312
|
+
|
|
313
|
+
print(run(scheduled(state()(price_table(price_of("ACME")))))) # 12.5
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
- A value expression may be written `!(effect)`. Hy's reader reads `!(f)` as `!` and
|
|
317
|
+
`(f)`; in declarations and `:=` both spellings mean `(! (f))`.
|
|
318
|
+
- `val` forbids rebinding the name only. Changing the value's contents
|
|
319
|
+
(`(setv (get x k) v)`, `(.append x v)`) is allowed. `(<- x effect)` is another way
|
|
320
|
+
to write `val`.
|
|
321
|
+
- A lazy name is referenced by its bare name. It cannot be referenced inside `fn`,
|
|
322
|
+
comprehensions (`lfor` and friends) or `handle` / `defhandler` clauses (they cannot
|
|
323
|
+
perform effects); take it out first with `(val v x)`.
|
|
324
|
+
- Session values are read and written through the state effects (`Get` / `Put`), so
|
|
325
|
+
**install `state()` outside the handler**. The key is
|
|
326
|
+
`doeff_hy.session.session_key(module, handler, name)` =
|
|
327
|
+
`"<module __name__>/<handler name>/<variable name>"`. An outer handler that answers
|
|
328
|
+
`Get` for this key replaces the value without running the initial expression, and
|
|
329
|
+
one that receives `Put` observes the writes.
|
|
330
|
+
- At module top level, `(require doeff-hy.macros [val var lazy])` lets you write
|
|
331
|
+
`(val x expr)`, `(var x expr)` and `(lazy val x expr)`. A top-level `lazy val` takes
|
|
332
|
+
only expressions that perform no effects (a module has no handler; put resources
|
|
333
|
+
built with effects in a `defhandler`'s `session val`). Top-level `(:= …)` does not
|
|
334
|
+
go through a macro in Hy, so rebind a module `var` with a top-level `setv`.
|
|
335
|
+
- The older forms `(lazy …)`, `(lazy-val …)`, `(lazy-var …)` and `(set! …)` keep
|
|
336
|
+
their behaviour in `defhandler` and emit a `DeprecationWarning` at expansion time
|
|
337
|
+
pointing to `session val` / `session var` / `:=` (the keys are the same, so values
|
|
338
|
+
carry over). In `defk` and `deftest` they are expansion-time errors.
|
|
339
|
+
- Rebinding the same name twice with the older forms (two `setv`s, for example) is a
|
|
340
|
+
`doeff-hy-rebind` error from `doeff-hy-check`. Loop variables and mutually
|
|
341
|
+
exclusive `if` branches are not counted.
|
|
342
|
+
|
|
343
|
+
## Static type checking
|
|
344
|
+
|
|
345
|
+
`doeff-hy-check PATH... --root <repo>` expands Hy sources with the real macros and runs
|
|
346
|
+
[pyright](https://github.com/microsoft/pyright) on the result, reporting errors at
|
|
347
|
+
`.hy` lines. It needs `pyright` on `PATH` (or `--pyright <command>`). Contract types
|
|
348
|
+
(`(: x T)`) become parameter and result annotations, so argument types, missing
|
|
349
|
+
arguments, result types, a forgotten `<-`, and effect constructor arguments are
|
|
350
|
+
checked. What it does not catch yet (effect result types, env / effect sets) and why:
|
|
351
|
+
[docs/static-check.md](https://github.com/proboscis/doeff/blob/main/packages/doeff-hy/docs/static-check.md).
|
|
352
|
+
|
|
353
|
+
## What installing doeff-hy changes in your environment
|
|
354
|
+
|
|
355
|
+
- **A startup hook.** doeff-hy installs `doeff_hy_bytecode_guard.pth`, which runs at
|
|
356
|
+
every Python start in the environment. It wraps the standard source loader so that a
|
|
357
|
+
Hy module is recompiled when a macro it used has changed (Python's `.pyc` check looks
|
|
358
|
+
only at the source file, not at the macros that expanded it). `import doeff_hy`
|
|
359
|
+
installs the same hook when `.pth` files are skipped (`python -S`).
|
|
360
|
+
- **A compiled-code cache.** Compiled Hy code is also kept in a shared directory:
|
|
361
|
+
`$DOEFF_HY_CODE_STORE`, by default `$XDG_CACHE_HOME/doeff-hy/code-store`
|
|
362
|
+
(`~/.cache/doeff-hy/code-store`). Set `DOEFF_HY_CODE_STORE=off` to turn it off.
|
|
363
|
+
- **Partial Hy type stubs** (`hy-stubs`), so that pyright in strict mode accepts the
|
|
364
|
+
`import hy` lines that Hy's expansion always emits.
|
|
365
|
+
|
|
366
|
+
## How it fits with the other doeff packages
|
|
367
|
+
|
|
368
|
+
doeff is published as five core packages. `pip install doeff` installs all of them.
|
|
369
|
+
|
|
370
|
+
| Package | Role |
|
|
371
|
+
| --- | --- |
|
|
372
|
+
| [doeff](https://github.com/proboscis/doeff) | The Python API: `@do`, handler installers, `run`, the `doeff` CLI |
|
|
373
|
+
| [doeff-vm](https://github.com/proboscis/doeff/tree/main/packages/doeff-vm) | The Rust VM that executes programs |
|
|
374
|
+
| [doeff-core-effects](https://github.com/proboscis/doeff/tree/main/packages/doeff-core-effects) | The standard effects and their handlers |
|
|
375
|
+
| **doeff-hy** | This package: macros for writing doeff code in Hy |
|
|
376
|
+
| [doeff-hy-pytest](https://github.com/proboscis/doeff/tree/main/packages/doeff-hy-pytest) | pytest plugin for tests written with doeff-hy |
|
|
377
|
+
|
|
378
|
+
Arrows point from a package to a package it depends on:
|
|
379
|
+
|
|
380
|
+
```text
|
|
381
|
+
doeff ──► doeff-vm
|
|
382
|
+
│
|
|
383
|
+
└─────► doeff-core-effects ──► doeff-hy ──► doeff-hy-pytest
|
|
384
|
+
```
|
|
385
|
+
|
|
386
|
+
doeff-hy also depends on doeff and doeff-core-effects, so the five packages are
|
|
387
|
+
released together with matching versions.
|
|
388
|
+
|
|
389
|
+
## License
|
|
390
|
+
|
|
391
|
+
MIT. See [LICENSE](https://github.com/proboscis/doeff/blob/main/LICENSE).
|
|
392
|
+
|
|
393
|
+
Its own dependencies — Hy (with funcparserlib) and pydantic — are MIT-licensed.
|
|
394
|
+
Through doeff-core-effects it also installs frozendict, which is LGPL-3.0; see
|
|
395
|
+
[the doeff-core-effects README](https://github.com/proboscis/doeff/tree/main/packages/doeff-core-effects#licenses-of-dependencies).
|