mirror of
https://github.com/leanprover/lean4.git
synced 2026-03-18 02:44:12 +00:00
Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f8149b1aa2 | ||
|
|
00b0c6a758 | ||
|
|
f2669aff78 |
3
.gitattributes
vendored
3
.gitattributes
vendored
@@ -1,6 +1,3 @@
|
||||
*.lean text eol=lf
|
||||
*.expected.out -text
|
||||
RELEASES.md merge=union
|
||||
stage0/** binary linguist-generated
|
||||
# The following file is often manually edited, so do show it in diffs
|
||||
stage0/src/stdlib_flags.h -binary -linguist-generated
|
||||
|
||||
30
.github/ISSUE_TEMPLATE.md
vendored
Normal file
30
.github/ISSUE_TEMPLATE.md
vendored
Normal file
@@ -0,0 +1,30 @@
|
||||
### Prerequisites
|
||||
|
||||
* [ ] Put an X between the brackets on this line if you have done all of the following:
|
||||
* Checked that your issue isn't already [filed](https://github.com/leanprover/lean4/issues).
|
||||
* Reduced the issue to a self-contained, reproducible test case.
|
||||
|
||||
### Description
|
||||
|
||||
[Description of the issue]
|
||||
|
||||
### Steps to Reproduce
|
||||
|
||||
1. [First Step]
|
||||
2. [Second Step]
|
||||
3. [and so on...]
|
||||
|
||||
**Expected behavior:** [What you expect to happen]
|
||||
|
||||
**Actual behavior:** [What actually happens]
|
||||
|
||||
**Reproduces how often:** [What percentage of the time does it reproduce?]
|
||||
|
||||
### Versions
|
||||
|
||||
You can get this information from copy and pasting the output of `lean --version`,
|
||||
please include the OS and what version of the OS you're running.
|
||||
|
||||
### Additional Information
|
||||
|
||||
Any additional information, configuration or data that might be necessary to reproduce the issue.
|
||||
45
.github/ISSUE_TEMPLATE/bug_report.md
vendored
45
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@@ -1,45 +0,0 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Create a bug report
|
||||
title: ''
|
||||
labels: bug
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
### Prerequisites
|
||||
|
||||
* [ ] Put an X between the brackets on this line if you have done all of the following:
|
||||
* Check that your issue is not already [filed](https://github.com/leanprover/lean4/issues).
|
||||
* Reduce the issue to a minimal, self-contained, reproducible test case. Avoid dependencies to mathlib4 or std4.
|
||||
|
||||
### Description
|
||||
|
||||
[Clear and concise description of the issue]
|
||||
|
||||
### Context
|
||||
|
||||
[Broader context that the issue occured in. If there was any prior discussion on [the Lean Zulip](https://leanprover.zulipchat.com), link it here as well.]
|
||||
|
||||
### Steps to Reproduce
|
||||
|
||||
1.
|
||||
2.
|
||||
3.
|
||||
|
||||
**Expected behavior:** [Clear and concise description of what you expect to happen]
|
||||
|
||||
**Actual behavior:** [Clear and concise description of what actually happens]
|
||||
|
||||
### Versions
|
||||
|
||||
[Output of `#eval Lean.versionString` or of `lean --version` in the folder that the issue occured in]
|
||||
[OS version]
|
||||
|
||||
### Additional Information
|
||||
|
||||
[Additional information, configuration or data that might be necessary to reproduce the issue]
|
||||
|
||||
### Impact
|
||||
|
||||
Add :+1: to [issues you consider important](https://github.com/leanprover/lean4/issues?q=is%3Aissue+is%3Aopen+sort%3Areactions-%2B1-desc). If others are impacted by this issue, please ask them to add :+1: to it.
|
||||
26
.github/ISSUE_TEMPLATE/rfc.md
vendored
26
.github/ISSUE_TEMPLATE/rfc.md
vendored
@@ -1,26 +0,0 @@
|
||||
---
|
||||
name: Request for comments
|
||||
about: Create a feature proposal
|
||||
title: 'RFC: '
|
||||
labels: RFC
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
### Proposal
|
||||
|
||||
Clear and detailed description of the proposal. Consider the following questions:
|
||||
|
||||
- **User Experience**: How does this feature improve the user experience?
|
||||
|
||||
- **Beneficiaries**: Which Lean users and projects benefit most from this feature/change?
|
||||
|
||||
- **Maintainability**: Will this change streamline code maintenance or simplify its structure?
|
||||
|
||||
### Community Feedback
|
||||
|
||||
Ideas should be discussed on [the Lean Zulip](https://leanprover.zulipchat.com) prior to submitting a proposal. Summarize all prior discussions and link them here.
|
||||
|
||||
### Impact
|
||||
|
||||
Add :+1: to [issues you consider important](https://github.com/leanprover/lean4/issues?q=is%3Aissue+is%3Aopen+sort%3Areactions-%2B1-desc). If others benefit from the changes in this proposal being added, please ask them to add :+1: to it.
|
||||
14
.github/PULL_REQUEST_TEMPLATE.md
vendored
14
.github/PULL_REQUEST_TEMPLATE.md
vendored
@@ -1,14 +0,0 @@
|
||||
# Read this section before submitting
|
||||
|
||||
* Ensure your PR follows the [External Contribution Guidelines](https://github.com/leanprover/lean4/blob/master/CONTRIBUTING.md).
|
||||
* Please make sure the PR has excellent documentation and tests. If we label it `missing documentation` or `missing tests` then it needs fixing!
|
||||
* Include the link to your `RFC` or `bug` issue in the description.
|
||||
* If the issue does not already have approval from a developer, submit the PR as draft.
|
||||
* The PR title/description will become the commit message. Keep it up-to-date as the PR evolves.
|
||||
* If you rebase your PR onto `nightly-with-mathlib` then CI will test Mathlib against your PR.
|
||||
* You can manage the `awaiting-review`, `awaiting-author`, and `WIP` labels yourself, by writing a comment containing one of these labels on its own line.
|
||||
* Remove this section, up to and including the `---` before submitting.
|
||||
|
||||
---
|
||||
|
||||
Closes #0000 (`RFC` or `bug` issue number fixed by this PR, if any)
|
||||
22
.github/workflows/actionlint.yml
vendored
22
.github/workflows/actionlint.yml
vendored
@@ -1,22 +0,0 @@
|
||||
name: Actionlint
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- 'master'
|
||||
paths:
|
||||
- '.github/**'
|
||||
pull_request:
|
||||
paths:
|
||||
- '.github/**'
|
||||
merge_group:
|
||||
|
||||
jobs:
|
||||
actionlint:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
- name: actionlint
|
||||
uses: raven-actions/actionlint@v1
|
||||
with:
|
||||
pyflakes: false # we do not use python scripts
|
||||
26
.github/workflows/backport.yml
vendored
26
.github/workflows/backport.yml
vendored
@@ -1,26 +0,0 @@
|
||||
name: Backport
|
||||
on:
|
||||
pull_request_target:
|
||||
types:
|
||||
- closed
|
||||
- labeled
|
||||
|
||||
jobs:
|
||||
backport:
|
||||
name: Backport
|
||||
runs-on: ubuntu-latest
|
||||
# Only react to merged PRs for security reasons.
|
||||
# See https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#pull_request_target.
|
||||
if: >
|
||||
github.event.pull_request.merged
|
||||
&& (
|
||||
github.event.action == 'closed'
|
||||
|| (
|
||||
github.event.action == 'labeled'
|
||||
&& contains(github.event.label.name, 'backport')
|
||||
)
|
||||
)
|
||||
steps:
|
||||
- uses: tibdex/backport@v2
|
||||
with:
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
455
.github/workflows/ci.yml
vendored
455
.github/workflows/ci.yml
vendored
@@ -6,252 +6,105 @@ on:
|
||||
tags:
|
||||
- '*'
|
||||
pull_request:
|
||||
types: [opened, synchronize, reopened, labeled]
|
||||
merge_group:
|
||||
branches:
|
||||
- master
|
||||
schedule:
|
||||
- cron: '0 7 * * *' # 8AM CET/11PM PT
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}-${{ github.event_name }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
|
||||
# This job determines various settings for the following CI runs; see the `outputs` for details
|
||||
configure:
|
||||
set-nightly:
|
||||
# don't schedule nightlies on forks
|
||||
if: github.event_name == 'schedule' && github.repository == 'leanprover/lean4'
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
# Should we run only a quick CI? Yes on a pull request without the full-ci label
|
||||
quick: ${{ steps.set-quick.outputs.quick }}
|
||||
# The build matrix, dynamically generated here
|
||||
matrix: ${{ steps.set-matrix.outputs.result }}
|
||||
# Should we make a nightly release? If so, this output contains the lean version string, else it is empty
|
||||
nightly: ${{ steps.set-nightly.outputs.nightly }}
|
||||
# Should this be the CI for a tagged release?
|
||||
# Yes only if a tag is pushed to the `leanprover` repository, and the tag is "v" followed by a valid semver.
|
||||
# It sets `set-release.outputs.RELEASE_TAG` to the tag
|
||||
# and sets `set-release.outputs.{LEAN_VERSION_MAJOR,LEAN_VERSION_MINOR,LEAN_VERSION_PATCH,LEAN_SPECIAL_VERSION_DESC}`
|
||||
# to the semver components parsed via regex.
|
||||
LEAN_VERSION_MAJOR: ${{ steps.set-release.outputs.LEAN_VERSION_MAJOR }}
|
||||
LEAN_VERSION_MINOR: ${{ steps.set-release.outputs.LEAN_VERSION_MINOR }}
|
||||
LEAN_VERSION_PATCH: ${{ steps.set-release.outputs.LEAN_VERSION_PATCH }}
|
||||
LEAN_SPECIAL_VERSION_DESC: ${{ steps.set-release.outputs.LEAN_SPECIAL_VERSION_DESC }}
|
||||
RELEASE_TAG: ${{ steps.set-release.outputs.RELEASE_TAG }}
|
||||
|
||||
nightly: ${{ steps.set.outputs.nightly }}
|
||||
steps:
|
||||
- name: Run quick CI?
|
||||
id: set-quick
|
||||
env:
|
||||
quick: ${{
|
||||
github.event_name == 'pull_request' && !contains( github.event.pull_request.labels.*.name, 'full-ci')
|
||||
}}
|
||||
run: |
|
||||
echo "quick=${{env.quick}}" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Configure build matrix
|
||||
id: set-matrix
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
const quick = ${{ steps.set-quick.outputs.quick }};
|
||||
console.log(`quick: ${quick}`)
|
||||
let matrix = [
|
||||
{
|
||||
// portable release build: use channel with older glibc (2.27)
|
||||
"name": "Linux LLVM",
|
||||
"os": "ubuntu-latest",
|
||||
"release": false,
|
||||
"quick": false,
|
||||
"shell": "nix-shell --arg pkgsDist \"import (fetchTarball \\\"channel:nixos-19.03\\\") {{}}\" --run \"bash -euxo pipefail {0}\"",
|
||||
"llvm-url": "https://github.com/leanprover/lean-llvm/releases/download/15.0.1/lean-llvm-x86_64-linux-gnu.tar.zst",
|
||||
"prepare-llvm": "../script/prepare-llvm-linux.sh lean-llvm*",
|
||||
"binary-check": "ldd -v",
|
||||
// foreign code may be linked against more recent glibc
|
||||
// reverse-ffi needs to be updated to link to LLVM libraries
|
||||
"CTEST_OPTIONS": "-E 'foreign|leanlaketest_reverse-ffi'",
|
||||
"CMAKE_OPTIONS": "-DLLVM=ON -DLLVM_CONFIG=${GITHUB_WORKSPACE}/build/llvm-host/bin/llvm-config"
|
||||
},
|
||||
{
|
||||
"name": "Linux release",
|
||||
"os": "ubuntu-latest",
|
||||
"release": true,
|
||||
"quick": true,
|
||||
"shell": "nix-shell --arg pkgsDist \"import (fetchTarball \\\"channel:nixos-19.03\\\") {{}}\" --run \"bash -euxo pipefail {0}\"",
|
||||
"llvm-url": "https://github.com/leanprover/lean-llvm/releases/download/15.0.1/lean-llvm-x86_64-linux-gnu.tar.zst",
|
||||
"prepare-llvm": "../script/prepare-llvm-linux.sh lean-llvm*",
|
||||
"binary-check": "ldd -v",
|
||||
// foreign code may be linked against more recent glibc
|
||||
"CTEST_OPTIONS": "-E 'foreign'"
|
||||
},
|
||||
{
|
||||
"name": "Linux",
|
||||
"os": "ubuntu-latest",
|
||||
"check-stage3": true,
|
||||
"test-speedcenter": true,
|
||||
"quick": false,
|
||||
},
|
||||
{
|
||||
"name": "Linux Debug",
|
||||
"os": "ubuntu-latest",
|
||||
"quick": false,
|
||||
"CMAKE_OPTIONS": "-DCMAKE_BUILD_TYPE=Debug",
|
||||
// exclude seriously slow tests
|
||||
"CTEST_OPTIONS": "-E 'interactivetest|leanpkgtest|laketest|benchtest'"
|
||||
},
|
||||
{
|
||||
"name": "Linux fsanitize",
|
||||
"os": "ubuntu-latest",
|
||||
"quick": false,
|
||||
// turn off custom allocator & symbolic functions to make LSAN do its magic
|
||||
"CMAKE_OPTIONS": "-DLEAN_EXTRA_CXX_FLAGS=-fsanitize=address,undefined -DLEANC_EXTRA_FLAGS='-fsanitize=address,undefined -fsanitize-link-c++-runtime' -DSMALL_ALLOCATOR=OFF -DBSYMBOLIC=OFF",
|
||||
// exclude seriously slow/problematic tests (laketests crash)
|
||||
"CTEST_OPTIONS": "-E 'interactivetest|leanpkgtest|laketest|benchtest'"
|
||||
},
|
||||
{
|
||||
"name": "macOS",
|
||||
"os": "macos-latest",
|
||||
"release": true,
|
||||
"quick": false,
|
||||
"shell": "bash -euxo pipefail {0}",
|
||||
"llvm-url": "https://github.com/leanprover/lean-llvm/releases/download/15.0.1/lean-llvm-x86_64-apple-darwin.tar.zst",
|
||||
"prepare-llvm": "../script/prepare-llvm-macos.sh lean-llvm*",
|
||||
"binary-check": "otool -L",
|
||||
"tar": "gtar" // https://github.com/actions/runner-images/issues/2619
|
||||
},
|
||||
{
|
||||
"name": "macOS aarch64",
|
||||
"os": "macos-latest",
|
||||
"release": true,
|
||||
"quick": false,
|
||||
"cross": true,
|
||||
"cross_target": "aarch64-apple-darwin",
|
||||
"shell": "bash -euxo pipefail {0}",
|
||||
"CMAKE_OPTIONS": "-DUSE_GMP=OFF -DLEAN_INSTALL_SUFFIX=-darwin_aarch64",
|
||||
"llvm-url": "https://github.com/leanprover/lean-llvm/releases/download/15.0.1/lean-llvm-aarch64-apple-darwin.tar.zst https://github.com/leanprover/lean-llvm/releases/download/15.0.1/lean-llvm-x86_64-apple-darwin.tar.zst",
|
||||
"prepare-llvm": "../script/prepare-llvm-macos.sh lean-llvm-aarch64-* lean-llvm-x86_64-*",
|
||||
"binary-check": "otool -L",
|
||||
"tar": "gtar" // https://github.com/actions/runner-images/issues/2619
|
||||
},
|
||||
{
|
||||
"name": "Windows",
|
||||
"os": "windows-2022",
|
||||
"release": true,
|
||||
"quick": false,
|
||||
"shell": "msys2 {0}",
|
||||
"CMAKE_OPTIONS": "-G \"Unix Makefiles\" -DUSE_GMP=OFF",
|
||||
// for reasons unknown, interactivetests are flaky on Windows
|
||||
"CTEST_OPTIONS": "--repeat until-pass:2",
|
||||
"llvm-url": "https://github.com/leanprover/lean-llvm/releases/download/15.0.1/lean-llvm-x86_64-w64-windows-gnu.tar.zst",
|
||||
"prepare-llvm": "../script/prepare-llvm-mingw.sh lean-llvm*",
|
||||
"binary-check": "ldd"
|
||||
},
|
||||
{
|
||||
"name": "Linux aarch64",
|
||||
"os": "ubuntu-latest",
|
||||
"CMAKE_OPTIONS": "-DUSE_GMP=OFF -DLEAN_INSTALL_SUFFIX=-linux_aarch64",
|
||||
"release": true,
|
||||
"quick": false,
|
||||
"cross": true,
|
||||
"cross_target": "aarch64-unknown-linux-gnu",
|
||||
"shell": "nix-shell --arg pkgsDist \"import (fetchTarball \\\"channel:nixos-19.03\\\") {{ localSystem.config = \\\"aarch64-unknown-linux-gnu\\\"; }}\" --run \"bash -euxo pipefail {0}\"",
|
||||
"llvm-url": "https://github.com/leanprover/lean-llvm/releases/download/15.0.1/lean-llvm-x86_64-linux-gnu.tar.zst https://github.com/leanprover/lean-llvm/releases/download/15.0.1/lean-llvm-aarch64-linux-gnu.tar.zst",
|
||||
"prepare-llvm": "../script/prepare-llvm-linux.sh lean-llvm-aarch64-* lean-llvm-x86_64-*"
|
||||
},
|
||||
{
|
||||
"name": "Linux 32bit",
|
||||
"os": "ubuntu-latest",
|
||||
// Use 32bit on stage0 and stage1 to keep oleans compatible
|
||||
"CMAKE_OPTIONS": "-DSTAGE0_USE_GMP=OFF -DSTAGE0_LEAN_EXTRA_CXX_FLAGS='-m32' -DSTAGE0_LEANC_OPTS='-m32' -DSTAGE0_MMAP=OFF -DUSE_GMP=OFF -DLEAN_EXTRA_CXX_FLAGS='-m32' -DLEANC_OPTS='-m32' -DMMAP=OFF -DLEAN_INSTALL_SUFFIX=-linux_x86",
|
||||
"cmultilib": true,
|
||||
"release": true,
|
||||
"quick": false,
|
||||
"cross": true,
|
||||
"shell": "bash -euxo pipefail {0}"
|
||||
},
|
||||
{
|
||||
"name": "Web Assembly",
|
||||
"os": "ubuntu-latest",
|
||||
// Build a native 32bit binary in stage0 and use it to compile the oleans and the wasm build
|
||||
"CMAKE_OPTIONS": "-DCMAKE_C_COMPILER_WORKS=1 -DSTAGE0_USE_GMP=OFF -DSTAGE0_LEAN_EXTRA_CXX_FLAGS='-m32' -DSTAGE0_LEANC_OPTS='-m32' -DSTAGE0_CMAKE_CXX_COMPILER=clang++ -DSTAGE0_CMAKE_C_COMPILER=clang -DSTAGE0_CMAKE_EXECUTABLE_SUFFIX=\"\" -DUSE_GMP=OFF -DMMAP=OFF -DSTAGE0_MMAP=OFF -DCMAKE_AR=../emsdk/emsdk-main/upstream/emscripten/emar -DCMAKE_TOOLCHAIN_FILE=../emsdk/emsdk-main/upstream/emscripten/cmake/Modules/Platform/Emscripten.cmake -DLEAN_INSTALL_SUFFIX=-linux_wasm32",
|
||||
"wasm": true,
|
||||
"cmultilib": true,
|
||||
"release": true,
|
||||
"quick": false,
|
||||
"cross": true,
|
||||
"shell": "bash -euxo pipefail {0}",
|
||||
// Just a few selected tests because wasm is slow
|
||||
"CTEST_OPTIONS": "-R \"leantest_1007\\.lean|leantest_Format\\.lean|leanruntest\\_1037.lean|leanruntest_ac_rfl\\.lean\""
|
||||
}
|
||||
];
|
||||
console.log(`matrix:\n${JSON.stringify(matrix, null, 2)}`)
|
||||
if (quick) {
|
||||
return matrix.filter((job) => job.quick)
|
||||
} else {
|
||||
return matrix
|
||||
}
|
||||
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
# don't schedule nightlies on forks
|
||||
if: github.event_name == 'schedule' && github.repository == 'leanprover/lean4'
|
||||
uses: actions/checkout@v2
|
||||
- name: Set Nightly
|
||||
if: github.event_name == 'schedule' && github.repository == 'leanprover/lean4'
|
||||
id: set-nightly
|
||||
id: set
|
||||
run: |
|
||||
if [[ -n '${{ secrets.PUSH_NIGHTLY_TOKEN }}' ]]; then
|
||||
git remote add nightly https://foo:'${{ secrets.PUSH_NIGHTLY_TOKEN }}'@github.com/${{ github.repository_owner }}/lean4-nightly.git
|
||||
git fetch nightly --tags
|
||||
LEAN_VERSION_STRING="nightly-$(date -u +%F)"
|
||||
# do nothing if commit already has a different tag
|
||||
if [[ "$(git name-rev --name-only --tags --no-undefined HEAD 2> /dev/null || echo "$LEAN_VERSION_STRING")" == "$LEAN_VERSION_STRING" ]]; then
|
||||
echo "nightly=$LEAN_VERSION_STRING" >> "$GITHUB_OUTPUT"
|
||||
if [[ $(git name-rev --name-only --tags --no-undefined HEAD 2> /dev/null || echo $LEAN_VERSION_STRING) == $LEAN_VERSION_STRING ]]; then
|
||||
echo "::set-output name=nightly::$LEAN_VERSION_STRING"
|
||||
fi
|
||||
fi
|
||||
|
||||
- name: Check for official release
|
||||
if: startsWith(github.ref, 'refs/tags/') && github.repository == 'leanprover/lean4'
|
||||
id: set-release
|
||||
run: |
|
||||
TAG_NAME="${GITHUB_REF##*/}"
|
||||
|
||||
# From https://github.com/fsaintjacques/semver-tool/blob/master/src/semver
|
||||
|
||||
NAT='0|[1-9][0-9]*'
|
||||
ALPHANUM='[0-9]*[A-Za-z-][0-9A-Za-z-]*'
|
||||
IDENT="$NAT|$ALPHANUM"
|
||||
FIELD='[0-9A-Za-z-]+'
|
||||
|
||||
SEMVER_REGEX="\
|
||||
^[vV]?\
|
||||
($NAT)\\.($NAT)\\.($NAT)\
|
||||
(\\-(${IDENT})(\\.(${IDENT}))*)?\
|
||||
(\\+${FIELD}(\\.${FIELD})*)?$"
|
||||
|
||||
if [[ ${TAG_NAME} =~ ${SEMVER_REGEX} ]]; then
|
||||
echo "Tag ${TAG_NAME} matches SemVer regex, with groups ${BASH_REMATCH[1]} ${BASH_REMATCH[2]} ${BASH_REMATCH[3]} ${BASH_REMATCH[4]}"
|
||||
{
|
||||
echo "LEAN_VERSION_MAJOR=${BASH_REMATCH[1]}"
|
||||
echo "LEAN_VERSION_MINOR=${BASH_REMATCH[2]}"
|
||||
echo "LEAN_VERSION_PATCH=${BASH_REMATCH[3]}"
|
||||
echo "LEAN_SPECIAL_VERSION_DESC=${BASH_REMATCH[4]##-}"
|
||||
echo "RELEASE_TAG=$TAG_NAME"
|
||||
} >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
echo "Tag ${TAG_NAME} did not match SemVer regex."
|
||||
fi
|
||||
|
||||
build:
|
||||
needs: [configure]
|
||||
if: github.event_name != 'schedule' || github.repository == 'leanprover/lean4'
|
||||
strategy:
|
||||
matrix:
|
||||
include: ${{fromJson(needs.configure.outputs.matrix)}}
|
||||
# complete all jobs
|
||||
fail-fast: false
|
||||
needs: set-nightly
|
||||
# `always` *must* be used to continue even after a dependency has been skipped
|
||||
if: always() && (github.event_name != 'schedule' || github.repository == 'leanprover/lean4')
|
||||
runs-on: ${{ matrix.os }}
|
||||
defaults:
|
||||
run:
|
||||
shell: ${{ matrix.shell || 'nix-shell --run "bash -euxo pipefail {0}"' }}
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
# portable release build: use channel with older glibc (2.27)
|
||||
- name: Linux release
|
||||
os: ubuntu-latest
|
||||
release: true
|
||||
shell: nix-shell --arg pkgsDist "import (fetchTarball \"channel:nixos-19.03\") {{}}" --run "bash -euxo pipefail {0}"
|
||||
llvm-url: https://github.com/leanprover/lean-llvm/releases/download/14.0.0/lean-llvm-x86_64-linux-gnu.tar.zst
|
||||
prepare-llvm: ../script/prepare-llvm-linux.sh lean-llvm*
|
||||
binary-check: ldd -v
|
||||
# foreign code may be linked against more recent glibc
|
||||
CTEST_OPTIONS: -E 'foreign|leanlaketest_git'
|
||||
- name: Linux
|
||||
os: ubuntu-latest
|
||||
check-stage3: true
|
||||
test-speedcenter: true
|
||||
- name: Linux Debug
|
||||
os: ubuntu-latest
|
||||
CMAKE_OPTIONS: -DCMAKE_BUILD_TYPE=Debug
|
||||
- name: Linux fsanitize
|
||||
os: ubuntu-latest
|
||||
# turn off custom allocator & symbolic functions to make LSAN do its magic
|
||||
CMAKE_OPTIONS: -DLEAN_EXTRA_CXX_FLAGS=-fsanitize=address,undefined -DLEANC_EXTRA_FLAGS='-fsanitize=address,undefined -fsanitize-link-c++-runtime' -DSMALL_ALLOCATOR=OFF -DBSYMBOLIC=OFF
|
||||
# exclude problematic tests
|
||||
CTEST_OPTIONS: -E laketest
|
||||
- name: macOS
|
||||
os: macos-latest
|
||||
release: true
|
||||
shell: bash -euxo pipefail {0}
|
||||
CMAKE_OPTIONS: -DCMAKE_OSX_DEPLOYMENT_TARGET=10.15
|
||||
llvm-url: https://github.com/leanprover/lean-llvm/releases/download/14.0.0/lean-llvm-x86_64-apple-darwin.tar.zst
|
||||
prepare-llvm: ../script/prepare-llvm-macos.sh lean-llvm*
|
||||
binary-check: otool -L
|
||||
- name: macOS aarch64
|
||||
os: macos-latest
|
||||
release: true
|
||||
cross: true
|
||||
shell: bash -euxo pipefail {0}
|
||||
CMAKE_OPTIONS: -DCMAKE_OSX_DEPLOYMENT_TARGET=10.15 -DUSE_GMP=OFF -DLEAN_INSTALL_SUFFIX=-darwin_aarch64
|
||||
llvm-url: https://github.com/leanprover/lean-llvm/releases/download/14.0.0/lean-llvm-aarch64-apple-darwin.tar.zst https://github.com/leanprover/lean-llvm/releases/download/14.0.0/lean-llvm-x86_64-apple-darwin.tar.zst
|
||||
prepare-llvm: EXTRA_FLAGS=--target=aarch64-apple-darwin ../script/prepare-llvm-macos.sh lean-llvm-aarch64-* lean-llvm-x86_64-*
|
||||
binary-check: otool -L
|
||||
- name: Windows
|
||||
os: windows-2022
|
||||
release: true
|
||||
shell: msys2 {0}
|
||||
CMAKE_OPTIONS: -G "Unix Makefiles"
|
||||
# for reasons unknown, interactivetests are flaky on Windows
|
||||
CTEST_OPTIONS: --repeat until-pass:2
|
||||
llvm-url: https://github.com/leanprover/lean-llvm/releases/download/14.0.0/lean-llvm-x86_64-w64-windows-gnu.tar.zst
|
||||
prepare-llvm: ../script/prepare-llvm-mingw.sh lean-llvm*
|
||||
binary-check: ldd
|
||||
- name: Linux aarch64
|
||||
os: ubuntu-latest
|
||||
CMAKE_OPTIONS: -DCMAKE_PREFIX_PATH=$GMP -DLEAN_INSTALL_SUFFIX=-linux_aarch64
|
||||
release: true
|
||||
cross: true
|
||||
shell: nix-shell --arg pkgsDist "import (fetchTarball \"channel:nixos-19.03\") {{ localSystem.config = \"aarch64-unknown-linux-gnu\"; }}" --run "bash -euxo pipefail {0}"
|
||||
llvm-url: https://github.com/leanprover/lean-llvm/releases/download/14.0.0/lean-llvm-x86_64-linux-gnu.tar.zst https://github.com/leanprover/lean-llvm/releases/download/14.0.0/lean-llvm-aarch64-linux-gnu.tar.zst
|
||||
prepare-llvm: EXTRA_FLAGS=--target=aarch64-unknown-linux-gnu ../script/prepare-llvm-linux.sh lean-llvm-aarch64-* lean-llvm-x86_64-*
|
||||
# complete all jobs
|
||||
fail-fast: false
|
||||
name: ${{ matrix.name }}
|
||||
env:
|
||||
# must be inside workspace
|
||||
@@ -264,19 +117,14 @@ jobs:
|
||||
LSAN_OPTIONS: max_leaks=10
|
||||
# somehow MinGW clang64 (or cmake?) defaults to `g++` even though it doesn't exist
|
||||
CXX: c++
|
||||
MACOSX_DEPLOYMENT_TARGET: 10.15
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
submodules: true
|
||||
# the default is to use a virtual merge commit between the PR and master: just use the PR
|
||||
ref: ${{ github.event.pull_request.head.sha }}
|
||||
- name: Install Nix
|
||||
uses: cachix/install-nix-action@v18
|
||||
with:
|
||||
install_url: https://releases.nixos.org/nix/nix-2.12.0/install
|
||||
if: matrix.os == 'ubuntu-latest' && !matrix.cmultilib
|
||||
uses: cachix/install-nix-action@v15
|
||||
if: matrix.os == 'ubuntu-latest'
|
||||
- name: Install MSYS2
|
||||
uses: msys2/setup-msys2@v2
|
||||
with:
|
||||
@@ -286,21 +134,10 @@ jobs:
|
||||
if: matrix.os == 'windows-2022'
|
||||
- name: Install Brew Packages
|
||||
run: |
|
||||
brew install ccache tree zstd coreutils gmp
|
||||
brew install ccache tree zstd coreutils
|
||||
if: matrix.os == 'macos-latest'
|
||||
- name: Setup emsdk
|
||||
uses: mymindstorm/setup-emsdk@v12
|
||||
with:
|
||||
version: 3.1.44
|
||||
actions-cache-folder: emsdk
|
||||
if: matrix.wasm
|
||||
- name: Install 32bit c libs
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y gcc-multilib g++-multilib ccache
|
||||
if: matrix.cmultilib
|
||||
- name: Cache
|
||||
uses: actions/cache@v3
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: .ccache
|
||||
key: ${{ matrix.name }}-build-v3-${{ github.sha }}
|
||||
@@ -312,40 +149,18 @@ jobs:
|
||||
# open nix-shell once for initial setup
|
||||
true
|
||||
if: matrix.os == 'ubuntu-latest'
|
||||
- name: Set up core dumps
|
||||
run: |
|
||||
mkdir -p $PWD/coredumps
|
||||
# store in current directory, for easy uploading together with binary
|
||||
echo $PWD/coredumps/%e.%p.%t | sudo tee /proc/sys/kernel/core_pattern
|
||||
if: matrix.os == 'ubuntu-latest'
|
||||
- name: Build
|
||||
run: |
|
||||
mkdir build
|
||||
cd build
|
||||
ulimit -c unlimited # coredumps
|
||||
# arguments passed to `cmake`
|
||||
# this also enables githash embedding into stage 1 library
|
||||
OPTIONS=(-DCHECK_OLEAN_VERSION=ON)
|
||||
OPTIONS+=(-DLEAN_EXTRA_MAKE_OPTS=-DwarningAsError=true)
|
||||
if [[ -n '${{ matrix.cross_target }}' ]]; then
|
||||
# used by `prepare-llvm`
|
||||
export EXTRA_FLAGS=--target=${{ matrix.cross_target }}
|
||||
OPTIONS+=(-DLEAN_PLATFORM_TARGET=${{ matrix.cross_target }})
|
||||
fi
|
||||
OPTIONS=()
|
||||
if [[ -n '${{ matrix.prepare-llvm }}' ]]; then
|
||||
wget -q ${{ matrix.llvm-url }}
|
||||
PREPARE="$(${{ matrix.prepare-llvm }})"
|
||||
eval "OPTIONS+=($PREPARE)"
|
||||
fi
|
||||
if [[ -n '${{ matrix.release }}' && -n '${{ needs.configure.outputs.nightly }}' ]]; then
|
||||
OPTIONS+=(-DLEAN_SPECIAL_VERSION_DESC=${{ needs.configure.outputs.nightly }})
|
||||
fi
|
||||
if [[ -n '${{ matrix.release }}' && -n '${{ needs.configure.outputs.RELEASE_TAG }}' ]]; then
|
||||
OPTIONS+=(-DLEAN_VERSION_MAJOR=${{ needs.configure.outputs.LEAN_VERSION_MAJOR }})
|
||||
OPTIONS+=(-DLEAN_VERSION_MINOR=${{ needs.configure.outputs.LEAN_VERSION_MINOR }})
|
||||
OPTIONS+=(-DLEAN_VERSION_PATCH=${{ needs.configure.outputs.LEAN_VERSION_PATCH }})
|
||||
OPTIONS+=(-DLEAN_VERSION_IS_RELEASE=1)
|
||||
OPTIONS+=(-DLEAN_SPECIAL_VERSION_DESC=${{ needs.configure.outputs.LEAN_SPECIAL_VERSION_DESC }})
|
||||
if [[ -n '${{ matrix.release }}' && -n '${{ needs.set-nightly.outputs.nightly }}' ]]; then
|
||||
OPTIONS+=(-DLEAN_SPECIAL_VERSION_DESC=${{ needs.set-nightly.outputs.nightly }})
|
||||
fi
|
||||
# contortion to support empty OPTIONS with old macOS bash
|
||||
cmake .. ${{ matrix.CMAKE_OPTIONS }} ${OPTIONS[@]+"${OPTIONS[@]}"} -DLEAN_INSTALL_PREFIX=$PWD/..
|
||||
@@ -356,23 +171,27 @@ jobs:
|
||||
- name: List Install Tree
|
||||
run: |
|
||||
# omit contents of Init/, ...
|
||||
tree --du -h lean-*-* | grep -E ' (Init|Lean|Lake|LICENSE|[a-z])'
|
||||
tree --du -h lean-* | grep -E ' (Init|Std|Lean|Lake|LICENSE|[a-z])'
|
||||
- name: Pack
|
||||
run: |
|
||||
dir=$(echo lean-*-*)
|
||||
dir=$(echo lean-*)
|
||||
mkdir pack
|
||||
# high-compression tar.zst + zip for release, fast tar.zst otherwise
|
||||
if [[ '${{ startsWith(github.ref, 'refs/tags/') && matrix.release }}' == true || -n '${{ needs.configure.outputs.nightly }}' || -n '${{ needs.configure.outputs.RELEASE_TAG }}' ]]; then
|
||||
${{ matrix.tar || 'tar' }} cf - $dir | zstd -T0 --no-progress -19 -o pack/$dir.tar.zst
|
||||
if [[ '${{ startsWith(github.ref, 'refs/tags/v') && matrix.release }}' == true || -n '${{ needs.set-nightly.outputs.nightly }}' ]]; then
|
||||
tar cf - $dir | zstd -T0 --no-progress -19 -o pack/$dir.tar.zst
|
||||
zip -rq pack/$dir.zip $dir
|
||||
else
|
||||
${{ matrix.tar || 'tar' }} cf - $dir | zstd -T0 --no-progress -o pack/$dir.tar.zst
|
||||
tar cf - $dir | zstd -T0 --no-progress -o pack/$dir.tar.zst
|
||||
fi
|
||||
- uses: actions/upload-artifact@v3
|
||||
- uses: actions/upload-artifact@v2
|
||||
if: matrix.release
|
||||
with:
|
||||
name: build-${{ matrix.name }}
|
||||
path: pack/*
|
||||
- name: Trigger TPIL build
|
||||
run: |
|
||||
curl -u '${{ github.repository_owner }}:${{ secrets.PUSH_NIGHTLY_TOKEN }}' 'https://api.github.com/repos/leanprover/theorem_proving_in_lean4/dispatches' -X POST -d '{ "event_type": "build_event" }'
|
||||
if: matrix.name == 'Linux release' && github.event_name == 'push' && github.repository == 'leanprover/lean4'
|
||||
- name: Lean stats
|
||||
run: |
|
||||
build/stage1/bin/lean --stats src/Lean.lean
|
||||
@@ -380,25 +199,22 @@ jobs:
|
||||
- name: Test
|
||||
run: |
|
||||
cd build/stage1
|
||||
ulimit -c unlimited # coredumps
|
||||
# exclude nonreproducible test
|
||||
ctest -j4 --output-on-failure ${{ matrix.CTEST_OPTIONS }} < /dev/null
|
||||
if: (matrix.wasm || !matrix.cross) && needs.configure.outputs.quick == 'false'
|
||||
ctest -j4 --output-on-failure -E leanlaketest_git ${{ matrix.CTEST_OPTIONS }} < /dev/null
|
||||
if: ${{ !matrix.cross }}
|
||||
- name: Check Test Binary
|
||||
run: ${{ matrix.binary-check }} tests/compiler/534.lean.out
|
||||
if: ${{ !matrix.cross && needs.configure.outputs.quick == 'false' }}
|
||||
if: ${{ !matrix.cross }}
|
||||
- name: Build Stage 2
|
||||
run: |
|
||||
cd build
|
||||
ulimit -c unlimited # coredumps
|
||||
make -j4 stage2
|
||||
if: matrix.test-speedcenter
|
||||
if: matrix.build-stage2 || matrix.check-stage3
|
||||
- name: Check Stage 3
|
||||
run: |
|
||||
cd build
|
||||
ulimit -c unlimited # coredumps
|
||||
make -j4 check-stage3
|
||||
if: matrix.test-speedcenter
|
||||
if: matrix.check-stage3
|
||||
- name: Test Speedcenter Benchmarks
|
||||
run: |
|
||||
echo -1 | sudo tee /proc/sys/kernel/perf_event_paranoid
|
||||
@@ -409,57 +225,21 @@ jobs:
|
||||
- name: Check rebootstrap
|
||||
run: |
|
||||
cd build
|
||||
ulimit -c unlimited # coredumps
|
||||
make update-stage0 && make -j4
|
||||
if: matrix.name == 'Linux' && needs.configure.outputs.quick == 'false'
|
||||
if: matrix.name == 'Linux'
|
||||
- name: CCache stats
|
||||
run: ccache -s
|
||||
- name: Show stacktrace for coredumps
|
||||
if: ${{ failure() && matrix.os == 'ubuntu-latest' }}
|
||||
run: |
|
||||
for c in coredumps/*; do
|
||||
progbin="$(file $c | sed "s/.*execfn: '\([^']*\)'.*/\1/")"
|
||||
echo bt | $GDB/bin/gdb -q $progbin $c || true
|
||||
done
|
||||
- name: Upload coredumps
|
||||
uses: actions/upload-artifact@v3
|
||||
if: ${{ failure() && matrix.os == 'ubuntu-latest' }}
|
||||
with:
|
||||
name: coredumps-${{ matrix.name }}
|
||||
path: |
|
||||
./coredumps
|
||||
./build/stage0/bin/lean
|
||||
./build/stage0/lib/lean/libleanshared.so
|
||||
./build/stage1/bin/lean
|
||||
./build/stage1/lib/lean/libleanshared.so
|
||||
./build/stage2/bin/lean
|
||||
./build/stage2/lib/lean/libleanshared.so
|
||||
|
||||
# This job collects results from all the matrix jobs
|
||||
# This can be made the “required” job, instead of listing each
|
||||
# matrix job separately
|
||||
all-done:
|
||||
name: Build matrix complete
|
||||
runs-on: ubuntu-latest
|
||||
needs: build
|
||||
if: ${{ always() }}
|
||||
steps:
|
||||
- if: contains(needs.*.result, 'failure') || contains(needs.*.result, 'cancelled')
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
core.setFailed('Some jobs failed')
|
||||
|
||||
# This job creates releases from tags
|
||||
# (whether they are "unofficial" releases for experiments, or official releases when the tag is "v" followed by a semver string.)
|
||||
# We do not attempt to automatically construct a changelog here:
|
||||
# unofficial releases don't need them, and official release notes will be written by a human.
|
||||
release:
|
||||
if: startsWith(github.ref, 'refs/tags/')
|
||||
# When GitHub says "If a job fails, all jobs that need it are skipped unless
|
||||
# the jobs use a conditional expression that causes the job to continue.", don't believe
|
||||
# their lies. It's actually the entire closure (i.e. including `set-nightly`) that
|
||||
# must succeed for subsequent to be run without `always()`.
|
||||
if: always() && needs.build.result == 'success' && startsWith(github.ref, 'refs/tags/v')
|
||||
runs-on: ubuntu-latest
|
||||
needs: build
|
||||
steps:
|
||||
- uses: actions/download-artifact@v3
|
||||
- uses: actions/download-artifact@v2
|
||||
with:
|
||||
path: artifacts
|
||||
- name: Release
|
||||
@@ -470,36 +250,33 @@ jobs:
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
# This job creates nightly releases during the cron job.
|
||||
# It is responsible for creating the tag, and automatically generating a changelog.
|
||||
release-nightly:
|
||||
needs: [configure, build]
|
||||
if: needs.configure.outputs.nightly
|
||||
needs: [set-nightly, build]
|
||||
if: needs.set-nightly.outputs.nightly
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
# needed for tagging
|
||||
fetch-depth: 0
|
||||
token: ${{ secrets.PUSH_NIGHTLY_TOKEN }}
|
||||
- uses: actions/download-artifact@v3
|
||||
- uses: actions/download-artifact@v2
|
||||
with:
|
||||
path: artifacts
|
||||
- name: Prepare Nightly Release
|
||||
run: |
|
||||
git remote add nightly https://foo:'${{ secrets.PUSH_NIGHTLY_TOKEN }}'@github.com/${{ github.repository_owner }}/lean4-nightly.git
|
||||
git fetch nightly --tags
|
||||
git tag "${{ needs.configure.outputs.nightly }}"
|
||||
git push nightly "${{ needs.configure.outputs.nightly }}"
|
||||
git push -f origin refs/tags/${{ needs.configure.outputs.nightly }}:refs/heads/nightly
|
||||
last_tag="$(git log HEAD^ --simplify-by-decoration --pretty="format:%d" | grep -o "nightly-[-0-9]*" | head -n 1)"
|
||||
git tag ${{ needs.set-nightly.outputs.nightly }}
|
||||
git push nightly ${{ needs.set-nightly.outputs.nightly }}
|
||||
last_tag=$(git describe HEAD^ --abbrev=0 --tags)
|
||||
echo -e "*Changes since ${last_tag}:*\n\n" > diff.md
|
||||
git show "$last_tag":RELEASES.md > old.md
|
||||
git show $last_tag:RELEASES.md > old.md
|
||||
#./script/diff_changelogs.py old.md doc/changes.md >> diff.md
|
||||
diff --changed-group-format='%>' --unchanged-group-format='' old.md RELEASES.md >> diff.md || true
|
||||
echo -e "\n*Full commit log*\n" >> diff.md
|
||||
git log --oneline "$last_tag"..HEAD | sed 's/^/* /' >> diff.md
|
||||
git log --oneline $last_tag..HEAD | sed 's/^/* /' >> diff.md
|
||||
- name: Release Nightly
|
||||
uses: softprops/action-gh-release@v1
|
||||
with:
|
||||
@@ -507,7 +284,7 @@ jobs:
|
||||
prerelease: true
|
||||
files: artifacts/*/*
|
||||
fail_on_unmatched_files: true
|
||||
tag_name: ${{ needs.configure.outputs.nightly }}
|
||||
tag_name: ${{ needs.set-nightly.outputs.nightly }}
|
||||
repository: ${{ github.repository_owner }}/lean4-nightly
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.PUSH_NIGHTLY_TOKEN }}
|
||||
|
||||
43
.github/workflows/labels-from-comments.yml
vendored
43
.github/workflows/labels-from-comments.yml
vendored
@@ -1,43 +0,0 @@
|
||||
# This workflow allows any user to add one of the `awaiting-review`, `awaiting-author`, or `WIP` labels,
|
||||
# by commenting on the PR or issue.
|
||||
# Other labels from this set are removed automatically at the same time.
|
||||
|
||||
name: Label PR based on Comment
|
||||
|
||||
on:
|
||||
issue_comment:
|
||||
types: [created]
|
||||
|
||||
jobs:
|
||||
update-label:
|
||||
if: github.event.issue.pull_request != null && (contains(github.event.comment.body, 'awaiting-review') || contains(github.event.comment.body, 'awaiting-author') || contains(github.event.comment.body, 'WIP'))
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Add label based on comment
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
script: |
|
||||
const { owner, repo, number: issue_number } = context.issue;
|
||||
const commentLines = context.payload.comment.body.split('\r\n');
|
||||
|
||||
const awaitingReview = commentLines.includes('awaiting-review');
|
||||
const awaitingAuthor = commentLines.includes('awaiting-author');
|
||||
const wip = commentLines.includes('WIP');
|
||||
|
||||
if (awaitingReview || awaitingAuthor || wip) {
|
||||
await github.rest.issues.removeLabel({ owner, repo, issue_number, name: 'awaiting-review' }).catch(() => {});
|
||||
await github.rest.issues.removeLabel({ owner, repo, issue_number, name: 'awaiting-author' }).catch(() => {});
|
||||
await github.rest.issues.removeLabel({ owner, repo, issue_number, name: 'WIP' }).catch(() => {});
|
||||
}
|
||||
|
||||
if (awaitingReview) {
|
||||
await github.rest.issues.addLabels({ owner, repo, issue_number, labels: ['awaiting-review'] });
|
||||
}
|
||||
if (awaitingAuthor) {
|
||||
await github.rest.issues.addLabels({ owner, repo, issue_number, labels: ['awaiting-author'] });
|
||||
}
|
||||
if (wip) {
|
||||
await github.rest.issues.addLabels({ owner, repo, issue_number, labels: ['WIP'] });
|
||||
}
|
||||
81
.github/workflows/nix-ci.yml
vendored
81
.github/workflows/nix-ci.yml
vendored
@@ -6,18 +6,15 @@ on:
|
||||
tags:
|
||||
- '*'
|
||||
pull_request:
|
||||
merge_group:
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
branches:
|
||||
- master
|
||||
|
||||
jobs:
|
||||
Build:
|
||||
runs-on: ${{ matrix.os }}
|
||||
defaults:
|
||||
run:
|
||||
shell: nix run .#ciShell -- bash -euxo pipefail {0}
|
||||
shell: nix -v --experimental-features "nix-command flakes" run .#ciShell -- bash -euxo pipefail {0}
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
@@ -29,15 +26,20 @@ jobs:
|
||||
fail-fast: false
|
||||
name: ${{ matrix.name }}
|
||||
env:
|
||||
NIX_BUILD_ARGS: --print-build-logs --fallback
|
||||
NIX_BUILD_ARGS: -v --print-build-logs --fallback
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v2
|
||||
- name: Install Nix
|
||||
uses: cachix/install-nix-action@v15
|
||||
with:
|
||||
# the default is to use a virtual merge commit between the PR and master: just use the PR
|
||||
ref: ${{ github.event.pull_request.head.sha }}
|
||||
# https://github.com/NixOS/nix/issues/6572
|
||||
install_url: https://releases.nixos.org/nix/nix-2.7.0/install
|
||||
extra_nix_config: |
|
||||
extra-sandbox-paths = /nix/var/cache/ccache
|
||||
substituters = file://${{ github.workspace }}/nix-store-cache-copy?priority=10&trusted=true https://cache.nixos.org
|
||||
- name: Set Up Nix Cache
|
||||
uses: actions/cache@v3
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: nix-store-cache
|
||||
key: ${{ matrix.name }}-nix-store-cache-${{ github.sha }}
|
||||
@@ -49,18 +51,13 @@ jobs:
|
||||
run: |
|
||||
# Nix seems to mutate the cache, so make a copy
|
||||
cp -r nix-store-cache nix-store-cache-copy || true
|
||||
- name: Install Nix
|
||||
uses: DeterminateSystems/nix-installer-action@main
|
||||
with:
|
||||
extra-conf: |
|
||||
extra-sandbox-paths = /nix/var/cache/ccache?
|
||||
substituters = file://${{ github.workspace }}/nix-store-cache-copy?priority=10&trusted=true https://cache.nixos.org
|
||||
- name: Prepare CCache Cache
|
||||
shell: bash -euxo pipefail {0}
|
||||
run: |
|
||||
sudo mkdir -m0770 -p /nix/var/cache/ccache
|
||||
sudo chown -R $USER /nix/var/cache/ccache
|
||||
- name: Setup CCache Cache
|
||||
uses: actions/cache@v3
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: /nix/var/cache/ccache
|
||||
key: ${{ matrix.name }}-nix-ccache-${{ github.sha }}
|
||||
@@ -68,36 +65,28 @@ jobs:
|
||||
restore-keys: |
|
||||
${{ matrix.name }}-nix-ccache
|
||||
- name: Further Set Up CCache Cache
|
||||
shell: bash -euxo pipefail {0}
|
||||
run: |
|
||||
sudo chown -R root:nixbld /nix/var/cache
|
||||
sudo chmod -R 770 /nix/var/cache
|
||||
- name: Install Cachix
|
||||
uses: cachix/cachix-action@v12
|
||||
uses: cachix/cachix-action@v10
|
||||
with:
|
||||
name: lean4
|
||||
authToken: '${{ secrets.CACHIX_AUTH_TOKEN }}'
|
||||
skipPush: true # we push specific outputs only
|
||||
- name: Build
|
||||
run: |
|
||||
nix build $NIX_BUILD_ARGS .#cacheRoots -o push-build
|
||||
# .o files are not a runtime dependency on macOS because of lack of thin archives
|
||||
nix build $NIX_BUILD_ARGS .#stage0 .#stage1.lean-all .#Lean.oTree .#iTree .#modDepsFiles -o push-build
|
||||
- name: Test
|
||||
run: |
|
||||
nix build $NIX_BUILD_ARGS .#test -o push-test
|
||||
- name: Build manual
|
||||
run: |
|
||||
nix build $NIX_BUILD_ARGS --update-input lean --no-write-lock-file ./doc#{lean-mdbook,leanInk,alectryon,test,inked} -o push-doc
|
||||
nix build $NIX_BUILD_ARGS --update-input lean --no-write-lock-file ./doc#{lean-mdbook,leanInk,alectryon,test} -o push-doc
|
||||
nix build $NIX_BUILD_ARGS --update-input lean --no-write-lock-file ./doc
|
||||
# https://github.com/netlify/cli/issues/1809
|
||||
cp -r --dereference ./result ./dist
|
||||
if: matrix.name == 'Nix Linux'
|
||||
- name: Check manual for broken links
|
||||
id: lychee
|
||||
uses: lycheeverse/lychee-action@v1.9.0
|
||||
with:
|
||||
fail: false # report errors but do not block CI on temporary failures
|
||||
# gmplib.org consistently times out from GH actions
|
||||
# the GitHub token is to avoid rate limiting
|
||||
args: --base './dist' --no-progress --github-token ${{ secrets.GITHUB_TOKEN }} --exclude 'gmplib.org' './dist/**/*.html'
|
||||
- name: Push to Cachix
|
||||
run: |
|
||||
[ -z "${{ secrets.CACHIX_AUTH_TOKEN }}" ] || cachix push -j4 lean4 ./push-* || true
|
||||
@@ -105,31 +94,15 @@ jobs:
|
||||
run: |
|
||||
rm -rf nix-store-cache || true
|
||||
nix copy ./push-* --to file://$PWD/nix-store-cache?compression=none
|
||||
- id: deploy-info
|
||||
name: Compute Deployment Metadata
|
||||
run: |
|
||||
set -e
|
||||
python3 -c 'import base64; print("alias="+base64.urlsafe_b64encode(bytes.fromhex("${{github.sha}}")).decode("utf-8").rstrip("="))' >> "$GITHUB_OUTPUT"
|
||||
echo "message=`git log -1 --pretty=format:"%s"`" >> "$GITHUB_OUTPUT"
|
||||
- name: Publish manual to Netlify
|
||||
uses: nwtgck/actions-netlify@v2.0
|
||||
id: publish-manual
|
||||
- name: Publish manual
|
||||
uses: peaceiris/actions-gh-pages@v3
|
||||
with:
|
||||
publish-dir: ./dist
|
||||
production-branch: master
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
deploy-message: |
|
||||
${{ github.event_name == 'pull_request' && format('pr#{0}: {1}', github.event.number, github.event.pull_request.title) || format('ref/{0}: {1}', github.ref_name, steps.deploy-info.outputs.message) }}
|
||||
alias: ${{ steps.deploy-info.outputs.alias }}
|
||||
enable-commit-comment: false
|
||||
enable-pull-request-comment: false
|
||||
github-deployment-environment: "lean-lang.org/lean4/doc"
|
||||
fails-without-credentials: false
|
||||
env:
|
||||
NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }}
|
||||
NETLIFY_SITE_ID: "b8e805d2-7e9b-4f80-91fb-a84d72fc4a68"
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
publish_dir: ./result
|
||||
destination_dir: ./doc
|
||||
if: matrix.name == 'Nix Linux' && github.ref == 'refs/heads/master' && github.event_name == 'push'
|
||||
- name: Fixup CCache Cache
|
||||
run: |
|
||||
sudo chown -R $USER /nix/var/cache
|
||||
sudo chown -R $USER /nix/var/cache/ccache
|
||||
- name: CCache stats
|
||||
run: CCACHE_DIR=/nix/var/cache/ccache nix run .#nixpkgs.ccache -- -s
|
||||
|
||||
339
.github/workflows/pr-release.yml
vendored
339
.github/workflows/pr-release.yml
vendored
@@ -1,339 +0,0 @@
|
||||
# Push a release to the lean4-pr-releases repository, whenever someone pushes to a PR branch.
|
||||
|
||||
# This needs to run with the `secrets.PR_RELEASES_TOKEN` token available,
|
||||
# but PR branches will generally come from forks,
|
||||
# so it is not possible to run this using the `pull_request` or `pull_request_target` workflows.
|
||||
# Instead we use `workflow_run`, which essentially allows us to escalate privileges
|
||||
# (but only runs the CI as described in the `master` branch, not in the PR branch).
|
||||
|
||||
# The main specification/documentation for this workflow is at
|
||||
# https://leanprover-community.github.io/contribute/tags_and_branches.html
|
||||
# Keep that in sync!
|
||||
|
||||
name: PR release
|
||||
|
||||
on:
|
||||
workflow_run: # https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#workflow_run
|
||||
workflows: [CI]
|
||||
types: [completed]
|
||||
|
||||
jobs:
|
||||
on-success:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event.workflow_run.conclusion == 'success' && github.event.workflow_run.event == 'pull_request' && github.repository == 'leanprover/lean4'
|
||||
steps:
|
||||
- name: Retrieve information about the original workflow
|
||||
uses: potiuk/get-workflow-origin@v1_1 # https://github.com/marketplace/actions/get-workflow-origin
|
||||
# This action is deprecated and archived, but it seems hard to find a better solution for getting the PR number
|
||||
# see https://github.com/orgs/community/discussions/25220 for some discussion
|
||||
id: workflow-info
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
sourceRunId: ${{ github.event.workflow_run.id }}
|
||||
|
||||
- name: Download artifact from the previous workflow.
|
||||
if: ${{ steps.workflow-info.outputs.pullRequestNumber != '' }}
|
||||
id: download-artifact
|
||||
uses: dawidd6/action-download-artifact@v2 # https://github.com/marketplace/actions/download-workflow-artifact
|
||||
with:
|
||||
run_id: ${{ github.event.workflow_run.id }}
|
||||
path: artifacts
|
||||
name: build-.*
|
||||
name_is_regexp: true
|
||||
|
||||
- name: Push tag
|
||||
if: ${{ steps.workflow-info.outputs.pullRequestNumber != '' }}
|
||||
run: |
|
||||
git init --bare lean4.git
|
||||
git -C lean4.git remote add origin https://github.com/${{ github.repository_owner }}/lean4.git
|
||||
git -C lean4.git fetch -n origin master
|
||||
git -C lean4.git fetch -n origin "${{ steps.workflow-info.outputs.sourceHeadSha }}"
|
||||
git -C lean4.git tag -f pr-release-${{ steps.workflow-info.outputs.pullRequestNumber }} "${{ steps.workflow-info.outputs.sourceHeadSha }}"
|
||||
git -C lean4.git remote add pr-releases https://foo:'${{ secrets.PR_RELEASES_TOKEN }}'@github.com/${{ github.repository_owner }}/lean4-pr-releases.git
|
||||
git -C lean4.git push -f pr-releases pr-release-${{ steps.workflow-info.outputs.pullRequestNumber }}
|
||||
- name: Delete existing release if present
|
||||
if: ${{ steps.workflow-info.outputs.pullRequestNumber != '' }}
|
||||
run: |
|
||||
# Try to delete any existing release for the current PR.
|
||||
gh release delete --repo ${{ github.repository_owner }}/lean4-pr-releases pr-release-${{ steps.workflow-info.outputs.pullRequestNumber }} -y || true
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.PR_RELEASES_TOKEN }}
|
||||
- name: Release
|
||||
if: ${{ steps.workflow-info.outputs.pullRequestNumber != '' }}
|
||||
uses: softprops/action-gh-release@v1
|
||||
with:
|
||||
name: Release for PR ${{ steps.workflow-info.outputs.pullRequestNumber }}
|
||||
# There are coredumps files here as well, but all in deeper subdirectories.
|
||||
files: artifacts/*/*
|
||||
fail_on_unmatched_files: true
|
||||
draft: false
|
||||
tag_name: pr-release-${{ steps.workflow-info.outputs.pullRequestNumber }}
|
||||
repository: ${{ github.repository_owner }}/lean4-pr-releases
|
||||
env:
|
||||
# The token used here must have `workflow` privileges.
|
||||
GITHUB_TOKEN: ${{ secrets.PR_RELEASES_TOKEN }}
|
||||
|
||||
- name: Report release status
|
||||
if: ${{ steps.workflow-info.outputs.pullRequestNumber != '' }}
|
||||
uses: actions/github-script@v6
|
||||
with:
|
||||
script: |
|
||||
await github.rest.repos.createCommitStatus({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
sha: "${{ steps.workflow-info.outputs.sourceHeadSha }}",
|
||||
state: "success",
|
||||
context: "PR toolchain",
|
||||
description: "${{ github.repository_owner }}/lean4-pr-releases:pr-release-${{ steps.workflow-info.outputs.pullRequestNumber }}",
|
||||
});
|
||||
|
||||
- name: Add label
|
||||
if: ${{ steps.workflow-info.outputs.pullRequestNumber != '' }}
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
await github.rest.issues.addLabels({
|
||||
issue_number: ${{ steps.workflow-info.outputs.pullRequestNumber }},
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
labels: ['toolchain-available']
|
||||
})
|
||||
|
||||
# Next, determine the most recent nightly release in this PR's history.
|
||||
- name: Find most recent nightly in feature branch
|
||||
id: most-recent-nightly-tag
|
||||
if: ${{ steps.workflow-info.outputs.pullRequestNumber != '' }}
|
||||
run: |
|
||||
git -C lean4.git remote add nightly https://github.com/leanprover/lean4-nightly.git
|
||||
git -C lean4.git fetch nightly '+refs/tags/nightly-*:refs/tags/nightly-*'
|
||||
git -C lean4.git tag --merged "${{ steps.workflow-info.outputs.sourceHeadSha }}" --list "nightly-*" \
|
||||
| sort -rV | head -n 1 | sed "s/^nightly-*/MOST_RECENT_NIGHTLY=/" | tee -a "$GITHUB_ENV"
|
||||
|
||||
- name: 'Setup jq'
|
||||
if: ${{ steps.workflow-info.outputs.pullRequestNumber != '' }}
|
||||
uses: dcarbone/install-jq-action@v1.0.1
|
||||
|
||||
# Check that the most recently nightly coincides with 'git merge-base HEAD master'
|
||||
- name: Check merge-base and nightly-testing-YYYY-MM-DD
|
||||
if: ${{ steps.workflow-info.outputs.pullRequestNumber != '' }}
|
||||
id: ready
|
||||
run: |
|
||||
echo "Most recent nightly release in your branch: $MOST_RECENT_NIGHTLY"
|
||||
NIGHTLY_SHA=$(git -C lean4.git rev-parse "nightly-$MOST_RECENT_NIGHTLY^{commit}")
|
||||
echo "SHA of most recent nightly release: $NIGHTLY_SHA"
|
||||
MERGE_BASE_SHA=$(git -C lean4.git merge-base origin/master "${{ steps.workflow-info.outputs.sourceHeadSha }}")
|
||||
echo "SHA of merge-base: $MERGE_BASE_SHA"
|
||||
if [ "$NIGHTLY_SHA" = "$MERGE_BASE_SHA" ]; then
|
||||
echo "The merge base of this PR coincides with the nightly release"
|
||||
|
||||
MATHLIB_REMOTE_TAGS="$(git ls-remote https://github.com/leanprover-community/mathlib4.git nightly-testing-"$MOST_RECENT_NIGHTLY")"
|
||||
|
||||
if [[ -n "$MATHLIB_REMOTE_TAGS" ]]; then
|
||||
echo "... and Mathlib has a 'nightly-testing-$MOST_RECENT_NIGHTLY' tag."
|
||||
MESSAGE=""
|
||||
else
|
||||
echo "... but Mathlib does not yet have a 'nightly-testing-$MOST_RECENT_NIGHTLY' tag."
|
||||
MESSAGE="- ❗ Mathlib CI can not be attempted yet, as the \`nightly-testing-$MOST_RECENT_NIGHTLY\` tag does not exist there yet. We will retry when you push more commits. If you rebase your branch onto \`nightly-with-mathlib\`, Mathlib CI should run now."
|
||||
fi
|
||||
|
||||
STD_REMOTE_TAGS="$(git ls-remote https://github.com/leanprover/std4.git nightly-testing-"$MOST_RECENT_NIGHTLY")"
|
||||
|
||||
if [[ -n "$STD_REMOTE_TAGS" ]]; then
|
||||
echo "... and Std has a 'nightly-testing-$MOST_RECENT_NIGHTLY' tag."
|
||||
MESSAGE=""
|
||||
else
|
||||
echo "... but Std does not yet have a 'nightly-testing-$MOST_RECENT_NIGHTLY' tag."
|
||||
MESSAGE="- ❗ Std CI can not be attempted yet, as the \`nightly-testing-$MOST_RECENT_NIGHTLY\` tag does not exist there yet. We will retry when you push more commits. If you rebase your branch onto \`nightly-with-mathlib\`, Std CI should run now."
|
||||
fi
|
||||
|
||||
else
|
||||
echo "The most recently nightly tag on this branch has SHA: $NIGHTLY_SHA"
|
||||
echo "but 'git merge-base origin/master HEAD' reported: $MERGE_BASE_SHA"
|
||||
git -C lean4.git log -10 origin/master
|
||||
|
||||
MESSAGE="- ❗ Std/Mathlib CI will not be attempted unless your PR branches off the \`nightly-with-mathlib\` branch."
|
||||
fi
|
||||
|
||||
if [[ -n "$MESSAGE" ]]; then
|
||||
|
||||
echo "Checking existing messages"
|
||||
|
||||
# The code for updating comments is duplicated in mathlib's
|
||||
# scripts/lean-pr-testing-comments.sh
|
||||
# so keep in sync
|
||||
|
||||
# Use GitHub API to check if a comment already exists
|
||||
existing_comment="$(curl -L -s -H "Authorization: token ${{ secrets.MATHLIB4_BOT }}" \
|
||||
-H "Accept: application/vnd.github.v3+json" \
|
||||
"https://api.github.com/repos/leanprover/lean4/issues/${{ steps.workflow-info.outputs.pullRequestNumber }}/comments" \
|
||||
| jq 'first(.[] | select(.body | test("^- . Mathlib") or startswith("Mathlib CI status")) | select(.user.login == "leanprover-community-mathlib4-bot"))')"
|
||||
existing_comment_id="$(echo "$existing_comment" | jq -r .id)"
|
||||
existing_comment_body="$(echo "$existing_comment" | jq -r .body)"
|
||||
|
||||
if [[ "$existing_comment_body" != *"$MESSAGE"* ]]; then
|
||||
MESSAGE="$MESSAGE ($(date "+%Y-%m-%d %H:%M:%S"))"
|
||||
|
||||
echo "Posting message to the comments: $MESSAGE"
|
||||
|
||||
# Append new result to the existing comment or post a new comment
|
||||
# It's essential we use the MATHLIB4_BOT token here, so that Mathlib CI can subsequently edit the comment.
|
||||
if [ -z "$existing_comment_id" ]; then
|
||||
INTRO="Mathlib CI status ([docs](https://leanprover-community.github.io/contribute/tags_and_branches.html)):"
|
||||
# Post new comment with a bullet point
|
||||
echo "Posting as new comment at leanprover/lean4/issues/${{ steps.workflow-info.outputs.pullRequestNumber }}/comments"
|
||||
curl -L -s \
|
||||
-X POST \
|
||||
-H "Authorization: token ${{ secrets.MATHLIB4_BOT }}" \
|
||||
-H "Accept: application/vnd.github.v3+json" \
|
||||
-d "$(jq --null-input --arg intro "$INTRO" --arg val "$MESSAGE" '{"body":($intro + "\n" + $val)}')" \
|
||||
"https://api.github.com/repos/leanprover/lean4/issues/${{ steps.workflow-info.outputs.pullRequestNumber }}/comments"
|
||||
else
|
||||
# Append new result to the existing comment
|
||||
echo "Appending to existing comment at leanprover/lean4/issues/${{ steps.workflow-info.outputs.pullRequestNumber }}/comments"
|
||||
curl -L -s \
|
||||
-X PATCH \
|
||||
-H "Authorization: token ${{ secrets.MATHLIB4_BOT }}" \
|
||||
-H "Accept: application/vnd.github.v3+json" \
|
||||
-d "$(jq --null-input --arg existing "$existing_comment_body" --arg message "$MESSAGE" '{"body":($existing + "\n" + $message)}')" \
|
||||
"https://api.github.com/repos/leanprover/lean4/issues/comments/$existing_comment_id"
|
||||
fi
|
||||
else
|
||||
echo "The message already exists in the comment body."
|
||||
fi
|
||||
echo "mathlib_ready=false" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
echo "mathlib_ready=true" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
|
||||
- name: Report mathlib base
|
||||
if: ${{ steps.workflow-info.outputs.pullRequestNumber != '' && steps.ready.outputs.mathlib_ready == 'true' }}
|
||||
uses: actions/github-script@v6
|
||||
with:
|
||||
script: |
|
||||
const description =
|
||||
process.env.MOST_RECENT_NIGHTLY ?
|
||||
"nightly-" + process.env.MOST_RECENT_NIGHTLY :
|
||||
"not branched off nightly";
|
||||
await github.rest.repos.createCommitStatus({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
sha: "${{ steps.workflow-info.outputs.sourceHeadSha }}",
|
||||
state: "success",
|
||||
context: "PR branched off:",
|
||||
description: description,
|
||||
});
|
||||
|
||||
# We next automatically create a Std branch using this toolchain.
|
||||
# Std doesn't itself have a mechanism to report results of CI from this branch back to Lean
|
||||
# Instead this is taken care of by Mathlib CI, which will fail if Std fails.
|
||||
- name: Cleanup workspace
|
||||
if: steps.workflow-info.outputs.pullRequestNumber != '' && steps.ready.outputs.mathlib_ready == 'true'
|
||||
run: |
|
||||
sudo rm -rf ./*
|
||||
|
||||
# Checkout the Std repository with all branches
|
||||
- name: Checkout Std repository
|
||||
if: steps.workflow-info.outputs.pullRequestNumber != '' && steps.ready.outputs.mathlib_ready == 'true'
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
repository: leanprover/std4
|
||||
token: ${{ secrets.MATHLIB4_BOT }}
|
||||
ref: nightly-testing
|
||||
fetch-depth: 0 # This ensures we check out all tags and branches.
|
||||
|
||||
- name: Check if tag exists
|
||||
if: steps.workflow-info.outputs.pullRequestNumber != '' && steps.ready.outputs.mathlib_ready == 'true'
|
||||
id: check_std_tag
|
||||
run: |
|
||||
git config user.name "leanprover-community-mathlib4-bot"
|
||||
git config user.email "leanprover-community-mathlib4-bot@users.noreply.github.com"
|
||||
|
||||
if git ls-remote --heads --tags --exit-code origin "nightly-testing-${MOST_RECENT_NIGHTLY}" >/dev/null; then
|
||||
BASE="nightly-testing-${MOST_RECENT_NIGHTLY}"
|
||||
else
|
||||
echo "This shouldn't be possible: couldn't find a 'nightly-testing-${MOST_RECENT_NIGHTLY}' tag at Std. Falling back to 'nightly-testing'."
|
||||
BASE=nightly-testing
|
||||
fi
|
||||
|
||||
echo "Using base branch: $BASE"
|
||||
|
||||
EXISTS="$(git ls-remote --heads origin lean-pr-testing-${{ steps.workflow-info.outputs.pullRequestNumber }} | wc -l)"
|
||||
echo "Branch exists: $EXISTS"
|
||||
if [ "$EXISTS" = "0" ]; then
|
||||
echo "Branch does not exist, creating it."
|
||||
git switch -c lean-pr-testing-${{ steps.workflow-info.outputs.pullRequestNumber }} "$BASE"
|
||||
echo "leanprover/lean4-pr-releases:pr-release-${{ steps.workflow-info.outputs.pullRequestNumber }}" > lean-toolchain
|
||||
git add lean-toolchain
|
||||
git commit -m "Update lean-toolchain for testing https://github.com/leanprover/lean4/pull/${{ steps.workflow-info.outputs.pullRequestNumber }}"
|
||||
else
|
||||
echo "Branch already exists, pushing an empty commit."
|
||||
git switch lean-pr-testing-${{ steps.workflow-info.outputs.pullRequestNumber }}
|
||||
# The Std `nightly-testing` or `nightly-testing-YYYY-MM-DD` branch may have moved since this branch was created, so merge their changes.
|
||||
# (This should no longer be possible once `nightly-testing-YYYY-MM-DD` is a tag, but it is still safe to merge.)
|
||||
git merge "$BASE" --strategy-option ours --no-commit --allow-unrelated-histories
|
||||
git commit --allow-empty -m "Trigger CI for https://github.com/leanprover/lean4/pull/${{ steps.workflow-info.outputs.pullRequestNumber }}"
|
||||
fi
|
||||
|
||||
- name: Push changes
|
||||
if: steps.workflow-info.outputs.pullRequestNumber != '' && steps.ready.outputs.mathlib_ready == 'true'
|
||||
run: |
|
||||
git push origin lean-pr-testing-${{ steps.workflow-info.outputs.pullRequestNumber }}
|
||||
|
||||
|
||||
# We next automatically create a Mathlib branch using this toolchain.
|
||||
# Mathlib CI will be responsible for reporting back success or failure
|
||||
# to the PR comments asynchronously.
|
||||
- name: Cleanup workspace
|
||||
if: steps.workflow-info.outputs.pullRequestNumber != '' && steps.ready.outputs.mathlib_ready == 'true'
|
||||
run: |
|
||||
sudo rm -rf ./*
|
||||
|
||||
# Checkout the mathlib4 repository with all branches
|
||||
- name: Checkout mathlib4 repository
|
||||
if: steps.workflow-info.outputs.pullRequestNumber != '' && steps.ready.outputs.mathlib_ready == 'true'
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
repository: leanprover-community/mathlib4
|
||||
token: ${{ secrets.MATHLIB4_BOT }}
|
||||
ref: nightly-testing
|
||||
fetch-depth: 0 # This ensures we check out all tags and branches.
|
||||
|
||||
- name: Check if tag exists
|
||||
if: steps.workflow-info.outputs.pullRequestNumber != '' && steps.ready.outputs.mathlib_ready == 'true'
|
||||
id: check_mathlib_tag
|
||||
run: |
|
||||
git config user.name "leanprover-community-mathlib4-bot"
|
||||
git config user.email "leanprover-community-mathlib4-bot@users.noreply.github.com"
|
||||
|
||||
if git ls-remote --heads --tags --exit-code origin "nightly-testing-${MOST_RECENT_NIGHTLY}" >/dev/null; then
|
||||
BASE="nightly-testing-${MOST_RECENT_NIGHTLY}"
|
||||
else
|
||||
echo "This shouldn't be possible: couldn't find a 'nightly-testing-${MOST_RECENT_NIGHTLY}' branch at Mathlib. Falling back to 'nightly-testing'."
|
||||
BASE=nightly-testing
|
||||
fi
|
||||
|
||||
echo "Using base tag: $BASE"
|
||||
|
||||
EXISTS="$(git ls-remote --heads origin lean-pr-testing-${{ steps.workflow-info.outputs.pullRequestNumber }} | wc -l)"
|
||||
echo "Branch exists: $EXISTS"
|
||||
if [ "$EXISTS" = "0" ]; then
|
||||
echo "Branch does not exist, creating it."
|
||||
git switch -c lean-pr-testing-${{ steps.workflow-info.outputs.pullRequestNumber }} "$BASE"
|
||||
echo "leanprover/lean4-pr-releases:pr-release-${{ steps.workflow-info.outputs.pullRequestNumber }}" > lean-toolchain
|
||||
git add lean-toolchain
|
||||
sed -i "s/require std from git \"https:\/\/github.com\/leanprover\/std4\" @ \".\+\"/require std from git \"https:\/\/github.com\/leanprover\/std4\" @ \"nightly-testing-${MOST_RECENT_NIGHTLY}\"/" lakefile.lean
|
||||
git add lakefile.lean
|
||||
git commit -m "Update lean-toolchain for testing https://github.com/leanprover/lean4/pull/${{ steps.workflow-info.outputs.pullRequestNumber }}"
|
||||
else
|
||||
echo "Branch already exists, pushing an empty commit."
|
||||
git switch lean-pr-testing-${{ steps.workflow-info.outputs.pullRequestNumber }}
|
||||
# The Mathlib `nightly-testing` branch or `nightly-testing-YYYY-MM-DD` tag may have moved since this branch was created, so merge their changes.
|
||||
# (This should no longer be possible once `nightly-testing-YYYY-MM-DD` is a tag, but it is still safe to merge.)
|
||||
git merge "$BASE" --strategy-option ours --no-commit --allow-unrelated-histories
|
||||
git commit --allow-empty -m "Trigger CI for https://github.com/leanprover/lean4/pull/${{ steps.workflow-info.outputs.pullRequestNumber }}"
|
||||
fi
|
||||
|
||||
- name: Push changes
|
||||
if: steps.workflow-info.outputs.pullRequestNumber != '' && steps.ready.outputs.mathlib_ready == 'true'
|
||||
run: |
|
||||
git push origin lean-pr-testing-${{ steps.workflow-info.outputs.pullRequestNumber }}
|
||||
20
.github/workflows/pr-title.yml
vendored
20
.github/workflows/pr-title.yml
vendored
@@ -1,20 +0,0 @@
|
||||
name: Check PR title for commit convention
|
||||
|
||||
on:
|
||||
merge_group:
|
||||
pull_request:
|
||||
types: [opened, synchronize, reopened, edited]
|
||||
|
||||
jobs:
|
||||
check-pr-title:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check PR title
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
const msg = context.payload.pull_request? context.payload.pull_request.title : context.payload.merge_group.head_commit.message;
|
||||
console.log(`Message: ${msg}`)
|
||||
if (!/^(feat|fix|doc|style|refactor|test|chore|perf): .*[^.]($|\n\n)/.test(msg)) {
|
||||
core.setFailed('PR title does not follow the Commit Convention (https://leanprover.github.io/lean4/doc/dev/commit_convention.html).');
|
||||
}
|
||||
31
.github/workflows/pr.yml
vendored
Normal file
31
.github/workflows/pr.yml
vendored
Normal file
@@ -0,0 +1,31 @@
|
||||
name: sanity-check opened PRs
|
||||
|
||||
on:
|
||||
# needs read/write GH token, do *not* execute arbitrary code from PR
|
||||
pull_request_target:
|
||||
types: [opened]
|
||||
|
||||
jobs:
|
||||
check-pr:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check Commit Message
|
||||
uses: actions/github-script@v3
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
script: |
|
||||
const { data: commits } = await github.pulls.listCommits({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
pull_number: context.issue.number,
|
||||
});
|
||||
console.log(commits[0].commit.message);
|
||||
// check first commit only (and only once) since later commits might be intended to be squashed away
|
||||
if (!/^(feat|fix|doc|style|refactor|test|chore|perf): .*[^.]($|\n\n)/.test(commits[0].commit.message)) {
|
||||
await github.issues.createComment({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: context.issue.number,
|
||||
body: 'Thanks for your contribution! Please make sure to follow our [Commit Convention](https://leanprover.github.io/lean4/doc/dev/commit_convention.html).',
|
||||
});
|
||||
}
|
||||
20
.github/workflows/stale.yml
vendored
20
.github/workflows/stale.yml
vendored
@@ -1,20 +0,0 @@
|
||||
name: 'Label stale PRs'
|
||||
on:
|
||||
schedule:
|
||||
- cron: '30 1 * * *'
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
pull-requests: write
|
||||
|
||||
jobs:
|
||||
stale:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/stale@v8
|
||||
with:
|
||||
days-before-stale: -1
|
||||
days-before-pr-stale: 30
|
||||
days-before-close: -1
|
||||
stale-pr-label: 'stale'
|
||||
only-labels: 'awaiting-author'
|
||||
64
.github/workflows/update-stage0.yml
vendored
64
.github/workflows/update-stage0.yml
vendored
@@ -1,64 +0,0 @@
|
||||
name: Update stage0
|
||||
|
||||
# This action will update stage0 on master as soon as
|
||||
# src/stdlib_flags.h and stage0/src/stdlib_flags.h
|
||||
# are out of sync there, or when manually triggered.
|
||||
# The update bypasses the merge queue to be quick.
|
||||
# Also see <doc/dev/bootstrap.md>.
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- 'master'
|
||||
workflow_dispatch:
|
||||
|
||||
concurrency:
|
||||
group: stage0
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
update-stage0:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
# This action should push to an otherwise protected branch, so it
|
||||
# uses a deploy key with write permissions, as suggested at
|
||||
# https://stackoverflow.com/a/76135647/946226
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
ssh-key: ${{secrets.STAGE0_SSH_KEY}}
|
||||
- run: echo "should_update_stage0=yes" >> "$GITHUB_ENV"
|
||||
- name: Check if automatic update is needed
|
||||
if: github.event_name == 'push'
|
||||
run: |
|
||||
if diff -u src/stdlib_flags.h stage0/src/stdlib_flags.h
|
||||
then
|
||||
echo "src/stdlib_flags.h and stage0/src/stdlib_flags.h agree, nothing to do"
|
||||
echo "should_update_stage0=no" >> "$GITHUB_ENV"
|
||||
fi
|
||||
- name: Setup git user
|
||||
if: env.should_update_stage0 == 'yes'
|
||||
run: |
|
||||
git config --global user.name "Lean stage0 autoupdater"
|
||||
git config --global user.email "<>"
|
||||
- if: env.should_update_stage0 == 'yes'
|
||||
uses: DeterminateSystems/nix-installer-action@main
|
||||
# Would be nice, but does not work yet:
|
||||
# https://github.com/DeterminateSystems/magic-nix-cache/issues/39
|
||||
# This action does not run that often and building runs in a few minutes, so ok for now
|
||||
#- if: env.should_update_stage0 == 'yes'
|
||||
# uses: DeterminateSystems/magic-nix-cache-action@v2
|
||||
- if: env.should_update_stage0 == 'yes'
|
||||
name: Install Cachix
|
||||
uses: cachix/cachix-action@v12
|
||||
with:
|
||||
name: lean4
|
||||
- if: env.should_update_stage0 == 'yes'
|
||||
run: nix run .#update-stage0-commit
|
||||
- if: env.should_update_stage0 == 'yes'
|
||||
run: git show --stat
|
||||
- if: env.should_update_stage0 == 'yes' && github.event_name == 'push'
|
||||
name: Sanity check # to avoid loops
|
||||
run: |
|
||||
diff -u src/stdlib_flags.h stage0/src/stdlib_flags.h || exit 1
|
||||
- if: env.should_update_stage0 == 'yes'
|
||||
run: git push origin
|
||||
5
.gitignore
vendored
5
.gitignore
vendored
@@ -2,10 +2,7 @@
|
||||
\#*
|
||||
.#*
|
||||
*.lock
|
||||
.lake
|
||||
lake-manifest.json
|
||||
build
|
||||
!/src/lake/Lake/Build
|
||||
GPATH
|
||||
GRTAGS
|
||||
GSYMS
|
||||
@@ -28,4 +25,4 @@ fwIn.txt
|
||||
fwOut.txt
|
||||
wdErr.txt
|
||||
wdIn.txt
|
||||
wdOut.txt
|
||||
wdOut.txt
|
||||
4
.gitmodules
vendored
Normal file
4
.gitmodules
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
[submodule "lake"]
|
||||
path = src/lake
|
||||
url = https://github.com/leanprover/lake.git
|
||||
ignore = untracked
|
||||
7
.vscode/settings.json
vendored
Normal file
7
.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"files.insertFinalNewline": true,
|
||||
"files.trimTrailingWhitespace": true,
|
||||
"[markdown]": {
|
||||
"rewrap.wrappingColumn": 70
|
||||
}
|
||||
}
|
||||
@@ -11,13 +11,10 @@ foreach(var ${vars})
|
||||
list(APPEND STAGE0_ARGS "-D${CMAKE_MATCH_1}=${${var}}")
|
||||
elseif("${currentHelpString}" MATCHES "No help, variable specified on the command line." OR "${currentHelpString}" STREQUAL "")
|
||||
list(APPEND CL_ARGS "-D${var}=${${var}}")
|
||||
if("${var}" MATCHES "USE_GMP|CHECK_OLEAN_VERSION")
|
||||
if("${var}" STREQUAL "USE_GMP")
|
||||
# must forward options that generate incompatible .olean format
|
||||
list(APPEND STAGE0_ARGS "-D${var}=${${var}}")
|
||||
endif()
|
||||
if("${var}" MATCHES "LLVM*")
|
||||
list(APPEND STAGE0_ARGS "-D${var}=${${var}}")
|
||||
endif()
|
||||
elseif(("${var}" MATCHES "CMAKE_.*") AND NOT ("${var}" MATCHES "CMAKE_BUILD_TYPE") AND NOT ("${var}" MATCHES "CMAKE_HOME_DIRECTORY"))
|
||||
list(APPEND PLATFORM_ARGS "-D${var}=${${var}}")
|
||||
endif()
|
||||
@@ -26,17 +23,28 @@ endforeach()
|
||||
include(ExternalProject)
|
||||
project(LEAN CXX C)
|
||||
|
||||
if(NOT (DEFINED STAGE0_CMAKE_EXECUTABLE_SUFFIX))
|
||||
set(STAGE0_CMAKE_EXECUTABLE_SUFFIX "${CMAKE_EXECUTABLE_SUFFIX}")
|
||||
if("${CMAKE_SYSTEM_NAME}" MATCHES "Emscripten")
|
||||
# For Emscripten, we build GMP before any of the stages and reuse it in all of them.
|
||||
set(GMP_INSTALL_PREFIX ${CMAKE_BINARY_DIR}/gmp-root)
|
||||
set(EMSCRIPTEN_FLAGS "-s ALLOW_MEMORY_GROWTH=1 -s MAIN_MODULE=1 -O3")
|
||||
ExternalProject_Add(
|
||||
gmp
|
||||
URL https://gmplib.org/download/gmp/gmp-6.2.1.tar.bz2
|
||||
URL_HASH SHA256=eae9326beb4158c386e39a356818031bd28f3124cf915f8c5b1dc4c7a36b4d7c
|
||||
BUILD_IN_SOURCE 1
|
||||
CONFIGURE_COMMAND emconfigure ./configure "CFLAGS=${EMSCRIPTEN_FLAGS}" --host=wasm32-unknown-emscripten --disable-assembly --prefix=${GMP_INSTALL_PREFIX}
|
||||
BUILD_COMMAND emmake make -j4
|
||||
INSTALL_COMMAND emmake make install
|
||||
)
|
||||
set(EXTRA_DEPENDS "gmp")
|
||||
list(APPEND CL_ARGS "-DGMP_INSTALL_PREFIX=${GMP_INSTALL_PREFIX}")
|
||||
list(APPEND PLATFORM_ARGS "-DGMP_INSTALL_PREFIX=${GMP_INSTALL_PREFIX}")
|
||||
endif()
|
||||
|
||||
ExternalProject_add(stage0
|
||||
SOURCE_DIR "${LEAN_SOURCE_DIR}/stage0"
|
||||
SOURCE_SUBDIR src
|
||||
BINARY_DIR stage0
|
||||
# do not rebuild stage0 when git hash changes; it's not from this commit anyway
|
||||
# (however, `CHECK_OLEAN_VERSION=ON` in CI will override this as we need to
|
||||
# embed the githash into the stage 1 library built by stage 0)
|
||||
CMAKE_ARGS -DSTAGE=0 -DUSE_GITHASH=OFF ${PLATFORM_ARGS} ${STAGE0_ARGS}
|
||||
BUILD_ALWAYS ON # cmake doesn't auto-detect changes without a download method
|
||||
INSTALL_COMMAND "" # skip install
|
||||
@@ -46,7 +54,7 @@ ExternalProject_add(stage1
|
||||
SOURCE_DIR "${LEAN_SOURCE_DIR}"
|
||||
SOURCE_SUBDIR src
|
||||
BINARY_DIR stage1
|
||||
CMAKE_ARGS -DSTAGE=1 -DPREV_STAGE=${CMAKE_BINARY_DIR}/stage0 -DPREV_STAGE_CMAKE_EXECUTABLE_SUFFIX=${STAGE0_CMAKE_EXECUTABLE_SUFFIX} ${CL_ARGS}
|
||||
CMAKE_ARGS -DSTAGE=1 -DPREV_STAGE=${CMAKE_BINARY_DIR}/stage0 ${CL_ARGS}
|
||||
BUILD_ALWAYS ON
|
||||
INSTALL_COMMAND ""
|
||||
DEPENDS stage0
|
||||
@@ -55,7 +63,7 @@ ExternalProject_add(stage2
|
||||
SOURCE_DIR "${LEAN_SOURCE_DIR}"
|
||||
SOURCE_SUBDIR src
|
||||
BINARY_DIR stage2
|
||||
CMAKE_ARGS -DSTAGE=2 -DPREV_STAGE=${CMAKE_BINARY_DIR}/stage1 -DPREV_STAGE_CMAKE_EXECUTABLE_SUFFIX=${CMAKE_EXECUTABLE_SUFFIX} ${CL_ARGS}
|
||||
CMAKE_ARGS -DSTAGE=2 -DPREV_STAGE=${CMAKE_BINARY_DIR}/stage1 ${CL_ARGS}
|
||||
BUILD_ALWAYS ON
|
||||
INSTALL_COMMAND ""
|
||||
DEPENDS stage1
|
||||
@@ -65,7 +73,7 @@ ExternalProject_add(stage3
|
||||
SOURCE_DIR "${LEAN_SOURCE_DIR}"
|
||||
SOURCE_SUBDIR src
|
||||
BINARY_DIR stage3
|
||||
CMAKE_ARGS -DSTAGE=3 -DPREV_STAGE=${CMAKE_BINARY_DIR}/stage2 -DPREV_STAGE_CMAKE_EXECUTABLE_SUFFIX=${CMAKE_EXECUTABLE_SUFFIX} ${CL_ARGS}
|
||||
CMAKE_ARGS -DSTAGE=3 -DPREV_STAGE=${CMAKE_BINARY_DIR}/stage2 ${CL_ARGS}
|
||||
BUILD_ALWAYS ON
|
||||
INSTALL_COMMAND ""
|
||||
DEPENDS stage2
|
||||
|
||||
22
CODEOWNERS
22
CODEOWNERS
@@ -1,22 +0,0 @@
|
||||
# Code Owners
|
||||
#
|
||||
# Documents responsible people per component.
|
||||
# Listed persons will automatically be asked by GitHub to review a PR touching these paths.
|
||||
# If multiple names are listed, a review by any of them is considered sufficient by default.
|
||||
|
||||
/.github/ @Kha @semorrison
|
||||
/RELEASES.md @semorrison
|
||||
/src/ @leodemoura @Kha
|
||||
/src/Init/IO.lean @joehendrix
|
||||
/src/kernel/ @leodemoura
|
||||
/src/lake/ @tydeu
|
||||
/src/Lean/Compiler/ @leodemoura
|
||||
/src/Lean/Data/Lsp/ @mhuisi
|
||||
/src/Lean/Elab/Deriving/ @semorrison
|
||||
/src/Lean/Elab/Tactic/ @semorrison
|
||||
/src/Lean/Meta/Tactic/ @leodemoura
|
||||
/src/Lean/Parser/ @Kha
|
||||
/src/Lean/PrettyPrinter/ @Kha
|
||||
/src/Lean/Server/ @mhuisi
|
||||
/src/Lean/Widget/ @Vtec234
|
||||
/src/runtime/io.cpp @joehendrix
|
||||
100
CONTRIBUTING.md
100
CONTRIBUTING.md
@@ -1,79 +1,57 @@
|
||||
External Contribution Guidelines
|
||||
============
|
||||
# Contribution Guidelines
|
||||
|
||||
In the past, we accepted most pull requests. This practice produced hard to maintain code, performance problems, and bugs. In order to improve the quality and maintainability of our codebase, we've established the following guidelines for external contributions.
|
||||
Thank you for your interest in contributing to Lean! There are many ways to contribute and we appreciate all of them.
|
||||
|
||||
Helpful links
|
||||
-------
|
||||
## Bug reports
|
||||
|
||||
* [Development Setup](./doc/dev/index.md)
|
||||
* [Testing](./doc/dev/testing.md)
|
||||
* [Commit convention](./doc/dev/commit_convention.md)
|
||||
Bug reports as new issues are always welcome. Please check the existing [issues](https://github.com/leanprover/lean4/issues) first.
|
||||
Reduce the issue to a self-contained, reproducible test case.
|
||||
If you have the chance, before reporting a bug, please search existing issues, as it's possible that
|
||||
someone else has already reported your error.
|
||||
If you're not sure if something is a bug or not, feel free to file a bug anyway. You may also want to discuss it with the Lean
|
||||
community using the [lean4 Zulip channel](https://leanprover.zulipchat.com/#narrow/stream/270676-lean4).
|
||||
|
||||
Before You Submit a Pull Request (PR):
|
||||
-------
|
||||
## Simple fixes
|
||||
|
||||
**Start with an Issue**: Before submitting a PR, always open an issue discussing the problem you wish to solve or the feature you'd like to add. Use the prefix `RFC:` (request for comments) if you are proposing a new feature. Ask for feedback from other users. Take the time to summarize all the feedback. This allows the maintainers to evaluate your proposal more efficiently. When creating a RFC, consider the following questions:
|
||||
Simple fixes for **typos and clear bugs** are welcome.
|
||||
|
||||
- **User Experience**: How does this feature improve the user experience?
|
||||
## Documentation
|
||||
|
||||
- **Beneficiaries**: Which Lean users and projects do benefit most from this feature/change?
|
||||
Tutorial-like examples are very welcome.
|
||||
They are useful for finding rough edges and bugs in Lean 4, for highlighting new features, and for showing how to use Lean.
|
||||
If you want to store your tutorial in the Lean 4 repository to make sure future changes will not break it, we suggest the following workflow:
|
||||
* Contact one of the Lean developers on Zulip, and check whether your tutorial is a good match for the Lean 4 repository.
|
||||
* Send bug reports and report rough edges. We will work with you until the tutorial looks great.
|
||||
* Add plenty of comments and make sure others will be able to follow it.
|
||||
* Create a pull request in the Lean 4 repository. After merging, we will link it to the official documentation and make sure it becomes part of our test suite.
|
||||
|
||||
- **Community Feedback**: Have you sought feedback or insights from other Lean users?
|
||||
You can use `.lean` or `.md` files to create your tutorial. The `.md` files are ideal when you want to format your prose using markdown. For an example, see [this `.md` file](https://github.com/leanprover/lean4/blob/master/doc/lean3changes.md).
|
||||
|
||||
- **Maintainability**: Will this change streamline code maintenance or simplify its structure?
|
||||
Contributions to the reference manual are also welcome, but since Lean 4 is changing rapidly, please contact us first using Zulip
|
||||
to find out which parts are stable enough to document. We will work with you to get this kind of
|
||||
pull request merged. We are also happy to meet using Zoom, Skype or Google hangout to coordinate this kind of effort.
|
||||
|
||||
**Understand the Project**: Familiarize yourself with the project, existing issues, and latest commits. Ensure your contribution aligns with the project's direction and priorities.
|
||||
As Lean 4 matures, other forms of documentation (e.g., doc-strings) will be welcome too.
|
||||
|
||||
**Stay Updated**: Regularly fetch and merge changes from the main branch to ensure your branch is up-to-date and can be smoothly integrated.
|
||||
## "Help wanted"
|
||||
|
||||
**Help wanted**: We have issues tagged with ["help wanted"](https://github.com/leanprover/lean4/issues?q=is%3Aissue+is%3Aopen+label%3A%22help+wanted%22), if you want to contribute to the project, please take a look at them. If you are interested in one of them, post comments, ask questions, and engage with the core developers there.
|
||||
For issues marked as [`help wanted`](https://github.com/leanprover/lean4/issues?q=is%3Aissue+is%3Aopen+label%3A%22help+wanted%22), pull requests (PR) are welcome and we will work with you to get a PR merged. Some of these issues are nontrivial. If you are interested, please consider adding comments to the issue and/or messaging the Lean developers in [Zulip](https://leanprover.zulipchat.com/#).
|
||||
|
||||
Quality Over Quantity:
|
||||
-----
|
||||
## Unexpected Pull Requests
|
||||
|
||||
**Focused Changes**: Each PR should address a single, clearly-defined issue or feature. Avoid making multiple unrelated changes in a single PR.
|
||||
We have very few core developers, and we cannot review arbitrary pull requests (PRs). Moreover, many features involve subtle tradeoffs, and it may require significant time and energy to even assess a proposed design. We suggest the following workflow:
|
||||
|
||||
**Write Tests**: Every new feature or bug fix should come with relevant tests. This ensures the robustness and reliability of the contribution.
|
||||
* First, discuss your idea with the Lean community on Zulip. Ask the community to help collect examples, document the requirements, and detect complications.
|
||||
* If there is broad support, create a detailed issue for it on the Lean 4 repository at GitHub, and tag the issue with `RFC`.
|
||||
* Ask the community for help documenting the requirements, and for collecting examples and concerns.
|
||||
* Wait for one of the core developers to give you a "go ahead". At this point, the core developers will work with you to make sure your PR gets merged.
|
||||
|
||||
**Documentation**: Update relevant documentation, including comments in the code, to explain the logic and reasoning behind your changes.
|
||||
We don't want to waste your time by you implementing a feature and then us not being able to merge it.
|
||||
|
||||
Coding Standards:
|
||||
----
|
||||
## How to Contribute
|
||||
|
||||
**Follow the Code Style**: Ensure that your code follows the established coding style of the project.
|
||||
|
||||
**Lean on Lean**: Use Lean's built-in features and libraries effectively, avoiding reinventions.
|
||||
|
||||
**Performance**: Make sure that your changes do not introduce performance regressions. If possible, optimize the solution for speed and resource usage.
|
||||
|
||||
PR Submission:
|
||||
---
|
||||
|
||||
**Descriptive Title and Summary**: The PR title should briefly explain the purpose of the PR. The summary should give more detailed information on what changes are made and why. Links to Zulip threads are not acceptable as a summary. You are responsible for summarizing the discussion, and getting support for it.
|
||||
|
||||
**Follow the commit convention**: Pull requests are squash merged, and the
|
||||
commit message is taken from the pull request title and body, so make sure they adhere to the [commit convention](https://github.com/leanprover/lean4/blob/master/doc/dev/commit_convention.md). Put questions and extra information, which should not be part of the final commit message, into a first comment rather than the Pull Request description.
|
||||
Because the change will be squashed, there is no need to polish the commit messages and history on the branch.
|
||||
|
||||
**Link to Relevant Issues**: Reference any issues that your PR addresses to provide context.
|
||||
|
||||
**Stay Responsive**: Once the PR is submitted, stay responsive to feedback and be prepared to make necessary revisions. We will close any PR that has been inactive (no response or updates from the submitter) for more than a month.
|
||||
|
||||
Reviews and Feedback:
|
||||
----
|
||||
|
||||
**Be Patient**: Given the limited number of full-time maintainers and the volume of PRs, reviews may take some time.
|
||||
|
||||
**Engage Constructively**: Always approach feedback positively and constructively. Remember, reviews are about ensuring the best quality for the project, not personal criticism.
|
||||
|
||||
**Continuous Integration**: Ensure that all CI checks pass on your PR. Failed checks will delay the review process. The maintainers will not check PRs containing failures.
|
||||
|
||||
What to Expect:
|
||||
----
|
||||
|
||||
**Not All PRs Get Merged**: While we appreciate every contribution, not all PRs will be merged. Ensure your changes align with the project's goals and quality standards.
|
||||
|
||||
**Feedback is a Gift**: It helps improve the project and can also help you grow as a developer or contributor.
|
||||
|
||||
**Community Involvement**: Engage with the Lean community on our communication channels. This can lead to better collaboration and understanding of the project's direction.
|
||||
* Always follow the [commit convention](https://leanprover.github.io/lean4/doc/dev/commit_convention.html).
|
||||
* Follow the style of the surrounding code. When in doubt, look at other files using the particular syntax as well.
|
||||
* Make sure your code is documented.
|
||||
* New features or bug fixes should come with appropriate tests.
|
||||
* Ensure all tests work before submitting a PR; see [Development Setup](https://leanprover.github.io/lean4/doc/make/index.html#development-setup) and [Fixing Tests](https://leanprover.github.io/lean4/doc/dev/fixing_tests.html).
|
||||
|
||||
24
README.md
24
README.md
@@ -1,20 +1,22 @@
|
||||
This is the repository for **Lean 4**.
|
||||
This is the repository for **Lean 4**, which is currently being released as milestone releases towards a first stable release.
|
||||
[Lean 3](https://github.com/leanprover/lean) is still the latest stable release.
|
||||
|
||||
# About
|
||||
|
||||
- [Quickstart](https://lean-lang.org/lean4/doc/quickstart.html)
|
||||
- [Homepage](https://lean-lang.org)
|
||||
- [Theorem Proving Tutorial](https://lean-lang.org/theorem_proving_in_lean4/)
|
||||
- [Functional Programming in Lean](https://lean-lang.org/functional_programming_in_lean/)
|
||||
- [Manual](https://lean-lang.org/lean4/doc/)
|
||||
- [Quickstart](https://github.com/leanprover/lean4/blob/master/doc/quickstart.md)
|
||||
- [Walkthrough installation video](https://www.youtube.com/watch?v=yZo6k48L0VY)
|
||||
- [Quick tour video](https://youtu.be/zyXtbb_eYbY)
|
||||
- [Homepage](https://leanprover.github.io)
|
||||
- [Theorem Proving Tutorial](https://leanprover.github.io/theorem_proving_in_lean4/)
|
||||
- [Functional Programming in Lean](https://leanprover.github.io/functional_programming_in_lean/) **first chapter is available!**
|
||||
- [Manual](https://leanprover.github.io/lean4/doc/)
|
||||
- [Release notes](RELEASES.md) starting at v4.0.0-m3
|
||||
- [Examples](https://lean-lang.org/lean4/doc/examples.html)
|
||||
- [External Contribution Guidelines](CONTRIBUTING.md)
|
||||
- [FAQ](https://lean-lang.org/lean4/doc/faq.html)
|
||||
- [Examples](https://leanprover.github.io/lean4/doc/examples.html)
|
||||
- [FAQ](https://leanprover.github.io/lean4/doc/faq.html)
|
||||
|
||||
# Installation
|
||||
|
||||
See [Setting Up Lean](https://lean-lang.org/lean4/doc/setup.html).
|
||||
See [Setting Up Lean](https://leanprover.github.io/lean4/doc/setup.html).
|
||||
|
||||
# Contributing
|
||||
|
||||
@@ -22,4 +24,4 @@ Please read our [Contribution Guidelines](CONTRIBUTING.md) first.
|
||||
|
||||
# Building from Source
|
||||
|
||||
See [Building Lean](https://lean-lang.org/lean4/doc/make/index.html).
|
||||
See [Building Lean](https://leanprover.github.io/lean4/doc/make/index.html).
|
||||
|
||||
727
RELEASES.md
727
RELEASES.md
@@ -1,709 +1,6 @@
|
||||
# Lean 4 releases
|
||||
|
||||
We intend to provide regular "minor version" releases of the Lean language at approximately monthly intervals.
|
||||
There is not yet a strong guarantee of backwards compatibility between versions,
|
||||
only an expectation that breaking changes will be documented in this file.
|
||||
|
||||
This file contains work-in-progress notes for the upcoming release, as well as previous stable releases.
|
||||
Please check the [releases](https://github.com/leanprover/lean4/releases) page for the current status
|
||||
of each version.
|
||||
|
||||
v4.7.0 (development in progress)
|
||||
Unreleased
|
||||
---------
|
||||
|
||||
v4.6.0
|
||||
---------
|
||||
|
||||
* Add custom simplification procedures (aka `simproc`s) to `simp`. Simprocs can be triggered by the simplifier on a specified term-pattern. Here is an small example:
|
||||
```lean
|
||||
import Lean.Meta.Tactic.Simp.BuiltinSimprocs.Nat
|
||||
|
||||
def foo (x : Nat) : Nat :=
|
||||
x + 10
|
||||
|
||||
/--
|
||||
The `simproc` `reduceFoo` is invoked on terms that match the pattern `foo _`.
|
||||
-/
|
||||
simproc reduceFoo (foo _) :=
|
||||
/- A term of type `Expr → SimpM Step -/
|
||||
fun e => do
|
||||
/-
|
||||
The `Step` type has three constructors: `.done`, `.visit`, `.continue`.
|
||||
* The constructor `.done` instructs `simp` that the result does
|
||||
not need to be simplied further.
|
||||
* The constructor `.visit` instructs `simp` to visit the resulting expression.
|
||||
* The constructor `.continue` instructs `simp` to try other simplification procedures.
|
||||
|
||||
All three constructors take a `Result`. The `.continue` contructor may also take `none`.
|
||||
`Result` has two fields `expr` (the new expression), and `proof?` (an optional proof).
|
||||
If the new expression is definitionally equal to the input one, then `proof?` can be omitted or set to `none`.
|
||||
-/
|
||||
/- `simp` uses matching modulo reducibility. So, we ensure the term is a `foo`-application. -/
|
||||
unless e.isAppOfArity ``foo 1 do
|
||||
return .continue
|
||||
/- `Nat.fromExpr?` tries to convert an expression into a `Nat` value -/
|
||||
let some n ← Nat.fromExpr? e.appArg!
|
||||
| return .continue
|
||||
return .done { expr := Lean.mkNatLit (n+10) }
|
||||
```
|
||||
We disable simprocs support by using the command `set_option simprocs false`. This command is particularly useful when porting files to v4.6.0.
|
||||
Simprocs can be scoped, manually added to `simp` commands, and suppressed using `-`. They are also supported by `simp?`. `simp only` does not execute any `simproc`. Here are some examples for the `simproc` defined above.
|
||||
```lean
|
||||
example : x + foo 2 = 12 + x := by
|
||||
set_option simprocs false in
|
||||
/- This `simp` command does not make progress since `simproc`s are disabled. -/
|
||||
fail_if_success simp
|
||||
simp_arith
|
||||
|
||||
example : x + foo 2 = 12 + x := by
|
||||
/- `simp only` must not use the default simproc set. -/
|
||||
fail_if_success simp only
|
||||
simp_arith
|
||||
|
||||
example : x + foo 2 = 12 + x := by
|
||||
/-
|
||||
`simp only` does not use the default simproc set,
|
||||
but we can provide simprocs as arguments. -/
|
||||
simp only [reduceFoo]
|
||||
simp_arith
|
||||
|
||||
example : x + foo 2 = 12 + x := by
|
||||
/- We can use `-` to disable `simproc`s. -/
|
||||
fail_if_success simp [-reduceFoo]
|
||||
simp_arith
|
||||
```
|
||||
The command `register_simp_attr <id>` now creates a `simp` **and** a `simproc` set with the name `<id>`. The following command instructs Lean to insert the `reduceFoo` simplification procedure into the set `my_simp`. If no set is specified, Lean uses the default `simp` set.
|
||||
```lean
|
||||
simproc [my_simp] reduceFoo (foo _) := ...
|
||||
```
|
||||
|
||||
* The syntax of the `termination_by` and `decreasing_by` termination hints is overhauled:
|
||||
|
||||
* They are now placed directly after the function they apply to, instead of
|
||||
after the whole `mutual` block.
|
||||
* Therefore, the function name no longer has to be mentioned in the hint.
|
||||
* If the function has a `where` clause, the `termination_by` and
|
||||
`decreasing_by` for that function come before the `where`. The
|
||||
functions in the `where` clause can have their own termination hints, each
|
||||
following the corresponding definition.
|
||||
* The `termination_by` clause can only bind “extra parameters”, that are not
|
||||
already bound by the function header, but are bound in a lambda (`:= fun x
|
||||
y z =>`) or in patterns (`| x, n + 1 => …`). These extra parameters used to
|
||||
be understood as a suffix of the function parameters; now it is a prefix.
|
||||
|
||||
Migration guide: In simple cases just remove the function name, and any
|
||||
variables already bound at the header.
|
||||
```diff
|
||||
def foo : Nat → Nat → Nat := …
|
||||
-termination_by foo a b => a - b
|
||||
+termination_by a b => a - b
|
||||
```
|
||||
or
|
||||
```diff
|
||||
def foo : Nat → Nat → Nat := …
|
||||
-termination_by _ a b => a - b
|
||||
+termination_by a b => a - b
|
||||
```
|
||||
|
||||
If the parameters are bound in the function header (before the `:`), remove them as well:
|
||||
```diff
|
||||
def foo (a b : Nat) : Nat := …
|
||||
-termination_by foo a b => a - b
|
||||
+termination_by a - b
|
||||
```
|
||||
|
||||
Else, if there are multiple extra parameters, make sure to refer to the right
|
||||
ones; the bound variables are interpreted from left to right, no longer from
|
||||
right to left:
|
||||
```diff
|
||||
def foo : Nat → Nat → Nat → Nat
|
||||
| a, b, c => …
|
||||
-termination_by foo b c => b
|
||||
+termination_by a b => b
|
||||
```
|
||||
|
||||
In the case of a `mutual` block, place the termination arguments (without the
|
||||
function name) next to the function definition:
|
||||
```diff
|
||||
-mutual
|
||||
-def foo : Nat → Nat → Nat := …
|
||||
-def bar : Nat → Nat := …
|
||||
-end
|
||||
-termination_by
|
||||
- foo a b => a - b
|
||||
- bar a => a
|
||||
+mutual
|
||||
+def foo : Nat → Nat → Nat := …
|
||||
+termination_by a b => a - b
|
||||
+def bar : Nat → Nat := …
|
||||
+termination_by a => a
|
||||
+end
|
||||
```
|
||||
|
||||
Similarly, if you have (mutual) recursion through `where` or `let rec`, the
|
||||
termination hints are now placed directly after the function they apply to:
|
||||
```diff
|
||||
-def foo (a b : Nat) : Nat := …
|
||||
- where bar (x : Nat) : Nat := …
|
||||
-termination_by
|
||||
- foo a b => a - b
|
||||
- bar x => x
|
||||
+def foo (a b : Nat) : Nat := …
|
||||
+termination_by a - b
|
||||
+ where
|
||||
+ bar (x : Nat) : Nat := …
|
||||
+ termination_by x
|
||||
|
||||
-def foo (a b : Nat) : Nat :=
|
||||
- let rec bar (x : Nat) : Nat := …
|
||||
- …
|
||||
-termination_by
|
||||
- foo a b => a - b
|
||||
- bar x => x
|
||||
+def foo (a b : Nat) : Nat :=
|
||||
+ let rec bar (x : Nat) : Nat := …
|
||||
+ termination_by x
|
||||
+ …
|
||||
+termination_by a - b
|
||||
```
|
||||
|
||||
In cases where a single `decreasing_by` clause applied to multiple mutually
|
||||
recursive functions before, the tactic now has to be duplicated.
|
||||
|
||||
* The semantics of `decreasing_by` changed; the tactic is applied to all
|
||||
termination proof goals together, not individually.
|
||||
|
||||
This helps when writing termination proofs interactively, as one can focus
|
||||
each subgoal individually, for example using `·`. Previously, the given
|
||||
tactic script had to work for _all_ goals, and one had to resort to tactic
|
||||
combinators like `first`:
|
||||
|
||||
```diff
|
||||
def foo (n : Nat) := … foo e1 … foo e2 …
|
||||
-decreasing_by
|
||||
-simp_wf
|
||||
-first | apply something_about_e1; …
|
||||
- | apply something_about_e2; …
|
||||
+decreasing_by
|
||||
+all_goals simp_wf
|
||||
+· apply something_about_e1; …
|
||||
+· apply something_about_e2; …
|
||||
```
|
||||
|
||||
To obtain the old behaviour of applying a tactic to each goal individually,
|
||||
use `all_goals`:
|
||||
```diff
|
||||
def foo (n : Nat) := …
|
||||
-decreasing_by some_tactic
|
||||
+decreasing_by all_goals some_tactic
|
||||
```
|
||||
|
||||
In the case of mutual recursion each `decreasing_by` now applies to just its
|
||||
function. If some functions in a recursive group do not have their own
|
||||
`decreasing_by`, the default `decreasing_tactic` is used. If the same tactic
|
||||
ought to be applied to multiple functions, the `decreasing_by` clause has to
|
||||
be repeated at each of these functions.
|
||||
|
||||
* Modify `InfoTree.context` to facilitate augmenting it with partial contexts while elaborating a command. This breaks backwards compatibility with all downstream projects that traverse the `InfoTree` manually instead of going through the functions in `InfoUtils.lean`, as well as those manually creating and saving `InfoTree`s. See [PR #3159](https://github.com/leanprover/lean4/pull/3159) for how to migrate your code.
|
||||
|
||||
* Add language server support for [call hierarchy requests](https://www.youtube.com/watch?v=r5LA7ivUb2c) ([PR #3082](https://github.com/leanprover/lean4/pull/3082)). The change to the .ilean format in this PR means that projects must be fully rebuilt once in order to generate .ilean files with the new format before features like "find references" work correctly again.
|
||||
|
||||
* Structure instances with multiple sources (for example `{a, b, c with x := 0}`) now have their fields filled from these sources
|
||||
in strict left-to-right order. Furthermore, the structure instance elaborator now aggressively use sources to fill in subobject
|
||||
fields, which prevents unnecessary eta expansion of the sources,
|
||||
and hence greatly reduces the reliance on costly structure eta reduction. This has a large impact on mathlib,
|
||||
reducing total CPU instructions by 3% and enabling impactful refactors like leanprover-community/mathlib4#8386
|
||||
which reduces the build time by almost 20%.
|
||||
See PR [#2478](https://github.com/leanprover/lean4/pull/2478) and RFC [#2451](https://github.com/leanprover/lean4/issues/2451).
|
||||
|
||||
* Add pretty printer settings to omit deeply nested terms (`pp.deepTerms false` and `pp.deepTerms.threshold`) ([PR #3201](https://github.com/leanprover/lean4/pull/3201))
|
||||
|
||||
* Add pretty printer options `pp.numeralTypes` and `pp.natLit`.
|
||||
When `pp.numeralTypes` is true, then natural number literals, integer literals, and rational number literals
|
||||
are pretty printed with type ascriptions, such as `(2 : Rat)`, `(-2 : Rat)`, and `(-2 / 3 : Rat)`.
|
||||
When `pp.natLit` is true, then raw natural number literals are pretty printed as `nat_lit 2`.
|
||||
[PR #2933](https://github.com/leanprover/lean4/pull/2933) and [RFC #3021](https://github.com/leanprover/lean4/issues/3021).
|
||||
|
||||
Lake updates:
|
||||
* improved platform information & control [#3226](https://github.com/leanprover/lean4/pull/3226)
|
||||
* `lake update` from unsupported manifest versions [#3149](https://github.com/leanprover/lean4/pull/3149)
|
||||
|
||||
Other improvements:
|
||||
* make `intro` be aware of `let_fun` [#3115](https://github.com/leanprover/lean4/pull/3115)
|
||||
* produce simpler proof terms in `rw` [#3121](https://github.com/leanprover/lean4/pull/3121)
|
||||
* fuse nested `mkCongrArg` calls in proofs generated by `simp` [#3203](https://github.com/leanprover/lean4/pull/3203)
|
||||
* `induction using` followed by a general term [#3188](https://github.com/leanprover/lean4/pull/3188)
|
||||
* allow generalization in `let` [#3060](https://github.com/leanprover/lean4/pull/3060, fixing [#3065](https://github.com/leanprover/lean4/issues/3065)
|
||||
* reducing out-of-bounds `swap!` should return `a`, not `default`` [#3197](https://github.com/leanprover/lean4/pull/3197), fixing [#3196](https://github.com/leanprover/lean4/issues/3196)
|
||||
* derive `BEq` on structure with `Prop`-fields [#3191](https://github.com/leanprover/lean4/pull/3191), fixing [#3140](https://github.com/leanprover/lean4/issues/3140)
|
||||
* refine through more `casesOnApp`/`matcherApp` [#3176](https://github.com/leanprover/lean4/pull/3176), fixing [#3175](https://github.com/leanprover/lean4/pull/3175)
|
||||
* do not strip dotted components from lean module names [#2994](https://github.com/leanprover/lean4/pull/2994), fixing [#2999](https://github.com/leanprover/lean4/issues/2999)
|
||||
* fix `deriving` only deriving the first declaration for some handlers [#3058](https://github.com/leanprover/lean4/pull/3058), fixing [#3057](https://github.com/leanprover/lean4/issues/3057)
|
||||
* do not instantiate metavariables in kabstract/rw for disallowed occurrences [#2539](https://github.com/leanprover/lean4/pull/2539), fixing [#2538](https://github.com/leanprover/lean4/issues/2538)
|
||||
* hover info for `cases h : ...` [#3084](https://github.com/leanprover/lean4/pull/3084)
|
||||
|
||||
v4.5.0
|
||||
---------
|
||||
|
||||
* Modify the lexical syntax of string literals to have string gaps, which are escape sequences of the form `"\" newline whitespace*`.
|
||||
These have the interpetation of an empty string and allow a string to flow across multiple lines without introducing additional whitespace.
|
||||
The following is equivalent to `"this is a string"`.
|
||||
```lean
|
||||
"this is \
|
||||
a string"
|
||||
```
|
||||
[PR #2821](https://github.com/leanprover/lean4/pull/2821) and [RFC #2838](https://github.com/leanprover/lean4/issues/2838).
|
||||
|
||||
* Add raw string literal syntax. For example, `r"\n"` is equivalent to `"\\n"`, with no escape processing.
|
||||
To include double quote characters in a raw string one can add sufficiently many `#` characters before and after
|
||||
the bounding `"`s, as in `r#"the "the" is in quotes"#` for `"the \"the\" is in quotes"`.
|
||||
[PR #2929](https://github.com/leanprover/lean4/pull/2929) and [issue #1422](https://github.com/leanprover/lean4/issues/1422).
|
||||
|
||||
* The low-level `termination_by'` clause is no longer supported.
|
||||
|
||||
Migration guide: Use `termination_by` instead, e.g.:
|
||||
```diff
|
||||
-termination_by' measure (fun ⟨i, _⟩ => as.size - i)
|
||||
+termination_by i _ => as.size - i
|
||||
```
|
||||
|
||||
If the well-founded relation you want to use is not the one that the
|
||||
`WellFoundedRelation` type class would infer for your termination argument,
|
||||
you can use `WellFounded.wrap` from the std libarary to explicitly give one:
|
||||
```diff
|
||||
-termination_by' ⟨r, hwf⟩
|
||||
+termination_by x => hwf.wrap x
|
||||
```
|
||||
|
||||
* Support snippet edits in LSP `TextEdit`s. See `Lean.Lsp.SnippetString` for more details.
|
||||
|
||||
* Deprecations and changes in the widget API.
|
||||
- `Widget.UserWidgetDefinition` is deprecated in favour of `Widget.Module`. The annotation `@[widget]` is deprecated in favour of `@[widget_module]`. To migrate a definition of type `UserWidgetDefinition`, remove the `name` field and replace the type with `Widget.Module`. Removing the `name` results in a title bar no longer being drawn above your panel widget. To add it back, draw it as part of the component using `<details open=true><summary class='mv2 pointer'>{name}</summary>{rest_of_widget}</details>`. See an example migration [here](https://github.com/leanprover/std4/pull/475/files#diff-857376079661a0c28a53b7ff84701afabbdf529836a6944d106c5294f0e68109R43-R83).
|
||||
- The new command `show_panel_widgets` allows displaying always-on and locally-on panel widgets.
|
||||
- `RpcEncodable` widget props can now be stored in the infotree.
|
||||
- See [RFC 2963](https://github.com/leanprover/lean4/issues/2963) for more details and motivation.
|
||||
|
||||
* If no usable lexicographic order can be found automatically for a termination proof, explain why.
|
||||
See [feat: GuessLex: if no measure is found, explain why](https://github.com/leanprover/lean4/pull/2960).
|
||||
|
||||
* Option to print [inferred termination argument](https://github.com/leanprover/lean4/pull/3012).
|
||||
With `set_option showInferredTerminationBy true` you will get messages like
|
||||
```
|
||||
Inferred termination argument:
|
||||
termination_by
|
||||
ackermann n m => (sizeOf n, sizeOf m)
|
||||
```
|
||||
for automatically generated `termination_by` clauses.
|
||||
|
||||
* More detailed error messages for [invalid mutual blocks](https://github.com/leanprover/lean4/pull/2949).
|
||||
|
||||
* [Multiple](https://github.com/leanprover/lean4/pull/2923) [improvements](https://github.com/leanprover/lean4/pull/2969) to the output of `simp?` and `simp_all?`.
|
||||
|
||||
* Tactics with `withLocation *` [no longer fail](https://github.com/leanprover/lean4/pull/2917) if they close the main goal.
|
||||
|
||||
* Implementation of a `test_extern` command for writing tests for `@[extern]` and `@[implemented_by]` functions.
|
||||
Usage is
|
||||
```
|
||||
import Lean.Util.TestExtern
|
||||
|
||||
test_extern Nat.add 17 37
|
||||
```
|
||||
The head symbol must be the constant with the `@[extern]` or `@[implemented_by]` attribute. The return type must have a `DecidableEq` instance.
|
||||
|
||||
Bug fixes for
|
||||
[#2853](https://github.com/leanprover/lean4/issues/2853), [#2953](https://github.com/leanprover/lean4/issues/2953), [#2966](https://github.com/leanprover/lean4/issues/2966),
|
||||
[#2971](https://github.com/leanprover/lean4/issues/2971), [#2990](https://github.com/leanprover/lean4/issues/2990), [#3094](https://github.com/leanprover/lean4/issues/3094).
|
||||
|
||||
Bug fix for [eager evaluation of default value](https://github.com/leanprover/lean4/pull/3043) in `Option.getD`.
|
||||
Avoid [panic in `leanPosToLspPos`](https://github.com/leanprover/lean4/pull/3071) when file source is unavailable.
|
||||
Improve [short-circuiting behavior](https://github.com/leanprover/lean4/pull/2972) for `List.all` and `List.any`.
|
||||
|
||||
Several Lake bug fixes: [#3036](https://github.com/leanprover/lean4/issues/3036), [#3064](https://github.com/leanprover/lean4/issues/3064), [#3069](https://github.com/leanprover/lean4/issues/3069).
|
||||
|
||||
v4.4.0
|
||||
---------
|
||||
|
||||
* Lake and the language server now support per-package server options using the `moreServerOptions` config field, as well as options that apply to both the language server and `lean` using the `leanOptions` config field. Setting either of these fields instead of `moreServerArgs` ensures that viewing files from a dependency uses the options for that dependency. Additionally, `moreServerArgs` is being deprecated in favor of the `moreGlobalServerArgs` field. See PR [#2858](https://github.com/leanprover/lean4/pull/2858).
|
||||
|
||||
A Lakefile with the following deprecated package declaration:
|
||||
```lean
|
||||
def moreServerArgs := #[
|
||||
"-Dpp.unicode.fun=true"
|
||||
]
|
||||
def moreLeanArgs := moreServerArgs
|
||||
|
||||
package SomePackage where
|
||||
moreServerArgs := moreServerArgs
|
||||
moreLeanArgs := moreLeanArgs
|
||||
```
|
||||
|
||||
... can be updated to the following package declaration to use per-package options:
|
||||
```lean
|
||||
package SomePackage where
|
||||
leanOptions := #[⟨`pp.unicode.fun, true⟩]
|
||||
```
|
||||
* [Rename request handler](https://github.com/leanprover/lean4/pull/2462).
|
||||
* [Import auto-completion](https://github.com/leanprover/lean4/pull/2904).
|
||||
* [`pp.beta`` to apply beta reduction when pretty printing](https://github.com/leanprover/lean4/pull/2864).
|
||||
* [Embed and check githash in .olean](https://github.com/leanprover/lean4/pull/2766).
|
||||
* [Guess lexicographic order for well-founded recursion](https://github.com/leanprover/lean4/pull/2874).
|
||||
* [Allow trailing comma in tuples, lists, and tactics](https://github.com/leanprover/lean4/pull/2643).
|
||||
|
||||
Bug fixes for [#2628](https://github.com/leanprover/lean4/issues/2628), [#2883](https://github.com/leanprover/lean4/issues/2883),
|
||||
[#2810](https://github.com/leanprover/lean4/issues/2810), [#2925](https://github.com/leanprover/lean4/issues/2925), and [#2914](https://github.com/leanprover/lean4/issues/2914).
|
||||
|
||||
**Lake:**
|
||||
|
||||
* `lake init .` and a bare `lake init` and will now use the current directory as the package name. [#2890](https://github.com/leanprover/lean4/pull/2890)
|
||||
* `lake new` and `lake init` will now produce errors on invalid package names such as `..`, `foo/bar`, `Init`, `Lean`, `Lake`, and `Main`. See issue [#2637](https://github.com/leanprover/lean4/issues/2637) and PR [#2890](https://github.com/leanprover/lean4/pull/2890).
|
||||
* `lean_lib` no longer converts its name to upper camel case (e.g., `lean_lib bar` will include modules named `bar.*` rather than `Bar.*`). See issue [#2567](https://github.com/leanprover/lean4/issues/2567) and PR [#2889](https://github.com/leanprover/lean4/pull/2889).
|
||||
* Lean and Lake now properly support non-identifier library names (e.g., `lake new 123-hello` and `import «123Hello»` now work correctly). See issue [#2865](https://github.com/leanprover/lean4/issues/2865) and PR [#2889](https://github.com/leanprover/lean4/pull/2888).
|
||||
* Lake now filters the environment extensions loaded from a compiled configuration (`lakefile.olean`) to include only those relevant to Lake's workspace loading process. This resolves segmentation faults caused by environment extension type mismatches (e.g., when defining custom elaborators via `elab` in configurations). See issue [#2632](https://github.com/leanprover/lean4/issues/2632) and PR [#2896](https://github.com/leanprover/lean4/pull/2896).
|
||||
* Cloud releases will now properly be re-unpacked if the build directory is removed. See PR [#2928](https://github.com/leanprover/lean4/pull/2928).
|
||||
* Lake's `math` template has been simplified. See PR [#2930](https://github.com/leanprover/lean4/pull/2930).
|
||||
* `lake exe <target>` now parses `target` like a build target (as the help text states it should) rather than as a basic name. For example, `lake exe @mathlib/runLinter` should now work. See PR [#2932](https://github.com/leanprover/lean4/pull/2932).
|
||||
* `lake new foo.bar [std]` now generates executables named `foo-bar` and `lake new foo.bar exe` properly creates `foo/bar.lean`. See PR [#2932](https://github.com/leanprover/lean4/pull/2932).
|
||||
* Later packages and libraries in the dependency tree are now preferred over earlier ones. That is, the later ones "shadow" the earlier ones. Such an ordering is more consistent with how declarations generally work in programming languages. This will break any package that relied on the previous ordering. See issue [#2548](https://github.com/leanprover/lean4/issues/2548) and PR [#2937](https://github.com/leanprover/lean4/pull/2937).
|
||||
* Executable roots are no longer mistakenly treated as importable. They will no longer be picked up by `findModule?`. See PR [#2937](https://github.com/leanprover/lean4/pull/2937).
|
||||
|
||||
v4.3.0
|
||||
---------
|
||||
|
||||
* `simp [f]` does not unfold partial applications of `f` anymore. See issue [#2042](https://github.com/leanprover/lean4/issues/2042).
|
||||
To fix proofs affected by this change, use `unfold f` or `simp (config := { unfoldPartialApp := true }) [f]`.
|
||||
* By default, `simp` will no longer try to use Decidable instances to rewrite terms. In particular, not all decidable goals will be closed by `simp`, and the `decide` tactic may be useful in such cases. The `decide` simp configuration option can be used to locally restore the old `simp` behavior, as in `simp (config := {decide := true})`; this includes using Decidable instances to verify side goals such as numeric inequalities.
|
||||
|
||||
* Many bug fixes:
|
||||
* [Add left/right actions to term tree coercion elaborator and make `^`` a right action](https://github.com/leanprover/lean4/pull/2778)
|
||||
* [Fix for #2775, don't catch max recursion depth errors](https://github.com/leanprover/lean4/pull/2790)
|
||||
* [Reduction of `Decidable` instances very slow when using `cases` tactic](https://github.com/leanprover/lean4/issues/2552)
|
||||
* [`simp` not rewriting in binder](https://github.com/leanprover/lean4/issues/1926)
|
||||
* [`simp` unfolding `let` even with `zeta := false` option](https://github.com/leanprover/lean4/issues/2669)
|
||||
* [`simp` (with beta/zeta disabled) and discrimination trees](https://github.com/leanprover/lean4/issues/2281)
|
||||
* [unknown free variable introduced by `rw ... at h`](https://github.com/leanprover/lean4/issues/2711)
|
||||
* [`dsimp` doesn't use `rfl` theorems which consist of an unapplied constant](https://github.com/leanprover/lean4/issues/2685)
|
||||
* [`dsimp` does not close reflexive equality goals if they are wrapped in metadata](https://github.com/leanprover/lean4/issues/2514)
|
||||
* [`rw [h]` uses `h` from the environment in preference to `h` from the local context](https://github.com/leanprover/lean4/issues/2729)
|
||||
* [missing `withAssignableSyntheticOpaque` for `assumption` tactic](https://github.com/leanprover/lean4/issues/2361)
|
||||
* [ignoring default value for field warning](https://github.com/leanprover/lean4/issues/2178)
|
||||
* [Cancel outstanding tasks on document edit in the language server](https://github.com/leanprover/lean4/pull/2648).
|
||||
* [Remove unnecessary `%` operations in `Fin.mod` and `Fin.div`](https://github.com/leanprover/lean4/pull/2688)
|
||||
* [Avoid `DecidableEq` in `Array.mem`](https://github.com/leanprover/lean4/pull/2774)
|
||||
* [Ensure `USize.size` unifies with `?m + 1`](https://github.com/leanprover/lean4/issues/1926)
|
||||
* [Improve compatibility with emacs eglot client](https://github.com/leanprover/lean4/pull/2721)
|
||||
|
||||
**Lake:**
|
||||
|
||||
* [Sensible defaults for `lake new MyProject math`](https://github.com/leanprover/lean4/pull/2770)
|
||||
* Changed `postUpdate?` configuration option to a `post_update` declaration. See the `post_update` syntax docstring for more information on the new syntax.
|
||||
* [A manifest is automatically created on workspace load if one does not exists.](https://github.com/leanprover/lean4/pull/2680).
|
||||
* The `:=` syntax for configuration declarations (i.e., `package`, `lean_lib`, and `lean_exe`) has been deprecated. For example, `package foo := {...}` is deprecated.
|
||||
* [support for overriding package URLs via `LAKE_PKG_URL_MAP`](https://github.com/leanprover/lean4/pull/2709)
|
||||
* Moved the default build directory (e.g., `build`), default packages directory (e.g., `lake-packages`), and the compiled configuration (e.g., `lakefile.olean`) into a new dedicated directory for Lake outputs, `.lake`. The cloud release build archives are also stored here, fixing [#2713](https://github.com/leanprover/lean4/issues/2713).
|
||||
* Update manifest format to version 7 (see [lean4#2801](https://github.com/leanprover/lean4/pull/2801) for details on the changes).
|
||||
* Deprecate the `manifestFile` field of a package configuration.
|
||||
* There is now a more rigorous check on `lakefile.olean` compatibility (see [#2842](https://github.com/leanprover/lean4/pull/2842) for more details).
|
||||
|
||||
v4.2.0
|
||||
---------
|
||||
|
||||
* [isDefEq cache for terms not containing metavariables.](https://github.com/leanprover/lean4/pull/2644).
|
||||
* Make [`Environment.mk`](https://github.com/leanprover/lean4/pull/2604) and [`Environment.add`](https://github.com/leanprover/lean4/pull/2642) private, and add [`replay`](https://github.com/leanprover/lean4/pull/2617) as a safer alternative.
|
||||
* `IO.Process.output` no longer inherits the standard input of the caller.
|
||||
* [Do not inhibit caching](https://github.com/leanprover/lean4/pull/2612) of default-level `match` reduction.
|
||||
* [List the valid case tags](https://github.com/leanprover/lean4/pull/2629) when the user writes an invalid one.
|
||||
* The derive handler for `DecidableEq` [now handles](https://github.com/leanprover/lean4/pull/2591) mutual inductive types.
|
||||
* [Show path of failed import in Lake](https://github.com/leanprover/lean4/pull/2616).
|
||||
* [Fix linker warnings on macOS](https://github.com/leanprover/lean4/pull/2598).
|
||||
* **Lake:** Add `postUpdate?` package configuration option. Used by a package to specify some code which should be run after a successful `lake update` of the package or one of its downstream dependencies. ([lake#185](https://github.com/leanprover/lake/issues/185))
|
||||
* Improvements to Lake startup time ([#2572](https://github.com/leanprover/lean4/pull/2572), [#2573](https://github.com/leanprover/lean4/pull/2573))
|
||||
* `refine e` now replaces the main goal with metavariables which were created during elaboration of `e` and no longer captures pre-existing metavariables that occur in `e` ([#2502](https://github.com/leanprover/lean4/pull/2502)).
|
||||
* This is accomplished via changes to `withCollectingNewGoalsFrom`, which also affects `elabTermWithHoles`, `refine'`, `calc` (tactic), and `specialize`. Likewise, all of these now only include newly-created metavariables in their output.
|
||||
* Previously, both newly-created and pre-existing metavariables occurring in `e` were returned inconsistently in different edge cases, causing duplicated goals in the infoview (issue [#2495](https://github.com/leanprover/lean4/issues/2495)), erroneously closed goals (issue [#2434](https://github.com/leanprover/lean4/issues/2434)), and unintuitive behavior due to `refine e` capturing previously-created goals appearing unexpectedly in `e` (no issue; see PR).
|
||||
|
||||
v4.1.0
|
||||
---------
|
||||
|
||||
* The error positioning on missing tokens has been [improved](https://github.com/leanprover/lean4/pull/2393). In particular, this should make it easier to spot errors in incomplete tactic proofs.
|
||||
|
||||
* After elaborating a configuration file, Lake will now cache the configuration to a `lakefile.olean`. Subsequent runs of Lake will import this OLean instead of elaborating the configuration file. This provides a significant performance improvement (benchmarks indicate that using the OLean cuts Lake's startup time in half), but there are some important details to keep in mind:
|
||||
+ Lake will regenerate this OLean after each modification to the `lakefile.lean` or `lean-toolchain`. You can also force a reconfigure by passing the new `--reconfigure` / `-R` option to `lake`.
|
||||
+ Lake configuration options (i.e., `-K`) will be fixed at the moment of elaboration. Setting these options when `lake` is using the cached configuration will have no effect. To change options, run `lake` with `-R` / `--reconfigure`.
|
||||
+ **The `lakefile.olean` is a local configuration and should not be committed to Git. Therefore, existing Lake packages need to add it to their `.gitignore`.**
|
||||
|
||||
* The signature of `Lake.buildO` has changed, `args` has been split into `weakArgs` and `traceArgs`. `traceArgs` are included in the input trace and `weakArgs` are not. See Lake's [FFI example](src/lake/examples/ffi/lib/lakefile.lean) for a demonstration of how to adapt to this change.
|
||||
|
||||
* The signatures of `Lean.importModules`, `Lean.Elab.headerToImports`, and `Lean.Elab.parseImports`
|
||||
have [changed](https://github.com/leanprover/lean4/pull/2480) from taking `List Import` to `Array Import`.
|
||||
|
||||
* There is now [an `occs` field](https://github.com/leanprover/lean4/pull/2470)
|
||||
in the configuration object for the `rewrite` tactic,
|
||||
allowing control of which occurrences of a pattern should be rewritten.
|
||||
This was previously a separate argument for `Lean.MVarId.rewrite`,
|
||||
and this has been removed in favour of an additional field of `Rewrite.Config`.
|
||||
It was not previously accessible from user tactics.
|
||||
|
||||
v4.0.0
|
||||
---------
|
||||
|
||||
* [`Lean.Meta.getConst?` has been renamed](https://github.com/leanprover/lean4/pull/2454).
|
||||
We have renamed `getConst?` to `getUnfoldableConst?` (and `getConstNoEx?` to `getUnfoldableConstNoEx?`).
|
||||
These were not intended to be part of the public API, but downstream projects had been using them
|
||||
(sometimes expecting different behaviour) incorrectly instead of `Lean.getConstInfo`.
|
||||
|
||||
* [`dsimp` / `simp` / `simp_all` now fail by default if they make no progress](https://github.com/leanprover/lean4/pull/2336).
|
||||
|
||||
This can be overridden with the `(config := { failIfUnchanged := false })` option.
|
||||
This change was made to ease manual use of `simp` (with complicated goals it can be hard to tell if it was effective)
|
||||
and to allow easier flow control in tactics internally using `simp`.
|
||||
See the [summary discussion](https://leanprover.zulipchat.com/#narrow/stream/270676-lean4/topic/simp.20fails.20if.20no.20progress/near/380153295)
|
||||
on zulip for more details.
|
||||
|
||||
* [`simp_all` now preserves order of hypotheses](https://github.com/leanprover/lean4/pull/2334).
|
||||
|
||||
In order to support the `failIfUnchanged` configuration option for `dsimp` / `simp` / `simp_all`
|
||||
the way `simp_all` replaces hypotheses has changed.
|
||||
In particular it is now more likely to preserve the order of hypotheses.
|
||||
See [`simp_all` reorders hypotheses unnecessarily](https://github.com/leanprover/lean4/pull/2334).
|
||||
(Previously all non-dependent propositional hypotheses were reverted and reintroduced.
|
||||
Now only such hypotheses which were changed, or which come after a changed hypothesis,
|
||||
are reverted and reintroduced.
|
||||
This has the effect of preserving the ordering amongst the non-dependent propositional hypotheses,
|
||||
but now any dependent or non-propositional hypotheses retain their position amongst the unchanged
|
||||
non-dependent propositional hypotheses.)
|
||||
This may affect proofs that use `rename_i`, `case ... =>`, or `next ... =>`.
|
||||
|
||||
* [New `have this` implementation](https://github.com/leanprover/lean4/pull/2247).
|
||||
|
||||
`this` is now a regular identifier again that is implicitly introduced by anonymous `have :=` for the remainder of the tactic block. It used to be a keyword that was visible in all scopes and led to unexpected behavior when explicitly used as a binder name.
|
||||
|
||||
* [Show typeclass and tactic names in profile output](https://github.com/leanprover/lean4/pull/2170).
|
||||
|
||||
* [Make `calc` require the sequence of relation/proof-s to have the same indentation](https://github.com/leanprover/lean4/pull/1844),
|
||||
and [add `calc` alternative syntax allowing underscores `_` in the first relation](https://github.com/leanprover/lean4/pull/1844).
|
||||
|
||||
The flexible indentation in `calc` was often used to align the relation symbols:
|
||||
```lean
|
||||
example (x y : Nat) : (x + y) * (x + y) = x * x + y * x + x * y + y * y :=
|
||||
calc
|
||||
(x + y) * (x + y) = (x + y) * x + (x + y) * y := by rw [Nat.mul_add]
|
||||
-- improper indentation
|
||||
_ = x * x + y * x + (x + y) * y := by rw [Nat.add_mul]
|
||||
_ = x * x + y * x + (x * y + y * y) := by rw [Nat.add_mul]
|
||||
_ = x * x + y * x + x * y + y * y := by rw [←Nat.add_assoc]
|
||||
```
|
||||
|
||||
This is no longer legal. The new syntax puts the first term right after the `calc` and each step has the same indentation:
|
||||
```lean
|
||||
example (x y : Nat) : (x + y) * (x + y) = x * x + y * x + x * y + y * y :=
|
||||
calc (x + y) * (x + y)
|
||||
_ = (x + y) * x + (x + y) * y := by rw [Nat.mul_add]
|
||||
_ = x * x + y * x + (x + y) * y := by rw [Nat.add_mul]
|
||||
_ = x * x + y * x + (x * y + y * y) := by rw [Nat.add_mul]
|
||||
_ = x * x + y * x + x * y + y * y := by rw [←Nat.add_assoc]
|
||||
```
|
||||
|
||||
|
||||
* Update Lake to latest prerelease.
|
||||
|
||||
* [Make go-to-definition on a typeclass projection application go to the instance(s)](https://github.com/leanprover/lean4/pull/1767).
|
||||
|
||||
* [Include timings in trace messages when `profiler` is true](https://github.com/leanprover/lean4/pull/1995).
|
||||
|
||||
* [Pretty-print signatures in hover and `#check <ident>`](https://github.com/leanprover/lean4/pull/1943).
|
||||
|
||||
* [Introduce parser memoization to avoid exponential behavior](https://github.com/leanprover/lean4/pull/1799).
|
||||
|
||||
* [feat: allow `doSeq` in `let x <- e | seq`](https://github.com/leanprover/lean4/pull/1809).
|
||||
|
||||
* [Add hover/go-to-def/refs for options](https://github.com/leanprover/lean4/pull/1783).
|
||||
|
||||
* [Add empty type ascription syntax `(e :)`](https://github.com/leanprover/lean4/pull/1797).
|
||||
|
||||
* [Make tokens in `<|>` relevant to syntax match](https://github.com/leanprover/lean4/pull/1744).
|
||||
|
||||
* [Add `linter.deprecated` option to silence deprecation warnings](https://github.com/leanprover/lean4/pull/1768).
|
||||
|
||||
* [Improve fuzzy-matching heuristics](https://github.com/leanprover/lean4/pull/1710).
|
||||
|
||||
* [Implementation-detail hypotheses](https://github.com/leanprover/lean4/pull/1692).
|
||||
|
||||
* [Hover information for `cases`/`induction` case names](https://github.com/leanprover/lean4/pull/1660).
|
||||
|
||||
* [Prefer longer parse even if unsuccessful](https://github.com/leanprover/lean4/pull/1658).
|
||||
|
||||
* [Show declaration module in hover](https://github.com/leanprover/lean4/pull/1638).
|
||||
|
||||
* [New `conv` mode structuring tactics](https://github.com/leanprover/lean4/pull/1636).
|
||||
|
||||
* `simp` can track information and can print an equivalent `simp only`. [PR #1626](https://github.com/leanprover/lean4/pull/1626).
|
||||
|
||||
* Enforce uniform indentation in tactic blocks / do blocks. See issue [#1606](https://github.com/leanprover/lean4/issues/1606).
|
||||
|
||||
* Moved `AssocList`, `HashMap`, `HashSet`, `RBMap`, `RBSet`, `PersistentArray`, `PersistentHashMap`, `PersistentHashSet` to the Lean package. The [standard library](https://github.com/leanprover/std4) contains versions that will evolve independently to simplify bootstrapping process.
|
||||
|
||||
* Standard library moved to the [std4 GitHub repository](https://github.com/leanprover/std4).
|
||||
|
||||
* `InteractiveGoals` now has information that a client infoview can use to show what parts of the goal have changed after applying a tactic. [PR #1610](https://github.com/leanprover/lean4/pull/1610).
|
||||
|
||||
* Add `[inheritDoc]` attribute. [PR #1480](https://github.com/leanprover/lean4/pull/1480).
|
||||
|
||||
* Expose that `panic = default`. [PR #1614](https://github.com/leanprover/lean4/pull/1614).
|
||||
|
||||
* New [code generator](https://github.com/leanprover/lean4/tree/master/src/Lean/Compiler/LCNF) project has started.
|
||||
|
||||
* Remove description argument from `register_simp_attr`. [PR #1566](https://github.com/leanprover/lean4/pull/1566).
|
||||
|
||||
* [Additional concurrency primitives](https://github.com/leanprover/lean4/pull/1555).
|
||||
|
||||
* [Collapsible traces with messages](https://github.com/leanprover/lean4/pull/1448).
|
||||
|
||||
* [Hygienic resolution of namespaces](https://github.com/leanprover/lean4/pull/1442).
|
||||
|
||||
* [New `Float` functions](https://github.com/leanprover/lean4/pull/1460).
|
||||
|
||||
* Many new doc strings have been added to declarations at `Init`.
|
||||
|
||||
v4.0.0-m5 (07 August 2022)
|
||||
---------
|
||||
|
||||
* Update Lake to v4.0.0. See the [v4.0.0 release notes](https://github.com/leanprover/lake/releases/tag/v4.0.0) for detailed changes.
|
||||
|
||||
* Mutual declarations in different namespaces are now supported. Example:
|
||||
```lean
|
||||
mutual
|
||||
def Foo.boo (x : Nat) :=
|
||||
match x with
|
||||
| 0 => 1
|
||||
| x + 1 => 2*Boo.bla x
|
||||
|
||||
def Boo.bla (x : Nat) :=
|
||||
match x with
|
||||
| 0 => 2
|
||||
| x+1 => 3*Foo.boo x
|
||||
end
|
||||
```
|
||||
A `namespace` is automatically created for the common prefix. Example:
|
||||
```lean
|
||||
mutual
|
||||
def Tst.Foo.boo (x : Nat) := ...
|
||||
def Tst.Boo.bla (x : Nat) := ...
|
||||
end
|
||||
```
|
||||
expands to
|
||||
```lean
|
||||
namespace Tst
|
||||
mutual
|
||||
def Foo.boo (x : Nat) := ...
|
||||
def Boo.bla (x : Nat) := ...
|
||||
end
|
||||
end Tst
|
||||
```
|
||||
|
||||
* Allow users to install their own `deriving` handlers for existing type classes.
|
||||
See example at [Simple.lean](https://github.com/leanprover/lean4/blob/master/tests/pkg/deriving/UserDeriving/Simple.lean).
|
||||
|
||||
* Add tactic `congr (num)?`. See doc string for additional details.
|
||||
|
||||
* [Missing doc linter](https://github.com/leanprover/lean4/pull/1390)
|
||||
|
||||
* `match`-syntax notation now checks for unused alternatives. See issue [#1371](https://github.com/leanprover/lean4/issues/1371).
|
||||
|
||||
* Auto-completion for structure instance fields. Example:
|
||||
```lean
|
||||
example : Nat × Nat := {
|
||||
f -- HERE
|
||||
}
|
||||
```
|
||||
`fst` now appears in the list of auto-completion suggestions.
|
||||
|
||||
* Auto-completion for dotted identifier notation. Example:
|
||||
```lean
|
||||
example : Nat :=
|
||||
.su -- HERE
|
||||
```
|
||||
`succ` now appears in the list of auto-completion suggestions.
|
||||
|
||||
* `nat_lit` is not needed anymore when declaring `OfNat` instances. See issues [#1389](https://github.com/leanprover/lean4/issues/1389) and [#875](https://github.com/leanprover/lean4/issues/875). Example:
|
||||
```lean
|
||||
inductive Bit where
|
||||
| zero
|
||||
| one
|
||||
|
||||
instance inst0 : OfNat Bit 0 where
|
||||
ofNat := Bit.zero
|
||||
|
||||
instance : OfNat Bit 1 where
|
||||
ofNat := Bit.one
|
||||
|
||||
example : Bit := 0
|
||||
example : Bit := 1
|
||||
```
|
||||
|
||||
* Add `[elabAsElim]` attribute (it is called `elab_as_eliminator` in Lean 3). Motivation: simplify the Mathlib port to Lean 4.
|
||||
|
||||
* `Trans` type class now accepts relations in `Type u`. See this [Zulip issue](https://leanprover.zulipchat.com/#narrow/stream/270676-lean4/topic/Calc.20mode/near/291214574).
|
||||
|
||||
* Accept unescaped keywords as inductive constructor names. Escaping can often be avoided at use sites via dot notation.
|
||||
```lean
|
||||
inductive MyExpr
|
||||
| let : ...
|
||||
|
||||
def f : MyExpr → MyExpr
|
||||
| .let ... => .let ...
|
||||
```
|
||||
|
||||
* Throw an error message at parametric local instances such as `[Nat -> Decidable p]`. The type class resolution procedure
|
||||
cannot use this kind of local instance because the parameter does not have a forward dependency.
|
||||
This check can be disabled using `set_option checkBinderAnnotations false`.
|
||||
|
||||
* Add option `pp.showLetValues`. When set to `false`, the info view hides the value of `let`-variables in a goal.
|
||||
By default, it is `true` when visualizing tactic goals, and `false` otherwise.
|
||||
See [issue #1345](https://github.com/leanprover/lean4/issues/1345) for additional details.
|
||||
|
||||
* Add option `warningAsError`. When set to true, warning messages are treated as errors.
|
||||
|
||||
* Support dotted notation and named arguments in patterns. Example:
|
||||
```lean
|
||||
def getForallBinderType (e : Expr) : Expr :=
|
||||
match e with
|
||||
| .forallE (binderType := type) .. => type
|
||||
| _ => panic! "forall expected"
|
||||
```
|
||||
|
||||
* "jump-to-definition" now works for function names embedded in the following attributes
|
||||
`@[implementedBy funName]`, `@[tactic parserName]`, `@[termElab parserName]`, `@[commandElab parserName]`,
|
||||
`@[builtinTactic parserName]`, `@[builtinTermElab parserName]`, and `@[builtinCommandElab parserName]`.
|
||||
See [issue #1350](https://github.com/leanprover/lean4/issues/1350).
|
||||
|
||||
* Improve `MVarId` methods discoverability. See [issue #1346](https://github.com/leanprover/lean4/issues/1346).
|
||||
We still have to add similar methods for `FVarId`, `LVarId`, `Expr`, and other objects.
|
||||
Many existing methods have been marked as deprecated.
|
||||
|
||||
* Add attribute `[deprecated]` for marking deprecated declarations. Examples:
|
||||
```lean
|
||||
def g (x : Nat) := x + 1
|
||||
|
||||
-- Whenever `f` is used, a warning message is generated suggesting to use `g` instead.
|
||||
@[deprecated g]
|
||||
def f (x : Nat) := x + 1
|
||||
|
||||
#check f 0 -- warning: `f` has been deprecated, use `g` instead
|
||||
|
||||
-- Whenever `h` is used, a warning message is generated.
|
||||
@[deprecated]
|
||||
def h (x : Nat) := x + 1
|
||||
|
||||
#check h 0 -- warning: `h` has been deprecated
|
||||
```
|
||||
|
||||
* Add type `LevelMVarId` (and abbreviation `LMVarId`) for universe level metavariable ids.
|
||||
Motivation: prevent meta-programmers from mixing up universe and expression metavariable ids.
|
||||
|
||||
* Improve `calc` term and tactic. See [issue #1342](https://github.com/leanprover/lean4/issues/1342).
|
||||
|
||||
* [Relaxed antiquotation parsing](https://github.com/leanprover/lean4/pull/1272) further reduces the need for explicit `$x:p` antiquotation kind annotations.
|
||||
|
||||
* Add support for computed fields in inductives. Example:
|
||||
```lean
|
||||
inductive Exp
|
||||
@@ -719,8 +16,8 @@ v4.0.0-m5 (07 August 2022)
|
||||
|
||||
* Update `a[i]` notation. It is now based on the typeclass
|
||||
```lean
|
||||
class GetElem (cont : Type u) (idx : Type v) (elem : outParam (Type w)) (dom : outParam (cont → idx → Prop)) where
|
||||
getElem (xs : cont) (i : idx) (h : dom xs i) : Elem
|
||||
class GetElem (Cont : Type u) (Idx : Type v) (Elem : outParam (Type w)) (Dom : outParam (Cont → Idx → Prop)) where
|
||||
getElem (xs : Cont) (i : Idx) (h : Dom xs i) : Elem
|
||||
```
|
||||
The notation `a[i]` is now defined as follows
|
||||
```lean
|
||||
@@ -738,13 +35,13 @@ v4.0.0-m5 (07 August 2022)
|
||||
index `i` is not valid.
|
||||
The three new notations are defined as follows:
|
||||
```lean
|
||||
@[inline] def getElem' [GetElem cont idx elem dom] (xs : cont) (i : idx) (h : dom xs i) : elem :=
|
||||
@[inline] def getElem' [GetElem Cont Idx Elem Dom] (xs : Cont) (i : Idx) (h : Dom xs i) : Elem :=
|
||||
getElem xs i h
|
||||
|
||||
@[inline] def getElem! [GetElem cont idx elem dom] [Inhabited elem] (xs : cont) (i : idx) [Decidable (dom xs i)] : elem :=
|
||||
@[inline] def getElem! [GetElem Cont Idx Elem Dom] [Inhabited Elem] (xs : Cont) (i : Idx) [Decidable (Dom xs i)] : Elem :=
|
||||
if h : _ then getElem xs i h else panic! "index out of bounds"
|
||||
|
||||
@[inline] def getElem? [GetElem cont idx elem dom] (xs : cont) (i : idx) [Decidable (dom xs i)] : Option elem :=
|
||||
@[inline] def getElem? [GetElem Cont Idx Elem Dom] (xs : Cont) (i : Idx) [Decidable (Dom xs i)] : Option Elem :=
|
||||
if h : _ then some (getElem xs i h) else none
|
||||
|
||||
macro:max x:term noWs "[" i:term "]" noWs "?" : term => `(getElem? $x $i)
|
||||
@@ -793,7 +90,7 @@ v4.0.0-m5 (07 August 2022)
|
||||
```
|
||||
Note that `Idx`'s type in `GetElem` does not depend on `Cont`. So, you cannot write the instance `instance : GetElem (Array α) (Fin ??) α fun xs i => ...`, but the Lean library comes equipped with the following auxiliary instance:
|
||||
```lean
|
||||
instance [GetElem cont Nat elem dom] : GetElem cont (Fin n) elem fun xs i => dom xs i where
|
||||
instance [GetElem Cont Nat Elem Dom] : GetElem Cont (Fin n) Elem fun xs i => Dom xs i where
|
||||
getElem xs i h := getElem xs i.1 h
|
||||
```
|
||||
and helper tactic
|
||||
@@ -819,6 +116,8 @@ v4.0.0-m5 (07 August 2022)
|
||||
end Nat
|
||||
```
|
||||
|
||||
* Update Lake to v3.2.1. See the [v3.2.1 release notes](https://github.com/leanprover/lake/releases/tag/v3.2.1) for detailed changes.
|
||||
|
||||
* Add support for `CommandElabM` monad at `#eval`. Example:
|
||||
```lean
|
||||
import Lean
|
||||
@@ -1072,7 +371,7 @@ v4.0.0-m5 (07 August 2022)
|
||||
`Foo : {Foo : Type u} → List Foo → Type`.
|
||||
|
||||
|
||||
* Fix syntax highlighting for recursive declarations. Example
|
||||
* Fix syntax hightlighting for recursive declarations. Example
|
||||
```lean
|
||||
inductive List (α : Type u) where
|
||||
| nil : List α -- `List` is not highlighted as a variable anymore
|
||||
@@ -1125,7 +424,7 @@ v4.0.0-m5 (07 August 2022)
|
||||
...
|
||||
```
|
||||
|
||||
* Remove support for `{}` annotation from inductive datatype constructors. This annotation was barely used, and we can control the binder information for parameter bindings using the new inductive family indices to parameter promotion. Example: the following declaration using `{}`
|
||||
* Remove support for `{}` annotation from inductive datatype contructors. This annotation was barely used, and we can control the binder information for parameter bindings using the new inductive family indices to parameter promotion. Example: the following declaration using `{}`
|
||||
```lean
|
||||
inductive LE' (n : Nat) : Nat → Prop where
|
||||
| refl {} : LE' n n -- Want `n` to be explicit
|
||||
@@ -1290,7 +589,7 @@ v4.0.0-m4 (23 March 2022)
|
||||
|
||||
initialize my_ext : SimpExtension ← registerSimpAttr `my_simp "my own simp attribute"
|
||||
```
|
||||
If you don't need to access `my_ext`, you can also use the macro
|
||||
If you don't neet to acces `my_ext`, you can also use the macro
|
||||
```lean
|
||||
import Lean
|
||||
|
||||
@@ -1381,7 +680,7 @@ For example, given `f : Nat → Nat` and `g : Nat → Nat`, `f.comp g` is now no
|
||||
|
||||
* Various improvements to go-to-definition & find-all-references accuracy.
|
||||
|
||||
* Auto generated congruence lemmas with support for casts on proofs and `Decidable` instances (see [wishlist](https://github.com/leanprover/lean4/issues/988)).
|
||||
* Auto generated congruence lemmas with support for casts on proofs and `Decidable` instances (see [whishlist](https://github.com/leanprover/lean4/issues/988)).
|
||||
|
||||
* Rename option `autoBoundImplicitLocal` => `autoImplicit`.
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import Std
|
||||
open Std
|
||||
open Lean
|
||||
|
||||
@@ -103,9 +104,9 @@ syntax entry := ident " ↦ " term:max
|
||||
syntax entry,* "⊢" term : term
|
||||
|
||||
macro_rules
|
||||
| `( $[$xs ↦ $vs],* ⊢ $p) =>
|
||||
| `( $[$xs:ident ↦ $vs:term],* ⊢ $p:term ) =>
|
||||
let xs := xs.map fun x => quote x.getId.toString
|
||||
`(denote (List.toAssocList [$[($xs, $vs)],*]) `[BExpr| $p])
|
||||
`(denote (List.toAssocList [$[( $xs , $vs )],*]) `[BExpr| $p])
|
||||
|
||||
#check b ↦ true ⊢ b ∨ b
|
||||
#eval a ↦ false, b ↦ false ⊢ b ∨ a
|
||||
|
||||
@@ -59,23 +59,12 @@
|
||||
- [Thunk](./thunk.md)
|
||||
- [Task and Thread](./task.md)
|
||||
- [Functions](./functions.md)
|
||||
- [Monads](./monads/intro.md)
|
||||
- [Functor](./monads/functors.lean.md)
|
||||
- [Applicative](./monads/applicatives.lean.md)
|
||||
- [Monad](./monads/monads.lean.md)
|
||||
- [Reader](./monads/readers.lean.md)
|
||||
- [State](./monads/states.lean.md)
|
||||
- [Except](./monads/except.lean.md)
|
||||
- [Transformers](./monads/transformers.lean.md)
|
||||
- [Laws](./monads/laws.lean.md)
|
||||
|
||||
# Other
|
||||
|
||||
- [Frequently Asked Questions](./faq.md)
|
||||
- [Significant Changes from Lean 3](./lean3changes.md)
|
||||
- [Syntax Highlighting Lean in LaTeX](./syntax_highlight_in_latex.md)
|
||||
- [User Widgets](examples/widgets.lean.md)
|
||||
- [Semantic Highlighting](./semantic_highlighting.md)
|
||||
|
||||
# Development
|
||||
|
||||
@@ -85,6 +74,7 @@
|
||||
- [macOS Setup](./make/osx-10.9.md)
|
||||
- [Windows MSYS2 Setup](./make/msys2.md)
|
||||
- [Windows with WSL](./make/wsl.md)
|
||||
- [Nix Setup (*Experimental*)](./make/nix.md)
|
||||
- [Bootstrapping](./dev/bootstrap.md)
|
||||
- [Testing](./dev/testing.md)
|
||||
- [Debugging](./dev/debugging.md)
|
||||
|
||||
@@ -43,5 +43,3 @@ set_option autoImplicit false
|
||||
-- def compose (g : β → γ) (f : α → β) (x : α) : γ :=
|
||||
-- g (f x)
|
||||
```
|
||||
The Lean language server provides [semantic highlighting](./semantic_highlighting.md) information to editors, and it provides
|
||||
visual feedback whether an identifier has been interpreted as an auto bound implicit argument.
|
||||
|
||||
@@ -11,4 +11,4 @@ the following command executes a simple set of examples
|
||||
|
||||
% bin/lean examples/ex.lean
|
||||
|
||||
For more information on Lean and supported editors, please see https://lean-lang.org/documentation/.
|
||||
For more information on Lean and supported editors, please see https://leanprover.github.io/documentation/.
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -22,7 +22,7 @@ stage1/
|
||||
lib/
|
||||
lean/**/*.olean # the Lean library (incl. the compiler) compiled by the previous stage's `lean`
|
||||
temp/**/*.{c,o} # the library extracted to C and compiled by `leanc`
|
||||
libInit.a libLean.a # static libraries of the Lean library
|
||||
libInit.a libStd.a libLean.a # static libraries of the Lean library
|
||||
libleancpp.a # a static library of the C++ sources of Lean
|
||||
libleanshared.so # a dynamic library including the static libraries above
|
||||
bin/
|
||||
@@ -65,36 +65,9 @@ You now have a Lean binary and library that include your changes, though their
|
||||
own compilation was not influenced by them, that you can use to test your
|
||||
changes on test programs whose compilation *will* be influenced by the changes.
|
||||
|
||||
## Updating stage0
|
||||
|
||||
Finally, when we want to use new language features in the library, we need to
|
||||
update the archived C source code of the stage 0 compiler in `stage0/src`.
|
||||
|
||||
The github repository will automatically update stage0 on `master` once
|
||||
`src/stdlib_flags.h` and `stage0/src/stdlib_flags.h` are out of sync.
|
||||
|
||||
If you have write access to the lean4 repository, you can also also manually
|
||||
trigger that process, for example to be able to use new features in the compiler itself.
|
||||
You can do that on <https://github.com/nomeata/lean4/actions/workflows/update-stage0.yml>
|
||||
or using Github CLI with
|
||||
```
|
||||
gh workflow run update-stage0.yml
|
||||
```
|
||||
|
||||
Leaving stage0 updates to the CI automation is preferrable, but should you need
|
||||
to do it locally, you can use `make update-stage0` in `build/release`, to
|
||||
update `stage0` from `stage1`, `make -C stageN update-stage0` to update from
|
||||
another stage, or `nix run .#update-stage0-commit` to update using nix.
|
||||
|
||||
Updates to `stage0` should be their own commits in the Git history. So should
|
||||
you have to include the stage0 update in your PR (rather than using above
|
||||
automation after merging changes), commit your work before running `make
|
||||
update-stage0`, commit the updated `stage0` compiler code with the commit
|
||||
message:
|
||||
```
|
||||
chore: update stage0
|
||||
```
|
||||
and coordinate with the admins to not squash your PR.
|
||||
update the stage 0 compiler, which can be done via `make -C stageN update-stage0`.
|
||||
`make update-stage0` without `-C` defaults to stage1.
|
||||
|
||||
## Further Bootstrapping Complications
|
||||
|
||||
|
||||
@@ -1,15 +1,10 @@
|
||||
Git Commit Convention
|
||||
=====================
|
||||
|
||||
We are using the following convention for writing git commit messages. For pull
|
||||
requests, make sure the pull request title and description follow this
|
||||
convention, as the squash-merge commit will inherit title and body from the
|
||||
pull request.
|
||||
|
||||
This convention is based on the one from the AngularJS project ([doc][angularjs-doc],
|
||||
We are using the following convention for writing git-commit messages.
|
||||
It is based on the one from AngularJS project([doc][angularjs-doc],
|
||||
[commits][angularjs-git]).
|
||||
|
||||
|
||||
[angularjs-git]: https://github.com/angular/angular.js/commits/master
|
||||
[angularjs-doc]: https://docs.google.com/document/d/1QrDFcIiPjSLDn3EL15IJygNPiHORgU1_OOAqWjiDU5Y/edit#
|
||||
|
||||
|
||||
@@ -11,8 +11,6 @@ There are two primary attributes for interoperating with other languages:
|
||||
It can also be used with `def` to provide an internal definition, but ensuring consistency of both definitions is up to the user.
|
||||
* `@[export sym] def leanSym : ...` exports `leanSym` under the unmangled symbol name `sym`.
|
||||
|
||||
For simple examples of how to call foreign code from Lean and vice versa, see <https://github.com/leanprover/lean4/blob/master/src/lake/examples/ffi> and <https://github.com/leanprover/lean4/blob/master/src/lake/examples/reverse-ffi>, respectively.
|
||||
|
||||
## The Lean ABI
|
||||
|
||||
The Lean Application Binary Interface (ABI) describes how the signature of a Lean declaration is encoded as a native calling convention.
|
||||
@@ -23,7 +21,7 @@ If `n` is 0, the corresponding C declaration is
|
||||
extern s sym;
|
||||
```
|
||||
where `s` is the C translation of `β` as specified in the next section.
|
||||
In the case of an `@[extern]` definition, the symbol's value is guaranteed to be initialized only after calling the Lean module's initializer or that of an importing module; see [Initialization](#initialization).
|
||||
In the case of an `@[extern]` definition, the symbol's value is guaranteed to be initialized only after calling the Lean module's initializer or that of an importing module; see [Initialization](.#init).
|
||||
|
||||
If `n` is greater than 0, the corresponding C declaration is
|
||||
```c
|
||||
@@ -34,7 +32,7 @@ In the case of `@[extern]` all *irrelevant* types are removed first; see next se
|
||||
|
||||
### Translating Types from Lean to C
|
||||
|
||||
* The integer types `UInt8`, ..., `UInt64`, `USize` are represented by the C types `uint8_t`, ..., `uint64_t`, `size_t`, respectively
|
||||
* The integer types `UInt8`, ..., `UInt64`, `USize` are represented by the C types `uint8_t`, ..., `uint64_t`, `usize_t`, respectively
|
||||
* `Char` is represented by `uint32_t`
|
||||
* `Float` is represented by `double`
|
||||
* An *enum* inductive type of at least 2 and at most 2^32 constructors, each of which with no parameters, is represented by the first type of `uint8_t`, `uint16_t`, `uint32_t` that is sufficient to represent all constructor indices.
|
||||
@@ -45,9 +43,8 @@ In the case of `@[extern]` all *irrelevant* types are removed first; see next se
|
||||
* it is none of the types described above
|
||||
* it is not marked `unsafe`
|
||||
* it has a single constructor with a single parameter of *relevant* type
|
||||
|
||||
is represented by the representation of that parameter's type.
|
||||
|
||||
|
||||
For example, `{ x : α // p }`, the `Subtype` structure of a value of type `α` and an irrelevant proof, is represented by the representation of `α`.
|
||||
* `Nat` is represented by `lean_object *`.
|
||||
Its runtime value is either a pointer to an opaque bignum object or, if the lowest bit of the "pointer" is 1 (`lean_is_scalar`), an encoded unboxed natural number (`lean_box`/`lean_unbox`).
|
||||
@@ -72,13 +69,13 @@ When including Lean code as part of a larger program, modules must be *initializ
|
||||
Module initialization entails
|
||||
* initialization of all "constants" (nullary functions), including closed terms lifted out of other functions
|
||||
* execution of all `[init]` functions
|
||||
* execution of all `[builtin_init]` functions, if the `builtin` parameter of the module initializer has been set
|
||||
* execution of all `[builtinInit]` functions, if the `builtin` parameter of the module initializer has been set
|
||||
|
||||
The module initializer is automatically run with the `builtin` flag for executables compiled from Lean code and for "plugins" loaded with `lean --plugin`.
|
||||
For all other modules imported by `lean`, the initializer is run without `builtin`.
|
||||
Thus `[init]` functions are run iff their module is imported, regardless of whether they have native code available or not, while `[builtin_init]` functions are only run for native executable or plugins, regardless of whether their module is imported or not.
|
||||
Thus `[init]` functions are run iff their module is imported, regardless of whether they have native code available or not, while `[builtinInit]` functions are only run for native executable or plugins, regardless of whether their module is imported or not.
|
||||
`lean` uses built-in initializers for e.g. registering basic parsers that should be available even without importing their module (which is necessary for bootstrapping).
|
||||
|
||||
|
||||
The initializer for module `A.B` is called `initialize_A_B` and will automatically initialize any imported modules.
|
||||
Module initializers are idempotent (when run with the same `builtin` flag), but not thread-safe.
|
||||
Together with initialization of the Lean runtime, you should execute code like the following exactly once before accessing any Lean declarations:
|
||||
@@ -121,4 +118,4 @@ Thus to e.g. run `#eval` on such a declaration, you need to
|
||||
Note that it is not sufficient to load the foreign library containing the external symbol because the interpreter depends on code that is emitted for each `@[extern]` declaration.
|
||||
Thus it is not possible to interpret an `@[extern]` declaration in the same file.
|
||||
|
||||
See [`tests/compiler/foreign`](https://github.com/leanprover/lean4/tree/master/tests/compiler/foreign/) for an example.
|
||||
See `tests/compiler/foreign` for an example.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Development Workflow
|
||||
|
||||
If you want to make changes to Lean itself, start by [building Lean](../make/index.md) from a clean checkout to make sure that everything is set up correctly.
|
||||
If you want to make changes to Lean itself, start by [building Lean](../make/index.html) from a clean checkout to make sure that everything is set up correctly.
|
||||
After that, read on below to find out how to set up your editor for changing the Lean source code, followed by further sections of the development manual where applicable such as on the [test suite](testing.md) and [commit convention](commit_convention.md).
|
||||
|
||||
If you are planning to make any changes that may affect the compilation of Lean itself, e.g. changes to the parser, elaborator, or compiler, you should first read about the [bootstrapping pipeline](bootstrap.md).
|
||||
@@ -30,14 +30,20 @@ powershell -f elan-init.ps1 --default-toolchain none
|
||||
del elan-init.ps1
|
||||
```
|
||||
|
||||
The `lean-toolchain` files in the Lean 4 repository are set up to use the `lean4-stage0`
|
||||
toolchain for editing files in `src` and the `lean4` toolchain for editing files in `tests`.
|
||||
|
||||
Run the following commands to make `lean4` point at `stage1` and `lean4-stage0` point at `stage0`:
|
||||
You can use `elan toolchain link` to give a specific stage build
|
||||
directory a reference name, then use `elan override set` to associate
|
||||
such a name to the current directory. We usually want to use `stage0`
|
||||
for editing files in `src` and `stage1` for everything else (e.g.
|
||||
tests).
|
||||
```bash
|
||||
# in the Lean rootdir
|
||||
elan toolchain link lean4 build/release/stage1
|
||||
elan toolchain link lean4-stage0 build/release/stage0
|
||||
# make `lean` etc. point to stage1 in the rootdir and subdirs
|
||||
elan override set lean4
|
||||
cd src
|
||||
# make `lean` etc. point to stage0 anywhere inside `src`
|
||||
elan override set lean4-stage0
|
||||
```
|
||||
|
||||
You can also use the `+toolchain` shorthand (e.g. `lean +lean4-debug`) to switch
|
||||
@@ -51,26 +57,3 @@ You might find that debugging through elan, e.g. via `gdb lean`, disables some
|
||||
things like symbol autocompletion because at first only the elan proxy binary
|
||||
is loaded. You can instead pass the explicit path to `bin/lean` in your build
|
||||
folder to gdb, or use `gdb $(elan which lean)`.
|
||||
|
||||
It is also possible to generate releases that others can use,
|
||||
simply by pushing a tag to your fork of the Lean 4 github repository
|
||||
(and waiting about an hour; check the `Actions` tab for completion).
|
||||
If you push `my-tag` to a fork in your github account `my_name`,
|
||||
you can then put `my_name/lean4:my-tag` in your `lean-toolchain` file in a project using `lake`.
|
||||
(You must use a tag name that does not start with a numeral, or contain `_`).
|
||||
|
||||
### VS Code
|
||||
|
||||
There is a `lean.code-workspace` file that correctly sets up VS Code with workspace roots for the stage0/stage1 setup described above as well as with other settings.
|
||||
You should always load it when working on Lean, such as by invoking
|
||||
```
|
||||
code lean.code-workspace
|
||||
```
|
||||
on the command line.
|
||||
|
||||
### `ccache`
|
||||
|
||||
Lean's build process uses [`ccache`](https://ccache.dev/) if it is
|
||||
installed to speed up recompilation of the generated C code. Without
|
||||
`ccache`, you'll likely spend more time than necessary waiting on
|
||||
rebuilds - it's a good idea to make sure it's installed.
|
||||
|
||||
@@ -1,12 +1,3 @@
|
||||
# Documentation
|
||||
|
||||
The Lean `doc` folder contains the [Lean Manual](https://lean-lang.org/lean4/doc/) and is
|
||||
authored in a combination of markdown (`*.md`) files and literate Lean files. The .lean files are
|
||||
preprocessed using a tool called [LeanInk](https://github.com/leanprover/leanink) and
|
||||
[Alectryon](https://github.com/Kha/alectryon) which produces a generated markdown file. We then run
|
||||
`mdbook` on the result to generate the html pages.
|
||||
|
||||
|
||||
## Settings
|
||||
|
||||
We are using the following settings while editing the markdown docs.
|
||||
@@ -23,87 +14,30 @@ We are using the following settings while editing the markdown docs.
|
||||
|
||||
## Build
|
||||
|
||||
### Using Nix
|
||||
|
||||
Building the manual using Nix (which is what the CI does) is as easy as
|
||||
```bash
|
||||
$ nix build --update-input lean ./doc
|
||||
```
|
||||
You can also open a shell with `mdbook` for running the commands mentioned below with
|
||||
`nix develop ./doc#book`. Otherwise, read on.
|
||||
|
||||
### Manually
|
||||
|
||||
To build and test the book you have to preprocess the .lean files with Alectryon then use our own
|
||||
fork of the Rust tool named [mdbook](https://github.com/leanprover/mdbook). We have our own fork of
|
||||
mdBook with the following additional features:
|
||||
This manual is generated by
|
||||
[mdBook](https://github.com/rust-lang/mdBook). We are currently using
|
||||
a [fork](https://github.com/leanprover/mdBook) of it for the following
|
||||
additional features:
|
||||
|
||||
* Add support for hiding lines in other languages
|
||||
[#1339](https://github.com/rust-lang/mdBook/pull/1339)
|
||||
* Make `mdbook test` call the `lean` compiler to test the snippets.
|
||||
* Ability to test a single chapter at a time which is handy when you
|
||||
are working on that chapter. See the `--chapter` option.
|
||||
* Replace calling `rustdoc --test` from `mdbook test` with `./test`
|
||||
|
||||
So you need to setup these tools before you can run `mdBook`.
|
||||
To build this manual, first install the fork via
|
||||
```bash
|
||||
cargo install --git https://github.com/leanprover/mdBook mdbook
|
||||
```
|
||||
Then use e.g. [`mdbook watch`](https://rust-lang.github.io/mdBook/cli/watch.html) in the `doc/` folder:
|
||||
|
||||
1. install [Rust](https://www.rust-lang.org/tools/install)
|
||||
which provides you with the `cargo` tool for building rust packages.
|
||||
Then run the following:
|
||||
```bash
|
||||
cargo install --git https://github.com/leanprover/mdBook mdbook
|
||||
```
|
||||
```bash
|
||||
cd doc
|
||||
mdbook watch --open # opens the output in `out/` in your default browser
|
||||
```
|
||||
|
||||
1. Clone https://github.com/leanprover/LeanInk.git and run `lake build` then make the resulting
|
||||
binary available to Alectryon using e.g.
|
||||
```bash
|
||||
# make `leanInk` available in the current shell
|
||||
export PATH=$PWD/build/bin:$PATH
|
||||
```
|
||||
Run `mdbook test` to test all `lean` code blocks.
|
||||
|
||||
1. Create a Python 3.10 environment.
|
||||
|
||||
1. Install Alectryon:
|
||||
```
|
||||
python3 -m pip install git+https://github.com/Kha/alectryon.git@typeid
|
||||
```
|
||||
|
||||
1. Now you are ready to process the `*.lean` files using Alectryon as follows:
|
||||
|
||||
```
|
||||
cd lean4/doc
|
||||
alectryon --frontend lean4+markup examples/palindromes.lean --backend webpage -o palindromes.lean.md
|
||||
```
|
||||
|
||||
Repeat this for the other .lean files you care about or write a script to process them all.
|
||||
|
||||
1. Now you can build the book using:
|
||||
```
|
||||
cd lean4/doc
|
||||
mdbook build
|
||||
```
|
||||
|
||||
This will put the HTML in a `out` folder so you can load `out/index.html` in your web browser and
|
||||
it should look like https://lean-lang.org/lean4/doc/.
|
||||
|
||||
1. It is also handy to use e.g. [`mdbook watch`](https://rust-lang.github.io/mdBook/cli/watch.html)
|
||||
in the `doc/` folder so that it keeps the html up to date while you are editing.
|
||||
|
||||
```bash
|
||||
mdbook watch --open # opens the output in `out/` in your default browser
|
||||
```
|
||||
|
||||
## Testing Lean Snippets
|
||||
|
||||
You can run the following in the `doc/` folder to test all the lean code snippets.
|
||||
|
||||
```bash
|
||||
mdbook test
|
||||
```
|
||||
|
||||
and you can use the `--chapter` option to test a specific chapter that you are working on:
|
||||
|
||||
```bash
|
||||
mdbook test --chapter Array
|
||||
```
|
||||
|
||||
Use chapter name `?` to get a list of all the chapter names.
|
||||
Using the [Nix setup](make/nix.md), you can instead open a shell with
|
||||
the mdBook fork downloaded from our binary cache:
|
||||
```bash
|
||||
nix develop .#doc
|
||||
```
|
||||
|
||||
@@ -5,6 +5,7 @@ After [building Lean](../make/index.md) you can run all the tests using
|
||||
cd build/release
|
||||
make test ARGS=-j4
|
||||
```
|
||||
|
||||
Change the 4 to the maximum number of parallel tests you want to
|
||||
allow. The best choice is the number of CPU cores on your machine as
|
||||
the tests are mostly CPU bound. You can find the number of processors
|
||||
@@ -16,12 +17,6 @@ adding the `-C stageN` argument. The default when run as above is stage 1. The
|
||||
Lean tests will automatically use that stage's corresponding Lean
|
||||
executables
|
||||
|
||||
Running `make test` will not pick up new test files; run
|
||||
```bash
|
||||
cmake build/release/stage1
|
||||
```
|
||||
to update the list of tests.
|
||||
|
||||
You can also use `ctest` directly if you are in the right folder. So
|
||||
to run stage1 tests with a 300 second timeout run this:
|
||||
|
||||
@@ -29,9 +24,6 @@ to run stage1 tests with a 300 second timeout run this:
|
||||
cd build/release/stage1
|
||||
ctest -j 4 --output-on-failure --timeout 300
|
||||
```
|
||||
Useful `ctest` flags are `-R <name of test>` to run a single test, and
|
||||
`--rerun-failed` to run all tests that failed during the last run.
|
||||
You can also pass `ctest` flags via `make test ARGS="--rerun-failed"`.
|
||||
|
||||
To get verbose output from ctest pass the `--verbose` command line
|
||||
option. Test output is normally suppressed and only summary
|
||||
@@ -41,17 +33,17 @@ information is displayed. This option will show all test output.
|
||||
|
||||
All these tests are included by [src/shell/CMakeLists.txt](https://github.com/leanprover/lean4/blob/master/src/shell/CMakeLists.txt):
|
||||
|
||||
- [`tests/lean`](https://github.com/leanprover/lean4/tree/master/tests/lean/): contains tests that come equipped with a
|
||||
.lean.expected.out file. The driver script [`test_single.sh`](https://github.com/leanprover/lean4/tree/master/tests/lean/test_single.sh) runs
|
||||
- `tests/lean`: contains tests that come equipped with a
|
||||
.lean.expected.out file. The driver script `test_single.sh` runs
|
||||
each test and checks the actual output (*.produced.out) with the
|
||||
checked in expected output.
|
||||
|
||||
- [`tests/lean/run`](https://github.com/leanprover/lean4/tree/master/tests/lean/run/): contains tests that are run through the lean
|
||||
- `tests/lean/run`: contains tests that are run through the lean
|
||||
command line one file at a time. These tests only look for error
|
||||
codes and do not check the expected output even though output is
|
||||
produced, it is ignored.
|
||||
|
||||
- [`tests/lean/interactive`](https://github.com/leanprover/lean4/tree/master/tests/lean/interactive/): are designed to test server requests at a
|
||||
- `tests/lean/interactive`: are designed to test server requests at a
|
||||
given position in the input file. Each .lean file contains comments
|
||||
that indicate how to simulate a client request at that position.
|
||||
using a `--^` point to the line position. Example:
|
||||
@@ -61,7 +53,7 @@ All these tests are included by [src/shell/CMakeLists.txt](https://github.com/le
|
||||
Bla.
|
||||
--^ textDocument/completion
|
||||
```
|
||||
In this example, the test driver [`test_single.sh`](https://github.com/leanprover/lean4/tree/master/tests/lean/interactive/test_single.sh) will simulate an
|
||||
In this example, the test driver `test_single.sh` will simulate an
|
||||
auto-completion request at `Bla.`. The expected output is stored in
|
||||
a .lean.expected.out in the json format that is part of the
|
||||
[Language Server
|
||||
@@ -78,33 +70,23 @@ All these tests are included by [src/shell/CMakeLists.txt](https://github.com/le
|
||||
--^ collectDiagnostics
|
||||
```
|
||||
|
||||
- [`tests/lean/server`](https://github.com/leanprover/lean4/tree/master/tests/lean/server/): Tests more of the Lean `--server` protocol.
|
||||
- `tests/lean/server`: Tests more of the Lean `--server` protocol.
|
||||
There are just a few of them, and it uses .log files containing
|
||||
JSON.
|
||||
|
||||
- [`tests/compiler`](https://github.com/leanprover/lean4/tree/master/tests/compiler/): contains tests that will run the Lean compiler and
|
||||
- `tests/compiler`: contains tests that will run the Lean compiler and
|
||||
build an executable that is executed and the output is compared to
|
||||
the .lean.expected.out file. This test also contains a subfolder
|
||||
[`foreign`](https://github.com/leanprover/lean4/tree/master/tests/compiler/foreign/) which shows how to extend Lean using C++.
|
||||
`foreign` which shows how to extend Lean using C++.
|
||||
|
||||
- [`tests/lean/trust0`](https://github.com/leanprover/lean4/tree/master/tests/lean/trust0): tests that run Lean in a mode that Lean doesn't
|
||||
- `tests/lean/trust0`: tests that run Lean in a mode that Lean doesn't
|
||||
even trust the .olean files (i.e., trust 0).
|
||||
|
||||
- [`tests/bench`](https://github.com/leanprover/lean4/tree/master/tests/bench/): contains performance tests.
|
||||
- `tests/bench`: contains performance tests.
|
||||
|
||||
- [`tests/plugin`](https://github.com/leanprover/lean4/tree/master/tests/plugin/): tests that compiled Lean code can be loaded into
|
||||
- `tests/plugin`: tests that compiled Lean code can be loaded into
|
||||
`lean` via the `--plugin` command line option.
|
||||
|
||||
## Writing Good Tests
|
||||
|
||||
Every test file should contain:
|
||||
* an initial `/-! -/` module docstring summarizing the test's purpose
|
||||
* a module docstring for each test section that describes what is tested
|
||||
and, if not 100% clear, why that is the desirable behavior
|
||||
|
||||
At the time of writing, most tests do not follow these new guidelines yet.
|
||||
For an example of a conforming test, see [`tests/lean/1971.lean`](https://github.com/leanprover/lean4/tree/master/tests/lean/1971.lean).
|
||||
|
||||
## Fixing Tests
|
||||
|
||||
When the Lean source code or the standard library are modified, some of the
|
||||
@@ -119,7 +101,7 @@ First, we must install [meld](http://meldmerge.org/). On Ubuntu, we can do it by
|
||||
sudo apt-get install meld
|
||||
```
|
||||
|
||||
Now, suppose `bad_class.lean` test is broken. We can see the problem by going to [`tests/lean`](https://github.com/leanprover/lean4/tree/master/tests/lean) directory and
|
||||
Now, suppose `bad_class.lean` test is broken. We can see the problem by going to `test/lean` directory and
|
||||
executing
|
||||
|
||||
```
|
||||
@@ -132,3 +114,8 @@ outputs. `meld` can also be used to repair the problems.
|
||||
|
||||
In Emacs, we can also execute `M-x lean4-diff-test-file` to check/diff the file of the current buffer.
|
||||
To mass-copy all `.produced.out` files to the respective `.expected.out` file, use `tests/lean/copy-produced`.
|
||||
When using the Nix setup, add `--keep-failed` to the `nix build` call and then call
|
||||
```sh
|
||||
tests/lean/copy-produced <build-dir>/source/tests/lean
|
||||
```
|
||||
instead where `<build-dir>` is the path printed out by `nix build`.
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
/- "Hello world" -/
|
||||
|
||||
#eval "hello" ++ " " ++ "world"
|
||||
-- "hello world"
|
||||
|
||||
#check true
|
||||
-- Bool
|
||||
|
||||
def x := 10
|
||||
|
||||
#eval x + 2
|
||||
-- 12
|
||||
|
||||
def double (x : Int) := 2*x
|
||||
|
||||
#eval double 3
|
||||
-- 6
|
||||
#check double
|
||||
-- Int → Int
|
||||
example : double 4 = 8 := rfl
|
||||
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
/- Dependent pattern matching -/
|
||||
|
||||
inductive Vector (α : Type u) : Nat → Type u
|
||||
| nil : Vector α 0
|
||||
| cons : α → Vector α n → Vector α (n+1)
|
||||
|
||||
infix:67 "::" => Vector.cons
|
||||
|
||||
def Vector.zip : Vector α n → Vector β n → Vector (α × β) n
|
||||
| nil, nil => nil
|
||||
| a::as, b::bs => (a, b) :: zip as bs
|
||||
|
||||
#print Vector.zip
|
||||
/-
|
||||
def Vector.zip.{u_1, u_2} : {α : Type u_1} → {n : Nat} → {β : Type u_2} → Vector α n → Vector β n → Vector (α × β) n :=
|
||||
fun {α} {n} {β} x x_1 =>
|
||||
Vector.brecOn (motive := fun {n} x => {β : Type u_2} → Vector β n → Vector (α × β) n) x
|
||||
...
|
||||
-/
|
||||
@@ -1,22 +0,0 @@
|
||||
/- Structures -/
|
||||
|
||||
structure Point where
|
||||
x : Int := 0
|
||||
y : Int := 0
|
||||
deriving Repr
|
||||
|
||||
#eval Point.x (Point.mk 10 20)
|
||||
-- 10
|
||||
|
||||
#eval { x := 10, y := 20 : Point }
|
||||
|
||||
def p : Point := { y := 20 }
|
||||
|
||||
#eval p.x
|
||||
#eval p.y
|
||||
#eval { p with x := 5 }
|
||||
-- { x := 5, y := 20 }
|
||||
|
||||
structure Point3D extends Point where
|
||||
z : Int
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
/- Type classes -/
|
||||
namespace Example
|
||||
|
||||
class ToString (α : Type u) where
|
||||
toString : α → String
|
||||
|
||||
#check @ToString.toString
|
||||
-- {α : Type u_1} → [self : ToString α] → α → String
|
||||
|
||||
instance : ToString String where
|
||||
toString s := s
|
||||
|
||||
instance : ToString Bool where
|
||||
toString b := if b then "true" else "false"
|
||||
|
||||
#eval ToString.toString "hello"
|
||||
export ToString (toString)
|
||||
#eval toString true
|
||||
-- "true"
|
||||
-- #eval toString (true, "hello") -- Error
|
||||
|
||||
instance [ToString α] [ToString β] : ToString (α × β) where
|
||||
toString p := "(" ++ toString p.1 ++ ", " ++ toString p.2 ++ ")"
|
||||
|
||||
#eval toString (true, "hello")
|
||||
-- "(true, hello)"
|
||||
|
||||
end Example
|
||||
@@ -1,44 +0,0 @@
|
||||
/- Type classes are heavily used in Lean -/
|
||||
namespace Example
|
||||
|
||||
class Mul (α : Type u) where
|
||||
mul : α → α → α
|
||||
|
||||
infixl:70 " * " => Mul.mul
|
||||
|
||||
def double [Mul α] (a : α) := a * a
|
||||
|
||||
class Semigroup (α : Type u) extends Mul α where
|
||||
mul_assoc : ∀ a b c : α, (a * b) * c = a * (b * c)
|
||||
|
||||
instance : Semigroup Nat where
|
||||
mul := Nat.mul
|
||||
mul_assoc := Nat.mul_assoc
|
||||
|
||||
#eval double 5
|
||||
|
||||
class Functor (f : Type u → Type v) : Type (max (u+1) v) where
|
||||
map : (α → β) → f α → f β
|
||||
|
||||
infixr:100 " <$> " => Functor.map
|
||||
|
||||
class LawfulFunctor (f : Type u → Type v) [Functor f] : Prop where
|
||||
id_map (x : f α) : id <$> x = x
|
||||
comp_map (g : α → β) (h : β → γ) (x : f α) :(h ∘ g) <$> x = h <$> g <$> x
|
||||
|
||||
end Example
|
||||
|
||||
/-
|
||||
`Deriving instances automatically`
|
||||
|
||||
We have seen `deriving Repr` in a few examples.
|
||||
It is an instance generator.
|
||||
Lean comes equipped with generators for the following classes.
|
||||
`Repr`, `Inhabited`, `BEq`, `DecidableEq`,
|
||||
`Hashable`, `Ord`, `FromToJson`, `SizeOf`
|
||||
-/
|
||||
|
||||
inductive Tree (α : Type u) where
|
||||
| leaf (val : α)
|
||||
| node (left right : Tree α)
|
||||
deriving DecidableEq, Ord, Inhabited, Repr
|
||||
@@ -1,31 +0,0 @@
|
||||
/- Tactics -/
|
||||
|
||||
example : p → q → p ∧ q ∧ p := by
|
||||
intro hp hq
|
||||
apply And.intro
|
||||
exact hp
|
||||
apply And.intro
|
||||
exact hq
|
||||
exact hp
|
||||
|
||||
example : p → q → p ∧ q ∧ p := by
|
||||
intro hp hq; apply And.intro hp; exact And.intro hq hp
|
||||
|
||||
/- Structuring proofs -/
|
||||
|
||||
example : p → q → p ∧ q ∧ p := by
|
||||
intro hp hq
|
||||
apply And.intro
|
||||
case left => exact hp
|
||||
case right =>
|
||||
apply And.intro
|
||||
case left => exact hq
|
||||
case right => exact hp
|
||||
|
||||
example : p → q → p ∧ q ∧ p := by
|
||||
intro hp hq
|
||||
apply And.intro
|
||||
. exact hp
|
||||
. apply And.intro
|
||||
. exact hq
|
||||
. exact hp
|
||||
@@ -1,19 +0,0 @@
|
||||
/- intro tactic variants -/
|
||||
|
||||
example (p q : α → Prop) : (∃ x, p x ∧ q x) → ∃ x, q x ∧ p x := by
|
||||
intro h
|
||||
match h with
|
||||
| Exists.intro w (And.intro hp hq) => exact Exists.intro w (And.intro hq hp)
|
||||
|
||||
example (p q : α → Prop) : (∃ x, p x ∧ q x) → ∃ x, q x ∧ p x := by
|
||||
intro (Exists.intro _ (And.intro hp hq))
|
||||
exact Exists.intro _ (And.intro hq hp)
|
||||
|
||||
example (p q : α → Prop) : (∃ x, p x ∧ q x) → ∃ x, q x ∧ p x := by
|
||||
intro ⟨_, hp, hq⟩
|
||||
exact ⟨_, hq, hp⟩
|
||||
|
||||
example (α : Type) (p q : α → Prop) : (∃ x, p x ∨ q x) → ∃ x, q x ∨ p x := by
|
||||
intro
|
||||
| ⟨_, .inl h⟩ => exact ⟨_, .inr h⟩
|
||||
| ⟨_, .inr h⟩ => exact ⟨_, .inl h⟩
|
||||
@@ -1,12 +0,0 @@
|
||||
/- Inaccessible names -/
|
||||
|
||||
example : ∀ x y : Nat, x = y → y = x := by
|
||||
intros
|
||||
apply Eq.symm
|
||||
assumption
|
||||
|
||||
example : ∀ x y : Nat, x = y → y = x := by
|
||||
intros
|
||||
apply Eq.symm
|
||||
rename_i a b hab
|
||||
exact hab
|
||||
@@ -1,19 +0,0 @@
|
||||
/- More tactics -/
|
||||
|
||||
example (p q : Nat → Prop) : (∃ x, p x ∧ q x) → ∃ x, q x ∧ p x := by
|
||||
intro h
|
||||
cases h with
|
||||
| intro x hpq =>
|
||||
cases hpq with
|
||||
| intro hp hq =>
|
||||
exists x
|
||||
|
||||
example : p ∧ q → q ∧ p := by
|
||||
intro p
|
||||
cases p
|
||||
constructor <;> assumption
|
||||
|
||||
example : p ∧ ¬ p → q := by
|
||||
intro h
|
||||
cases h
|
||||
contradiction
|
||||
@@ -1,20 +0,0 @@
|
||||
/- Structuring proofs (cont.) -/
|
||||
|
||||
example : p ∧ (q ∨ r) → (p ∧ q) ∨ (p ∧ r) := by
|
||||
intro h
|
||||
have hp : p := h.left
|
||||
have hqr : q ∨ r := h.right
|
||||
show (p ∧ q) ∨ (p ∧ r)
|
||||
cases hqr with
|
||||
| inl hq => exact Or.inl ⟨hp, hq⟩
|
||||
| inr hr => exact Or.inr ⟨hp, hr⟩
|
||||
|
||||
example : p ∧ (q ∨ r) → (p ∧ q) ∨ (p ∧ r) := by
|
||||
intro ⟨hp, hqr⟩
|
||||
cases hqr with
|
||||
| inl hq =>
|
||||
have := And.intro hp hq
|
||||
apply Or.inl; exact this
|
||||
| inr hr =>
|
||||
have := And.intro hp hr
|
||||
apply Or.inr; exact this
|
||||
@@ -1,10 +0,0 @@
|
||||
/- Tactic combinators -/
|
||||
|
||||
example : p → q → r → p ∧ ((p ∧ q) ∧ r) ∧ (q ∧ r ∧ p) := by
|
||||
intros
|
||||
repeat (any_goals constructor)
|
||||
all_goals assumption
|
||||
|
||||
example : p → q → r → p ∧ ((p ∧ q) ∧ r) ∧ (q ∧ r ∧ p) := by
|
||||
intros
|
||||
repeat (any_goals (first | assumption | constructor))
|
||||
@@ -1,14 +0,0 @@
|
||||
/- First-class functions -/
|
||||
|
||||
def twice (f : Nat → Nat) (a : Nat) :=
|
||||
f (f a)
|
||||
|
||||
#check twice
|
||||
-- (Nat → Nat) → Nat → Nat
|
||||
|
||||
#eval twice (fun x => x + 2) 10
|
||||
|
||||
theorem twice_add_2 (a : Nat) : twice (fun x => x + 2) a = a + 4 := rfl
|
||||
|
||||
-- `(· + 2)` is syntax sugar for `(fun x => x + 2)`.
|
||||
#eval twice (· + 2) 10
|
||||
@@ -1,22 +0,0 @@
|
||||
/- Rewriting -/
|
||||
|
||||
example (f : Nat → Nat) (k : Nat) (h₁ : f 0 = 0) (h₂ : k = 0) : f k = 0 := by
|
||||
rw [h₂] -- replace k with 0
|
||||
rw [h₁] -- replace f 0 with 0
|
||||
|
||||
example (f : Nat → Nat) (k : Nat) (h₁ : f 0 = 0) (h₂ : k = 0) : f k = 0 := by
|
||||
rw [h₂, h₁]
|
||||
|
||||
example (f : Nat → Nat) (a b : Nat) (h₁ : a = b) (h₂ : f a = 0) : f b = 0 := by
|
||||
rw [← h₁, h₂]
|
||||
|
||||
example (f : Nat → Nat) (a : Nat) (h : 0 + a = 0) : f a = f 0 := by
|
||||
rw [Nat.zero_add] at h
|
||||
rw [h]
|
||||
|
||||
def Tuple (α : Type) (n : Nat) :=
|
||||
{ as : List α // as.length = n }
|
||||
|
||||
example (n : Nat) (h : n = 0) (t : Tuple α n) : Tuple α 0 := by
|
||||
rw [h] at t
|
||||
exact t
|
||||
@@ -1,21 +0,0 @@
|
||||
/- Simplifier -/
|
||||
|
||||
example (p : Nat → Prop) : (x + 0) * (0 + y * 1 + z * 0) = x * y := by
|
||||
simp
|
||||
|
||||
example (p : Nat → Prop) (h : p (x * y)) : p ((x + 0) * (0 + y * 1 + z * 0)) := by
|
||||
simp; assumption
|
||||
|
||||
example (p : Nat → Prop) (h : p ((x + 0) * (0 + y * 1 + z * 0))) : p (x * y) := by
|
||||
simp at h; assumption
|
||||
|
||||
def f (m n : Nat) : Nat :=
|
||||
m + n + m
|
||||
|
||||
example (h : n = 1) (h' : 0 = m) : (f m n) = n := by
|
||||
simp [h, ←h', f]
|
||||
|
||||
example (p : Nat → Prop) (h₁ : x + 0 = x') (h₂ : y + 0 = y')
|
||||
: x + y + 0 = x' + y' := by
|
||||
simp at *
|
||||
simp [*]
|
||||
@@ -1,13 +0,0 @@
|
||||
/- Simplifier -/
|
||||
|
||||
def mk_symm (xs : List α) :=
|
||||
xs ++ xs.reverse
|
||||
|
||||
@[simp] theorem reverse_mk_symm : (mk_symm xs).reverse = mk_symm xs := by
|
||||
simp [mk_symm]
|
||||
|
||||
theorem tst : (xs ++ mk_symm ys).reverse = mk_symm ys ++ xs.reverse := by
|
||||
simp
|
||||
|
||||
#print tst
|
||||
-- Lean reverse_mk_symm, and List.reverse_append
|
||||
@@ -1,26 +0,0 @@
|
||||
/- split tactic -/
|
||||
|
||||
def f (x y z : Nat) : Nat :=
|
||||
match x, y, z with
|
||||
| 5, _, _ => y
|
||||
| _, 5, _ => y
|
||||
| _, _, 5 => y
|
||||
| _, _, _ => 1
|
||||
|
||||
example : x ≠ 5 → y ≠ 5 → z ≠ 5 → z = w → f x y w = 1 := by
|
||||
intros
|
||||
simp [f]
|
||||
split
|
||||
. contradiction
|
||||
. contradiction
|
||||
. contradiction
|
||||
. rfl
|
||||
|
||||
def g (xs ys : List Nat) : Nat :=
|
||||
match xs, ys with
|
||||
| [a, b], _ => a+b+1
|
||||
| _, [b, c] => b+1
|
||||
| _, _ => 1
|
||||
|
||||
example (xs ys : List Nat) (h : g xs ys = 0) : False := by
|
||||
unfold g at h; split at h <;> simp_arith at h
|
||||
@@ -1,9 +0,0 @@
|
||||
/- induction tactic -/
|
||||
|
||||
example (as : List α) (a : α) : (as.concat a).length = as.length + 1 := by
|
||||
induction as with
|
||||
| nil => rfl
|
||||
| cons _ xs ih => simp [List.concat, ih]
|
||||
|
||||
example (as : List α) (a : α) : (as.concat a).length = as.length + 1 := by
|
||||
induction as <;> simp! [*]
|
||||
@@ -1,59 +0,0 @@
|
||||
/- Enumerated types -/
|
||||
|
||||
inductive Weekday where
|
||||
| sunday | monday | tuesday | wednesday
|
||||
| thursday | friday | saturday
|
||||
|
||||
#check Weekday.sunday
|
||||
-- Weekday
|
||||
|
||||
open Weekday
|
||||
#check sunday
|
||||
|
||||
def natOfWeekday (d : Weekday) : Nat :=
|
||||
match d with
|
||||
| sunday => 1
|
||||
| monday => 2
|
||||
| tuesday => 3
|
||||
| wednesday => 4
|
||||
| thursday => 5
|
||||
| friday => 6
|
||||
| saturday => 7
|
||||
|
||||
def Weekday.next (d : Weekday) : Weekday :=
|
||||
match d with
|
||||
| sunday => monday
|
||||
| monday => tuesday
|
||||
| tuesday => wednesday
|
||||
| wednesday => thursday
|
||||
| thursday => friday
|
||||
| friday => saturday
|
||||
| saturday => sunday
|
||||
|
||||
def Weekday.previous : Weekday → Weekday
|
||||
| sunday => saturday
|
||||
| monday => sunday
|
||||
| tuesday => monday
|
||||
| wednesday => tuesday
|
||||
| thursday => wednesday
|
||||
| friday => thursday
|
||||
| saturday => friday
|
||||
|
||||
/- Proving theorems using tactics -/
|
||||
|
||||
theorem Weekday.next_previous (d : Weekday) : d.next.previous = d :=
|
||||
match d with
|
||||
| sunday => rfl
|
||||
| monday => rfl
|
||||
| tuesday => rfl
|
||||
| wednesday => rfl
|
||||
| thursday => rfl
|
||||
| friday => rfl
|
||||
| saturday => rfl
|
||||
|
||||
theorem Weekday.next_previous' (d : Weekday) : d.next.previous = d := by -- switch to tactic mode
|
||||
cases d -- Creates 7 goals
|
||||
rfl; rfl; rfl; rfl; rfl; rfl; rfl
|
||||
|
||||
theorem Weekday.next_previous'' (d : Weekday) : d.next.previous = d := by
|
||||
cases d <;> rfl
|
||||
@@ -1,20 +0,0 @@
|
||||
/- What is the type of Nat? -/
|
||||
|
||||
#check 0
|
||||
-- Nat
|
||||
#check Nat
|
||||
-- Type
|
||||
#check Type
|
||||
-- Type 1
|
||||
#check Type 1
|
||||
-- Type 2
|
||||
#check Eq.refl 2
|
||||
-- 2 = 2
|
||||
#check 2 = 2
|
||||
-- Prop
|
||||
#check Prop
|
||||
-- Type
|
||||
|
||||
example : Prop = Sort 0 := rfl
|
||||
example : Type = Sort 1 := rfl
|
||||
example : Type 1 = Sort 2 := rfl
|
||||
@@ -1,21 +0,0 @@
|
||||
/- Implicit arguments and universe polymorphism -/
|
||||
|
||||
def f (α β : Sort u) (a : α) (b : β) : α := a
|
||||
|
||||
#eval f Nat String 1 "hello"
|
||||
-- 1
|
||||
|
||||
def g {α β : Sort u} (a : α) (b : β) : α := a
|
||||
|
||||
#eval g 1 "hello"
|
||||
|
||||
def h (a : α) (b : β) : α := a
|
||||
|
||||
#check g
|
||||
-- ?m.1 → ?m.2 → ?m.1
|
||||
#check @g
|
||||
-- {α β : Sort u} → α → β → α
|
||||
#check @h
|
||||
-- {α : Sort u_1} → {β : Sort u_2} → α → β → α
|
||||
#check g (α := Nat) (β := String)
|
||||
-- Nat → String → Nat
|
||||
@@ -1,14 +0,0 @@
|
||||
/- Inductive Types -/
|
||||
|
||||
inductive Tree (β : Type v) where
|
||||
| leaf
|
||||
| node (left : Tree β) (key : Nat) (value : β) (right : Tree β)
|
||||
deriving Repr
|
||||
|
||||
#eval Tree.node .leaf 10 true .leaf
|
||||
-- Tree.node Tree.leaf 10 true Tree.leaf
|
||||
|
||||
inductive Vector (α : Type u) : Nat → Type u
|
||||
| nil : Vector α 0
|
||||
| cons : α → Vector α n → Vector α (n+1)
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
/- Recursive functions -/
|
||||
|
||||
#print Nat -- Nat is an inductive datatype
|
||||
|
||||
def fib (n : Nat) : Nat :=
|
||||
match n with
|
||||
| 0 => 1
|
||||
| 1 => 1
|
||||
| n+2 => fib (n+1) + fib n
|
||||
|
||||
example : fib 5 = 8 := rfl
|
||||
|
||||
example : fib (n+2) = fib (n+1) + fib n := rfl
|
||||
|
||||
#print fib
|
||||
/-
|
||||
def fib : Nat → Nat :=
|
||||
fun n =>
|
||||
Nat.brecOn n fun n f =>
|
||||
(match (motive := (n : Nat) → Nat.below n → Nat) n with
|
||||
| 0 => fun x => 1
|
||||
| 1 => fun x => 1
|
||||
| Nat.succ (Nat.succ n) => fun x => x.fst.fst + x.fst.snd.fst.fst)
|
||||
f
|
||||
-/
|
||||
@@ -1,25 +0,0 @@
|
||||
/- Well-founded recursion -/
|
||||
|
||||
def ack : Nat → Nat → Nat
|
||||
| 0, y => y+1
|
||||
| x+1, 0 => ack x 1
|
||||
| x+1, y+1 => ack x (ack (x+1) y)
|
||||
termination_by ack x y => (x, y)
|
||||
|
||||
def sum (a : Array Int) : Int :=
|
||||
let rec go (i : Nat) :=
|
||||
if i < a.size then
|
||||
a[i] + go (i+1)
|
||||
else
|
||||
0
|
||||
go 0
|
||||
termination_by go i => a.size - i
|
||||
|
||||
set_option pp.proofs true
|
||||
#print sum.go
|
||||
/-
|
||||
def sum.go : Array Int → Nat → Int :=
|
||||
fun a =>
|
||||
WellFounded.fix (sum.go.proof_1 a) fun i a_1 =>
|
||||
if h : i < Array.size a then Array.getOp a i + a_1 (i + 1) (sum.go.proof_2 a i h) else 0
|
||||
-/
|
||||
@@ -1,44 +0,0 @@
|
||||
/- Mutual recursion -/
|
||||
|
||||
inductive Term where
|
||||
| const : String → Term
|
||||
| app : String → List Term → Term
|
||||
|
||||
namespace Term
|
||||
mutual
|
||||
def numConsts : Term → Nat
|
||||
| const _ => 1
|
||||
| app _ cs => numConstsLst cs
|
||||
|
||||
def numConstsLst : List Term → Nat
|
||||
| [] => 0
|
||||
| c :: cs => numConsts c + numConstsLst cs
|
||||
end
|
||||
|
||||
mutual
|
||||
def replaceConst (a b : String) : Term → Term
|
||||
| const c => if a = c then const b else const c
|
||||
| app f cs => app f (replaceConstLst a b cs)
|
||||
|
||||
def replaceConstLst (a b : String) : List Term → List Term
|
||||
| [] => []
|
||||
| c :: cs => replaceConst a b c :: replaceConstLst a b cs
|
||||
end
|
||||
|
||||
/- Mutual recursion in theorems -/
|
||||
|
||||
mutual
|
||||
theorem numConsts_replaceConst (a b : String) (e : Term)
|
||||
: numConsts (replaceConst a b e) = numConsts e := by
|
||||
match e with
|
||||
| const c => simp [replaceConst]; split <;> simp [numConsts]
|
||||
| app f cs => simp [replaceConst, numConsts, numConsts_replaceConstLst a b cs]
|
||||
|
||||
theorem numConsts_replaceConstLst (a b : String) (es : List Term)
|
||||
: numConstsLst (replaceConstLst a b es) = numConstsLst es := by
|
||||
match es with
|
||||
| [] => simp [replaceConstLst, numConstsLst]
|
||||
| c :: cs =>
|
||||
simp [replaceConstLst, numConstsLst, numConsts_replaceConst a b c,
|
||||
numConsts_replaceConstLst a b cs]
|
||||
end
|
||||
@@ -1,46 +0,0 @@
|
||||
import Lean
|
||||
|
||||
open Lean Meta
|
||||
|
||||
def ctor (mvarId : MVarId) (idx : Nat) : MetaM (List MVarId) := do
|
||||
/- Set `MetaM` context using `mvarId` -/
|
||||
withMVarContext mvarId do
|
||||
/- Fail if the metavariable is already assigned. -/
|
||||
checkNotAssigned mvarId `ctor
|
||||
/- Retrieve the target type, instantiateMVars, and use `whnf`. -/
|
||||
let target ← getMVarType' mvarId
|
||||
let .const declName us := target.getAppFn
|
||||
| throwTacticEx `ctor mvarId "target is not an inductive datatype"
|
||||
let .inductInfo { ctors, .. } ← getConstInfo declName
|
||||
| throwTacticEx `ctor mvarId "target is not an inductive datatype"
|
||||
if idx = 0 then
|
||||
throwTacticEx `ctor mvarId "invalid index, it must be > 0"
|
||||
else if h : idx - 1 < ctors.length then
|
||||
apply mvarId (.const ctors[idx - 1] us)
|
||||
else
|
||||
throwTacticEx `ctor mvarId "invalid index, inductive datatype has only {ctors.length} contructors"
|
||||
|
||||
open Elab Tactic
|
||||
|
||||
elab "ctor" idx:num : tactic =>
|
||||
liftMetaTactic (ctor · idx.getNat)
|
||||
|
||||
example (p : Prop) : p := by
|
||||
ctor 1 -- Error
|
||||
|
||||
example (h : q) : p ∨ q := by
|
||||
ctor 0 -- Error
|
||||
exact h
|
||||
|
||||
example (h : q) : p ∨ q := by
|
||||
ctor 3 -- Error
|
||||
exact h
|
||||
|
||||
example (h : q) : p ∨ q := by
|
||||
ctor 2
|
||||
exact h
|
||||
|
||||
example (h : q) : p ∨ q := by
|
||||
ctor 1
|
||||
exact h -- Error
|
||||
|
||||
@@ -1,80 +0,0 @@
|
||||
import Lean
|
||||
|
||||
open Lean Meta
|
||||
|
||||
def ex1 (declName : Name) : MetaM Unit := do
|
||||
let info ← getConstInfo declName
|
||||
IO.println s!"{declName} : {← ppExpr info.type}"
|
||||
if let some val := info.value? then
|
||||
IO.println s!"{declName} : {← ppExpr val}"
|
||||
|
||||
#eval ex1 ``Nat
|
||||
|
||||
def ex2 (declName : Name) : MetaM Unit := do
|
||||
let info ← getConstInfo declName
|
||||
trace[Meta.debug] "{declName} : {info.type}"
|
||||
if let some val := info.value? then
|
||||
trace[Meta.debug] "{declName} : {val}"
|
||||
|
||||
#eval ex2 ``Add.add
|
||||
|
||||
set_option trace.Meta.debug true in
|
||||
#eval ex2 ``Add.add
|
||||
|
||||
def ex3 (declName : Name) : MetaM Unit := do
|
||||
let info ← getConstInfo declName
|
||||
forallTelescope info.type fun xs type => do
|
||||
trace[Meta.debug] "hypotheses : {xs}"
|
||||
trace[Meta.debug] "resultType : {type}"
|
||||
for x in xs do
|
||||
trace[Meta.debug] "{x} : {← inferType x}"
|
||||
|
||||
def myMin [LT α] [DecidableRel (α := α) (·<·)] (a b : α) : α :=
|
||||
if a < b then
|
||||
a
|
||||
else
|
||||
b
|
||||
|
||||
set_option trace.Meta.debug true in
|
||||
#eval ex3 ``myMin
|
||||
|
||||
def ex4 : MetaM Unit := do
|
||||
let nat := mkConst ``Nat
|
||||
withLocalDeclD `a nat fun a =>
|
||||
withLocalDeclD `b nat fun b => do
|
||||
let e ← mkAppM ``HAdd.hAdd #[a, b]
|
||||
trace[Meta.debug] "{e} : {← inferType e}"
|
||||
let e ← mkAdd a (mkNatLit 5)
|
||||
trace[Meta.debug] "added 5: {e}"
|
||||
let e ← whnf e
|
||||
trace[Meta.debug] "whnf: {e}"
|
||||
let e ← reduce e
|
||||
trace[Meta.debug] "reduced: {e}"
|
||||
let a_plus_1 ← mkAdd a (mkNatLit 1)
|
||||
let succ_a := mkApp (mkConst ``Nat.succ) a
|
||||
trace[Meta.debug] "({a_plus_1} =?= {succ_a}) == {← isDefEq a_plus_1 succ_a}"
|
||||
let m ← mkFreshExprMVar nat
|
||||
let m_plus_1 ← mkAdd m (mkNatLit 1)
|
||||
trace[Meta.debug] "m_plus_1: {m_plus_1}"
|
||||
unless (← isDefEq m_plus_1 succ_a) do throwError "isDefEq failed"
|
||||
trace[Meta.debug] "m_plus_1: {m_plus_1}"
|
||||
|
||||
set_option trace.Meta.debug true in
|
||||
#eval ex4
|
||||
|
||||
open Elab Term
|
||||
|
||||
def ex5 : TermElabM Unit := do
|
||||
let nat := Lean.mkConst ``Nat
|
||||
withLocalDeclD `a nat fun a => do
|
||||
withLocalDeclD `b nat fun b => do
|
||||
let ab ← mkAppM ``HAdd.hAdd #[a, b]
|
||||
let stx ← `(fun x => if x < 10 then $(← exprToSyntax ab) + x else x + $(← exprToSyntax a))
|
||||
let e ← elabTerm stx none
|
||||
trace[Meta.debug] "{e} : {← inferType e}"
|
||||
let e := mkApp e (mkNatLit 5)
|
||||
let e ← whnf e
|
||||
trace[Meta.debug] "{e}"
|
||||
|
||||
set_option trace.Meta.debug true in
|
||||
#eval ex5
|
||||
@@ -1,49 +0,0 @@
|
||||
import Lean
|
||||
|
||||
def f (x y : Nat) := x * y + 1
|
||||
|
||||
infixl:65 " *' " => f
|
||||
|
||||
#check 2 *' 3
|
||||
|
||||
notation "unitTest " x => Prod.mk x ()
|
||||
|
||||
#check unitTest 42
|
||||
|
||||
notation "parenthesisTest " x => Nat.sub (x)
|
||||
#check parenthesisTest 12
|
||||
|
||||
def Set (α : Type u) := α → Prop
|
||||
def setOf {α : Type} (p : α → Prop) : Set α := p
|
||||
notation "{ " x " | " p " }" => setOf (fun x => p)
|
||||
|
||||
#check { x | x ≤ 1 }
|
||||
|
||||
notation "cdotTest " "(" x ", " y ")" => Prod.map (· + 1) (1 + ·) (x, y)
|
||||
|
||||
#check cdotTest (13, 12)
|
||||
|
||||
notation "tupleFunctionTest " "(" x ", " y ")"=> Prod.map (Nat.add 1) (Nat.add 2) (x, y)
|
||||
|
||||
#check tupleFunctionTest (15, 12)
|
||||
|
||||
notation "diag " x => Prod.mk x x
|
||||
|
||||
#check diag 12
|
||||
|
||||
open Lean Meta PrettyPrinter Delaborator SubExpr in
|
||||
@[delab app.Prod.mk] def delabDoubleRhsTest : Delab := do
|
||||
let e ← getExpr
|
||||
let #[_, _, x, y] := e.getAppArgs | failure
|
||||
guard (← isDefEq x y)
|
||||
let stx ← withAppArg delab
|
||||
`(diag $stx)
|
||||
|
||||
#check diag 3
|
||||
#check (3, 3)
|
||||
#check (3, 4)
|
||||
#check (2+1, 3)
|
||||
#check (true, true)
|
||||
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ If the type of keys can be totally ordered -- that is, it supports a well-behave
|
||||
then maps can be implemented with binary search trees (BSTs). Insert and lookup operations on BSTs take time
|
||||
proportional to the height of the tree. If the tree is balanced, the operations therefore take logarithmic time.
|
||||
|
||||
This example is based on a similar example found in the ["Software Foundations"](https://softwarefoundations.cis.upenn.edu/vfa-current/SearchTree.html)
|
||||
This example is based on a similar example found in the ["Sofware Foundations"](https://softwarefoundations.cis.upenn.edu/vfa-current/SearchTree.html)
|
||||
book (volume 3).
|
||||
-/
|
||||
|
||||
@@ -81,9 +81,9 @@ def Tree.toList (t : Tree β) : List (Nat × β) :=
|
||||
|>.toList
|
||||
|
||||
/-!
|
||||
The implementation of `Tree.toList` is inefficient because of how it uses the `++` operator.
|
||||
The implemention of `Tree.toList` is inefficient because of how it uses the `++` operator.
|
||||
On a balanced tree its running time is linearithmic, because it does a linear number of
|
||||
concatenations at each level of the tree. On an unbalanced tree it's quadratic time.
|
||||
concatentations at each level of the tree. On an unbalanced tree it's quadratic time.
|
||||
Here's a tail-recursive implementation than runs in linear time, regardless of whether the tree is balanced:
|
||||
-/
|
||||
def Tree.toListTR (t : Tree β) : List (Nat × β) :=
|
||||
@@ -114,9 +114,9 @@ concatenating all goals produced by `tac'`. In this theorem, we use it to apply
|
||||
|
||||
The `simp` parameters `toListTR.go` and `toList` instruct the simplifier to try to reduce
|
||||
and/or apply auto generated equation theorems for these two functions.
|
||||
The parameter `*` instructs the simplifier to use any equation in a goal as rewriting rules.
|
||||
The parameter `*` intructs the simplifier to use any equation in a goal as rewriting rules.
|
||||
In this particular case, `simp` uses the induction hypotheses as rewriting rules.
|
||||
Finally, the parameter `List.append_assoc` instructs the simplifier to use the
|
||||
Finally, the parameter `List.append_assoc` intructs the simplifier to use the
|
||||
`List.append_assoc` theorem as a rewriting rule.
|
||||
-/
|
||||
theorem Tree.toList_eq_toListTR (t : Tree β)
|
||||
@@ -176,23 +176,22 @@ The modifier `local` specifies the scope of the macro.
|
||||
/-- The `have_eq lhs rhs` tactic (tries to) prove that `lhs = rhs`,
|
||||
and then replaces `lhs` with `rhs`. -/
|
||||
local macro "have_eq " lhs:term:max rhs:term:max : tactic =>
|
||||
`(tactic|
|
||||
(have h : $lhs = $rhs :=
|
||||
`((have h : $lhs:term = $rhs:term :=
|
||||
-- TODO: replace with linarith
|
||||
by simp_arith at *; apply Nat.le_antisymm <;> assumption
|
||||
try subst $lhs))
|
||||
try subst $lhs:term))
|
||||
|
||||
/-!
|
||||
The `by_cases' e` is just the regular `by_cases` followed by `simp` using all
|
||||
hypotheses in the current goal as rewriting rules.
|
||||
Recall that the `by_cases` tactic creates two goals. One where we have `h : e` and
|
||||
another one containing `h : ¬ e`. The simplifier uses the `h` to rewrite `e` to `True`
|
||||
another one containing `h : ¬ e`. The simplier uses the `h` to rewrite `e` to `True`
|
||||
in the first subgoal, and `e` to `False` in the second. This is particularly
|
||||
useful if `e` is the condition of an `if`-statement.
|
||||
-/
|
||||
/-- `by_cases' e` is a shorthand form `by_cases e <;> simp[*]` -/
|
||||
local macro "by_cases' " e:term : tactic =>
|
||||
`(tactic| by_cases $e <;> simp [*])
|
||||
`(by_cases $e:term <;> simp [*])
|
||||
|
||||
|
||||
/-!
|
||||
@@ -282,7 +281,7 @@ theorem BinTree.find_insert_of_ne (b : BinTree β) (h : k ≠ k') (v : β)
|
||||
let ⟨t, h⟩ := b; simp
|
||||
induction t with simp
|
||||
| leaf =>
|
||||
split <;> (try simp) <;> split <;> (try simp)
|
||||
split <;> simp <;> split <;> simp
|
||||
have_eq k k'
|
||||
contradiction
|
||||
| node left key value right ihl ihr =>
|
||||
|
||||
@@ -152,7 +152,7 @@ We prove all cases but the one for `plus` using `simp [*]`. This tactic instruct
|
||||
use hypotheses such as `a = b` as rewriting/simplications rules.
|
||||
We use the `split` to break the nested `match` expression in the `plus` case into two cases.
|
||||
The local variables `iha` and `ihb` are the induction hypotheses for `a` and `b`.
|
||||
The modifier `←` in a term simplifier argument instructs the term simplifier to use the equation as a rewriting rule in
|
||||
The modifier `←` in a term simplifier argument instructs the term simplier to use the equation as a rewriting rule in
|
||||
the "reverse direction". That is, given `h : a = b`, `← h` instructs the term simplifier to rewrite `b` subterms to `a`.
|
||||
-/
|
||||
theorem Term.constFold_sound (e : Term ctx ty) : e.constFold.denote env = e.denote env := by
|
||||
|
||||
@@ -83,7 +83,7 @@ In practice, this means we use `stop` to refer to the most recently defined vari
|
||||
|
||||
A value `Expr.val` carries a concrete representation of an integer.
|
||||
|
||||
A lambda `Expr.lam` creates a function. In the scope of a function of type `Ty.fn a ty`, there is a
|
||||
A lambda `Expr.lam` creates a function. In the scope of a function ot type `Ty.fn a ty`, there is a
|
||||
new local variable of type `a`.
|
||||
|
||||
A function application `Expr.app` produces a value of type `ty` given a function from `a` to `ty` and a value of type `a`.
|
||||
@@ -139,7 +139,7 @@ def add : Expr ctx (Ty.fn Ty.int (Ty.fn Ty.int Ty.int)) :=
|
||||
More interestingly, a factorial function fact (e.g. `fun x => if (x == 0) then 1 else (fact (x-1) * x)`), can be written as.
|
||||
Note that this is a recursive (non-terminating) definition. For every input value, the interpreter terminates, but the
|
||||
definition itself is non-terminating. We use two tricks to make sure Lean accepts it. First, we use the auxiliary constructor
|
||||
`Expr.delay` to delay its unfolding. Second, we add the annotation `decreasing_by sorry` which can be viewed as
|
||||
`Expr.delay` to delay its unfolding. Second, we add the annotation `decreasing_by sorry` which can be viwed as
|
||||
"trust me, this recursive definition makes sense". Recall that `sorry` is an unsound axiom in Lean.
|
||||
-/
|
||||
|
||||
|
||||
@@ -82,7 +82,7 @@ theorem List.palindrome_ind (motive : List α → Prop)
|
||||
have ih := palindrome_ind motive h₁ h₂ h₃ (a₂::as').dropLast
|
||||
have : [a₁] ++ (a₂::as').dropLast ++ [(a₂::as').last (by simp)] = a₁::a₂::as' := by simp
|
||||
this ▸ h₃ _ _ _ ih
|
||||
termination_by as.length
|
||||
termination_by _ as => as.length
|
||||
|
||||
/-!
|
||||
We use our new induction principle to prove that if `as.reverse = as`, then `Palindrome as` holds.
|
||||
|
||||
@@ -48,7 +48,7 @@ inductive Term' (rep : Ty → Type) : Ty → Type
|
||||
| plus : Term' rep .nat → Term' rep .nat → Term' rep .nat
|
||||
| lam : (rep dom → Term' rep ran) → Term' rep (.fn dom ran)
|
||||
| app : Term' rep (.fn dom ran) → Term' rep dom → Term' rep ran
|
||||
| let : Term' rep ty₁ → (rep ty₁ → Term' rep ty₂) → Term' rep ty₂
|
||||
| «let» : Term' rep ty₁ → (rep ty₁ → Term' rep ty₂) → Term' rep ty₂
|
||||
|
||||
/-!
|
||||
Lean accepts this definition because our embedded functions now merely take variables as
|
||||
@@ -62,6 +62,7 @@ choices of `rep` type family
|
||||
-/
|
||||
|
||||
open Ty (nat fn)
|
||||
open Term'
|
||||
|
||||
namespace FirstTry
|
||||
|
||||
@@ -71,11 +72,11 @@ def Term (ty : Ty) := (rep : Ty → Type) → Term' rep ty
|
||||
In the next two example, note how each is written as a function over a `rep` choice,
|
||||
such that the specific choice has no impact on the structure of the term.
|
||||
-/
|
||||
def add : Term (fn nat (fn nat nat)) := fun _rep =>
|
||||
.lam fun x => .lam fun y => .plus (.var x) (.var y)
|
||||
def add : Term (fn nat (fn nat nat)) := fun rep =>
|
||||
lam fun x => lam fun y => plus (var x) (var y)
|
||||
|
||||
def three_the_hard_way : Term nat := fun rep =>
|
||||
.app (.app (add rep) (.const 1)) (.const 2)
|
||||
app (app (add rep) (const 1)) (const 2)
|
||||
|
||||
end FirstTry
|
||||
|
||||
@@ -89,10 +90,10 @@ we can completely hide `rep` in these examples.
|
||||
def Term (ty : Ty) := {rep : Ty → Type} → Term' rep ty
|
||||
|
||||
def add : Term (fn nat (fn nat nat)) :=
|
||||
.lam fun x => .lam fun y => .plus (.var x) (.var y)
|
||||
lam fun x => lam fun y => plus (var x) (var y)
|
||||
|
||||
def three_the_hard_way : Term nat :=
|
||||
.app (.app add (.const 1)) (.const 2)
|
||||
app (app add (const 1)) (const 2)
|
||||
|
||||
/-!
|
||||
It may not be at all obvious that the PHOAS representation admits the crucial computable
|
||||
@@ -106,12 +107,12 @@ pass beneath. For our current choice of `Unit` data, we always pass `()`.
|
||||
-/
|
||||
|
||||
def countVars : Term' (fun _ => Unit) ty → Nat
|
||||
| .var _ => 1
|
||||
| .const _ => 0
|
||||
| .plus a b => countVars a + countVars b
|
||||
| .app f a => countVars f + countVars a
|
||||
| .lam b => countVars (b ())
|
||||
| .let a b => countVars a + countVars (b ())
|
||||
| var h => 1
|
||||
| const n => 0
|
||||
| plus a b => countVars a + countVars b
|
||||
| app f a => countVars f + countVars a
|
||||
| lam b => countVars (b ())
|
||||
| «let» a b => countVars a + countVars (b ())
|
||||
|
||||
/-! We can now easily prove that `add` has two variables by using reflexivity -/
|
||||
|
||||
@@ -127,14 +128,14 @@ We also use the string interpolation available in Lean. For example, `s!"x_{i}"`
|
||||
-/
|
||||
def pretty (e : Term' (fun _ => String) ty) (i : Nat := 1) : String :=
|
||||
match e with
|
||||
| .var s => s
|
||||
| .const n => toString n
|
||||
| .app f a => s!"({pretty f i} {pretty a i})"
|
||||
| .plus a b => s!"({pretty a i} + {pretty b i})"
|
||||
| .lam f =>
|
||||
| var s => s
|
||||
| const n => toString n
|
||||
| app f a => s!"({pretty f i} {pretty a i})"
|
||||
| plus a b => s!"({pretty a i} + {pretty b i})"
|
||||
| lam f =>
|
||||
let x := s!"x_{i}"
|
||||
s!"(fun {x} => {pretty (f x) (i+1)})"
|
||||
| .let a b =>
|
||||
| «let» a b =>
|
||||
let x := s!"x_{i}"
|
||||
s!"(let {x} := {pretty a i}; => {pretty (b x) (i+1)}"
|
||||
|
||||
@@ -150,12 +151,12 @@ new variables are added, but they are only tagged with their own term equivalent
|
||||
that this function squash is parameterized over a specific `rep` choice.
|
||||
-/
|
||||
def squash : Term' (Term' rep) ty → Term' rep ty
|
||||
| .var e => e
|
||||
| .const n => .const n
|
||||
| .plus a b => .plus (squash a) (squash b)
|
||||
| .lam f => .lam fun x => squash (f (.var x))
|
||||
| .app f a => .app (squash f) (squash a)
|
||||
| .let a b => .let (squash a) fun x => squash (b (.var x))
|
||||
| var e => e
|
||||
| const n => const n
|
||||
| plus a b => plus (squash a) (squash b)
|
||||
| lam f => lam fun x => squash (f (.var x))
|
||||
| app f a => app (squash f) (squash a)
|
||||
| «let» a b => «let» (squash a) fun x => squash (b (.var x))
|
||||
|
||||
/-!
|
||||
To define the final substitution function over terms with single free variables, we define
|
||||
@@ -180,7 +181,7 @@ We can view `Term1` as a term with hole. In the following example,
|
||||
the hole `_` is instantiated by `subst` with `three_the_hard_way`
|
||||
-/
|
||||
|
||||
#eval pretty <| subst (fun x => .plus (.var x) (.const 5)) three_the_hard_way
|
||||
#eval pretty <| subst (fun x => plus (var x) (const 5)) three_the_hard_way
|
||||
|
||||
/-!
|
||||
One further development, which may seem surprising at first,
|
||||
@@ -191,12 +192,12 @@ The attribute `[simp]` instructs Lean to always try to unfold `denote` applicati
|
||||
the `simp` tactic. We also say this is a hint for the Lean term simplifier.
|
||||
-/
|
||||
@[simp] def denote : Term' Ty.denote ty → ty.denote
|
||||
| .var x => x
|
||||
| .const n => n
|
||||
| .plus a b => denote a + denote b
|
||||
| .app f a => denote f (denote a)
|
||||
| .lam f => fun x => denote (f x)
|
||||
| .let a b => denote (b (denote a))
|
||||
| var x => x
|
||||
| const n => n
|
||||
| plus a b => denote a + denote b
|
||||
| app f a => denote f (denote a)
|
||||
| lam f => fun x => denote (f x)
|
||||
| «let» a b => denote (b (denote a))
|
||||
|
||||
example : denote three_the_hard_way = 3 :=
|
||||
rfl
|
||||
@@ -212,15 +213,15 @@ We now define the constant folding optimization that traverses a term if replace
|
||||
`plus (const m) (const n)` with `const (n+m)`.
|
||||
-/
|
||||
@[simp] def constFold : Term' rep ty → Term' rep ty
|
||||
| .var x => .var x
|
||||
| .const n => .const n
|
||||
| .app f a => .app (constFold f) (constFold a)
|
||||
| .lam f => .lam fun x => constFold (f x)
|
||||
| .let a b => .let (constFold a) fun x => constFold (b x)
|
||||
| .plus a b =>
|
||||
| var x => var x
|
||||
| const n => const n
|
||||
| app f a => app (constFold f) (constFold a)
|
||||
| lam f => lam fun x => constFold (f x)
|
||||
| «let» a b => «let» (constFold a) fun x => constFold (b x)
|
||||
| plus a b =>
|
||||
match constFold a, constFold b with
|
||||
| .const n, .const m => .const (n+m)
|
||||
| a', b' => .plus a' b'
|
||||
| const n, const m => const (n+m)
|
||||
| a', b' => plus a' b'
|
||||
|
||||
/-!
|
||||
The correctness of the `constFold` is proved using induction, case-analysis, and the term simplifier.
|
||||
@@ -228,7 +229,7 @@ We prove all cases but the one for `plus` using `simp [*]`. This tactic instruct
|
||||
use hypotheses such as `a = b` as rewriting/simplications rules.
|
||||
We use the `split` to break the nested `match` expression in the `plus` case into two cases.
|
||||
The local variables `iha` and `ihb` are the induction hypotheses for `a` and `b`.
|
||||
The modifier `←` in a term simplifier argument instructs the term simplifier to use the equation as a rewriting rule in
|
||||
The modifier `←` in a term simplifier argument instructs the term simplier to use the equation as a rewriting rule in
|
||||
the "reverse direction. That is, given `h : a = b`, `← h` instructs the term simplifier to rewrite `b` subterms to `a`.
|
||||
-/
|
||||
theorem constFold_sound (e : Term' Ty.denote ty) : denote (constFold e) = denote e := by
|
||||
|
||||
@@ -38,7 +38,7 @@ theorem HasType.det (h₁ : HasType e t₁) (h₂ : HasType e t₂) : t₁ = t
|
||||
cases h₁ <;> cases h₂ <;> rfl
|
||||
|
||||
/-!
|
||||
The inductive type `Maybe p` has two constructors: `found a h` and `unknown`.
|
||||
The inductive type `Maybe p` has two contructors: `found a h` and `unknown`.
|
||||
The former contains an element `a : α` and a proof that `a` satisfies the predicate `p`.
|
||||
The constructor `unknown` is used to encode "failure".
|
||||
-/
|
||||
|
||||
@@ -1,217 +0,0 @@
|
||||
import Lean
|
||||
open Lean Widget
|
||||
|
||||
/-!
|
||||
# The user-widgets system
|
||||
|
||||
Proving and programming are inherently interactive tasks. Lots of mathematical objects and data
|
||||
structures are visual in nature. *User widgets* let you associate custom interactive UIs with
|
||||
sections of a Lean document. User widgets are rendered in the Lean infoview.
|
||||
|
||||

|
||||
|
||||
## Trying it out
|
||||
|
||||
To try it out, simply type in the following code and place your cursor over the `#widget` command.
|
||||
-/
|
||||
|
||||
@[widget_module]
|
||||
def helloWidget : Widget.Module where
|
||||
javascript := "
|
||||
import * as React from 'react';
|
||||
export default function(props) {
|
||||
const name = props.name || 'world'
|
||||
return React.createElement('p', {}, name + '!')
|
||||
}"
|
||||
|
||||
#widget helloWidget
|
||||
|
||||
/-!
|
||||
If you want to dive into a full sample right away, check out
|
||||
[`RubiksCube`](https://github.com/leanprover/lean4-samples/blob/main/RubiksCube/).
|
||||
Below, we'll explain the system piece by piece.
|
||||
|
||||
⚠️ WARNING: All of the user widget APIs are **unstable** and subject to breaking changes.
|
||||
|
||||
## Widget sources and instances
|
||||
|
||||
A *widget source* is a valid JavaScript [ESModule](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules)
|
||||
which exports a [React component](https://reactjs.org/docs/components-and-props.html). To access
|
||||
React, the module must use `import * as React from 'react'`. Our first example of a widget source
|
||||
is of course the value of `helloWidget.javascript`.
|
||||
|
||||
We can register a widget source with the `@[widget]` attribute, giving it a friendlier name
|
||||
in the `name` field. This is bundled together in a `UserWidgetDefinition`.
|
||||
|
||||
A *widget instance* is then the identifier of a `UserWidgetDefinition` (so `` `helloWidget ``,
|
||||
not `"Hello"`) associated with a range of positions in the Lean source code. Widget instances
|
||||
are stored in the *infotree* in the same manner as other information about the source file
|
||||
such as the type of every expression. In our example, the `#widget` command stores a widget instance
|
||||
with the entire line as its range. We can think of a widget instance as an instruction for the
|
||||
infoview: "when the user places their cursor here, please render the following widget".
|
||||
|
||||
Every widget instance also contains a `props : Json` value. This value is passed as an argument
|
||||
to the React component. In our first invocation of `#widget`, we set it to `.null`. Try out what
|
||||
happens when you type in:
|
||||
-/
|
||||
|
||||
structure HelloWidgetProps where
|
||||
name? : Option String := none
|
||||
deriving Server.RpcEncodable
|
||||
|
||||
#widget helloWidget with { name? := "<your name here>" : HelloWidgetProps }
|
||||
|
||||
/-!
|
||||
💡 NOTE: The RPC system presented below does not depend on JavaScript. However the primary use case
|
||||
is the web-based infoview in VSCode.
|
||||
|
||||
## Querying the Lean server
|
||||
|
||||
Besides enabling us to create cool client-side visualizations, user widgets come with the ability
|
||||
to communicate with the Lean server. Thanks to this, they have the same metaprogramming capabilities
|
||||
as custom elaborators or the tactic framework. To see this in action, let's implement a `#check`
|
||||
command as a web input form. This example assumes some familiarity with React.
|
||||
|
||||
The first thing we'll need is to create an *RPC method*. Meaning "Remote Procedure Call", this
|
||||
is basically a Lean function callable from widget code (possibly remotely over the internet).
|
||||
Our method will take in the `name : Name` of a constant in the environment and return its type.
|
||||
By convention, we represent the input data as a `structure`. Since it will be sent over from JavaScript,
|
||||
we need `FromJson` and `ToJson`. We'll see below why the position field is needed.
|
||||
-/
|
||||
|
||||
structure GetTypeParams where
|
||||
/-- Name of a constant to get the type of. -/
|
||||
name : Name
|
||||
/-- Position of our widget instance in the Lean file. -/
|
||||
pos : Lsp.Position
|
||||
deriving FromJson, ToJson
|
||||
|
||||
/-!
|
||||
After its arguments, we define the `getType` method. Every RPC method executes in the `RequestM`
|
||||
monad and must return a `RequestTask α` where `α` is its "actual" return type. The `Task` is so
|
||||
that requests can be handled concurrently. A first guess for `α` might be `Expr`. However,
|
||||
expressions in general can be large objects which depend on an `Environment` and `LocalContext`.
|
||||
Thus we cannot directly serialize an `Expr` and send it to the widget. Instead, there are two
|
||||
options:
|
||||
- One is to send a *reference* which points to an object residing on the server. From JavaScript's
|
||||
point of view, references are entirely opaque, but they can be sent back to other RPC methods for
|
||||
further processing.
|
||||
- Two is to pretty-print the expression and send its textual representation called `CodeWithInfos`.
|
||||
This representation contains extra data which the infoview uses for interactivity. We take this
|
||||
strategy here.
|
||||
|
||||
RPC methods execute in the context of a file, but not any particular `Environment` so they don't
|
||||
know about the available `def`initions and `theorem`s. Thus, we need to pass in a position at which
|
||||
we want to use the local `Environment`. This is why we store it in `GetTypeParams`. The `withWaitFindSnapAtPos`
|
||||
method launches a concurrent computation whose job is to find such an `Environment` and a bit
|
||||
more information for us, in the form of a `snap : Snapshot`. With this in hand, we can call
|
||||
`MetaM` procedures to find out the type of `name` and pretty-print it.
|
||||
-/
|
||||
|
||||
open Server RequestM in
|
||||
@[server_rpc_method]
|
||||
def getType (params : GetTypeParams) : RequestM (RequestTask CodeWithInfos) :=
|
||||
withWaitFindSnapAtPos params.pos fun snap => do
|
||||
runTermElabM snap do
|
||||
let name ← resolveGlobalConstNoOverloadCore params.name
|
||||
let c ← try getConstInfo name
|
||||
catch _ => throwThe RequestError ⟨.invalidParams, s!"no constant named '{name}'"⟩
|
||||
Widget.ppExprTagged c.type
|
||||
|
||||
/-!
|
||||
## Using infoview components
|
||||
|
||||
Now that we have all we need on the server side, let's write the widget source. By importing
|
||||
`@leanprover/infoview`, widgets can render UI components used to implement the infoview itself.
|
||||
For example, the `<InteractiveCode>` component displays expressions with `term : type` tooltips
|
||||
as seen in the goal view. We will use it to implement our custom `#check` display.
|
||||
|
||||
⚠️ WARNING: Like the other widget APIs, the infoview JS API is **unstable** and subject to breaking changes.
|
||||
|
||||
The code below demonstrates useful parts of the API. To make RPC method calls, we use the `RpcContext`.
|
||||
The `useAsync` helper packs the results of a call into an `AsyncState` structure which indicates
|
||||
whether the call has resolved successfully, has returned an error, or is still in-flight. Based
|
||||
on this we either display an `InteractiveCode` with the type, `mapRpcError` the error in order
|
||||
to turn it into a readable message, or show a `Loading..` message, respectively.
|
||||
-/
|
||||
|
||||
@[widget_module]
|
||||
def checkWidget : Widget.Module where
|
||||
javascript := "
|
||||
import * as React from 'react';
|
||||
const e = React.createElement;
|
||||
import { RpcContext, InteractiveCode, useAsync, mapRpcError } from '@leanprover/infoview';
|
||||
|
||||
export default function(props) {
|
||||
const rs = React.useContext(RpcContext)
|
||||
const [name, setName] = React.useState('getType')
|
||||
|
||||
const st = useAsync(() =>
|
||||
rs.call('getType', { name, pos: props.pos }), [name, rs, props.pos])
|
||||
|
||||
const type = st.state === 'resolved' ? st.value && e(InteractiveCode, {fmt: st.value})
|
||||
: st.state === 'rejected' ? e('p', null, mapRpcError(st.error).message)
|
||||
: e('p', null, 'Loading..')
|
||||
const onChange = (event) => { setName(event.target.value) }
|
||||
return e('div', null,
|
||||
e('input', { value: name, onChange }), ' : ', type)
|
||||
}
|
||||
"
|
||||
|
||||
/-!
|
||||
Finally we can try out the widget.
|
||||
-/
|
||||
|
||||
#widget checkWidget
|
||||
|
||||
/-!
|
||||

|
||||
|
||||
## Building widget sources
|
||||
|
||||
While typing JavaScript inline is fine for a simple example, for real developments we want to use
|
||||
packages from NPM, a proper build system, and JSX. Thus, most actual widget sources are built with
|
||||
Lake and NPM. They consist of multiple files and may import libraries which don't work as ESModules
|
||||
by default. On the other hand a widget source must be a single, self-contained ESModule in the form
|
||||
of a string. Readers familiar with web development may already have guessed that to obtain such a
|
||||
string, we need a *bundler*. Two popular choices are [`rollup.js`](https://rollupjs.org/guide/en/)
|
||||
and [`esbuild`](https://esbuild.github.io/). If we go with `rollup.js`, to make a widget work with
|
||||
the infoview we need to:
|
||||
- Set [`output.format`](https://rollupjs.org/guide/en/#outputformat) to `'es'`.
|
||||
- [Externalize](https://rollupjs.org/guide/en/#external) `react`, `react-dom`, `@leanprover/infoview`.
|
||||
These libraries are already loaded by the infoview so they should not be bundled.
|
||||
|
||||
In the RubiksCube sample, we provide a working `rollup.js` build configuration in
|
||||
[rollup.config.js](https://github.com/leanprover/lean4-samples/blob/main/RubiksCube/widget/rollup.config.js).
|
||||
|
||||
## Inserting text
|
||||
|
||||
We can also instruct the editor to insert text, copy text to the clipboard, or
|
||||
reveal a certain location in the document.
|
||||
To do this, use the `React.useContext(EditorContext)` React context.
|
||||
This will return an `EditorConnection` whose `api` field contains a number of methods to
|
||||
interact with the text editor.
|
||||
|
||||
You can see the full API for this [here](https://github.com/leanprover/vscode-lean4/blob/master/lean4-infoview-api/src/infoviewApi.ts#L52)
|
||||
-/
|
||||
|
||||
@[widget_module]
|
||||
def insertTextWidget : Widget.Module where
|
||||
javascript := "
|
||||
import * as React from 'react';
|
||||
const e = React.createElement;
|
||||
import { EditorContext } from '@leanprover/infoview';
|
||||
|
||||
export default function(props) {
|
||||
const editorConnection = React.useContext(EditorContext)
|
||||
function onClick() {
|
||||
editorConnection.api.insertText('-- hello!!!', 'above')
|
||||
}
|
||||
|
||||
return e('div', null, e('button', { value: name, onClick }, 'insert'))
|
||||
}
|
||||
"
|
||||
|
||||
/-! Finally, we can try this out: -/
|
||||
|
||||
#widget insertTextWidget
|
||||
@@ -1,5 +0,0 @@
|
||||
(this chapter is rendered by Alectryon in the CI)
|
||||
|
||||
```lean
|
||||
{{#include widgets.lean}}
|
||||
```
|
||||
@@ -222,7 +222,7 @@ def f2 (a b c : Bool) : Bool :=
|
||||
def p : Nat × Bool := (1, false)
|
||||
|
||||
section
|
||||
variable (a b c : Nat) (p : Nat × bool)
|
||||
variables (a b c : Nat) (p : Nat × bool)
|
||||
|
||||
#check (1, 2)
|
||||
#check p.1 * 2
|
||||
@@ -232,8 +232,8 @@ end
|
||||
|
||||
/- lists -/
|
||||
section
|
||||
variable x y z : Nat
|
||||
variable xs ys zs : list Nat
|
||||
variables x y z : Nat
|
||||
variables xs ys zs : list Nat
|
||||
open list
|
||||
|
||||
#check (1 :: xs) ++ (y :: zs) ++ [1,2,3]
|
||||
@@ -243,7 +243,7 @@ end
|
||||
|
||||
/- sets -/
|
||||
section
|
||||
variable s t u : set Nat
|
||||
variables s t u : set Nat
|
||||
|
||||
#check ({1, 2, 3} ∩ s) ∪ ({x | x < 7} ∩ t)
|
||||
end
|
||||
@@ -276,7 +276,7 @@ Finally, for data types with one constructor, one destruct an element by pattern
|
||||
.. code-block:: lean
|
||||
|
||||
universes u v
|
||||
variable {α : Type u} {β : Type v}
|
||||
variables {α : Type u} {β : Type v}
|
||||
|
||||
def p : Nat × ℤ := ⟨1, 2⟩
|
||||
#check p.fst
|
||||
@@ -369,7 +369,7 @@ Here is an example:
|
||||
|
||||
.. code-block:: lean
|
||||
|
||||
variable (a b c d e : Nat)
|
||||
variables (a b c d e : Nat)
|
||||
variable h1 : a = b
|
||||
variable h2 : b = c + 1
|
||||
variable h3 : c = d
|
||||
@@ -406,7 +406,7 @@ The reduction relation is transitive, which is to say, is ``s`` reduces to ``s'`
|
||||
|
||||
This last fact reflects the intuition that once we have proved a proposition ``p``, we only care that is has been proved; the proof does nothing more than witness the fact that ``p`` is true.
|
||||
|
||||
Definitional equality is a strong notion of equality of values. Lean's logical foundations sanction treating definitionally equal terms as being the same when checking that a term is well-typed and/or that it has a given type.
|
||||
Definitional equality is a strong notion of equalty of values. Lean's logical foundations sanction treating definitionally equal terms as being the same when checking that a term is well-typed and/or that it has a given type.
|
||||
|
||||
The reduction relation is believed to be strongly normalizing, which is to say, every sequence of reductions applied to a term will eventually terminate. The property guarantees that Lean's type-checking algorithm terminates, at least in principle. The consistency of Lean and its soundness with respect to set-theoretic semantics do not depend on either of these properties.
|
||||
|
||||
|
||||
13
doc/faq.md
13
doc/faq.md
@@ -7,6 +7,15 @@ Lean is a new open source theorem prover being developed at Microsoft Research.
|
||||
It is a research project that aims to bridge the gap between interactive and automated theorem proving.
|
||||
Lean can be also used as a programming language. Actually, some Lean features are implemented in Lean itself.
|
||||
|
||||
### Are pull requests welcome?
|
||||
|
||||
In the past, we accepted most pull requests. This practice produced hard to maintain code, performance problems, and bugs.
|
||||
It takes time to review a pull request and make sure it is correct, useful and is not in conflict with our plans.
|
||||
Small bug fixes (few lines of code) are always welcome. Any other kind of unrequested pull request is not.
|
||||
Thus, before implementing a feature or modifying the system, please ask whether the change is welcome or not.
|
||||
We have issues tagged with ["help wanted"](https://github.com/leanprover/lean4/issues?q=is%3Aissue+is%3Aopen+label%3A%22help+wanted%22), if you want to contribute to the project, please take a look at them.
|
||||
If you are interested in one of them, post comments, ask questions, and engage with the core developers there.
|
||||
|
||||
### Should I use Lean?
|
||||
|
||||
Lean is under heavy development, and we are constantly trying new
|
||||
@@ -27,7 +36,7 @@ It is a good place to interact with other Lean users.
|
||||
### Should I use Lean to teach a course?
|
||||
|
||||
Lean has been used to teach courses on logic, type theory and programming languages at CMU and the University of Washington.
|
||||
The lecture notes for the CMU course [Logic and Proof](https://lean-lang.org/logic_and_proof) are available online,
|
||||
The lecture notes for the CMU course [Logic and Proof](https://leanprover.github.io/logic_and_proof) are available online,
|
||||
but they are for Lean 3.
|
||||
If you decide to teach a course using Lean, we suggest you prepare all material before the beginning of the course, and
|
||||
make sure that Lean attends all your needs. You should not expect we will fix bugs and/or add features needed for your course.
|
||||
@@ -47,7 +56,7 @@ We expect similar independent checkers will be built for Lean 4.
|
||||
|
||||
We use [GitHub](https://github.com/leanprover/lean4/issues) to track bugs and new features.
|
||||
Bug reports are always welcome, but nitpicking issues are not (e.g., the error message is confusing).
|
||||
See also our [contribution guidelines](https://github.com/leanprover/lean4/blob/master/CONTRIBUTING.md).
|
||||
See also our [contribution guidelines](../CONTRIBUTING.md).
|
||||
|
||||
### Is it Lean, LEAN, or L∃∀N?
|
||||
|
||||
|
||||
73
doc/flake.lock
generated
73
doc/flake.lock
generated
@@ -19,11 +19,11 @@
|
||||
},
|
||||
"flake-utils": {
|
||||
"locked": {
|
||||
"lastModified": 1656928814,
|
||||
"narHash": "sha256-RIFfgBuKz6Hp89yRr7+NR5tzIAbn52h8vT6vXkYjZoM=",
|
||||
"lastModified": 1644229661,
|
||||
"narHash": "sha256-1YdnJAsNy69bpcjuoKdOYQX0YxZBiCYZo4Twxerqv7k=",
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"rev": "7e2a3b3dfd9af950a856d66b0a7d01e3c18aa249",
|
||||
"rev": "3cecb5b042f7f209c56ffd8371b2711a290ec797",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -35,13 +35,14 @@
|
||||
"lean": {
|
||||
"inputs": {
|
||||
"flake-utils": "flake-utils",
|
||||
"lean-stage0": "lean-stage0",
|
||||
"lean4-mode": "lean4-mode",
|
||||
"nix": "nix",
|
||||
"nixpkgs": "nixpkgs_2"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 0,
|
||||
"narHash": "sha256-YnYbmG0oou1Q/GE4JbMNb8/yqUVXBPIvcdQQJHBqtPk=",
|
||||
"narHash": "sha256-AfBkKX6Ahb9YbZke+eWLmsUk1Z9BwdJ1CpIoPY8Msx8=",
|
||||
"path": "../.",
|
||||
"type": "path"
|
||||
},
|
||||
@@ -50,14 +51,29 @@
|
||||
"type": "path"
|
||||
}
|
||||
},
|
||||
"lean-stage0": {
|
||||
"locked": {
|
||||
"lastModified": 0,
|
||||
"narHash": "sha256-3K/43lSW4WIHNG+HHVKCD1odS63mHuaQ4ueHyTIkcls=",
|
||||
"owner": "leanprover",
|
||||
"repo": "lean4",
|
||||
"rev": "0000000000000000000000000000000000000000",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "leanprover",
|
||||
"repo": "lean4",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"lean4-mode": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
"lastModified": 1659020985,
|
||||
"narHash": "sha256-+dRaXB7uvN/weSZiKcfSKWhcdJVNg9Vg8k0pJkDNjpc=",
|
||||
"lastModified": 1647694750,
|
||||
"narHash": "sha256-0rV61KhevG9IAjZDN2Ts2VS65fiUAPAezbf282u7yy8=",
|
||||
"owner": "leanprover",
|
||||
"repo": "lean4-mode",
|
||||
"rev": "37d5c99b7b29c80ab78321edd6773200deb0bca6",
|
||||
"rev": "c016c7aeee92564836355083664c49ed57024427",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -69,16 +85,15 @@
|
||||
"leanInk": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
"lastModified": 1704976501,
|
||||
"narHash": "sha256-FSBUsbX0HxakSnYRYzRBDN2YKmH9EkA0q9p7TSPEJTI=",
|
||||
"lastModified": 1656863690,
|
||||
"narHash": "sha256-9tmynTTeJGhYZaltS4xhSJgLTpe7Ta1ofV6U1SA/5V4=",
|
||||
"owner": "leanprover",
|
||||
"repo": "LeanInk",
|
||||
"rev": "51821e3c2c032c88e4b2956483899d373ec090c4",
|
||||
"rev": "4b5e606ea8cc54c2447ce48706f8ec1d133d19e9",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "leanprover",
|
||||
"ref": "refs/pull/57/merge",
|
||||
"repo": "LeanInk",
|
||||
"type": "github"
|
||||
}
|
||||
@@ -102,11 +117,11 @@
|
||||
"mdBook": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
"lastModified": 1660074464,
|
||||
"narHash": "sha256-W30G7AeWBjdJE/CQZJU5vJjaDGZtpmxEKNMEvaYtuF8=",
|
||||
"lastModified": 1644567966,
|
||||
"narHash": "sha256-fqdb2AUAMmi54vfa2qOF7VMFvN3rNku8/kUh1YEW86g=",
|
||||
"owner": "leanprover",
|
||||
"repo": "mdBook",
|
||||
"rev": "9321c10c502cd59eea8afc4325a84eab3ddf9391",
|
||||
"rev": "b7a9bc48e9881087cac684d0c6c2c0a3583417e8",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -122,11 +137,11 @@
|
||||
"nixpkgs-regression": "nixpkgs-regression"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1657097207,
|
||||
"narHash": "sha256-SmeGmjWM3fEed3kQjqIAO8VpGmkC2sL1aPE7kKpK650=",
|
||||
"lastModified": 1648022028,
|
||||
"narHash": "sha256-HtwmifW6STPcym+3uJ4YavgTKTYVIoiQHg3f0wXOm+Q=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nix",
|
||||
"rev": "f6316b49a0c37172bca87ede6ea8144d7d89832f",
|
||||
"rev": "98ce1a21b7d959c5575fac566c8699e91703a9f7",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -137,18 +152,17 @@
|
||||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1653988320,
|
||||
"narHash": "sha256-ZaqFFsSDipZ6KVqriwM34T739+KLYJvNmCWzErjAg7c=",
|
||||
"lastModified": 1632864508,
|
||||
"narHash": "sha256-d127FIvGR41XbVRDPVvozUPQ/uRHbHwvfyKHwEt5xFM=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "2fa57ed190fd6c7c746319444f34b5917666e5c1",
|
||||
"rev": "82891b5e2c2359d7e58d08849e4c89511ab94234",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "NixOS",
|
||||
"ref": "nixos-22.05-small",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
"id": "nixpkgs",
|
||||
"ref": "nixos-21.05-small",
|
||||
"type": "indirect"
|
||||
}
|
||||
},
|
||||
"nixpkgs-regression": {
|
||||
@@ -161,19 +175,18 @@
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"id": "nixpkgs",
|
||||
"rev": "215d4d0fd80ca5163643b03a33fde804a29cc1e2",
|
||||
"type": "github"
|
||||
"type": "indirect"
|
||||
}
|
||||
},
|
||||
"nixpkgs_2": {
|
||||
"locked": {
|
||||
"lastModified": 1657208011,
|
||||
"narHash": "sha256-BlIFwopAykvdy1DYayEkj6ZZdkn+cVgPNX98QVLc0jM=",
|
||||
"lastModified": 1648219316,
|
||||
"narHash": "sha256-Ctij+dOi0ZZIfX5eMhgwugfvB+WZSrvVNAyAuANOsnQ=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "2770cc0b1e8faa0e20eb2c6aea64c256a706d4f2",
|
||||
"rev": "30d3d79b7d3607d56546dd2a6b49e156ba0ec634",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
||||
@@ -4,15 +4,15 @@
|
||||
inputs.lean.url = path:../.;
|
||||
inputs.flake-utils.follows = "lean/flake-utils";
|
||||
inputs.mdBook = {
|
||||
url = "github:leanprover/mdBook";
|
||||
url = github:leanprover/mdBook;
|
||||
flake = false;
|
||||
};
|
||||
inputs.alectryon = {
|
||||
url = "github:Kha/alectryon/typeid";
|
||||
url = github:Kha/alectryon/typeid;
|
||||
flake = false;
|
||||
};
|
||||
inputs.leanInk = {
|
||||
url = "github:leanprover/LeanInk/refs/pull/57/merge";
|
||||
url = github:leanprover/LeanInk;
|
||||
flake = false;
|
||||
};
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
src = inputs.mdBook;
|
||||
cargoDeps = drv.cargoDeps.overrideAttrs (_: {
|
||||
inherit src;
|
||||
outputHash = "sha256-1YlPS6cqgxE4fjy9G8pWrpP27YrrbCDnfeyIsX81ZNw=";
|
||||
outputHash = "sha256-mhTWHs/bsmm3FH59SkUxBTl5lEH2Rlz/aF9CuBTu1TE=";
|
||||
});
|
||||
doCheck = false;
|
||||
});
|
||||
@@ -40,7 +40,7 @@
|
||||
# necessary for `additional-css`...?
|
||||
cp -r --no-preserve=mode $src/doc/* .
|
||||
# overwrite stub .lean.md files
|
||||
cp -r ${inked}/* .
|
||||
cp -r ${examples}/* examples/
|
||||
mdbook build -d $out
|
||||
'';
|
||||
};
|
||||
@@ -78,30 +78,17 @@
|
||||
(with python3Packages; [ pygments dominate beautifulsoup4 docutils ]);
|
||||
doCheck = false;
|
||||
};
|
||||
renderLeanMod = mod: mod.overrideAttrs (final: prev: {
|
||||
name = "${prev.name}.md";
|
||||
buildInputs = prev.buildInputs ++ [ alectryon ];
|
||||
outputs = [ "out" ];
|
||||
buildCommand = ''
|
||||
dir=$(dirname $relpath)
|
||||
mkdir -p $dir out/$dir
|
||||
if [ -d $src ]; then cp -r $src/. $dir/; else cp $src $leanPath; fi
|
||||
alectryon --frontend lean4+markup $leanPath --backend webpage -o $out/$leanPath.md
|
||||
'';
|
||||
});
|
||||
renderPackage = pkg: symlinkJoin {
|
||||
name = "${pkg.name}-mds";
|
||||
paths = map renderLeanMod (lib.attrValues pkg.mods);
|
||||
};
|
||||
literate = buildLeanPackage {
|
||||
name = "literate";
|
||||
src = ./.;
|
||||
roots = [
|
||||
{ mod = "examples"; glob = "submodules"; }
|
||||
{ mod = "monads"; glob = "submodules"; }
|
||||
];
|
||||
};
|
||||
inked = renderPackage literate;
|
||||
renderLean = name: file: runCommandNoCC "${name}.md" { buildInputs = [ alectryon ]; } ''
|
||||
mkdir -p $out/examples
|
||||
alectryon --frontend lean4+markup ${file} --backend webpage -o $out/${name}.md
|
||||
'';
|
||||
renderDir = name: dir: let
|
||||
ents = builtins.readDir dir;
|
||||
inputs = lib.filterAttrs (n: t: builtins.match ".*\.lean" n != null && t == "regular") ents;
|
||||
outputs = lib.mapAttrs (n: _: renderLean n "${dir}/${n}") inputs;
|
||||
in
|
||||
outputs // symlinkJoin { inherit name; paths = lib.attrValues outputs; };
|
||||
examples = renderDir "examples" ./examples;
|
||||
doc = book;
|
||||
};
|
||||
defaultPackage = self.packages.${system}.doc;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
Functional Programming in Lean
|
||||
=======================
|
||||
|
||||
The goal of [this book](https://lean-lang.org/functional_programming_in_lean/) is to be an accessible introduction to using Lean 4 as a programming language.
|
||||
The goal of [this book](https://leanprover.github.io/functional_programming_in_lean/) is to be an accessible introduction to using Lean 4 as a programming language.
|
||||
It should be useful both to people who want to use Lean as a general-purpose programming language and to mathematicians who want to develop larger-scale proof automation but do not have a background in functional programming.
|
||||
It does not assume any background with functional programming, though it's probably not a good first book on programming in general.
|
||||
New content will be added once per month until it's done.
|
||||
|
||||
@@ -32,8 +32,8 @@ def fact x :=
|
||||
|
||||
#eval fact 100
|
||||
```
|
||||
By default, Lean only accepts total functions.
|
||||
The `partial` keyword may be used to define a recursive function without a termination proof; `partial` functions compute in compiled programs, but are opaque in proofs and during type checking.
|
||||
By default, Lean only accepts total functions. The `partial` keyword should be used when Lean cannot
|
||||
establish that a function always terminates.
|
||||
```lean
|
||||
partial def g (x : Nat) (p : Nat -> Bool) : Nat :=
|
||||
if p x then
|
||||
|
||||
@@ -1111,9 +1111,9 @@ hljs.registerLanguage("lean", function(hljs) {
|
||||
'axiom constant ' +
|
||||
'partial unsafe private protected ' +
|
||||
'if then else ' +
|
||||
'universe variable ' +
|
||||
'import open export prelude renaming hiding ' +
|
||||
'calc match with do by let extends ' +
|
||||
'universe variable variables ' +
|
||||
'import open export theory prelude renaming hiding exposing ' +
|
||||
'calc match with do by let extends ' +
|
||||
'for in unless try catch finally mutual mut return continue break where rec ' +
|
||||
'syntax macro_rules macro deriving ' +
|
||||
'fun ' +
|
||||
@@ -1123,7 +1123,7 @@ hljs.registerLanguage("lean", function(hljs) {
|
||||
'Type Prop|10 Sort rw|10 rewrite rwa erw subst substs ' +
|
||||
'simp dsimp simpa simp_intros finish using generalizing ' +
|
||||
'unfold unfold1 dunfold unfold_projs unfold_coes ' +
|
||||
'delta cc ac_rfl ' +
|
||||
'delta cc ac_reflexivity ac_refl ' +
|
||||
'existsi|10 cases rcases intro intros introv by_cases ' +
|
||||
'refl rfl funext case focus propext exact exacts ' +
|
||||
'refine apply eapply fapply apply_with apply_instance ' +
|
||||
@@ -1143,7 +1143,7 @@ hljs.registerLanguage("lean", function(hljs) {
|
||||
'sorry admit',
|
||||
};
|
||||
|
||||
var LEAN_IDENT_RE = /[A-Za-z_][\\w\u207F-\u209C\u1D62-\u1D6A\u2079\'0-9?]*/;
|
||||
var LEAN_IDENT_RE = /[A-Za-z_][\\w\u207F-\u209C\u1D62-\u1D6A\u2079\'0-9]*/;
|
||||
|
||||
var DASH_COMMENT = hljs.COMMENT('--', '$');
|
||||
var MULTI_LINE_COMMENT = hljs.COMMENT('/-[^-]', '-/');
|
||||
@@ -1167,7 +1167,7 @@ hljs.registerLanguage("lean", function(hljs) {
|
||||
|
||||
var LEAN_DEFINITION = {
|
||||
className: 'theorem',
|
||||
begin: '\\b(def|theorem|lemma|class|structure|(?<!deriving\\s+)instance)\\b',
|
||||
beginKeywords: 'def theorem lemma class instance structure',
|
||||
end: ':= | where',
|
||||
excludeEnd: true,
|
||||
contains: [
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 28 KiB |
@@ -1,55 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<DirectedGraph GraphDirection="TopToBottom" Layout="Sugiyama" Offset="-1264.63833333333,-729.52" ZoomLevel="1" xmlns="http://schemas.microsoft.com/vs/2009/dgml">
|
||||
<Nodes>
|
||||
<Node Id="Applicative" Bounds="-839,-412,78.3066666666667,25.96" UseManualLocation="True" />
|
||||
<Node Id="Bind" Bounds="-703,-412.990048779297,50,25.96" UseManualLocation="True" />
|
||||
<Node Id="Except" Category="concrete" Bounds="-631,-238,54.5166666666667,25.96" UseManualLocation="True" />
|
||||
<Node Id="Functor" Bounds="-890.021657986111,-509,60.2533333333333,25.96" UseManualLocation="True" />
|
||||
<Node Id="List" Category="concrete" Bounds="-922,-412,50,25.96" UseManualLocation="True" />
|
||||
<Node Id="Monad" Bounds="-765,-339,57.77,25.96" UseManualLocation="True" />
|
||||
<Node Id="Option" Category="concrete" Bounds="-903,-238,56.8933333333333,25.96" UseManualLocation="True" />
|
||||
<Node Id="Pure" Bounds="-799,-509,50,25.96" UseManualLocation="True" />
|
||||
<Node Id="ReaderM" Category="concrete" Bounds="-816,-238,67.5033333333333,25.96" UseManualLocation="True" />
|
||||
<Node Id="Seq" Bounds="-719,-509,50,25.96" UseManualLocation="True" />
|
||||
<Node Id="SeqLeft" Bounds="-639,-509,59.4666666666667,25.96" UseManualLocation="True" />
|
||||
<Node Id="SeqRight" Bounds="-544,-509,67.7233333333333,25.96" UseManualLocation="True" />
|
||||
<Node Id="StateM" Category="concrete" Bounds="-718.358888888889,-238,57.28,25.96" UseManualLocation="True" />
|
||||
</Nodes>
|
||||
<Links>
|
||||
<Link Source="Applicative" Target="Functor" Bounds="-847.12242702921,-475.387638592472,39.2404278471513,63.3876385924717" />
|
||||
<Link Source="Applicative" Target="Pure" Bounds="-796.388009621993,-474.343439288465,16.6120628262117,62.3434392884653" />
|
||||
<Link Source="Applicative" Target="Seq" Bounds="-785.682854982818,-476.959367842732,70.8838407724442,64.9593678427323" />
|
||||
<Link Source="Applicative" Target="SeqLeft" Bounds="-774.344312027491,-478.957606209781,131.554439087217,66.9576062097807" />
|
||||
<Link Source="Applicative" Target="SeqRight" Bounds="-762.225406557428,-481.91850535998,209.970366938847,70.3021737715571" />
|
||||
<Link Source="Except" Target="Monad" Bounds="-711.95378637201,-307.579654923495,91.1658051838909,69.5796549234947" />
|
||||
<Link Source="List" Target="Functor" Bounds="-892.034814302334,-474.634018472681,23.9591319497622,62.6340184726814" />
|
||||
<Link Source="Monad" Target="Applicative" Bounds="-782.595654197137,-379.260216894652,35.1486400418858,40.2602168946518" />
|
||||
<Link Source="Monad" Target="Bind" Bounds="-725.919943874952,-379.952252619104,32.1656790368972,40.9522526191042" />
|
||||
<Link Source="Option" Target="Monad" Bounds="-856.761951485149,-307.735551794831,95.5848867777901,69.7355517948313" />
|
||||
<Link Source="ReaderM" Target="Monad" Bounds="-776.319514851485,-304.853562563497,30.5364127352738,66.8535625634966" />
|
||||
<Link Source="StateM" Target="Monad" Bounds="-726.395530552052,-304.861622913356,30.7140523342301,66.8616229133556" />
|
||||
</Links>
|
||||
<Categories>
|
||||
<Category Id="concrete" />
|
||||
</Categories>
|
||||
<Properties>
|
||||
<Property Id="Bounds" DataType="System.Windows.Rect" />
|
||||
<Property Id="Expression" DataType="System.String" />
|
||||
<Property Id="GraphDirection" DataType="Microsoft.VisualStudio.Diagrams.Layout.LayoutOrientation" />
|
||||
<Property Id="GroupLabel" DataType="System.String" />
|
||||
<Property Id="IsEnabled" DataType="System.Boolean" />
|
||||
<Property Id="Layout" DataType="System.String" />
|
||||
<Property Id="Offset" DataType="System.String" />
|
||||
<Property Id="TargetType" DataType="System.Type" />
|
||||
<Property Id="UseManualLocation" DataType="System.Boolean" />
|
||||
<Property Id="Value" DataType="System.String" />
|
||||
<Property Id="ValueLabel" DataType="System.String" />
|
||||
<Property Id="ZoomLevel" DataType="System.String" />
|
||||
</Properties>
|
||||
<Styles>
|
||||
<Style TargetType="Node" GroupLabel="concrete" ValueLabel="True">
|
||||
<Condition Expression="HasCategory('concrete')" />
|
||||
<Setter Property="Background" Value="#FF91E7ED" />
|
||||
</Style>
|
||||
</Styles>
|
||||
</DirectedGraph>
|
||||
@@ -1,106 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<svg stroke-linecap="round" font-size="12" font-family="Segoe UI" width="485.7233333333333" height="336.96000000000004" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns="http://www.w3.org/2000/svg">
|
||||
<defs />
|
||||
<g transform="translate(942,529)">
|
||||
<g id="Applicative->Functor">
|
||||
<path d="M -807.881999182059,-412 L -847.12242702921,-475.387638592472" fill="none" stroke="#A0A0A0" stroke-width="1" />
|
||||
<path d="M -851.859658804052,-483.04 L -849.997117457574,-472.431939869483 C -848.256110200696,-474.68582647768 -845.988743857725,-476.089450707263 -843.19501842866,-476.642812558231 z" fill="#A0A0A0" stroke="#A0A0A0" stroke-width="1" stroke-linejoin="round" />
|
||||
</g>
|
||||
<g id="Applicative->Pure">
|
||||
<path d="M -796.388009621993,-412 L -779.775946795781,-474.343439288465" fill="none" stroke="#A0A0A0" stroke-width="1" />
|
||||
<path d="M -777.458657044673,-483.04 L -783.898561528809,-474.407061321009 C -781.064326160453,-474.686741473815 -778.487567431109,-474.000137103116 -776.168285340778,-472.347248208913 z" fill="#A0A0A0" stroke="#A0A0A0" stroke-width="1" stroke-linejoin="round" />
|
||||
</g>
|
||||
<g id="Applicative->Seq">
|
||||
<path d="M -785.682854982818,-412 L -714.799014210374,-476.959367842732" fill="none" stroke="#A0A0A0" stroke-width="1" />
|
||||
<path d="M -708.163811683849,-483.04 L -718.238762116551,-479.232720948158 C -715.699848604043,-477.942360809625 -713.898179816704,-475.97637487584 -712.833755754535,-473.334763146803 z" fill="#A0A0A0" stroke="#A0A0A0" stroke-width="1" stroke-linejoin="round" />
|
||||
</g>
|
||||
<g id="Applicative->SeqLeft">
|
||||
<path d="M -774.344312027491,-412 L -642.789872940275,-478.957606209781" fill="none" stroke="#A0A0A0" stroke-width="1" />
|
||||
<path d="M -634.769021305842,-483.04 L -645.495475917531,-482.068829848393 C -643.394672020307,-480.145880525993 -642.185073860242,-477.769331893569 -641.866681437336,-474.93918395112 z" fill="#A0A0A0" stroke="#A0A0A0" stroke-width="1" stroke-linejoin="round" />
|
||||
</g>
|
||||
<g id="Applicative->SeqRight">
|
||||
<path d="M -762.225406557428,-411.616331588423 L -552.255039618581,-481.91850535998" fill="none" stroke="#A0A0A0" stroke-width="1" />
|
||||
<path d="M -543.720702607113,-484.775967831244 L -554.473282607084,-485.394048201604 C -552.678367392102,-483.182851583901 -551.831711845061,-480.654159136059 -551.93331596596,-477.807970858076 z" fill="#A0A0A0" stroke="#A0A0A0" stroke-width="1" stroke-linejoin="round" />
|
||||
</g>
|
||||
<g id="Except->Monad">
|
||||
<path d="M -620.787981188119,-238 L -711.95378637201,-307.579654923495" fill="none" stroke="#A0A0A0" stroke-width="1" />
|
||||
<path d="M -719.108129922992,-313.04 L -713.585679344792,-303.793241670113 C -712.762726383344,-306.519752175201 -711.144846360676,-308.639557671788 -708.732039276787,-310.152658159875 z" fill="#A0A0A0" stroke="#A0A0A0" stroke-width="1" stroke-linejoin="round" />
|
||||
</g>
|
||||
<g id="List->Functor">
|
||||
<path d="M -892.034814302334,-412 L -868.075682352572,-474.634018472681" fill="none" stroke="#A0A0A0" stroke-width="1" />
|
||||
<path d="M -864.86017701711,-483.04 L -872.168952513098,-475.129134007629 C -869.321012949211,-475.110389633491 -866.830351755932,-474.157647311872 -864.696968933259,-472.270907042774 z" fill="#A0A0A0" stroke="#A0A0A0" stroke-width="1" stroke-linejoin="round" />
|
||||
</g>
|
||||
<g id="Monad->Applicative">
|
||||
<path d="M -747.447014155251,-339 L -782.595654197137,-379.260216894652" fill="none" stroke="#A0A0A0" stroke-width="1" />
|
||||
<path d="M -788.514652511416,-386.04 L -784.951224653483,-375.876241743267 C -783.60006650904,-378.383328255499 -781.591241885233,-380.137105533804 -778.924750782062,-381.137573578181 z" fill="#A0A0A0" stroke="#A0A0A0" stroke-width="1" stroke-linejoin="round" />
|
||||
</g>
|
||||
<g id="Monad->Bind">
|
||||
<path d="M -725.919943874952,-339 L -693.754264838054,-379.952252619104" fill="none" stroke="#A0A0A0" stroke-width="1" />
|
||||
<path d="M -688.195056125048,-387.030048779297 L -697.517641877363,-381.63659025153 C -694.802827232157,-380.775839095105 -692.705702443952,-379.128666143103 -691.226267512747,-376.695071395525 z" fill="#A0A0A0" stroke="#A0A0A0" stroke-width="1" stroke-linejoin="round" />
|
||||
</g>
|
||||
<g id="Option->Monad">
|
||||
<path d="M -856.761951485149,-238 L -761.177064707358,-307.735551794831" fill="none" stroke="#A0A0A0" stroke-width="1" />
|
||||
<path d="M -753.906381848185,-313.04 L -764.342450894008,-310.377583265001 C -761.962908885902,-308.81268999619 -760.391220528815,-306.658413593472 -759.627385822747,-303.914754056846 z" fill="#A0A0A0" stroke="#A0A0A0" stroke-width="1" stroke-linejoin="round" />
|
||||
</g>
|
||||
<g id="ReaderM->Monad">
|
||||
<path d="M -776.319514851485,-238 L -745.783102116211,-304.853562563497" fill="none" stroke="#A0A0A0" stroke-width="1" />
|
||||
<path d="M -742.043818481848,-313.04 L -749.836994714031,-305.60586224138 C -746.99590766236,-305.407530509328 -744.570296570063,-304.299594617665 -742.560161437139,-302.28205456639 z" fill="#A0A0A0" stroke="#A0A0A0" stroke-width="1" stroke-linejoin="round" />
|
||||
</g>
|
||||
<g id="StateM->Monad">
|
||||
<path d="M -695.681478217822,-238 L -726.395530552052,-304.861622913356" fill="none" stroke="#A0A0A0" stroke-width="1" />
|
||||
<path d="M -730.152410671067,-313.04 L -729.612933688448,-302.283189850833 C -727.607141972296,-304.305048080909 -725.183919131808,-305.418197745802 -722.343265166986,-305.622638845513 z" fill="#A0A0A0" stroke="#A0A0A0" stroke-width="1" stroke-linejoin="round" />
|
||||
</g>
|
||||
<g id="Applicative">
|
||||
<rect x="-839" y="-412" rx="3" ry="3" width="78.3066666666667" height="25.96" fill="#FFFFFF" stroke="#A5A6A9" stroke-width="1" />
|
||||
<text x="-829" y="-394.05" fill="#3D3D3D">Applicative</text>
|
||||
</g>
|
||||
<g id="Bind">
|
||||
<rect x="-703" y="-412.990048779297" rx="3" ry="3" width="50" height="25.96" fill="#FFFFFF" stroke="#A5A6A9" stroke-width="1" />
|
||||
<text x="-690" y="-395.040048779297" fill="#3D3D3D">Bind</text>
|
||||
</g>
|
||||
<g id="Except">
|
||||
<rect x="-631" y="-238" rx="3" ry="3" width="54.5166666666667" height="25.96" fill="#91E7ED" stroke="#A5A6A9" stroke-width="1" />
|
||||
<text x="-621" y="-220.05" fill="#3D3D3D">Except</text>
|
||||
</g>
|
||||
<g id="Functor">
|
||||
<rect x="-890.021657986111" y="-509" rx="3" ry="3" width="60.2533333333333" height="25.96" fill="#FFFFFF" stroke="#A5A6A9" stroke-width="1" />
|
||||
<text x="-880.021657986111" y="-491.05" fill="#3D3D3D">Functor</text>
|
||||
</g>
|
||||
<g id="List">
|
||||
<rect x="-922" y="-412" rx="3" ry="3" width="50" height="25.96" fill="#91E7ED" stroke="#A5A6A9" stroke-width="1" />
|
||||
<text x="-906" y="-394.05" fill="#3D3D3D">List</text>
|
||||
</g>
|
||||
<g id="Monad">
|
||||
<rect x="-765" y="-339" rx="3" ry="3" width="57.77" height="25.96" fill="#FFFFFF" stroke="#A5A6A9" stroke-width="1" />
|
||||
<text x="-755" y="-321.05" fill="#3D3D3D">Monad</text>
|
||||
</g>
|
||||
<g id="Option">
|
||||
<rect x="-903" y="-238" rx="3" ry="3" width="56.8933333333333" height="25.96" fill="#91E7ED" stroke="#A5A6A9" stroke-width="1" />
|
||||
<text x="-893" y="-220.05" fill="#3D3D3D">Option</text>
|
||||
</g>
|
||||
<g id="Pure">
|
||||
<rect x="-799" y="-509" rx="3" ry="3" width="50" height="25.96" fill="#FFFFFF" stroke="#A5A6A9" stroke-width="1" />
|
||||
<text x="-786" y="-491.05" fill="#3D3D3D">Pure</text>
|
||||
</g>
|
||||
<g id="ReaderM">
|
||||
<rect x="-816" y="-238" rx="3" ry="3" width="67.5033333333333" height="25.96" fill="#91E7ED" stroke="#A5A6A9" stroke-width="1" />
|
||||
<text x="-806" y="-220.05" fill="#3D3D3D">ReaderM</text>
|
||||
</g>
|
||||
<g id="Seq">
|
||||
<rect x="-719" y="-509" rx="3" ry="3" width="50" height="25.96" fill="#FFFFFF" stroke="#A5A6A9" stroke-width="1" />
|
||||
<text x="-704" y="-491.05" fill="#3D3D3D">Seq</text>
|
||||
</g>
|
||||
<g id="SeqLeft">
|
||||
<rect x="-639" y="-509" rx="3" ry="3" width="59.4666666666667" height="25.96" fill="#FFFFFF" stroke="#A5A6A9" stroke-width="1" />
|
||||
<text x="-629" y="-491.05" fill="#3D3D3D">SeqLeft</text>
|
||||
</g>
|
||||
<g id="SeqRight">
|
||||
<rect x="-544" y="-509" rx="3" ry="3" width="67.7233333333333" height="25.96" fill="#FFFFFF" stroke="#A5A6A9" stroke-width="1" />
|
||||
<text x="-534" y="-491.05" fill="#3D3D3D">SeqRight</text>
|
||||
</g>
|
||||
<g id="StateM">
|
||||
<rect x="-718.358888888889" y="-238" rx="3" ry="3" width="57.28" height="25.96" fill="#91E7ED" stroke="#A5A6A9" stroke-width="1" />
|
||||
<text x="-708.358888888889" y="-220.05" fill="#3D3D3D">StateM</text>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 57 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 23 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 25 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 82 KiB |
@@ -64,7 +64,7 @@ It makes these three arguments implicit. Notationally, this hides the specificat
|
||||
making it look as though ``compose`` simply takes 3 arguments.
|
||||
|
||||
Variables can also be specified as implicit when they are declared with
|
||||
the ``variable`` command:
|
||||
the ``variables`` command:
|
||||
|
||||
```lean
|
||||
universe u
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
# Inductive Types
|
||||
|
||||
[Theorem Proving in Lean](https://lean-lang.org/theorem_proving_in_lean4/inductive_types.html) has a chapter about inductive datatypes.
|
||||
[Theorem Proving in Lean](https://leanprover.github.io/theorem_proving_in_lean4/inductive_types.html) has a chapter about inductive datatypes.
|
||||
|
||||
@@ -33,13 +33,14 @@ class Lean4Lexer(RegexLexer):
|
||||
|
||||
keywords1 = (
|
||||
'import', 'abbreviation', 'opaque_hint', 'tactic_hint', 'definition',
|
||||
'renaming', 'inline', 'hiding', 'parameter', 'lemma', 'variable',
|
||||
'renaming', 'inline', 'hiding', 'exposing', 'parameter', 'parameters',
|
||||
'conjecture', 'hypothesis', 'lemma', 'corollary', 'variable', 'variables',
|
||||
'theorem', 'axiom', 'inductive', 'structure', 'universe', 'alias',
|
||||
'help', 'options', 'precedence', 'postfix', 'prefix',
|
||||
'infix', 'infixl', 'infixr', 'notation', '#eval',
|
||||
'help', 'options', 'precedence', 'postfix', 'prefix', 'calc_trans',
|
||||
'calc_subst', 'calc_refl', 'infix', 'infixl', 'infixr', 'notation', '#eval',
|
||||
'#check', '#reduce', '#exit', 'coercion', 'end', 'private', 'using', 'namespace',
|
||||
'including', 'instance', 'section', 'context', 'protected', 'expose',
|
||||
'export', 'set_option', 'extends', 'open', 'example',
|
||||
'export', 'set_option', 'add_rewrite', 'extends', 'open', 'example',
|
||||
'constant', 'constants', 'print', 'opaque', 'reducible', 'irreducible',
|
||||
'def', 'macro', 'elab', 'syntax', 'macro_rules', 'reduce', 'where',
|
||||
'abbrev', 'noncomputable', 'class', 'attribute', 'synth', 'mutual',
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
\lstdefinelanguage{lean} {
|
||||
|
||||
% Anything between $ becomes LaTeX math mode
|
||||
% Anything betweeen $ becomes LaTeX math mode
|
||||
mathescape=false,
|
||||
% Comments may or not include Latex commands
|
||||
texcl=false,
|
||||
@@ -12,22 +12,21 @@ texcl=false,
|
||||
% keywords, list taken from lean-syntax.el
|
||||
morekeywords=[1]{
|
||||
import, prelude, protected, private, noncomputable, definition, meta, renaming,
|
||||
hiding, parameter, parameters, begin, constant, constants,
|
||||
lemma, variable, variables, theory,
|
||||
print, theorem, example,
|
||||
open, as, export, override, axiom, axioms, inductive, with,
|
||||
hiding, exposing, parameter, parameters, begin, conjecture, constant, constants,
|
||||
hypothesis, lemma, corollary, variable, variables, premise, premises, theory,
|
||||
print, theorem, proposition, example,
|
||||
open, as, export, override, axiom, axioms, inductive, with, without,
|
||||
structure, record, universe, universes,
|
||||
alias, help, precedence, reserve, declare_trace, add_key_equivalence,
|
||||
match, infix, infixl, infixr, notation, postfix, prefix, instance,
|
||||
eval, reduce, check, end, this,
|
||||
using, using_well_founded, namespace, section,
|
||||
attribute, local, set_option, extends, include, omit, class,
|
||||
raw, replacing,
|
||||
eval, vm_eval, check, coercion, end, this, suppose,
|
||||
using, using_well_founded, namespace, section, fields,
|
||||
attribute, local, set_option, extends, include, omit, classes, class,
|
||||
instances, coercions, attributes, raw, replacing,
|
||||
calc, have, show, suffices, by, in, at, let, forall, Pi, fun,
|
||||
exists, if, dif, then, else, assume, obtain, from, register_simp_ext, unless, break, continue,
|
||||
mutual, do, def, run_cmd, const,
|
||||
partial, mut, where, macro, syntax, deriving,
|
||||
return, try, catch, for, macro_rules, declare_syntax_cat, abbrev},
|
||||
exists, if, dif, then, else, assume, obtain, from, aliases, register_simp_ext, unless, break, continue,
|
||||
mutual, do, def, run_cmd, const
|
||||
partial,mut,where,macro,syntax,deriving, return, try, catch, for, macro_rules,declare_syntax_cat,abbrev},
|
||||
|
||||
% Sorts
|
||||
morekeywords=[2]{Sort, Type, Prop},
|
||||
@@ -201,7 +200,6 @@ literate=
|
||||
|
||||
{ₙ}{{\ensuremath{_n}}}1
|
||||
{ₘ}{{\ensuremath{_m}}}1
|
||||
{ₚ}{{\ensuremath{_p}}}1
|
||||
{↑}{{\ensuremath{\uparrow}}}1
|
||||
{↓}{{\ensuremath{\downarrow}}}1
|
||||
|
||||
@@ -265,7 +263,7 @@ columns=[l]fullflexible,
|
||||
% Style for (listings') identifiers
|
||||
identifierstyle={\ttfamily\color{black}},
|
||||
% Note : highlighting of Coq identifiers is done through a new
|
||||
% delimiter definition through an lstset at the beginning of the
|
||||
% delimiter definition through an lstset at the begining of the
|
||||
% document. Don't know how to do better.
|
||||
|
||||
% Style for declaration keywords
|
||||
|
||||
@@ -42,7 +42,7 @@ def Sum.str : Option Nat → String :=
|
||||
## Implicit lambdas
|
||||
|
||||
In Lean 3 stdlib, we find many [instances](https://github.com/leanprover/lean/blob/master/library/init/category/reader.lean#L39) of the dreadful `@`+`_` idiom.
|
||||
It is often used when the expected type is a function type with implicit arguments,
|
||||
It is often used when we the expected type is a function type with implicit arguments,
|
||||
and we have a constant (`reader_t.pure` in the example) which also takes implicit arguments. In Lean 4, the elaborator automatically introduces lambdas
|
||||
for consuming implicit arguments. We are still exploring this feature and analyzing its impact, but the experience so far has been very positive. As an example,
|
||||
here is the example in the link above using Lean 4 implicit lambdas.
|
||||
@@ -85,7 +85,7 @@ def id5 : {α : Type} → α → α :=
|
||||
|
||||
## Sugar for simple functions
|
||||
|
||||
In Lean 3, we can create simple functions from infix operators by using parentheses. For example, `(+1)` is sugar for `fun x, x + 1`. In Lean 4, we generalize this notation using `·` as a placeholder. Here are a few examples:
|
||||
In Lean 3, we can create simple functions from infix operators by using parentheses. For example, `(+1)` is sugar for `fun x, x + 1`. In Lean 4, we generalize this notation using `·` As a placeholder. Here are a few examples:
|
||||
|
||||
```lean
|
||||
# namespace ex3
|
||||
@@ -196,8 +196,6 @@ example (f : Nat → Nat) (a b c : Nat) : f (a + b + c) = f (a + (b + c)) :=
|
||||
congrArg f (Nat.add_assoc ..)
|
||||
```
|
||||
|
||||
In Lean 4, writing `f(x)` in place of `f x` is no longer allowed, you must use whitespace between the function and its arguments (e.g., `f (x)`).
|
||||
|
||||
## Dependent function types
|
||||
|
||||
Given `α : Type` and `β : α → Type`, `(x : α) → β x` denotes the type of functions `f` with the property that,
|
||||
@@ -289,11 +287,11 @@ Lean execution runtime. For example, we cannot prove in Lean that arrays have a
|
||||
the runtime used to execute Lean programs guarantees that an array cannot have more than 2^64 (2^32) elements
|
||||
in a 64-bit (32-bit) machine. We can take advantage of this fact to provide a more efficient implementation for
|
||||
array functions. However, the efficient version would not be very useful if it can only be used in
|
||||
unsafe code. Thus, Lean 4 provides the attribute `@[implemented_by functionName]`. The idea is to provide
|
||||
unsafe code. Thus, Lean 4 provides the attribute `@[implementedBy functionName]`. The idea is to provide
|
||||
an unsafe (and potentially more efficient) version of a safe definition or constant. The function `f`
|
||||
at the attribute `@[implemented_by f]` is very similar to an extern/foreign function,
|
||||
at the attribute `@[implementedBy f]` is very similar to an extern/foreign function,
|
||||
the key difference is that it is implemented in Lean itself. Again, the logical soundness of the system
|
||||
cannot be compromised by using the attribute `implemented_by`, but if the implementation is incorrect your
|
||||
cannot be compromised by using the attribute `implementedBy`, but if the implementation is incorrect your
|
||||
program may crash at runtime. In the following example, we define `withPtrUnsafe a k h` which
|
||||
executes `k` using the memory address where `a` is stored in memory. The argument `h` is proof
|
||||
that `k` is a constant function. Then, we "seal" this unsafe implementation at `withPtr`. The proof `h`
|
||||
@@ -304,7 +302,7 @@ unsafe
|
||||
def withPtrUnsafe {α β : Type} (a : α) (k : USize → β) (h : ∀ u, k u = k 0) : β :=
|
||||
k (ptrAddrUnsafe a)
|
||||
|
||||
@[implemented_by withPtrUnsafe]
|
||||
@[implementedBy withPtrUnsafe]
|
||||
def withPtr {α β : Type} (a : α) (k : USize → β) (h : ∀ u, k u = k 0) : β :=
|
||||
k 0
|
||||
```
|
||||
@@ -342,7 +340,8 @@ partial def f (x : Nat) : IO Unit := do
|
||||
|
||||
These are changes to the library which may trip up Lean 3 users:
|
||||
|
||||
- `List` is no longer a monad.
|
||||
- `Option` and `List` are no longer monads. Instead there is `OptionM`. This was done to avoid some performance traps. For example `o₁ <|> o₂` where `o₁ o₂ : Option α` will evaluate both `o₁` and `o₂` even if `o₁` evaluates to `some x`. This can be a problem if `o₂` requires a lot of compute to evaluate. A zulip discussion on this design choice is [here](https://leanprover.zulipchat.com/#narrow/stream/270676-lean4/topic/Option.20do.20notation.20regression.3F).
|
||||
|
||||
|
||||
## Style changes
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ A Lean program consists of a stream of UTF-8 tokens where each token
|
||||
is one of the following:
|
||||
|
||||
```
|
||||
token: symbol | command | ident | string | raw_string | char | numeral |
|
||||
token: symbol | command | ident | string | char | numeral |
|
||||
: decimal | doc_comment | mod_doc_comment | field_notation
|
||||
```
|
||||
|
||||
@@ -79,35 +79,15 @@ special characters:
|
||||
[Unicode table](https://unicode-table.com/en/) so "\xA9 Copyright 2021" is "© Copyright 2021".
|
||||
- `\uHHHH` puts the character represented by the 4 digit hexadecimal into the string, so the following
|
||||
string "\u65e5\u672c" will become "日本" which means "Japan".
|
||||
- `\` followed by a newline and then any amount of whitespace is a "gap" that is equivalent to the empty string,
|
||||
useful for letting a string literal span across multiple lines. Gaps spanning multiple lines can be confusing,
|
||||
so the parser raises an error if the trailing whitespace contains any newlines.
|
||||
|
||||
So the complete syntax is:
|
||||
|
||||
```
|
||||
string : '"' string_item '"'
|
||||
string_item : string_char | char_escape | string_gap
|
||||
string_char : [^"\\]
|
||||
char_escape : "\" ("\" | '"' | "'" | "n" | "t" | "x" hex_char{2} | "u" hex_char{4})
|
||||
string_item : string_char | string_escape
|
||||
string_char : [^\\]
|
||||
string_escape: "\" ("\" | '"' | "'" | "n" | "t" | "x" hex_char{2} | "u" hex_char{4} )
|
||||
hex_char : [0-9a-fA-F]
|
||||
string_gap : "\" newline whitespace*
|
||||
```
|
||||
|
||||
Raw String Literals
|
||||
===================
|
||||
|
||||
Raw string literals are string literals without any escape character processing.
|
||||
They begin with `r##...#"` (with zero or more `#` characters) and end with `"#...##` (with the same number of `#` characters).
|
||||
The contents of a raw string literal may contain `"##..#` so long as the number of `#` characters
|
||||
is less than the number of `#` characters used to begin the raw string literal.
|
||||
|
||||
```
|
||||
raw_string : raw_string_aux(0) | raw_string_aux(1) | raw_string_aux(2) | ...
|
||||
raw_string_aux(n) : 'r' '#'{n} '"' raw_string_item '"' '#'{n}
|
||||
raw_string_item(n) : raw_string_char | raw_string_quote(n)
|
||||
raw_string_char : [^"]
|
||||
raw_string_quote(n) : '"' '#'{0..n-1}
|
||||
```
|
||||
|
||||
Char Literals
|
||||
@@ -116,9 +96,7 @@ Char Literals
|
||||
Char literals are enclosed by single quotes (``'``).
|
||||
|
||||
```
|
||||
char : "'" char_item "'"
|
||||
char_item : char_char | char_escape
|
||||
char_char : [^'\\]
|
||||
char: "'" string_item "'"
|
||||
```
|
||||
|
||||
Numeric Literals
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
|
||||
# Macro Overview
|
||||
|
||||
The official paper describing the mechanics behind Lean 4's macro system can be
|
||||
The offical paper describing the mechanics behind Lean 4's macro system can be
|
||||
found in [Beyond Notations: Hygienic Macro Expansion for Theorem Proving
|
||||
Languages](https://arxiv.org/abs/2001.10490) by Sebastian Ullrich and Leonardo
|
||||
de Moura, and the accompanying repo with example code can be found in the
|
||||
@@ -68,7 +68,7 @@ carries state needed for macro expansion to work nicely, including the info
|
||||
needed to implement hygiene.
|
||||
|
||||
As an example, we again refer to Mathlib's set builder notation:
|
||||
```lean
|
||||
```
|
||||
/- Declares a parser -/
|
||||
syntax (priority := high) "{" term,+ "}" : term
|
||||
|
||||
@@ -95,8 +95,7 @@ simplified representation which omits details in the `atom` and `ident`
|
||||
constructors; users can create atoms and idents which comport with this
|
||||
simplified representation using the `mkAtom` and `mkIdent` methods provided in
|
||||
the `Lean` namespace.
|
||||
```lean
|
||||
# open Lean
|
||||
```
|
||||
inductive Syntax where
|
||||
| missing : Syntax
|
||||
| node (kind : SyntaxNodeKind) (args : Array Syntax) : Syntax
|
||||
@@ -107,14 +106,12 @@ inductive Syntax where
|
||||
|
||||
|
||||
For those interested, `MacroM` is a `ReaderT`:
|
||||
```lean
|
||||
# open Lean
|
||||
```
|
||||
abbrev MacroM := ReaderT Macro.Context (EStateM Macro.Exception Macro.State)
|
||||
```
|
||||
|
||||
The other relevant components are defined as follows:
|
||||
```lean
|
||||
# open Lean
|
||||
```
|
||||
structure Context where
|
||||
methods : MethodsRef
|
||||
mainModule : Name
|
||||
@@ -151,7 +148,7 @@ or mathlib4's `binderterm`. These are the different categories of things that
|
||||
can be referred to in a quote/antiquote. `declare_syntax_cat` results in a call
|
||||
to `registerParserCategory` and produces a new parser descriptor:
|
||||
|
||||
```lean
|
||||
```
|
||||
set_option trace.Elab.definition true in
|
||||
declare_syntax_cat binderterm
|
||||
|
||||
@@ -181,8 +178,7 @@ macro/pattern language by way of the `syntax` keyword. This is the recommended
|
||||
means of writing parsers. As an example, the parser for the `rwa` (rewrite, then
|
||||
use assumption) tactic is:
|
||||
|
||||
```lean
|
||||
# open Lean.Parser.Tactic
|
||||
```
|
||||
set_option trace.Elab.definition true in
|
||||
syntax "rwa " rwRuleSeq (location)? : tactic
|
||||
|
||||
@@ -211,17 +207,15 @@ mark, which is not what we want.
|
||||
The name `tacticRwa__` is automatically generated. You can name parser
|
||||
descriptors declared with the `syntax` keyword like so:
|
||||
|
||||
```lean
|
||||
```
|
||||
set_option trace.Elab.definition true in
|
||||
syntax (name := introv) "introv " (colGt ident)* : tactic
|
||||
|
||||
/-
|
||||
[Elab.definition.body] introv : Lean.ParserDescr :=
|
||||
Lean.ParserDescr.node `introv 1022
|
||||
(Lean.ParserDescr.binary `andthen (Lean.ParserDescr.nonReservedSymbol "introv " false)
|
||||
(Lean.ParserDescr.unary `many
|
||||
(Lean.ParserDescr.binary `andthen (Lean.ParserDescr.const `colGt) (Lean.ParserDescr.const `ident))))
|
||||
-/
|
||||
```
|
||||
|
||||
## The pattern language
|
||||
@@ -257,7 +251,7 @@ pretty printed output.
|
||||
## Syntax expansions with `macro_rules`, and how it desugars.
|
||||
|
||||
`macro_rules` lets you declare expansions for a given `Syntax` element using a
|
||||
syntax similar to a `match` statement. The left-hand side of a match arm is a
|
||||
syntax simlar to a `match` statement. The left-hand side of a match arm is a
|
||||
quotation (with a leading `<cat>|` for categories other than `term` and
|
||||
`command`) in which users can specify the pattern they'd like to write an
|
||||
expansion for. The right-hand side returns a syntax quotation which is the
|
||||
@@ -274,7 +268,7 @@ declared with `macro_rules`. This `transitivity` tactic is implemented such that
|
||||
it will work for either Nat.le or Nat.lt. The Nat.lt version was declared "most
|
||||
recently", so it will be tried first, but if it fails (for example, if the
|
||||
actual term in question is Nat.le) the next potential expansion will be tried:
|
||||
```lean
|
||||
```
|
||||
macro "transitivity" e:(colGt term) : tactic => `(tactic| apply Nat.le_trans (m := $e))
|
||||
macro_rules
|
||||
| `(tactic| transitivity $e) => `(tactic| apply Nat.lt_trans (m := $e))
|
||||
@@ -289,20 +283,18 @@ example (a b c : Nat) (h0 : a <= b) (h1 : b <= c) : a <= c := by
|
||||
|
||||
/- This will fail, but is interesting in that it exposes the "most-recent first" behavior, since the
|
||||
error message complains about being unable to unify mvar1 <= mvar2, rather than mvar1 < mvar2. -/
|
||||
/-
|
||||
example (a b c : Nat) (h0 : a <= b) (h1 : b <= c) : False := by
|
||||
transitivity b <;>
|
||||
assumption
|
||||
-/
|
||||
```
|
||||
|
||||
To see the desugared definition of the actual expansion, we can again use
|
||||
`set_option trace.Elab.definition true in` and observe the output of the humble
|
||||
`exfalso` tactic defined in Mathlib4:
|
||||
```lean
|
||||
```
|
||||
|
||||
set_option trace.Elab.definition true in
|
||||
macro "exfalso" : tactic => `(tactic| apply False.elim)
|
||||
macro "exfalso" : tactic => `(apply False.elim)
|
||||
|
||||
/-
|
||||
Results in the expansion:
|
||||
@@ -336,8 +328,7 @@ fun x =>
|
||||
We can also create the syntax transformer declaration ourselves instead of using
|
||||
`macro_rules`. We'll need to name our parser and use the attribute `@[macro
|
||||
myExFalsoParser]` to associate our declaration with the parser:
|
||||
```lean
|
||||
# open Lean
|
||||
```
|
||||
syntax (name := myExfalsoParser) "myExfalso" : tactic
|
||||
|
||||
-- remember that `Macro` is a synonym for `Syntax -> TacticM Unit`
|
||||
@@ -352,12 +343,12 @@ example (p : Prop) (h : p) (f : p -> False) : 3 = 2 := by
|
||||
In the above example, we're still using the sugar Lean provides for creating
|
||||
quotations, as it feels more intuitive and saves us some work. It is possible to
|
||||
forego the sugar altogether:
|
||||
```lean
|
||||
```
|
||||
syntax (name := myExfalsoParser) "myExfalso" : tactic
|
||||
|
||||
@[macro myExfalsoParser] def implMyExfalso : Lean.Macro :=
|
||||
fun stx => pure (Lean.mkNode `Lean.Parser.Tactic.apply
|
||||
#[Lean.mkAtomFrom stx "apply", Lean.mkCIdentFrom stx ``False.elim])
|
||||
fun stx => Lean.mkNode `Lean.Parser.Tactic.apply
|
||||
#[Lean.mkAtomFrom stx "apply", Lean.mkCIdentFrom stx ``False.elim]
|
||||
|
||||
example (p : Prop) (h : p) (f : p -> False) : 3 = 2 := by
|
||||
myExfalso
|
||||
|
||||
@@ -10,9 +10,12 @@ Platform-Specific Setup
|
||||
|
||||
- [Linux (Ubuntu)](ubuntu.md)
|
||||
- [Windows (msys2)](msys2.md)
|
||||
- [Windows (Visual Studio)](msvc.md)
|
||||
- [Windows (WSL)](wsl.md)
|
||||
- [macOS (homebrew)](osx-10.9.md)
|
||||
- Linux/macOS/WSL via [Nix](https://nixos.org/nix/): Call `nix-shell` in the project root. That's it.
|
||||
- There is also an [**experimental** setup based purely on Nix](nix.md) that works fundamentally differently from the
|
||||
make/CMake setup described on this page.
|
||||
|
||||
Generic Build Instructions
|
||||
--------------------------
|
||||
|
||||
110
doc/make/nix.md
Normal file
110
doc/make/nix.md
Normal file
@@ -0,0 +1,110 @@
|
||||
# Building with Nix
|
||||
|
||||
While [Nix](https://nixos.org/nix/) can be used to quickly open a shell with all dependencies for the [standard setup](index.md) installed, the user-facing [Nix Setup](../setup.md#nix-setup) can also be used to work *on* Lean.
|
||||
|
||||
## Setup
|
||||
|
||||
Follow the setup in the link above; to open the Lean shell inside a Lean checkout, you can also use
|
||||
```bash
|
||||
# in the Lean root directory
|
||||
$ nix-shell -A nix
|
||||
```
|
||||
|
||||
On top of the local and remote Nix cache, we do still rely on CCache as well to make C/C++ build steps incremental, which are atomic steps from Nix's point of view.
|
||||
To enable CCache, add the following line to the config file mentioned in the setup:
|
||||
```bash
|
||||
extra-sandbox-paths = /nix/var/cache/ccache
|
||||
```
|
||||
Then set up that directory as follows:
|
||||
```bash
|
||||
sudo mkdir -m0770 -p /nix/var/cache/ccache
|
||||
# macOS standard chown doesn't support --reference
|
||||
nix shell .#nixpkgs.coreutils -c sudo chown --reference=/nix/store /nix/var/cache/ccache
|
||||
```
|
||||
|
||||
## Basic Build Commands
|
||||
|
||||
From the Lean root directory inside the Lean shell:
|
||||
```bash
|
||||
nix build .#stage1 # build this stage's stdlib & executable
|
||||
nix build .#stage1.test # run all tests
|
||||
nix run .#stage1.update-stage0 # update ./stage0 from this stage
|
||||
nix run .#stage1.update-stage0-commit # ...and commit the results
|
||||
```
|
||||
The `stage1.` part in each command is optional:
|
||||
```bash
|
||||
nix build .#test # run tests for stage 1
|
||||
nix build . # build stage 1
|
||||
nix build # ditto
|
||||
```
|
||||
|
||||
## Build Process Description
|
||||
|
||||
The Nix build process conceptually works the same as described in [Lean Build Pipeline](index.md#lean-build-pipeline).
|
||||
However, there are two important differences in practice apart from the standard Nix properties (hermeneutic, reproducible builds stored in a global hash-indexed store etc.):
|
||||
* Only files tracked by git (using `git add` or at least `git add --intent-to-add`) are compiled.
|
||||
This is actually a general property of Nix flakes, and has the benefit of making it basically impossible to forget to commit a file (at least in `src/`).
|
||||
* Only files reachable from `src/Lean.lean` are compiled.
|
||||
This is because modules are discovered not from a directory listing anymore but by recursively compiling all dependencies of that top module.
|
||||
|
||||
## Editor Integration
|
||||
|
||||
As in the standard Nix setup.
|
||||
After adding `src/` as an LSP workspace, it should automatically fall back to using stage 0 in there.
|
||||
|
||||
Note that the UX of `{emacs,vscode}-dev` is quite different from the Make-based setup regarding the compilation of dependencies:
|
||||
there is no mutable directory incrementally filled by the build that we could point the editor at for .olean files.
|
||||
Instead, `emacs-dev` will gather the individual dependency outputs from the Nix store when checking a file -- and build them on the fly when necessary.
|
||||
However, it will only ever load changes saved to disk, not ones opened in other buffers.
|
||||
|
||||
The absence of a mutable output directory also means that the Lean server will not automatically pick up `.ilean` metadata from newly compiled files.
|
||||
Instead, you can run `nix run .#link-ilean` to symlink the `.ilean` tree of the stdlib state at that point in time to `src/build/lib`, where the server should automatically find them.
|
||||
|
||||
## Other Fun Stuff to Do with Nix
|
||||
|
||||
Open Emacs with Lean set up from an arbitrary commit (without even cloning Lean beforehand... if your Nix is new enough):
|
||||
```bash
|
||||
nix run github:leanprover/lean4/7e4edeb#emacs-package
|
||||
```
|
||||
|
||||
Open a shell with `lean` and `LEAN_PATH` set up for compiling a specific module (this is exactly what `emacs-dev` is doing internally):
|
||||
```bash
|
||||
nix develop .#mods.\"Lean.Parser.Basic\"
|
||||
# alternatively, directly pass a command to execute:
|
||||
nix develop .#stage2.mods.\"Init.Control.Basic\" -c bash -c 'lean $src -Dtrace.Elab.command=true'
|
||||
```
|
||||
|
||||
Not sure what you just broke? Run Lean from (e.g.) the previous commit on a file:
|
||||
```bash
|
||||
nix run .\?rev=$(git rev-parse @^) scratch.lean
|
||||
```
|
||||
|
||||
Work on two adjacent stages at the same time without the need for repeatedly updating and reverting `stage0/`:
|
||||
```bash
|
||||
# open an editor that will use only committed changes (so first commit them when changing files)
|
||||
nix run .#HEAD-as-stage1.emacs-dev&
|
||||
# open a second editor that will use those commited changes as stage 0
|
||||
# (so don't commit changes done here until you are done and ran a final `update-stage0-commit`)
|
||||
nix run .#HEAD-as-stage0.emacs-dev&
|
||||
```
|
||||
To run `nix build` on the second stage outside of the second editor, use
|
||||
```bash
|
||||
nix build .#stage0-from-input --override-input lean-stage0 .\?rev=$(git rev-parse HEAD)
|
||||
```
|
||||
This setup will inadvertently change your `flake.lock` file, which you can revert when you are done.
|
||||
|
||||
...more surely to come...
|
||||
|
||||
## Debugging
|
||||
|
||||
Since Nix copies all source files before compilation, you will need to map debug symbols back to the original path using `set substitute-path` in GDB.
|
||||
For example, for a build on Linux with the Nix sandbox activated:
|
||||
```bash
|
||||
(gdb) f
|
||||
#1 0x0000000000d23a4f in lean_inc (o=0x1) at /build/source/build/include/lean/lean.h:562
|
||||
562 /build/source/build/include/lean/lean.h: No such file or directory.
|
||||
(gdb) set substitute-path /build/source/build src
|
||||
(gdb) f
|
||||
#1 0x0000000000d23a4f in lean_inc (o=0x1) at /build/source/build/include/lean/lean.h:562
|
||||
562 static inline void lean_inc(lean_object * o) { if (!lean_is_scalar(o)) lean_inc_ref(o); }
|
||||
```
|
||||
@@ -18,9 +18,9 @@ syntax "`[Arith| " arith "]" : term
|
||||
macro_rules
|
||||
| `(`[Arith| $s:str]) => `(Arith.symbol $s)
|
||||
| `(`[Arith| $num:num]) => `(Arith.int $num)
|
||||
| `(`[Arith| $x + $y]) => `(Arith.add `[Arith| $x] `[Arith| $y])
|
||||
| `(`[Arith| $x * $y]) => `(Arith.mul `[Arith| $x] `[Arith| $y])
|
||||
| `(`[Arith| ($x)]) => `(`[Arith| $x])
|
||||
| `(`[Arith| $x:arith + $y:arith]) => `(Arith.add `[Arith| $x] `[Arith| $y])
|
||||
| `(`[Arith| $x:arith * $y:arith]) => `(Arith.mul `[Arith| $x] `[Arith| $y])
|
||||
| `(`[Arith| ($x:arith)]) => `(`[Arith| $x])
|
||||
|
||||
#check `[Arith| "x" * "y"] -- mul
|
||||
-- Arith.mul (Arith.symbol "x") (Arith.symbol "y")
|
||||
|
||||
@@ -60,7 +60,7 @@ While parsing `a * (b + c)`, `(b + c)` is assigned a precedence `60` by the addi
|
||||
the right argument to have precedence **at least** 71. Thus, this parse is invalid. In contrast, `(a * b) + c` assigns
|
||||
a precedence of `70` to `(a * b)`. This is compatible with addition which expects the left argument to have precedence
|
||||
**at least `60` ** (`70` is greater than `60`). Thus, the string `a * b + c` is parsed as `(a * b) + c`.
|
||||
For more details, please look at the [Lean manual on syntax extensions](./notation.md#notations-and-precedence).
|
||||
For more details, please look at the [Lean manual on syntax extensions](../syntax.md#notations-and-precedence).
|
||||
|
||||
To go from strings into `Arith`, we define a macro to
|
||||
translate the syntax category `arith` into an `Arith` inductive value that
|
||||
|
||||
1
doc/monads/.gitignore
vendored
1
doc/monads/.gitignore
vendored
@@ -1 +0,0 @@
|
||||
*.lean.md
|
||||
@@ -1,334 +0,0 @@
|
||||
/-!
|
||||
# Applicative Functors
|
||||
|
||||
Building on [Functors](functors.lean.md) is the [Applicative
|
||||
Functor](https://en.wikipedia.org/wiki/Applicative_functor). For simplicity, you can refer to these
|
||||
simply as "Applicatives". These are a little tricker than functors, but still simpler than monads.
|
||||
Let's see how they work!
|
||||
|
||||
## What is an Applicative Functor?
|
||||
|
||||
An applicative functor defines a default or "base" construction for an object and allows
|
||||
function application to be chained across multiple instances of the structure. All applicative
|
||||
functors are functors, meaning they must also support the "map" operation.
|
||||
|
||||
## How are Applicatives represented in Lean?
|
||||
|
||||
An [applicative functor](https://en.wikipedia.org/wiki/Applicative_functor) is an intermediate
|
||||
structure between `Functor` and `Monad`. It mainly consists of two operations:
|
||||
|
||||
* `pure : α → F α`
|
||||
* `seq : F (α → β) → F α → F β` (written as `<*>`)
|
||||
|
||||
The `pure` operator specifies how you can wrap a normal object `α` into an instance of this structure `F α`.
|
||||
This is the "default" mechanism mentioned above.
|
||||
|
||||
The `seq` operator allows you to chain operations by wrapping a function in a structure. The name
|
||||
"applicative" comes from the fact that you "apply" functions from within the structure, rather than
|
||||
simply from outside the structure, as was the case with `Functor.map`.
|
||||
|
||||
Applicative in Lean is built on some helper type classes, `Functor`, `Pure` and `Seq`:
|
||||
|
||||
-/
|
||||
namespace hidden -- hidden
|
||||
class Applicative (f : Type u → Type v) extends Functor f, Pure f, Seq f, SeqLeft f, SeqRight f where
|
||||
map := fun x y => Seq.seq (pure x) fun _ => y
|
||||
seqLeft := fun a b => Seq.seq (Functor.map (Function.const _) a) b
|
||||
seqRight := fun a b => Seq.seq (Functor.map (Function.const _ id) a) b
|
||||
end hidden -- hidden
|
||||
/-!
|
||||
Notice that as with `Functor` it is also a type transformer `(f : Type u → Type v)` and notice the
|
||||
`extends Functor f` is ensuring the base `Functor` also performs that same type transformation.
|
||||
|
||||
As stated above, all applicatives are then functors. This means you can assume that `map` already
|
||||
exists for all these types.
|
||||
|
||||
The `Pure` base type class is a very simple type class that supplies the `pure` function.
|
||||
|
||||
-/
|
||||
namespace hidden -- hidden
|
||||
class Pure (f : Type u → Type v) where
|
||||
pure {α : Type u} : α → f α
|
||||
end hidden -- hidden
|
||||
/-!
|
||||
|
||||
You can think of it as lifting the result of a pure value to some monadic type. The simplest example
|
||||
of `pure` is the `Option` type:
|
||||
|
||||
-/
|
||||
#eval (pure 10 : Option Nat) -- some 10
|
||||
/-!
|
||||
|
||||
Here we used the `Option` implementation of `pure` to wrap the `Nat 10` value in an `Option Nat`
|
||||
type resulting in the value `some 10`, and in fact if you look at the Monad instance of `Option` , you
|
||||
will see that `pure` is indeed implemented using `Option.some`:
|
||||
|
||||
-/
|
||||
instance : Monad Option where
|
||||
pure := Option.some
|
||||
/-!
|
||||
|
||||
The `Seq` type class is also a simple type class that provides the `seq` operator which can
|
||||
also be written using the special syntax `<*>`.
|
||||
|
||||
-/
|
||||
namespace hidden -- hidden
|
||||
class Seq (f : Type u → Type v) : Type (max (u+1) v) where
|
||||
seq : {α β : Type u} → f (α → β) → (Unit → f α) → f β
|
||||
end hidden -- hidden
|
||||
/-!
|
||||
|
||||
|
||||
## Basic Applicative Examples
|
||||
|
||||
Many of the basic functors also have instances of `Applicative`.
|
||||
For example, `Option` is also `Applicative`.
|
||||
|
||||
So let's take a look and what the `seq` operator can do. Suppose you want to multiply two `Option Nat`
|
||||
objects. Your first attempt might be this:
|
||||
|
||||
-/
|
||||
#check_failure (some 4) * (some 5) -- failed to synthesize instance
|
||||
/-!
|
||||
|
||||
You then might wonder how to use the `Functor.map` to solve this since you could do these before:
|
||||
|
||||
-/
|
||||
#eval (some 4).map (fun x => x * 5) -- some 20
|
||||
|
||||
#eval (some 4).map (· * 5) -- some 20
|
||||
|
||||
#eval (· * 5) <$> (some 4) -- some 20
|
||||
/-!
|
||||
|
||||
Remember that `<$>` is the infix notation for `Functor.map`.
|
||||
|
||||
The functor `map` operation can apply a multiplication to the value in the `Option` and then lift the
|
||||
result back up to become a new `Option` , but this isn't what you need here.
|
||||
|
||||
The `Seq.seq` operator `<*>` can help since it can apply a function to the items inside a
|
||||
container and then lift the result back up to the desired type, namely `Option` .
|
||||
|
||||
There are two ways to do this:
|
||||
|
||||
-/
|
||||
#eval pure (.*.) <*> some 4 <*> some 5 -- some 20
|
||||
|
||||
#eval (.*.) <$> some 4 <*> some 5 -- some 20
|
||||
/-!
|
||||
|
||||
In the first way, we start off by wrapping the function in an applicative using pure. Then we apply
|
||||
this to the first `Option` , and again to the second `Option` in a chain of operations. So you can see
|
||||
how `Seq.seq` can be chained in fact, `Seq.seq` is really all about chaining of operations.
|
||||
|
||||
But in this case there is a simpler way. In the second way, you can see that "applying" a single
|
||||
function to a container is the same as using `Functor.map`. So you use `<$>` to "transform" the first
|
||||
option into an `Option` containing a function, and then apply this function over the second value.
|
||||
|
||||
Now if either side is `none`, the result is `none`, as expected, and in this case the
|
||||
`seq` operator was able to eliminate the multiplication:
|
||||
|
||||
-/
|
||||
#eval (.*.) <$> none <*> some 5 -- none
|
||||
#eval (.*.) <$> some 4 <*> none -- none
|
||||
/-!
|
||||
|
||||
For a more interesting example, let's make `List` an applicative by adding the following
|
||||
definition:
|
||||
|
||||
-/
|
||||
instance : Applicative List where
|
||||
pure := List.pure
|
||||
seq f x := List.bind f fun y => Functor.map y (x ())
|
||||
/-!
|
||||
|
||||
Notice you can now sequence a _list_ of functions and a _list_ of items.
|
||||
The trivial case of sequencing a singleton list is in fact the same as `map`, as you saw
|
||||
earlier with the `Option` examples:
|
||||
|
||||
-/
|
||||
#eval [ (·+2)] <*> [4, 6] -- [6, 8]
|
||||
#eval (·+2) <$> [4,6] -- [6, 8]
|
||||
/-!
|
||||
|
||||
But now with list it is easier to show the difference when you do this:
|
||||
|
||||
-/
|
||||
#eval [(·+2), (· *3)] <*> [4, 6] -- [6, 8, 12, 18]
|
||||
/-!
|
||||
|
||||
Why did this produce 4 values? The reason is because `<*>` applies _every_ function to _every_
|
||||
value in a pairwise manner. This makes sequence really convenient for solving certain problems. For
|
||||
example, how do you get the pairwise combinations of all values from two lists?
|
||||
|
||||
-/
|
||||
#eval Prod.mk <$> [1, 2, 3] <*> [4, 5, 6]
|
||||
-- [(1, 4), (1, 5), (1, 6), (2, 4), (2, 5), (2, 6), (3, 4), (3, 5), (3, 6)]
|
||||
/-!
|
||||
|
||||
How do you get the sum of these pairwise values?
|
||||
-/
|
||||
#eval (·+·) <$> [1, 2, 3] <*> [4, 5, 6]
|
||||
-- [5, 6, 7, 6, 7, 8, 7, 8, 9]
|
||||
/-!
|
||||
|
||||
Here you can use `<$>` to "transform" each element of the first list into a function, and then apply
|
||||
these functions over the second list.
|
||||
|
||||
If you have 3 lists, and want to find all combinations of 3 values across those lists you
|
||||
would need helper function that can create a tuple out of 3 values, and Lean provides a
|
||||
very convenient syntax for that `(·,·,·)`:
|
||||
|
||||
-/
|
||||
#eval (·,·,·) <$> [1, 2] <*> [3, 4] <*> [5, 6]
|
||||
-- [(1, 3, 5), (1, 3, 6), (1, 4, 5), (1, 4, 6), (2, 3, 5), (2, 3, 6), (2, 4, 5), (2, 4, 6)]
|
||||
/-!
|
||||
|
||||
And you could sum these combinations if you first define a sum function that takes three inputs and
|
||||
then you could chain apply this over the three lists. Again lean can create such a function
|
||||
with the expression `(·+·+·)`:
|
||||
|
||||
-/
|
||||
#eval (·+·+·) <$> [1, 2] <*> [3, 4] <*> [5, 6]
|
||||
-- [9, 10, 10, 11, 10, 11, 11, 12]
|
||||
/-!
|
||||
|
||||
And indeed each sum here matches the expected values if you manually sum the triples we
|
||||
show above.
|
||||
|
||||
**Side note:** there is another way to combine lists with a function that does not do the pairwise
|
||||
combinatorics, it is called `List.zipWith`:
|
||||
|
||||
-/
|
||||
#eval List.zipWith (·+·) [1, 2, 3] [4, 5, 6]
|
||||
-- [5, 7, 9]
|
||||
/-!
|
||||
|
||||
And there is a helper function named `List.zip` that calls `zipWith` using the function `Prod.mk`
|
||||
so you get a nice zipped list like this:
|
||||
|
||||
-/
|
||||
#eval List.zip [1, 2, 3] [4, 5, 6]
|
||||
-- [(1, 4), (2, 5), (3, 6)]
|
||||
/-!
|
||||
|
||||
And of course, as you would expect, there is an `unzip` also:
|
||||
|
||||
-/
|
||||
#eval List.unzip (List.zip [1, 2, 3] [4, 5, 6])
|
||||
-- ([1, 2, 3], [4, 5, 6])
|
||||
/-!
|
||||
|
||||
## Example: A Functor that is not Applicative
|
||||
|
||||
From the chapter on [functors](functors.lean.md) you might remember this example of `LivingSpace`
|
||||
that had a `Functor` instance:
|
||||
|
||||
-/
|
||||
structure LivingSpace (α : Type) where
|
||||
totalSize : α
|
||||
numBedrooms : Nat
|
||||
masterBedroomSize : α
|
||||
livingRoomSize : α
|
||||
kitchenSize : α
|
||||
deriving Repr, BEq
|
||||
|
||||
def LivingSpace.map (f : α → β) (s : LivingSpace α) : LivingSpace β :=
|
||||
{ totalSize := f s.totalSize
|
||||
numBedrooms := s.numBedrooms
|
||||
masterBedroomSize := f s.masterBedroomSize
|
||||
livingRoomSize := f s.livingRoomSize
|
||||
kitchenSize := f s.kitchenSize }
|
||||
|
||||
instance : Functor LivingSpace where
|
||||
map := LivingSpace.map
|
||||
/-!
|
||||
|
||||
It wouldn't really make sense to make an `Applicative` instance here. How would you write `pure` in
|
||||
the `Applicative` instance? By taking a single value and plugging it in for total size _and_ the
|
||||
master bedroom size _and_ the living room size? That wouldn't really make sense. And what would the
|
||||
numBedrooms value be for the default? What would it mean to "chain" two of these objects together?
|
||||
|
||||
If you can't answer these questions very well, then it suggests this type isn't really an
|
||||
Applicative functor.
|
||||
|
||||
## SeqLeft and SeqRight
|
||||
|
||||
You may remember seeing the `SeqLeft` and `SeqRight` base types on `class Applicative` earlier.
|
||||
These provide the `seqLeft` and `seqRight` operations which also have some handy notation
|
||||
shorthands `<*` and `*>` respectively. Where: `x <* y` evaluates `x`, then `y`, and returns the
|
||||
result of `x` and `x *> y` evaluates `x`, then `y`, and returns the result of `y`.
|
||||
|
||||
To make it easier to remember, notice that it returns that value that the `<*` or `*>` notation is
|
||||
pointing at. For example:
|
||||
|
||||
-/
|
||||
#eval (some 1) *> (some 2) -- Some 2
|
||||
#eval (some 1) <* (some 2) -- Some 1
|
||||
/-!
|
||||
|
||||
So these are a kind of "discard" operation. Run all the actions, but only return the values that you
|
||||
care about. It will be easier to see these in action when you get to full Monads, but they are used
|
||||
heavily in the Lean `Parsec` parser combinator library where you will find parsing functions like
|
||||
this one which parses the XML declaration `<?xml version="1.0" encoding='utf-8' standalone="yes">`:
|
||||
|
||||
```lean
|
||||
def XMLdecl : Parsec Unit := do
|
||||
skipString "<?xml"
|
||||
VersionInfo
|
||||
optional EncodingDecl *> optional SDDecl *> optional S *> skipString "?>"
|
||||
```
|
||||
|
||||
But you will need to understand full Monads before this will make sense.
|
||||
|
||||
## Lazy Evaluation
|
||||
|
||||
Diving a bit deeper, (you can skip this and jump to the [Applicative
|
||||
Laws](laws.lean.md#what-are-the-applicative-laws) if don't want to dive into this implementation detail right
|
||||
now). But, if you write a simple `Option` example `(.*.) <$> some 4 <*> some 5` that produces `some 20`
|
||||
using `Seq.seq` you will see something interesting:
|
||||
|
||||
-/
|
||||
#eval Seq.seq ((.*.) <$> some 4) (fun (_ : Unit) => some 5) -- some 20
|
||||
/-!
|
||||
|
||||
This may look a bit cumbersome, specifically, why did we need to invent this funny looking function
|
||||
`fun (_ : Unit) => (some 5)`?
|
||||
|
||||
Well if you take a close look at the type class definition:
|
||||
```lean
|
||||
class Seq (f : Type u → Type v) where
|
||||
seq : {α β : Type u} → f (α → β) → (Unit → f α) → f β
|
||||
```
|
||||
|
||||
You will see this function defined here: `(Unit → f α)`, this is a function that takes `Unit` as input
|
||||
and produces the output of type `f α` where `f` is the container type `Type u -> Type v`, in this example `Option`
|
||||
and `α` is the element type `Nat`, so `fun (_ : Unit) => some 5` matches this definition because
|
||||
it is taking an input of type Unit and producing `some 5` which is type `Option Nat`.
|
||||
|
||||
The that `seq` is defined this way is because Lean is an eagerly evaluated language
|
||||
(call-by-value), you have to use this kind of Unit function whenever you want to explicitly delay
|
||||
evaluation and `seq` wants that so it can eliminate unnecessary function evaluations whenever
|
||||
possible.
|
||||
|
||||
Fortunately the `<*>` infix notation hides this from you by creating this wrapper function for you.
|
||||
If you look up the notation using F12 in VS Code you will find it contains `(fun _ : Unit => b)`.
|
||||
|
||||
Now to complete this picture you will find the default implementation of `seq` on the Lean `Monad`
|
||||
type class:
|
||||
|
||||
```lean
|
||||
class Monad (m : Type u → Type v) extends Applicative m, Bind m where
|
||||
seq f x := bind f fun y => Functor.map y (x ())
|
||||
```
|
||||
|
||||
Notice here that `x` is the `(Unit → f α)` function, and it is calling that function by passing the
|
||||
Unit value `()`, which is the Unit value (Unit.unit). All this just to ensure delayed evaluation.
|
||||
|
||||
## How do Applicatives help with Monads?
|
||||
|
||||
Applicatives are helpful for the same reasons as functors. They're a relatively simple abstract
|
||||
structure that has practical applications in your code. Now that you understand how chaining
|
||||
operations can fit into a structure definition, you're in a good position to start learning about
|
||||
[Monads](monads.lean.md)!
|
||||
-/
|
||||
@@ -1,178 +0,0 @@
|
||||
/-!
|
||||
# Except
|
||||
|
||||
The `Except` Monad adds exception handling behavior to your functions. Exception handling
|
||||
in other languages like Python or Java is done with a built in `throw` method that you
|
||||
can use anywhere. In `Lean` you can only `throw` an exception when your function is
|
||||
executing in the context of an `Except` monad.
|
||||
|
||||
-/
|
||||
def divide (x y: Float): Except String Float :=
|
||||
if y == 0 then
|
||||
throw "can't divide by zero"
|
||||
else
|
||||
pure (x / y)
|
||||
|
||||
#eval divide 5 2 -- Except.ok 2.500000
|
||||
#eval divide 5 0 -- Except.error "can't divide by zero"
|
||||
|
||||
/-!
|
||||
|
||||
Just as the `read` operation was available from the `ReaderM` monad and the `get` and `set`
|
||||
operations came with the `StateM` monad, here you can see a `throw` operation is provided by the
|
||||
`Except` monad.
|
||||
|
||||
So in Lean, `throw` is not available everywhere like it is in most imperative programming languages.
|
||||
You have to declare your function can throw by changing the type signature to `Except String Float`.
|
||||
This creates a function that might return an error of type `String` or it might return a value of
|
||||
type `Float` in the non-error case.
|
||||
|
||||
Once your function is monadic you also need to use the `pure` constructor of the `Except` monad to
|
||||
convert the pure non-monadic value `x / y` into the required `Except` object. See
|
||||
[Applicatives](applicatives.lean.md) for details on `pure`.
|
||||
|
||||
Now this return typing would get tedious if you had to include it everywhere that you call this
|
||||
function, however, Lean type inference can clean this up. For example, you can define a test
|
||||
function can calls the `divide` function and you don't need to say anything here about the fact that
|
||||
it might throw an error, because that is inferred:
|
||||
-/
|
||||
def test := divide 5 0
|
||||
|
||||
#check test -- Except String Float
|
||||
/-!
|
||||
|
||||
Notice the Lean compiler infers the required `Except String Float` type information for you.
|
||||
And now you can run this test and get the expected exception:
|
||||
|
||||
-/
|
||||
#eval test -- Except.error "can't divide by zero"
|
||||
/-!
|
||||
|
||||
|
||||
## Chaining
|
||||
|
||||
Now as before you can build a chain of monadic actions that can be composed together using `bind (>>=)`:
|
||||
-/
|
||||
def square (x : Float) : Except String Float :=
|
||||
if x >= 100 then
|
||||
throw "it's absolutely huge"
|
||||
else
|
||||
pure (x * x)
|
||||
|
||||
#eval divide 6 2 >>= square -- Except.ok 9.000000
|
||||
#eval divide 6 0 >>= square -- Except.error "can't divide by zero"
|
||||
#eval divide 100 1 >>= square -- Except.error "it's absolutely huge"
|
||||
|
||||
def chainUsingDoNotation := do
|
||||
let r ← divide 6 0
|
||||
square r
|
||||
|
||||
#eval chainUsingDoNotation -- Except.error "can't divide by zero"
|
||||
|
||||
/-!
|
||||
Notice in the second `divide 6 0` the exception from that division was nicely propagated along
|
||||
to the final result and the square function was ignored in that case. You can see why the
|
||||
`square` function was ignored if you look at the implementation of `Except.bind`:
|
||||
-/
|
||||
def bind (ma : Except ε α) (f : α → Except ε β) : Except ε β :=
|
||||
match ma with
|
||||
| Except.error err => Except.error err
|
||||
| Except.ok v => f v
|
||||
/-!
|
||||
|
||||
Specifically notice that it only calls the next function `f v` in the `Except.ok`, and
|
||||
in the error case it simply passes the same error along.
|
||||
|
||||
Remember also that you can chain the actions with implicit binding by using the `do` notation
|
||||
as you see in the `chainUsingDoNotation` function above.
|
||||
|
||||
## Try/Catch
|
||||
|
||||
Now with all good exception handling you also want to be able to catch exceptions so your program
|
||||
can continue on or do some error recovery task, which you can do like this:
|
||||
-/
|
||||
def testCatch :=
|
||||
try
|
||||
let r ← divide 8 0 -- 'r' is type Float
|
||||
pure (toString r)
|
||||
catch e =>
|
||||
pure s!"Caught exception: {e}"
|
||||
|
||||
#check testCatch -- Except String String
|
||||
/-!
|
||||
|
||||
Note that the type inferred by Lean for this function is `Except String String` so unlike the
|
||||
`test` function earlier, this time Lean type inference has figured out that since the pure
|
||||
value `(toString r)` is of type `String`, then this function must have type `Except String String`
|
||||
so you don't have to explicitly state this. You can always hover your mouse over `testCatch`
|
||||
or use `#check testCatch` to query Lean interactively to figure out what type inference
|
||||
has decided. Lean type inference makes life easy for you, so it's good to use it
|
||||
when you can.
|
||||
|
||||
You can now see the try/catch working in this eval:
|
||||
-/
|
||||
#eval testCatch -- Except.ok "Caught exception: can't divide by zero"
|
||||
/-!
|
||||
|
||||
Notice the `Caught exception:` wrapped message is returned, and that it is returned as an
|
||||
`Except.ok` value, meaning `testCatch` eliminated the error result as expected.
|
||||
|
||||
So you've interleaved a new concept into your functions (exception handling) and the compiler is still
|
||||
able to type check everything just as well as it does for pure functions and it's been able to infer
|
||||
some things along the way to make it even easier to manage.
|
||||
|
||||
Now you might be wondering why `testCatch` doesn't infer the return type `String`? Lean does this as a
|
||||
convenience since you could have a rethrow in or after the catch block. If you really want to stop
|
||||
the `Except` type from bubbling up you can unwrap it like this:
|
||||
|
||||
-/
|
||||
def testUnwrap : String := Id.run do
|
||||
let r ← divide 8 0 -- r is type Except String Float
|
||||
match r with
|
||||
| .ok a => toString a -- 'a' is type Float
|
||||
| .error e => s!"Caught exception: {e}"
|
||||
|
||||
#check testUnwrap -- String
|
||||
#eval testUnwrap -- "Caught exception: can't divide by zero"
|
||||
|
||||
/-!
|
||||
|
||||
The `Id.run` function is a helper function that executes the `do` block and returns the result where
|
||||
`Id` is the _identity monad_. So `Id.run do` is a pattern you can use to execute monads in a
|
||||
function that is not itself monadic. This works for all monads except `IO` which, as stated earlier,
|
||||
you cannot invent out of thin air, you must use the `IO` monad given to your `main` function.
|
||||
|
||||
## Monadic functions
|
||||
|
||||
You can also write functions that are designed to operate in the context of a monad.
|
||||
These functions typically end in upper case M like `List.forM` used below:
|
||||
-/
|
||||
|
||||
def validateList (x : List Nat) (max : Nat): Except String Unit := do
|
||||
x.forM fun a => do
|
||||
if a > max then throw "illegal value found in list"
|
||||
|
||||
#eval validateList [1, 2, 5, 3, 8] 10 -- Except.ok ()
|
||||
#eval validateList [1, 2, 5, 3, 8] 5 -- Except.error "illegal value found in list"
|
||||
|
||||
/-!
|
||||
Notice here that the `List.forM` function passes the monadic context through to the inner function
|
||||
so it can use the `throw` function from the `Except` monad.
|
||||
|
||||
The `List.forM` function is defined like this where `[Monad m]` means "in the context of a monad `m`":
|
||||
|
||||
-/
|
||||
def forM [Monad m] (as : List α) (f : α → m PUnit) : m PUnit :=
|
||||
match as with
|
||||
| [] => pure ⟨⟩
|
||||
| a :: as => do f a; List.forM as f
|
||||
/-!
|
||||
|
||||
## Summary
|
||||
|
||||
Now that you know all these different monad constructs, you might be wondering how you can combine
|
||||
them. What if there was some part of your state that you wanted to be able to modify (using the
|
||||
State monad), but you also needed exception handling. How can you get multiple monadic capabilities
|
||||
in the same function. To learn the answer, head to [Monad Transformers](transformers.lean.md).
|
||||
|
||||
-/
|
||||
@@ -1,227 +0,0 @@
|
||||
/-!
|
||||
# Functor
|
||||
|
||||
A `Functor` is any type that can act as a generic container that allows you to transform the
|
||||
underlying values inside the container using a function, so that the values are all updated, but the
|
||||
structure of the container is the same. This is called "mapping".
|
||||
|
||||
A List is one of the most basic examples of a `Functor`.
|
||||
|
||||
A list contains zero or more elements of the same, underlying type. When you `map` a function over
|
||||
a list, you create a new list with the same number of elements, where each has been transformed by
|
||||
the function:
|
||||
-/
|
||||
#eval List.map (λ x => toString x) [1,2,3] -- ["1", "2", "3"]
|
||||
|
||||
-- you can also write this using dot notation on the List object
|
||||
#eval [1,2,3].map (λ x => toString x) -- ["1", "2", "3"]
|
||||
|
||||
/-!
|
||||
Here we converted a list of natural numbers (Nat) to a list of strings where the lambda function
|
||||
here used `toString` to do the transformation of each element. Notice that when you apply `map` the
|
||||
"structure" of the object remains the same, in this case the result is always a `List` of the same
|
||||
size.
|
||||
|
||||
Note that in Lean a lambda function can be written using `fun` keyword or the unicode
|
||||
symbol `λ` which you can type in VS code using `\la `.
|
||||
|
||||
List has a specialized version of `map` defined as follows:
|
||||
-/
|
||||
def map (f : α → β) : List α → List β
|
||||
| [] => []
|
||||
| a::as => f a :: map f as
|
||||
|
||||
/-!
|
||||
This is a very generic `map` function that can take any function that converts `(α → β)` and use it
|
||||
to convert `List α → List β`. Notice the function call `f a` above, this application of `f` is
|
||||
producing the converted items for the new list.
|
||||
|
||||
Let's look at some more examples:
|
||||
|
||||
-/
|
||||
-- List String → List Nat
|
||||
#eval ["elephant", "tiger", "giraffe"].map (fun s => s.length)
|
||||
-- [8, 5, 7]
|
||||
|
||||
-- List Nat → List Float
|
||||
#eval [1,2,3,4,5].map (fun s => (s.toFloat) ^ 3.0)
|
||||
-- [1.000000, 8.000000, 27.000000, 64.000000, 125.000000]
|
||||
|
||||
--- List String → List String
|
||||
#eval ["chris", "david", "mark"].map (fun s => s.capitalize)
|
||||
-- ["Chris", "David", "Mark"]
|
||||
/-!
|
||||
|
||||
Another example of a functor is the `Option` type. Option contains a value or nothing and is handy
|
||||
for code that has to deal with optional values, like optional command line arguments.
|
||||
|
||||
Remember you can construct an Option using the type constructors `some` or `none`:
|
||||
|
||||
-/
|
||||
#check some 5 -- Option Nat
|
||||
#eval some 5 -- some 5
|
||||
#eval (some 5).map (fun x => x + 1) -- some 6
|
||||
#eval (some 5).map (fun x => toString x) -- some "5"
|
||||
/-!
|
||||
|
||||
Lean also provides a convenient short hand syntax for `(fun x => x + 1)`, namely `(· + 1)`
|
||||
using the middle dot unicode character which you can type in VS code using `\. `.
|
||||
|
||||
-/
|
||||
#eval (some 4).map (· * 5) -- some 20
|
||||
/-!
|
||||
|
||||
The `map` function preserves the `none` state of the Option, so again
|
||||
map preserves the structure of the object.
|
||||
|
||||
-/
|
||||
def x : Option Nat := none
|
||||
#eval x.map (fun x => toString x) -- none
|
||||
#check x.map (fun x => toString x) -- Option String
|
||||
/-!
|
||||
|
||||
Notice that even in the `none` case it has transformed `Option Nat` into `Option String` as
|
||||
you see in the `#check` command.
|
||||
|
||||
## How to make a Functor Instance?
|
||||
|
||||
The `List` type is made an official `Functor` by the following type class instance:
|
||||
|
||||
-/
|
||||
instance : Functor List where
|
||||
map := List.map
|
||||
/-!
|
||||
|
||||
Notice all you need to do is provide the `map` function implementation. For a quick
|
||||
example, let's supposed you create a new type describing the measurements of a home
|
||||
or apartment:
|
||||
|
||||
-/
|
||||
structure LivingSpace (α : Type) where
|
||||
totalSize : α
|
||||
numBedrooms : Nat
|
||||
masterBedroomSize : α
|
||||
livingRoomSize : α
|
||||
kitchenSize : α
|
||||
deriving Repr, BEq
|
||||
/-!
|
||||
|
||||
Now you can construct a `LivingSpace` in square feet using floating point values:
|
||||
-/
|
||||
abbrev SquareFeet := Float
|
||||
|
||||
def mySpace : LivingSpace SquareFeet :=
|
||||
{ totalSize := 1800, numBedrooms := 4, masterBedroomSize := 500,
|
||||
livingRoomSize := 900, kitchenSize := 400 }
|
||||
/-!
|
||||
|
||||
Now, suppose you want anyone to be able to map a `LivingSpace` from one type of measurement unit to
|
||||
another. Then you would provide a `Functor` instance as follows:
|
||||
|
||||
-/
|
||||
def LivingSpace.map (f : α → β) (s : LivingSpace α) : LivingSpace β :=
|
||||
{ totalSize := f s.totalSize
|
||||
numBedrooms := s.numBedrooms
|
||||
masterBedroomSize := f s.masterBedroomSize
|
||||
livingRoomSize := f s.livingRoomSize
|
||||
kitchenSize := f s.kitchenSize }
|
||||
|
||||
instance : Functor LivingSpace where
|
||||
map := LivingSpace.map
|
||||
/-!
|
||||
|
||||
Notice this functor instance takes `LivingSpace` and not the fully qualified type `LivingSpace SquareFeet`.
|
||||
Notice below that `LivingSpace` is a function from Type to Type. For example, if you give it type `SquareFeet`
|
||||
it gives you back the fully qualified type `LivingSpace SquareFeet`.
|
||||
|
||||
-/
|
||||
#check LivingSpace -- Type → Type
|
||||
/-!
|
||||
|
||||
So the `instance : Functor` then is operating on the more abstract, or generic `LivingSpace` saying
|
||||
for the whole family of types `LivingSpace α` you can map to `LivingSpace β` using the generic
|
||||
`LivingSpace.map` map function by simply providing a function that does the more primitive mapping
|
||||
from `(f : α → β)`. So `LivingSpace.map` is a sort of function applicator.
|
||||
This is called a "higher order function" because it takes a function as input
|
||||
`(α → β)` and returns another function as output `F α → F β`.
|
||||
|
||||
Notice that `LivingSpace.map` applies a function `f` to convert the units of all the LivingSpace
|
||||
fields, except for `numBedrooms` which is a count (and therefore is not a measurement that needs
|
||||
converting).
|
||||
|
||||
So now you can define a simple conversion function, let's say you want square meters instead:
|
||||
|
||||
-/
|
||||
abbrev SquareMeters := Float
|
||||
def squareFeetToMeters (ft : SquareFeet ) : SquareMeters := (ft / 10.7639104)
|
||||
/-!
|
||||
|
||||
and now bringing it all together you can use the simple function `squareFeetToMeters` to map
|
||||
`mySpace` to square meters:
|
||||
|
||||
-/
|
||||
#eval mySpace.map squareFeetToMeters
|
||||
/-
|
||||
{ totalSize := 167.225472,
|
||||
numBedrooms := 4,
|
||||
masterBedroomSize := 46.451520,
|
||||
livingRoomSize := 83.612736,
|
||||
kitchenSize := 37.161216 }
|
||||
-/
|
||||
/-!
|
||||
|
||||
Lean also defines custom infix operator `<$>` for `Functor.map` which allows you to write this:
|
||||
-/
|
||||
#eval (fun s => s.length) <$> ["elephant", "tiger", "giraffe"] -- [8, 5, 7]
|
||||
#eval (fun x => x + 1) <$> (some 5) -- some 6
|
||||
/-!
|
||||
|
||||
Note that the infix operator is left associative which means it binds more tightly to the
|
||||
function on the left than to the expression on the right, this means you can often drop the
|
||||
parentheses on the right like this:
|
||||
|
||||
-/
|
||||
#eval (fun x => x + 1) <$> some 5 -- some 6
|
||||
/-!
|
||||
|
||||
Note that Lean lets you define your own syntax, so `<$>` is nothing special.
|
||||
You can define your own infix operator like this:
|
||||
|
||||
-/
|
||||
infixr:100 " doodle " => Functor.map
|
||||
|
||||
#eval (· * 5) doodle [1, 2, 3] -- [5, 10, 15]
|
||||
|
||||
/-!
|
||||
Wow, this is pretty powerful. By providing a functor instance on `LivingSpace` with an
|
||||
implementation of the `map` function it is now super easy for anyone to come along and
|
||||
transform the units of a `LivingSpace` using very simple functions like `squareFeetToMeters`. Notice
|
||||
that squareFeetToMeters knows nothing about `LivingSpace`.
|
||||
|
||||
## How do Functors help with Monads ?
|
||||
|
||||
Functors are an abstract mathematical structure that is represented in Lean with a type class. The
|
||||
Lean functor defines both `map` and a special case for working on constants more efficiently called
|
||||
`mapConst`:
|
||||
|
||||
```lean
|
||||
class Functor (f : Type u → Type v) : Type (max (u+1) v) where
|
||||
map : {α β : Type u} → (α → β) → f α → f β
|
||||
mapConst : {α β : Type u} → α → f β → f α
|
||||
```
|
||||
|
||||
Note that `mapConst` has a default implementation, namely:
|
||||
`mapConst : {α β : Type u} → α → f β → f α := Function.comp map (Function.const _)` in the `Functor`
|
||||
type class. So you can use this default implementation and you only need to replace it if
|
||||
your functor has a more specialized variant than this (usually the custom version is more performant).
|
||||
|
||||
In general then, a functor is a function on types `F : Type u → Type v` equipped with an operator
|
||||
called `map` such that if you have a function `f` of type `α → β` then `map f` will convert your
|
||||
container type from `F α → F β`. This corresponds to the category-theory notion of
|
||||
[functor](https://en.wikipedia.org/wiki/Functor) in the special case where the category is the
|
||||
category of types and functions between them.
|
||||
|
||||
Understanding abstract mathematical structures is a little tricky for most people. So it helps to
|
||||
start with a simpler idea like functors before you try to understand monads. Building on
|
||||
functors is the next abstraction called [Applicatives](applicatives.lean.md).
|
||||
-/
|
||||
@@ -1,63 +0,0 @@
|
||||
# Monads
|
||||
|
||||
Monads are used heavily in Lean, as they are also in Haskell. Monads come from the wonderful world
|
||||
of [Category Theory](https://en.wikipedia.org/wiki/Monad_%28category_theory%29).
|
||||
|
||||
Monads in Lean are so similar to Haskell that this introduction to monads is heavily based on the
|
||||
similar chapter of the [Monday Morning Haskell](https://mmhaskell.com/monads/). Many thanks to
|
||||
the authors of that material for allowing us to reuse it here.
|
||||
|
||||
Monads build on the following fundamental type classes which you will need to understand
|
||||
first before fully understanding monads. Shown in light blue are some concrete functors
|
||||
and monads that will also be covered in this chapter:
|
||||
|
||||

|
||||
|
||||
This chapter is organized to give you a bottom up introduction to monads, starting with functors and
|
||||
applicative functors, you'll get an intuition for how these abstract structures work in Lean. Then
|
||||
you'll dive into monads and learn how to use some of the most useful built-in ones.
|
||||
|
||||
## [Functor](functors.lean.md)
|
||||
A functor is a type class that provides a map function and the map function is something many
|
||||
people are already familiar with so this should be easy to follow. Here you will see some
|
||||
concrete examples in action with `List` and `Option`.
|
||||
|
||||
## [Applicative Functors](applicatives.lean.md)
|
||||
Applicatives are a little more difficult to understand than functors, but their functionality can
|
||||
still be summed up in a couple simple functions. Here you will learn how to create an
|
||||
`Applicative List` and a completely custom `Applicative` type.
|
||||
|
||||
## [Monads Tutorial](monads.lean.md)
|
||||
Now that you have an intuition for how abstract structures work, you'll examine some of the problems
|
||||
that functors and applicative functors don't help you solve. Then you'll learn the specifics of how
|
||||
to actually use monads with some examples using the `Option` monad and the all important `IO` monad.
|
||||
|
||||
## [Reader Monad](readers.lean.md)
|
||||
Now that you understand the details of what makes a monadic structure work, in this section, you'll
|
||||
learn about one of the most useful built in monads `ReaderM`, which gives your programs a
|
||||
global read-only context.
|
||||
|
||||
## [State Monad](states.lean.md)
|
||||
This section introduces the `StateM` monad. This monad allows you to access a particular type that you can
|
||||
both read from and write to. It opens the door to fully stateful programming, allowing you to do many
|
||||
of the things a function programming language supposedly "can't" do.
|
||||
|
||||
## [Except Monad](except.lean.md)
|
||||
|
||||
Similar to the `Option` monad the `Except` monad allows you to change the signature of a function so
|
||||
that it can return an `ok` value or an `error` and it provides the classic exception handling
|
||||
operations `throw/try/catch` so that your programs can do monad-based exception handling.
|
||||
|
||||
## [Monad Transformers](transformers.lean.md)
|
||||
|
||||
Now that you are familiar with all the above monads it is time to answer the question - how you can
|
||||
make them work together? After all, there are definitely times when you need multiple kinds of
|
||||
monadic behavior. This section introduces the concept of monad transformers, which allow you to
|
||||
combine multiple monads into one.
|
||||
|
||||
## [Monad Laws](laws.lean.md)
|
||||
This section examines what makes a monad a legal monad. You could just implement your monadic type
|
||||
classes any way you want and write "monad" instances, but starting back with functors and
|
||||
applicative functors, you'll learn that all these structures have "laws" that they are expected to
|
||||
obey with respect to their behavior. You can make instances that don't follow these laws. But you do
|
||||
so at your peril, as other programmers will be very confused when they try to use them.
|
||||
@@ -1,322 +0,0 @@
|
||||
/-!
|
||||
# Monad Laws
|
||||
|
||||
In the previous sections you learned how to use [Functors](functors.lean.md),
|
||||
[Applicatives](applicatives.lean.md), and [Monads](monads.lean.md), and you played with some useful
|
||||
instances including [Option](monads.lean.md), [IO](monads.lean.md), [Reader](readers.lean.md),
|
||||
[State](states.lean.md) and [Except](except.lean.md) and you learned about composition using [Monad
|
||||
Transformers](transformers.lean.md).
|
||||
|
||||
So far, you've learned the concrete details you need in order to _use_ monads in your Lean programs.
|
||||
But there's still one more important concept you need if you want to _create_ new functors,
|
||||
applicatives and monads. Namely, the notion of _structural "laws"_ -- rules that these type
|
||||
classes should follow in order to meet other programmers' expectations about your code.
|
||||
|
||||
## Life without Laws
|
||||
|
||||
Remember Lean represents each of these abstract structures by a type class. Each of these type classes
|
||||
has one or two main functions. So, as long as you implement those functions and it type checks, you
|
||||
have a new functor, applicative, or monad, right?
|
||||
|
||||
Well not quite. Yes, your program will compile and you'll be able to use the instances. But this
|
||||
doesn't mean your instances follow the mathematical constructs. If they don't, your instances won't
|
||||
fulfill other programmers' expectations. Each type class has its own "laws". For instance, suppose
|
||||
you have the following Point Functor:
|
||||
-/
|
||||
structure Point (α : Type) where
|
||||
x : α
|
||||
y : α
|
||||
deriving Repr, BEq
|
||||
|
||||
def Point.map (f : α → β) (s : Point α) : Point β :=
|
||||
{ x := f s.y, -- an example of something weird
|
||||
y := f s.x }
|
||||
|
||||
instance : Functor Point where
|
||||
map := Point.map
|
||||
|
||||
#eval (·+2) <$> (Point.mk 1 2) -- { x := 4, y := 3 }
|
||||
|
||||
/-!
|
||||
This Point does something weird, when you `map` it because it transposes the `x` and `y` coordinates
|
||||
which is not what other people would expect from a `map` function. In fact, it breaks the rules
|
||||
as you will see below.
|
||||
|
||||
## What are the Functor laws?
|
||||
|
||||
Functors have two laws: the _identity_ law, and the _composition_ law. These laws express behaviors that
|
||||
your functor instances should follow. If they don't, other programmers will be very confused at the
|
||||
effect your instances have on their program.
|
||||
|
||||
The identity law says that if you "map" the identity function (`id`) over your functor, the
|
||||
resulting functor should be the same. A succinct way of showing this on a `List` functor is:
|
||||
|
||||
-/
|
||||
def list1 := [1,2,3]
|
||||
|
||||
#eval id <$> list1 == list1 -- true
|
||||
/-!
|
||||
|
||||
Now let's try the same test on the `Point` functor:
|
||||
-/
|
||||
|
||||
def p1 : Point Nat := (Point.mk 1 2)
|
||||
|
||||
#eval id <$> p1 == p1 -- false
|
||||
|
||||
/-!
|
||||
Oh, and look while the `List` is behaving well, the `Point` functor fails this identity test.
|
||||
|
||||
The _composition_ law says that if you "map" two functions in succession over a functor, this
|
||||
should be the same as "composing" the functions and simply mapping that one super-function over the
|
||||
functor. In Lean you can compose two functions using `Function.comp f g` (or the syntax `f ∘ g`,
|
||||
which you can type in VS code using `\o `) and you will get the same results from both of these
|
||||
showing that the composition law holds for `List Nat`:
|
||||
|
||||
-/
|
||||
def double (x : Nat) := x + x
|
||||
def square (x : Nat) := x * x
|
||||
|
||||
#eval double <$> (square <$> list1) -- [2, 8, 18]
|
||||
|
||||
#eval (double <$> (square <$> list1)) == ((double ∘ square) <$> list1) -- true
|
||||
|
||||
-- ok, what about the Point class?
|
||||
#eval double <$> (square <$> p1) -- { x := 2, y := 8 }
|
||||
#eval (double ∘ square) <$> p1 -- { x := 8, y := 2 }
|
||||
|
||||
#eval double <$> (square <$> p1) == (double ∘ square) <$> p1 -- false
|
||||
/-!
|
||||
Note that composition also fails on the bad `Point` because the x/y transpose.
|
||||
|
||||
As you can see this bad `Point` implementation violates both of the functor laws. In this case it
|
||||
would not be a true functor. Its behavior would confuse any other programmers trying to use it. You
|
||||
should take care to make sure that your instances make sense. Once you get a feel for these type
|
||||
classes, the likelihood is that the instances you'll create will follow the laws.
|
||||
|
||||
You can also write a bad functor that passes one law but not the other like this:
|
||||
-/
|
||||
def bad_option_map {α β : Type u} : (α → β) → Option α → Option β
|
||||
| _, _ => none
|
||||
|
||||
instance : Functor Option where
|
||||
map := bad_option_map
|
||||
|
||||
def t1 : Option Nat := some 10
|
||||
|
||||
#eval id <$> t1 == t1 -- false
|
||||
#eval double <$> (square <$> t1) == (double ∘ square) <$> t1 -- true
|
||||
/-!
|
||||
|
||||
This fails the id law but obeys the composition law. Hopefully this explains the value of these
|
||||
laws, and you don't need to see any more bad examples!
|
||||
|
||||
## What are the Applicative Laws?
|
||||
|
||||
While functors have two laws, applicatives have four laws:
|
||||
|
||||
- Identity
|
||||
- Homomorphism
|
||||
- Interchange
|
||||
- Composition
|
||||
|
||||
### Identity
|
||||
|
||||
`pure id <*> v = v`
|
||||
|
||||
Applying the identity function through an applicative structure should not change the underlying
|
||||
values or structure. For example:
|
||||
-/
|
||||
instance : Applicative List where
|
||||
pure := List.pure
|
||||
seq f x := List.bind f fun y => Functor.map y (x ())
|
||||
|
||||
#eval pure id <*> [1, 2, 3] -- [1, 2, 3]
|
||||
/-!
|
||||
|
||||
The `pure id` statement here is wrapping the identity function in an applicative structure
|
||||
so that you can apply that over the container `[1, 2, 3]` using the Applicative `seq` operation
|
||||
which has the notation `<*>`.
|
||||
|
||||
To prove this for all values `v` and any applicative `m` you can write this theorem:
|
||||
-/
|
||||
example [Applicative m] [LawfulApplicative m] (v : m α) :
|
||||
pure id <*> v = v :=
|
||||
by simp -- Goals accomplished 🎉
|
||||
/-!
|
||||
|
||||
### Homomorphism
|
||||
|
||||
`pure f <*> pure x = pure (f x)`
|
||||
|
||||
Suppose you wrap a function and an object in `pure`. You can then apply the wrapped function over the
|
||||
wrapped object. Of course, you could also apply the normal function over the normal object, and then
|
||||
wrap it in `pure`. The homomorphism law states these results should be the same.
|
||||
|
||||
For example:
|
||||
|
||||
-/
|
||||
def x := 1
|
||||
def f := (· + 2)
|
||||
|
||||
#eval pure f <*> pure x = (pure (f x) : List Nat) -- true
|
||||
/-!
|
||||
|
||||
You should see a distinct pattern here. The overriding theme of almost all these laws is that these
|
||||
`Applicative` types should behave like normal containers. The `Applicative` functions should not
|
||||
have any side effects. All they should do is facilitate the wrapping, unwrapping, and transformation
|
||||
of data contained in the container resulting in a new container that has the same structure.
|
||||
|
||||
### Interchange
|
||||
|
||||
`u <*> pure y = pure (. y) <*> u`.
|
||||
|
||||
This law is is a little more complicated, so don't sweat it too much. It states that the order that
|
||||
you wrap things shouldn't matter. One the left, you apply any applicative `u` over a pure wrapped
|
||||
object. On the right, you first wrap a function applying the object as an argument. Note that `(·
|
||||
y)` is short hand for: `fun f => f y`. Then you apply this to the first applicative `u`. These
|
||||
should be the same.
|
||||
|
||||
For example:
|
||||
|
||||
-/
|
||||
def y := 4
|
||||
def g : List (Nat → Nat) := [(· + 2)]
|
||||
|
||||
#eval g <*> pure y = pure (· y) <*> g -- true
|
||||
/-!
|
||||
|
||||
You can prove this with the following theorem:
|
||||
-/
|
||||
example [Applicative m] [LawfulApplicative m] (u : m (α → β)) (y : α) :
|
||||
u <*> pure y = pure (· y) <*> u :=
|
||||
by simp [pure_seq] -- Goals accomplished 🎉
|
||||
|
||||
/-!
|
||||
|
||||
### Composition:
|
||||
|
||||
`u <*> v <*> w = u <*> (v <*> w)`
|
||||
|
||||
This final applicative law mimics the second functor law. It is a composition law. It states that
|
||||
function composition holds across applications within the applicative:
|
||||
|
||||
For example:
|
||||
|
||||
-/
|
||||
def u := [1, 2]
|
||||
def v := [3, 4]
|
||||
def w := [5, 6]
|
||||
|
||||
#eval pure (·+·+·) <*> u <*> v <*> w
|
||||
-- [9, 10, 10, 11, 10, 11, 11, 12]
|
||||
|
||||
#eval let grouping := pure (·+·) <*> v <*> w
|
||||
pure (·+·) <*> u <*> grouping
|
||||
-- [9, 10, 10, 11, 10, 11, 11, 12]
|
||||
/-!
|
||||
|
||||
To test composition you see the separate grouping `(v <*> w)` then that can be used in the outer
|
||||
sequence `u <*> grouping` to get the same final result `[9, 10, 10, 11, 10, 11, 11, 12]`.
|
||||
|
||||
## What are the Monad Laws?
|
||||
|
||||
Monads have three laws:
|
||||
|
||||
- Left Identity
|
||||
- Right Identity
|
||||
- Associativity
|
||||
|
||||
### Left Identity
|
||||
|
||||
Identity laws for monads specify that `pure` by itself shouldn't really change anything about the
|
||||
structure or its values.
|
||||
|
||||
Left identity is `x >>= pure = x` and is demonstrated by the following examples on a monadic `List`:
|
||||
-/
|
||||
instance : Monad List where
|
||||
pure := List.pure
|
||||
bind := List.bind
|
||||
|
||||
def a := ["apple", "orange"]
|
||||
|
||||
#eval a >>= pure -- ["apple", "orange"]
|
||||
|
||||
#eval a >>= pure = a -- true
|
||||
|
||||
/-!
|
||||
|
||||
### Right Identity
|
||||
|
||||
Right identity is `pure x >>= f = f x` and is demonstrated by the following example:
|
||||
-/
|
||||
def h (x : Nat) : Option Nat := some (x + 1)
|
||||
def z := 5
|
||||
|
||||
#eval pure z >>= h -- some 6
|
||||
#eval h z -- some 6
|
||||
|
||||
#eval pure z >>= h = h z -- true
|
||||
/-!
|
||||
|
||||
So in this example, with this specific `z` and `h`, you see that the rule holds true.
|
||||
|
||||
|
||||
### Associativity
|
||||
|
||||
The associativity law is written as:
|
||||
```lean,ignore
|
||||
x >>= f >>= g = x >>= (λ x => f x >>= g)
|
||||
```
|
||||
where `(x : m α)` and `(f : α → m β)` and `(g : β → m γ)`.
|
||||
|
||||
The associativity law is difficult to parse like some of the applicative laws, but what it is saying
|
||||
is that if you change the grouping of `bind` operations, you should still get the same result.
|
||||
This law has a parallel structure to the other composition laws.
|
||||
|
||||
You can see this in action in the following rewrite of `runOptionFuncsBind` from [monads](monads.lean.md):
|
||||
-/
|
||||
def optionFunc1 : String -> Option Nat
|
||||
| "" => none
|
||||
| str => some str.length
|
||||
|
||||
def optionFunc2 (i : Nat) : Option Float :=
|
||||
if i % 2 == 0 then none else some (i.toFloat * 3.14159)
|
||||
|
||||
def optionFunc3 (f : Float) : Option (List Nat) :=
|
||||
if f > 15.0 then none else some [f.floor.toUInt32.toNat, f.ceil.toUInt32.toNat]
|
||||
|
||||
|
||||
def runOptionFuncsBind (input : String) : Option (List Nat) :=
|
||||
optionFunc1 input >>= optionFunc2 >>= optionFunc3
|
||||
|
||||
def runOptionFuncsBindGrouped (input : String) : Option (List Nat) :=
|
||||
optionFunc1 input >>= (λ x => optionFunc2 x >>= optionFunc3)
|
||||
|
||||
#eval runOptionFuncsBind "big" -- some [9, 10]
|
||||
#eval runOptionFuncsBindGrouped "big" -- some [9, 10]
|
||||
/-!
|
||||
|
||||
Notice here we had to insert a `λ` function just like the definition says: `(λ x => f x >>= g)`.
|
||||
This is because unlike applicatives, you can't resolve the structure of later operations without the
|
||||
results of earlier operations quite as well because of the extra context monads provide. But you can
|
||||
still group their later operations into composite functions taking their inputs from earlier on, and
|
||||
the result should be the same.
|
||||
|
||||
|
||||
## Summary
|
||||
|
||||
While these laws may be a bit difficult to understand just by looking at them, the good news is that
|
||||
most of the instances you'll make will naturally follow the laws so long as you keep it simple, so
|
||||
you shouldn't have to worry about them too much.
|
||||
|
||||
There are two main ideas from all the laws:
|
||||
|
||||
1. Applying the identity or pure function should not change the underlying values or structure.
|
||||
1. It should not matter what order you group operations in. Another way to state this is function
|
||||
composition should hold across your structures.
|
||||
|
||||
Following these laws will ensure other programmers are not confused by the behavior of your
|
||||
new functors, applicatives and monads.
|
||||
|
||||
-/
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user