phel-lang / phel-lang
Phel is a functional programming language that compiles to PHP
Fund package maintenance!
Requires
- php: >=8.4
- amphp/amp: ^3.1
- gacela-project/gacela: ^2.0
- symfony/console: ^7.3|^8.0
- symfony/polyfill-mbstring: ^1.30
- symfony/routing: ^7.3|^8.0
Requires (Dev)
- ext-readline: *
- brianium/paratest: ^7
- ergebnis/composer-normalize: ^2.50
- friendsofphp/php-cs-fixer: ^3.94
- infection/infection: ^0.35
- phpbench/phpbench: ^1.6
- phpstan/phpstan: ^2.1
- phpstan/phpstan-deprecation-rules: ^2.0
- phpstan/phpstan-phpunit: ^2.0
- phpunit/phpunit: ^11.0|^12.0|^13.0
- psalm/plugin-phpunit: ^0.19|^0.20
- rector/rector: ^2.3
- vimeo/psalm: ^6.16
Suggests
None
Provides
None
Conflicts
None
Replaces
None
- dev-main
- v0.52.0
- v0.51.0
- v0.50.0
- v0.49.0
- v0.48.0
- v0.47.0
- v0.46.0
- v0.45.1
- v0.45.0
- v0.44.0
- v0.43.0
- v0.42.0
- v0.41.0
- v0.40.0
- v0.39.0
- v0.38.0
- v0.37.0
- v0.36.0
- v0.35.0
- v0.34.1
- v0.34.0
- v0.33.0
- v0.32.0
- v0.31.0
- v0.30.0
- v0.29.0
- v0.28.0
- v0.27.0
- v0.26.0
- v0.25.0
- v0.24.0
- v0.23.1
- v0.23.0
- v0.22.2
- v0.22.1
- v0.22.0
- v0.21.0
- v0.20.0
- v0.19.1
- v0.19.0
- v0.18.1
- v0.18.0
- v0.17.0
- v0.16.1
- v0.16.0
- v0.15.3
- v0.15.2
- v0.15.1
- v0.15.0
- v0.14.1
- v0.14.0
- v0.13.0
- v0.12.0
- v0.11.0
- v0.10.1
- v0.10.0
- v0.9.0
- v0.8.0
- v0.7.0
- v0.6.0
- v0.5.0
- v0.4.0
- v0.3.3
- v0.3.2
- v0.3.1
- v0.3.0
- v0.2.0
- v0.1.0
- dev-fix/integration-fixture-order-dependence
- dev-feat/3303-nodiscard-collections
- dev-test/3312-absolute-assert
- dev-docs/superseded-forms-shipped-in-052
- dev-chore/3302-php-85-minimum
- dev-fix/3301-octal-escape-overflow
- dev-feat/balance-improvement-poc
This package is auto-updated.
Last update: 2026-09-20 05:59:15 UTC
README
Lisp for PHP, macros, persistent data structures, REPL.
Requirements
PHP 8.5 or newer. Linux and macOS are supported: the full compiler, core and PHAR suites run on both in CI, and a failure on either blocks a release. Windows is best effort, running a reduced suite. What a version number promises, and for how long, is in the stability policy.
Get Started
composer require phel-lang/phel-lang
1. Open a REPL
./vendor/bin/phel repl
phel:1:> (->> [1 2 3 4 5] (filter odd?) (map #(* % %)) (reduce +)) 35 phel:2:> (defn greet [name] (str "Hello, " name "!")) | user/greet phel:3:> (greet "Phel") | "Hello, Phel!"
2. Scaffold a project
./vendor/bin/phel init # add `--minimal` for a single-file layout
Creates phel-config.php, src/main.phel, tests/main_test.phel. With
--minimal the two sources sit at the project root instead. Then:
./vendor/bin/phel run src/main.phel # run ./vendor/bin/phel test # tests ./vendor/bin/phel build # compile to PHP ./vendor/bin/phel config # inspect the merged config
3. Eval inline or via stdin
./vendor/bin/phel eval '(+ 1 2)' # prints 3 echo '(println "hi")' | ./vendor/bin/phel eval - ./vendor/bin/phel eval - < script.phel
4. Enable shell completion (optional)
./vendor/bin/phel completion dumps a tab-completion script for bash, zsh, or fish. It completes commands, their options, and dynamic values (function names for doc, project namespaces for run/test).
# bash — restart your shell afterwards ./vendor/bin/phel completion bash | sudo tee /etc/bash_completion.d/phel # zsh — write into a directory on your $fpath ./vendor/bin/phel completion zsh > "${fpath[1]}/_phel" # fish ./vendor/bin/phel completion fish > ~/.config/fish/completions/phel.fish
The zsh script starts with #compdef phel, so completion only triggers for a binary named phel on your $PATH. If you call ./vendor/bin/phel (or ./bin/phel in a dev checkout), symlink a global phel first, e.g. on macOS + Homebrew:
phel completion zsh > "$(brew --prefix)/share/zsh/site-functions/_phel" ln -sf "$PWD/bin/phel" "$(brew --prefix)/bin/phel" # global `phel` so #compdef matches rm -f ~/.zcompdump* # force compinit to rebuild # then open a new shell
Prefer a project template?
web-skeletonorcli-skeleton: click Use this template for a one-click start.
More examples →
|
Data pipeline (def users [{:name "Ada" :age 36} {:name "Bob" :age 17} {:name "Cam" :age 41}]) (->> users (filter #(>= (:age %) 18)) (map :name) sort) ;; => ["Ada" "Cam"] |
HTTP response (ns app (:require phel.http :as h)) (def req (h/request-from-globals)) (h/emit-response (h/response-from-map {:status 200 :headers {"Content-Type" "text/plain"} :body (str "Hello " (:uri req))})) |
|
Macros (defmacro unless [pred & body] `(if (not ~pred) (do ~@body))) (unless (zero? 1) (println "not zero")) ;; => not zero (unless false (println "ok")) ;; => ok |
PHP interop (ns app) (def now (new DateTime)) (.format now "Y-m-d") ;; => "2026-04-20" (def epoch (new DateTime "1970-01-01")) (.-days (.diff now epoch)) ;; => 20564 |
Documentation
- Getting Started: install, REPL, first script (5 min)
- CLI Reference & DX Guide: every command, the dev loop, compile vs eval vs run vs build
- phel-lang.org: full documentation, tutorials, exercises, blog
- Contributor docs: repository internals, agent tooling, project layout
- Architecture decisions: why the repository is shaped the way it is
- Packagist
- CONTRIBUTING.md: setup and workflow
- AGENTS.md: architecture and review expectations
AI Coding Agents
Skill files for Claude Code, Cursor, Codex, Gemini, Copilot, Aider: resources/agents/
./vendor/bin/phel agent-install [platform] # install skill file for one agent (claude, cursor, ...) ./vendor/bin/phel agent-install --auto # only agents detected in this project ./vendor/bin/phel agent-install --all # every supported platform
Repo-level AI tooling
Claude Code (.claude/) and Codex (.codex/, .agents/, AGENTS.md) configs generate from a single source tree under .agnostic-ai/ via agnostic-ai. Those directories are gitignored; run sync after cloning to materialize them. Add more targets (Gemini, Cursor, ...) by appending to targets: in agnostic-ai.yaml.
brew install Chemaclass/tap/agnostic-ai # or: go install github.com/chemaclass/agnostic-ai/cmd/agnostic-ai@latest agnostic-ai sync # regenerate per-tool configs
Edit specs under .agnostic-ai/{rules,agents,skills,hooks,scripts,overlays}/, then agnostic-ai sync again. A CI gate runs sync --check on every PR to block drift between the source and the (gitignored) emitted files.