mirror of
https://github.com/leanprover/lean4.git
synced 2026-03-26 06:44:08 +00:00
Compare commits
1 Commits
parser_wit
...
task-avoid
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5908a3449a |
2
.gitattributes
vendored
2
.gitattributes
vendored
@@ -1,3 +1,3 @@
|
||||
*.expected.out -text
|
||||
RELEASES.md merge=union
|
||||
doc/changes.md merge=union
|
||||
stage0/** binary linguist-generated
|
||||
|
||||
186
.github/workflows/ci.yml
vendored
186
.github/workflows/ci.yml
vendored
@@ -9,35 +9,12 @@ on:
|
||||
branches:
|
||||
- master
|
||||
schedule:
|
||||
- cron: '0 7 * * *' # 8AM CET/11PM PT
|
||||
- cron: '0 0 * * *'
|
||||
|
||||
jobs:
|
||||
set-nightly:
|
||||
Build:
|
||||
# don't schedule nightlies on forks
|
||||
if: github.event_name == 'schedule' && github.repository == 'leanprover/lean4'
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
nightly: ${{ steps.set.outputs.nightly }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
- name: 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 "::set-output name=nightly::$LEAN_VERSION_STRING"
|
||||
fi
|
||||
fi
|
||||
|
||||
build:
|
||||
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')
|
||||
if: github.event_name != 'schedule' || github.repository == 'leanprover/lean4'
|
||||
runs-on: ${{ matrix.os }}
|
||||
defaults:
|
||||
run:
|
||||
@@ -50,11 +27,9 @@ jobs:
|
||||
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*
|
||||
llvm-url: https://github.com/leanprover/lean-llvm/releases/download/13.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
|
||||
@@ -66,27 +41,14 @@ jobs:
|
||||
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*
|
||||
llvm-url: https://github.com/leanprover/lean-llvm/releases/download/13.0.0/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
|
||||
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
|
||||
tar: gtar # https://github.com/actions/runner-images/issues/2619
|
||||
- name: Windows
|
||||
os: windows-2022
|
||||
release: true
|
||||
@@ -94,17 +56,9 @@ jobs:
|
||||
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*
|
||||
llvm-url: https://github.com/leanprover/lean-llvm/releases/download/13.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 }}
|
||||
@@ -116,13 +70,12 @@ jobs:
|
||||
CCACHE_MAXSIZE: 200M
|
||||
# squelch error message about missing nixpkgs channel
|
||||
NIX_BUILD_SHELL: bash
|
||||
LSAN_OPTIONS: max_leaks=10
|
||||
# somehow MinGW clang64 (or cmake?) defaults to `g++` even though it doesn't exist
|
||||
CXX: c++
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
# interferes with lean4-nightly authentication
|
||||
persist-credentials: false
|
||||
submodules: true
|
||||
- name: Install Nix
|
||||
uses: cachix/install-nix-action@v15
|
||||
@@ -131,8 +84,7 @@ jobs:
|
||||
uses: msys2/setup-msys2@v2
|
||||
with:
|
||||
msystem: clang64
|
||||
# `:p` means prefix with appropriate msystem prefix
|
||||
pacboy: "make python cmake:p clang:p ccache:p gmp:p git zip unzip diffutils binutils tree zstd:p tar"
|
||||
install: make python mingw-w64-clang-x86_64-cmake mingw-w64-clang-x86_64-clang mingw-w64-clang-x86_64-ccache git zip unzip diffutils binutils tree mingw-w64-clang-x86_64-zstd tar
|
||||
if: matrix.os == 'windows-2022'
|
||||
- name: Install Brew Packages
|
||||
run: |
|
||||
@@ -142,27 +94,35 @@ jobs:
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: .ccache
|
||||
key: ${{ matrix.name }}-build-v3-${{ github.sha }}
|
||||
key: ${{ matrix.name }}-build-${{ github.sha }}
|
||||
# fall back to (latest) previous cache
|
||||
restore-keys: |
|
||||
${{ matrix.name }}-build-v3
|
||||
${{ matrix.name }}-build
|
||||
- name: Setup
|
||||
run: |
|
||||
# open nix-shell once for initial setup
|
||||
true
|
||||
if: matrix.os == 'ubuntu-latest'
|
||||
# remove problematic tests for sanitized build
|
||||
- name: Pre build
|
||||
run: rm tests/compiler/StackOverflow.lean tests/compiler/StackOverflowTask.lean
|
||||
if: matrix.name == 'Linux fsanitize'
|
||||
- name: Build
|
||||
run: |
|
||||
mkdir build
|
||||
cd build
|
||||
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.set-nightly.outputs.nightly }}' ]]; then
|
||||
OPTIONS+=(-DLEAN_SPECIAL_VERSION_DESC=${{ needs.set-nightly.outputs.nightly }})
|
||||
[[ -z '${{ matrix.llvm-url }}' ]] || wget -q ${{ matrix.llvm-url }}
|
||||
[[ -z '${{ matrix.prepare-llvm }}' ]] || eval "OPTIONS+=($(../${{ matrix.prepare-llvm }}))"
|
||||
if [[ $GITHUB_EVENT_NAME == 'schedule' && -n '${{ matrix.release }}' && -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
|
||||
OPTIONS+=(-DLEAN_SPECIAL_VERSION_DESC=$LEAN_VERSION_STRING)
|
||||
echo "LEAN_VERSION_STRING=$LEAN_VERSION_STRING" >> $GITHUB_ENV
|
||||
fi
|
||||
fi
|
||||
# contortion to support empty OPTIONS with old macOS bash
|
||||
cmake .. ${{ matrix.CMAKE_OPTIONS }} ${OPTIONS[@]+"${OPTIONS[@]}"} -DLEAN_INSTALL_PREFIX=$PWD/..
|
||||
@@ -173,36 +133,31 @@ 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-*)
|
||||
mkdir pack
|
||||
# high-compression tar.zst + zip for release, fast tar.zst otherwise
|
||||
if [[ '${{ startsWith(github.ref, 'refs/tags/v') && matrix.release }}' == true || -n '${{ needs.set-nightly.outputs.nightly }}' ]]; then
|
||||
${{ matrix.tar || 'tar' }} cf - $dir | zstd -T0 --no-progress -19 -o pack/$dir.tar.zst
|
||||
zip -rq pack/$dir.zip $dir
|
||||
if [[ ${{ startsWith(github.ref, 'refs/tags/v') && matrix.release }} == true || -n ${LEAN_VERSION_STRING:-} ]]; then
|
||||
tar cf - $dir | zstd -T0 --no-progress -19 -o pack/$dir.tar.zst
|
||||
zip -r 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@v2
|
||||
if: matrix.release
|
||||
with:
|
||||
name: build-${{ matrix.name }}
|
||||
path: pack/*
|
||||
path: pack/*.zst
|
||||
- name: Lean stats
|
||||
run: |
|
||||
build/stage1/bin/lean --stats src/Lean.lean
|
||||
if: ${{ !matrix.cross }}
|
||||
- name: Test
|
||||
run: |
|
||||
cd build/stage1
|
||||
# exclude nonreproducible test
|
||||
ctest -j4 --output-on-failure -E leanlaketest_git ${{ matrix.CTEST_OPTIONS }} < /dev/null
|
||||
if: ${{ !matrix.cross }}
|
||||
ctest -j4 --output-on-failure --timeout 300 ${{ matrix.CTEST_OPTIONS }} < /dev/null
|
||||
- name: Check Test Binary
|
||||
run: ${{ matrix.binary-check }} tests/compiler/534.lean.out
|
||||
if: ${{ !matrix.cross }}
|
||||
- name: Build Stage 2
|
||||
run: |
|
||||
cd build
|
||||
@@ -217,8 +172,7 @@ jobs:
|
||||
run: |
|
||||
echo -1 | sudo tee /proc/sys/kernel/perf_event_paranoid
|
||||
export BUILD=$PWD/build PATH=$PWD/build/stage1/bin:$PATH
|
||||
cd tests/bench
|
||||
nix shell .#temci -c temci exec --config speedcenter.yaml --included_blocks fast --runs 1
|
||||
nix-shell -A with-temci --run "cd tests/bench; temci exec --config speedcenter.yaml --included_blocks fast --runs 1"
|
||||
if: matrix.test-speedcenter
|
||||
- name: Check rebootstrap
|
||||
run: |
|
||||
@@ -227,62 +181,40 @@ jobs:
|
||||
if: matrix.name == 'Linux'
|
||||
- name: CCache stats
|
||||
run: ccache -s
|
||||
|
||||
release:
|
||||
# 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@v2
|
||||
with:
|
||||
path: artifacts
|
||||
- name: Release
|
||||
uses: softprops/action-gh-release@v1
|
||||
if: ${{ startsWith(github.ref, 'refs/tags/v') && matrix.release }}
|
||||
with:
|
||||
files: artifacts/*/*
|
||||
fail_on_unmatched_files: true
|
||||
files: pack/*
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
release-nightly:
|
||||
needs: [set-nightly, build]
|
||||
if: needs.set-nightly.outputs.nightly
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
# needed for tagging
|
||||
fetch-depth: 0
|
||||
token: ${{ secrets.PUSH_NIGHTLY_TOKEN }}
|
||||
- uses: actions/download-artifact@v2
|
||||
with:
|
||||
path: artifacts
|
||||
- name: Prepare Nightly Release
|
||||
if: env.LEAN_VERSION_STRING
|
||||
run: |
|
||||
git remote add nightly https://foo:'${{ secrets.PUSH_NIGHTLY_TOKEN }}'@github.com/${{ github.repository_owner }}/lean4-nightly.git
|
||||
# can't push shallow checkout
|
||||
git fetch --unshallow
|
||||
git fetch nightly --tags
|
||||
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
|
||||
#./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
|
||||
if git tag $LEAN_VERSION_STRING && git push nightly $LEAN_VERSION_STRING; then
|
||||
last_tag=$(git describe HEAD^ --abbrev=0 --tags)
|
||||
echo -e "Changes since ${last_tag}:\n\n" > diff.md
|
||||
#git show $last_tag:doc/changes.md > old.md
|
||||
#./script/diff_changelogs.py old.md doc/changes.md >> diff.md
|
||||
echo -e "*Full commit log*\n" >> diff.md
|
||||
git log --oneline $last_tag..HEAD | sed 's/^/* /' >> diff.md
|
||||
else
|
||||
# make sure every runner is building the same commit
|
||||
[ $(git rev-parse HEAD) == $(git rev-parse $LEAN_VERSION_STRING) ] || exit 11
|
||||
echo -n > diff.md
|
||||
fi
|
||||
- name: Release Nightly
|
||||
uses: softprops/action-gh-release@v1
|
||||
# need unreleased version for fixed `repository`
|
||||
uses: Kha/action-gh-release@master
|
||||
if: env.LEAN_VERSION_STRING
|
||||
with:
|
||||
body_path: diff.md
|
||||
prerelease: true
|
||||
files: artifacts/*/*
|
||||
fail_on_unmatched_files: true
|
||||
tag_name: ${{ needs.set-nightly.outputs.nightly }}
|
||||
files: pack/*
|
||||
tag_name: ${{ env.LEAN_VERSION_STRING }}
|
||||
repository: ${{ github.repository_owner }}/lean4-nightly
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.PUSH_NIGHTLY_TOKEN }}
|
||||
|
||||
95
.github/workflows/nix-ci.yml
vendored
95
.github/workflows/nix-ci.yml
vendored
@@ -14,14 +14,18 @@ jobs:
|
||||
runs-on: ${{ matrix.os }}
|
||||
defaults:
|
||||
run:
|
||||
# Can't use `nix-shell` without configured nixpkgs path on macOS
|
||||
shell: nix -v --experimental-features "nix-command flakes" run .#ciShell -- bash -euxo pipefail {0}
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- name: Nix Linux
|
||||
- name: Linux
|
||||
os: ubuntu-latest
|
||||
#- name: Nix macOS
|
||||
# latest builds on https://hydra.nixos.org/jobset/nix/master/all at the time
|
||||
nix_url: https://hydra.nixos.org/build/135773533/download/1/nix-2.4pre20210125_36c4d6f-x86_64-linux.tar.xz
|
||||
#- name: macOS
|
||||
# os: macos-latest
|
||||
# nix_url: https://hydra.nixos.org/build/135773542/download/1/nix-2.4pre20210125_36c4d6f-x86_64-darwin.tar.xz
|
||||
# complete all jobs
|
||||
fail-fast: false
|
||||
name: ${{ matrix.name }}
|
||||
@@ -30,32 +34,39 @@ jobs:
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
# Install flakes-enabled Nix manually from Hydra since `install-nix-action` doesn't accept raw tarballs
|
||||
- name: Install Nix
|
||||
uses: cachix/install-nix-action@v15
|
||||
with:
|
||||
# https://github.com/NixOS/nix/issues/6572
|
||||
install_url: https://releases.nixos.org/nix/nix-2.7.0/install
|
||||
extra_nix_config: |
|
||||
shell: bash -euo pipefail {0}
|
||||
run: |
|
||||
curl ${{ matrix.nix_url }} | tar -xJ
|
||||
# Do a single-user install so actions/cache doesn't get confused about permissions
|
||||
nix-*/install --no-daemon --no-channel-add --darwin-use-unencrypted-nix-store-volume
|
||||
rm -rf nix-*
|
||||
# Call `install-nix-action` anyways to run its Actions-specific setup
|
||||
- name: Setup Nix
|
||||
uses: cachix/install-nix-action@v12
|
||||
- name: Fixup install-nix-action
|
||||
shell: bash -euo pipefail {0}
|
||||
run: |
|
||||
# the path set by install-nix-action is valid only for multi-user installations
|
||||
echo "NIX_SSL_CERT_FILE=$HOME/.nix-profile/etc/ssl/certs/ca-bundle.crt" > $GITHUB_ENV
|
||||
if: matrix.name == 'macOS'
|
||||
- name: Further setup Nix
|
||||
run: |
|
||||
mkdir -p ~/.config/nix
|
||||
echo '
|
||||
max-jobs = auto
|
||||
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@v2
|
||||
with:
|
||||
path: nix-store-cache
|
||||
key: ${{ matrix.name }}-nix-store-cache-${{ github.sha }}
|
||||
# fall back to (latest) previous cache
|
||||
restore-keys: |
|
||||
${{ matrix.name }}-nix-store-cache
|
||||
- name: Further Set Up Nix Cache
|
||||
shell: bash -euxo pipefail {0}
|
||||
run: |
|
||||
# Nix seems to mutate the cache, so make a copy
|
||||
cp -r nix-store-cache nix-store-cache-copy || true
|
||||
- name: Prepare CCache Cache
|
||||
shell: bash -euxo pipefail {0}
|
||||
run: |
|
||||
extra-substituters = file://${{ github.workspace }}/nix-store-cache?priority=10&trusted=true
|
||||
extra-trusted-substituters = https://lean4.cachix.org/
|
||||
extra-trusted-public-keys = lean4.cachix.org-1:mawtxSxcaiWE24xCXXgh3qnvlTkyU7evRRnGeAhD4Wk=' > ~/.config/nix/nix.conf
|
||||
sudo mkdir -m0770 -p /nix/var/cache/ccache
|
||||
sudo chown -R $USER /nix/var/cache/ccache
|
||||
# macOS standard chown doesn't support --reference
|
||||
nix shell .#nixpkgs.coreutils -c sudo chown --reference=/nix /nix/var/cache/ccache
|
||||
echo 'max_size = 50M' > /nix/var/cache/ccache/ccache.conf
|
||||
|
||||
# install & use Cachix manually since `cachix-action` pushes *all* derivations (incl. `$mod-deps`, stage 2&3, etc.)
|
||||
[ -z '${{ secrets.CACHIX_AUTH_TOKEN }}' ] || nix-env -iA cachix -f https://cachix.org/api/v1/install
|
||||
- name: Setup CCache Cache
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
@@ -64,32 +75,31 @@ jobs:
|
||||
# fall back to (latest) previous cache
|
||||
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@v10
|
||||
- name: Setup Nix Cache
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
name: lean4
|
||||
authToken: '${{ secrets.CACHIX_AUTH_TOKEN }}'
|
||||
skipPush: true # we push specific outputs only
|
||||
path: nix-store-cache
|
||||
key: ${{ matrix.name }}-nix-store-cache-${{ github.sha }}
|
||||
# fall back to (latest) previous cache
|
||||
restore-keys: |
|
||||
${{ matrix.name }}-nix-store-cache
|
||||
- name: Build
|
||||
run: |
|
||||
# .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
|
||||
nix build $NIX_BUILD_ARGS .#stage0 .#stage1.lean-all .#Lean.oTree -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} -o push-doc
|
||||
nix build $NIX_BUILD_ARGS --update-input lean --no-write-lock-file ./doc
|
||||
if: matrix.name == 'Nix Linux'
|
||||
nix build $NIX_BUILD_ARGS .#mdbook .#doc-test -o push-doc
|
||||
nix build $NIX_BUILD_ARGS .#doc
|
||||
if: matrix.name == 'Linux'
|
||||
- name: Push to Cachix
|
||||
run: |
|
||||
[ -z "${{ secrets.CACHIX_AUTH_TOKEN }}" ] || cachix push -j4 lean4 ./push-* || true
|
||||
[ -z "$CACHIX_AUTH_TOKEN" ] || cachix push -j4 lean4 ./push-* || true
|
||||
env:
|
||||
CACHIX_AUTH_TOKEN: '${{ secrets.CACHIX_AUTH_TOKEN }}'
|
||||
- name: Rebuild Nix Store Cache
|
||||
run: |
|
||||
rm -rf nix-store-cache || true
|
||||
@@ -100,9 +110,6 @@ jobs:
|
||||
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
|
||||
if: matrix.name == 'Linux' && github.ref == 'refs/heads/master' && github.event_name == 'push'
|
||||
- name: CCache stats
|
||||
run: CCACHE_DIR=/nix/var/cache/ccache nix run .#nixpkgs.ccache -- -s
|
||||
|
||||
@@ -3,15 +3,9 @@ This is the repository for **Lean 4**, which is currently being released as mile
|
||||
|
||||
# About
|
||||
|
||||
- [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://leanprover.github.io/lean4/doc/examples.html)
|
||||
- [FAQ](https://leanprover.github.io/lean4/doc/faq.html)
|
||||
|
||||
# Installation
|
||||
|
||||
1008
RELEASES.md
1008
RELEASES.md
File diff suppressed because it is too large
Load Diff
@@ -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
|
||||
|
||||
@@ -2,44 +2,32 @@
|
||||
|
||||
- [What is Lean](./whatIsLean.md)
|
||||
- [Tour of Lean](./tour.md)
|
||||
- [Setting Up Lean](./quickstart.md)
|
||||
- [Extended Setup Notes](./setup.md)
|
||||
- [Theorem Proving in Lean](./tpil.md)
|
||||
- [Functional Programming in Lean](fplean.md)
|
||||
- [Examples](./examples.md)
|
||||
- [Palindromes](examples/palindromes.lean.md)
|
||||
- [Binary Search Trees](examples/bintree.lean.md)
|
||||
- [A Certified Type Checker](examples/tc.lean.md)
|
||||
- [The Well-Typed Interpreter](examples/interp.lean.md)
|
||||
- [Dependent de Bruijn Indices](examples/deBruijn.lean.md)
|
||||
- [Parametric Higher-Order Abstract Syntax](examples/phoas.lean.md)
|
||||
- [Setting Up Lean](./setup.md)
|
||||
- [Quickstart](./quickstart.md)
|
||||
|
||||
# Language Manual
|
||||
<!-- - [Using Lean](./using_lean.md) -->
|
||||
<!-- - [Lexical Structure](./lexical_structure.md) -->
|
||||
<!-- - [Expressions](./expressions.md) -->
|
||||
<!-- - [Declarations](./declarations.md) -->
|
||||
- [Using Lean](./using_lean.md)
|
||||
- [Lexical Structure](./lexical_structure.md)
|
||||
- [Expressions](./expressions.md)
|
||||
- [Declarations](./declarations.md)
|
||||
- [Organizational features](./organization.md)
|
||||
- [Sections](./sections.md)
|
||||
- [Namespaces](./namespaces.md)
|
||||
- [Implicit Arguments](./implicit.md)
|
||||
- [Auto Bound Implicit Arguments](./autobound.md)
|
||||
<!-- - [Dependent Types](./deptypes.md) -->
|
||||
<!-- - [Simple Type Theory](./simptypes.md) -->
|
||||
<!-- - [Types as objects](./typeobjs.md) -->
|
||||
<!-- - [Function Abstraction and Evaluation](./funabst.md) -->
|
||||
<!-- - [Introducing Definitions](./introdef.md) -->
|
||||
<!-- - [What makes dependent type theory dependent?](./dep.md) -->
|
||||
<!-- - [Tactics](./tactics.md) -->
|
||||
- [Dependent Types](./deptypes.md)
|
||||
- [Simple Type Theory](./simptypes.md)
|
||||
- [Types as objects](./typeobjs.md)
|
||||
- [Function Abstraction and Evaluation](./funabst.md)
|
||||
- [Introducing Definitions](./introdef.md)
|
||||
- [What makes dependent type theory dependent?](./dep.md)
|
||||
- [Tactics](./tactics.md)
|
||||
- [Syntax Extensions](./syntax.md)
|
||||
- [The `do` Notation](./do.md)
|
||||
- [User-defined notation](./notation.md)
|
||||
- [String Interpolation](./stringinterp.md)
|
||||
- [User-Defined Notation](./notation.md)
|
||||
- [Macro Overview](./macro_overview.md)
|
||||
- [Elaborators](./elaborators.md)
|
||||
- [Examples](./syntax_examples.md)
|
||||
- [Balanced Parentheses](./syntax_example.md)
|
||||
- [Arithmetic DSL](./metaprogramming-arith.md)
|
||||
- [A Guided Example](./syntax_example.md)
|
||||
- [Declaring New Types](./decltypes.md)
|
||||
- [Enumerated Types](./enum.md)
|
||||
- [Inductive Types](./inductive.md)
|
||||
@@ -59,35 +47,27 @@
|
||||
- [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)
|
||||
- [Tactics](./tactics.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)
|
||||
|
||||
# Development
|
||||
|
||||
- [Development Guide](./dev/index.md)
|
||||
- [Commit Convention](./dev/commit_convention.md)
|
||||
- [Building Lean](./make/index.md)
|
||||
- [Ubuntu Setup](./make/ubuntu.md)
|
||||
- [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)
|
||||
- [Commit Convention](./dev/commit_convention.md)
|
||||
- [Building This Manual](./dev/mdbook.md)
|
||||
- [Foreign Function Interface](./dev/ffi.md)
|
||||
- [Unit Testing](./dev/testing.md)
|
||||
- [Building This Manual](./dev/mdbook.md)
|
||||
- [Fixing Tests](./dev/fixing_tests.md)
|
||||
- [Debugging](./dev/debugging.md)
|
||||
- [C++ Coding Style](./dev/cpp_coding_style.md)
|
||||
|
||||
@@ -1,786 +0,0 @@
|
||||
@charset "UTF-8";
|
||||
/*
|
||||
Copyright © 2019 Clément Pit-Claudel
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
*/
|
||||
|
||||
/*******************************/
|
||||
/* CSS reset for .alectryon-io */
|
||||
/*******************************/
|
||||
|
||||
.content {
|
||||
/*
|
||||
Use `initial` instead of `contents` to avoid a browser bug which removes
|
||||
the element from the accessibility tree.
|
||||
https://developer.mozilla.org/en-US/docs/Web/CSS/display#display_contents
|
||||
*/
|
||||
display: initial;
|
||||
}
|
||||
|
||||
.alectryon-io blockquote {
|
||||
line-height: inherit;
|
||||
}
|
||||
|
||||
.alectryon-io blockquote:after {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.alectryon-io label {
|
||||
display: inline;
|
||||
font-size: inherit;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.alectryon-io a {
|
||||
text-decoration: none !important;
|
||||
font-style: oblique !important;
|
||||
color: unset;
|
||||
}
|
||||
|
||||
/* Undo <small> and <blockquote>, added to improve RSS rendering. */
|
||||
|
||||
.alectryon-io small.alectryon-output,
|
||||
.alectryon-io small.alectryon-type-info {
|
||||
font-size: inherit;
|
||||
}
|
||||
|
||||
.alectryon-io blockquote.alectryon-goal,
|
||||
.alectryon-io blockquote.alectryon-message {
|
||||
font-weight: normal;
|
||||
font-size: inherit;
|
||||
}
|
||||
|
||||
/***************/
|
||||
/* Main styles */
|
||||
/***************/
|
||||
|
||||
.alectryon-coqdoc .doc .code,
|
||||
.alectryon-coqdoc .doc .comment,
|
||||
.alectryon-coqdoc .doc .inlinecode,
|
||||
.alectryon-mref,
|
||||
.alectryon-block, .alectryon-io,
|
||||
.alectryon-toggle-label, .alectryon-banner {
|
||||
font-family: "Source Code Pro", Consolas, "Ubuntu Mono", Menlo, "DejaVu Sans Mono", monospace, monospace !important;
|
||||
font-size: 0.875em;
|
||||
font-feature-settings: "COQX" 1 /* Coq ligatures */, "XV00" 1 /* Legacy */, "calt" 1 /* Fallback */;
|
||||
line-height: initial;
|
||||
}
|
||||
|
||||
.alectryon-io, .alectryon-block, .alectryon-toggle-label, .alectryon-banner {
|
||||
overflow: visible;
|
||||
overflow-wrap: break-word;
|
||||
position: relative;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
/*
|
||||
CoqIDE doesn't turn off the unicode bidirectional algorithm (and PG simply
|
||||
respects the user's `bidi-display-reordering` setting), so don't turn it off
|
||||
here either. But beware unexpected results like `Definition test_אב := 0.`
|
||||
|
||||
.alectryon-io span {
|
||||
direction: ltr;
|
||||
unicode-bidi: bidi-override;
|
||||
}
|
||||
|
||||
In any case, make an exception for comments:
|
||||
|
||||
.highlight .c {
|
||||
direction: embed;
|
||||
unicode-bidi: initial;
|
||||
}
|
||||
*/
|
||||
|
||||
.alectryon-mref,
|
||||
.alectryon-mref-marker {
|
||||
align-self: center;
|
||||
box-sizing: border-box;
|
||||
display: inline-block;
|
||||
font-size: 80%;
|
||||
font-weight: bold;
|
||||
line-height: 1;
|
||||
box-shadow: 0 0 0 1pt black;
|
||||
padding: 1pt 0.3em;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.alectryon-block .alectryon-mref-marker,
|
||||
.alectryon-io .alectryon-mref-marker {
|
||||
user-select: none;
|
||||
margin: -0.25em 0 -0.25em 0.5em;
|
||||
}
|
||||
|
||||
.alectryon-inline .alectryon-mref-marker {
|
||||
margin: -0.25em 0.15em -0.25em 0.625em; /* 625 = 0.5em / 80% */
|
||||
}
|
||||
|
||||
.alectryon-mref {
|
||||
color: inherit;
|
||||
margin: -0.5em 0.25em;
|
||||
}
|
||||
|
||||
.alectryon-goal:target .goal-separator .alectryon-mref-marker,
|
||||
:target > .alectryon-mref-marker {
|
||||
animation: blink 0.2s step-start 0s 3 normal none;
|
||||
background-color: #fcaf3e;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
@keyframes blink {
|
||||
50% {
|
||||
box-shadow: 0 0 0 3pt #fcaf3e, 0 0 0 4pt black;
|
||||
z-index: 10;
|
||||
}
|
||||
}
|
||||
|
||||
.alectryon-toggle,
|
||||
.alectryon-io .alectryon-extra-goal-toggle {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.alectryon-bubble,
|
||||
.alectryon-io label,
|
||||
.alectryon-toggle-label {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.alectryon-toggle-label {
|
||||
display: block;
|
||||
font-size: 0.8em;
|
||||
}
|
||||
|
||||
.alectryon-io .alectryon-input {
|
||||
padding: 0.1em 0; /* Enlarge the hitbox slightly to fill interline gaps */
|
||||
}
|
||||
|
||||
.alectryon-io .alectryon-token {
|
||||
white-space: pre-wrap;
|
||||
display: inline;
|
||||
}
|
||||
|
||||
.alectryon-io .alectryon-sentence.alectryon-target .alectryon-input {
|
||||
/* FIXME if keywords were ‘bolder’ we wouldn't need !important */
|
||||
font-weight: bold !important; /* Use !important to avoid a * selector */
|
||||
}
|
||||
|
||||
.alectryon-bubble:before,
|
||||
.alectryon-toggle-label:before,
|
||||
.alectryon-io label.alectryon-input:after,
|
||||
.alectryon-io .alectryon-goal > label:before {
|
||||
border: 1px solid #babdb6;
|
||||
border-radius: 1em;
|
||||
box-sizing: border-box;
|
||||
content: '';
|
||||
display: inline-block;
|
||||
font-weight: bold;
|
||||
height: 0.25em;
|
||||
margin-bottom: 0.15em;
|
||||
vertical-align: middle;
|
||||
width: 0.75em;
|
||||
}
|
||||
|
||||
.alectryon-toggle-label:before,
|
||||
.alectryon-io .alectryon-goal > label:before {
|
||||
margin-right: 0.25em;
|
||||
}
|
||||
|
||||
.alectryon-io .alectryon-goal > label:before {
|
||||
margin-top: 0.125em;
|
||||
}
|
||||
|
||||
.alectryon-io label.alectryon-input {
|
||||
padding-right: 1em; /* Prevent line wraps before the checkbox bubble */
|
||||
}
|
||||
|
||||
.alectryon-io label.alectryon-input:after {
|
||||
margin-left: 0.25em;
|
||||
margin-right: -1em; /* Compensate for the anti-wrapping space */
|
||||
}
|
||||
|
||||
.alectryon-failed {
|
||||
/* Underlines are broken in Chrome (they reset at each element boundary)… */
|
||||
/* text-decoration: red wavy underline; */
|
||||
/* … but it isn't too noticeable with dots */
|
||||
text-decoration: red dotted underline;
|
||||
text-decoration-skip-ink: none;
|
||||
/* Chrome prints background images in low resolution, yielding a blurry underline */
|
||||
/* background: bottom / 0.3em auto repeat-x url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyLjY0NiAxLjg1MiIgaGVpZ2h0PSI4IiB3aWR0aD0iMTAiPjxwYXRoIGQ9Ik0wIC4yNjVjLjc5NCAwIC41MyAxLjMyMiAxLjMyMyAxLjMyMi43OTQgMCAuNTMtMS4zMjIgMS4zMjMtMS4zMjIiIGZpbGw9Im5vbmUiIHN0cm9rZT0icmVkIiBzdHJva2Utd2lkdGg9Ii41MjkiLz48L3N2Zz4=); */
|
||||
}
|
||||
|
||||
/* Wrapping :hover rules in a media query ensures that tapping a Coq sentence
|
||||
doesn't trigger its :hover state (otherwise, on mobile, tapping a sentence to
|
||||
hide its output causes it to remain visible (its :hover state gets triggered.
|
||||
We only do it for the default style though, since other styles don't put the
|
||||
output over the main text, so showing too much is not an issue. */
|
||||
@media (any-hover: hover) {
|
||||
.alectryon-bubble:hover:before,
|
||||
.alectryon-toggle-label:hover:before,
|
||||
.alectryon-io label.alectryon-input:hover:after {
|
||||
background: #eeeeec;
|
||||
}
|
||||
|
||||
.alectryon-io label.alectryon-input:hover {
|
||||
text-decoration: underline dotted #babdb6;
|
||||
text-shadow: 0 0 1px rgb(46, 52, 54, 0.3); /* #2e3436 + opacity */
|
||||
}
|
||||
|
||||
.alectryon-io .alectryon-sentence:hover .alectryon-output,
|
||||
.alectryon-io .alectryon-token:hover .alectryon-type-info-wrapper,
|
||||
.alectryon-io .alectryon-token:hover .alectryon-type-info-wrapper {
|
||||
z-index: 2; /* Place hovered goals above .alectryon-sentence.alectryon-target ones */
|
||||
}
|
||||
}
|
||||
|
||||
.alectryon-toggle:checked + .alectryon-toggle-label:before,
|
||||
.alectryon-io .alectryon-sentence > .alectryon-toggle:checked + label.alectryon-input:after,
|
||||
.alectryon-io .alectryon-extra-goal-toggle:checked + .alectryon-goal > label:before {
|
||||
background-color: #babdb6;
|
||||
border-color: #babdb6;
|
||||
}
|
||||
|
||||
/* Disable clicks on sentences when the document-wide toggle is set. */
|
||||
.alectryon-toggle:checked + label + .alectryon-container label.alectryon-input {
|
||||
cursor: unset;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
/* Hide individual checkboxes when the document-wide toggle is set. */
|
||||
.alectryon-toggle:checked + label + .alectryon-container label.alectryon-input:after {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* .alectryon-output is displayed by toggles, :hover, and .alectryon-target rules */
|
||||
.alectryon-io .alectryon-output {
|
||||
box-sizing: border-box;
|
||||
display: none;
|
||||
left: 0;
|
||||
right: 0;
|
||||
position: absolute;
|
||||
padding: 0.25em 0;
|
||||
overflow: visible; /* Let box-shadows overflow */
|
||||
z-index: 1; /* Default to an index lower than that used by :hover */
|
||||
}
|
||||
|
||||
.alectryon-io .alectryon-type-info-wrapper {
|
||||
position: absolute;
|
||||
display: inline-block;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.alectryon-io .alectryon-type-info-wrapper.full-width {
|
||||
left: 0;
|
||||
min-width: 100%;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.alectryon-io .alectryon-type-info .goal-separator {
|
||||
height: unset;
|
||||
margin-top: 0em;
|
||||
}
|
||||
|
||||
.alectryon-io .alectryon-type-info-wrapper .alectryon-type-info {
|
||||
box-sizing: border-box;
|
||||
bottom: 100%;
|
||||
position: absolute;
|
||||
/*padding: 0.25em 0;*/
|
||||
visibility: hidden;
|
||||
overflow: visible; /* Let box-shadows overflow */
|
||||
z-index: 1; /* Default to an index lower than that used by :hover */
|
||||
white-space: pre-wrap !important;
|
||||
}
|
||||
|
||||
.alectryon-io .alectryon-type-info-wrapper .alectryon-type-info .alectryon-goal.alectryon-docstring {
|
||||
white-space: pre-wrap !important;
|
||||
}
|
||||
|
||||
@media (any-hover: hover) { /* See note above about this @media query */
|
||||
.alectryon-io .alectryon-sentence:hover .alectryon-output:not(:hover) {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.alectryon-io.output-hidden .alectryon-sentence:hover .alectryon-output:not(:hover) {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.alectryon-io.type-info-hidden .alectryon-token:hover .alectryon-type-info-wrapper .alectryon-type-info,
|
||||
.alectryon-io.type-info-hidden .alectryon-token:hover .alectryon-type-info-wrapper .alectryon-type-info {
|
||||
/*visibility: hidden !important;*/
|
||||
}
|
||||
|
||||
.alectryon-io .alectryon-token:hover .alectryon-type-info-wrapper .alectryon-type-info,
|
||||
.alectryon-io .alectryon-token:hover .alectryon-type-info-wrapper .alectryon-type-info {
|
||||
visibility: visible;
|
||||
transition-delay: 0.5s;
|
||||
}
|
||||
}
|
||||
|
||||
.alectryon-io .alectryon-sentence.alectryon-target .alectryon-output {
|
||||
display: block;
|
||||
}
|
||||
|
||||
/* Indicate active (hovered or targeted) goals with a shadow. */
|
||||
.alectryon-io .alectryon-sentence:hover .alectryon-output:not(:hover) .alectryon-messages,
|
||||
.alectryon-io .alectryon-sentence.alectryon-target .alectryon-output .alectryon-messages,
|
||||
.alectryon-io .alectryon-sentence:hover .alectryon-output:not(:hover) .alectryon-goals,
|
||||
.alectryon-io .alectryon-sentence.alectryon-target .alectryon-output .alectryon-goals,
|
||||
.alectryon-io .alectryon-token:hover .alectryon-type-info-wrapper .alectryon-type-info {
|
||||
box-shadow: 2px 2px 2px gray;
|
||||
}
|
||||
|
||||
.alectryon-io .alectryon-extra-goals .alectryon-goal .goal-hyps {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.alectryon-io .alectryon-extra-goals .alectryon-extra-goal-toggle:not(:checked) + .alectryon-goal label.goal-separator hr {
|
||||
/* Dashes indicate that the hypotheses are hidden */
|
||||
border-top-style: dashed;
|
||||
}
|
||||
|
||||
|
||||
/* Show just a small preview of the other goals; this is undone by the
|
||||
"extra-goal" toggle and by :hover and .alectryon-target in windowed mode. */
|
||||
.alectryon-io .alectryon-extra-goals .alectryon-goal .goal-conclusion {
|
||||
max-height: 5.2em;
|
||||
overflow-y: auto;
|
||||
/* Combining ‘overflow-y: auto’ with ‘display: inline-block’ causes extra space
|
||||
to be added below the box. ‘vertical-align: middle’ gets rid of it. */
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.alectryon-io .alectryon-goals,
|
||||
.alectryon-io .alectryon-messages {
|
||||
background: #f6f7f6;
|
||||
/*border: thin solid #d3d7cf; /* Convenient when pre's background is already #EEE */
|
||||
display: block;
|
||||
padding: 0.25em;
|
||||
}
|
||||
|
||||
.alectryon-message::before {
|
||||
content: '';
|
||||
float: right;
|
||||
/* etc/svg/square-bubble-xl.svg */
|
||||
background: url("data:image/svg+xml,%3Csvg width='14' height='14' viewBox='0 0 3.704 3.704' xmlns='http://www.w3.org/2000/svg'%3E%3Cg fill-rule='evenodd' stroke='%23000' stroke-width='.264'%3E%3Cpath d='M.794.934h2.115M.794 1.463h1.455M.794 1.992h1.852'/%3E%3C/g%3E%3Cpath d='M.132.14v2.646h.794v.661l.926-.661h1.72V.14z' fill='none' stroke='%23000' stroke-width='.265'/%3E%3C/svg%3E") top right no-repeat;
|
||||
height: 14px;
|
||||
width: 14px;
|
||||
}
|
||||
|
||||
.alectryon-toggle:checked + label + .alectryon-container {
|
||||
width: unset;
|
||||
}
|
||||
|
||||
/* Show goals when a toggle is set */
|
||||
.alectryon-toggle:checked + label + .alectryon-container label.alectryon-input + .alectryon-output,
|
||||
.alectryon-io .alectryon-sentence > .alectryon-toggle:checked ~ .alectryon-output {
|
||||
display: block;
|
||||
position: static;
|
||||
width: unset;
|
||||
background: unset; /* Override the backgrounds set in floating in windowed mode */
|
||||
padding: 0.25em 0; /* Re-assert so that later :hover rules don't override this padding */
|
||||
}
|
||||
|
||||
.alectryon-toggle:checked + label + .alectryon-container label.alectryon-input + .alectryon-output .goal-hyps,
|
||||
.alectryon-io .alectryon-sentence > .alectryon-toggle:checked ~ .alectryon-output .goal-hyps {
|
||||
/* Overridden back in windowed style */
|
||||
flex-flow: row wrap;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
.alectryon-toggle:checked + label + .alectryon-container .alectryon-sentence .alectryon-output > div,
|
||||
.alectryon-io .alectryon-sentence > .alectryon-toggle:checked ~ .alectryon-output > div {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.alectryon-io .alectryon-extra-goal-toggle:checked + .alectryon-goal .goal-hyps {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.alectryon-io .alectryon-extra-goal-toggle:checked + .alectryon-goal .goal-conclusion {
|
||||
max-height: unset;
|
||||
overflow-y: unset;
|
||||
}
|
||||
|
||||
.alectryon-toggle:checked + label + .alectryon-container .alectryon-sentence > .alectryon-toggle ~ .alectryon-wsp,
|
||||
.alectryon-io .alectryon-sentence > .alectryon-toggle:checked ~ .alectryon-wsp {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.alectryon-io .alectryon-messages,
|
||||
.alectryon-io .alectryon-message,
|
||||
.alectryon-io .alectryon-goals,
|
||||
.alectryon-io .alectryon-goal,
|
||||
.alectryon-io .goal-hyps > span,
|
||||
.alectryon-io .goal-conclusion {
|
||||
border-radius: 0.15em;
|
||||
}
|
||||
|
||||
.alectryon-io .alectryon-goal,
|
||||
.alectryon-io .alectryon-message {
|
||||
align-items: center;
|
||||
background: #f6f7f6;
|
||||
border: 0em;
|
||||
display: block;
|
||||
flex-direction: column;
|
||||
margin: 0.25em;
|
||||
padding: 0.5em;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.alectryon-io .goal-hyps {
|
||||
align-content: space-around;
|
||||
align-items: baseline;
|
||||
display: flex;
|
||||
flex-flow: column nowrap; /* re-stated in windowed mode */
|
||||
justify-content: space-around;
|
||||
/* LATER use a ‘gap’ property instead of margins once supported */
|
||||
margin: -0.15em -0.25em; /* -0.15em to cancel the item spacing */
|
||||
padding-bottom: 0.35em; /* 0.5em-0.15em to cancel the 0.5em of .goal-separator */
|
||||
}
|
||||
|
||||
.alectryon-io .goal-hyps > br {
|
||||
display: none; /* Only for RSS readers */
|
||||
}
|
||||
|
||||
.alectryon-io .goal-hyps > span,
|
||||
.alectryon-io .goal-conclusion {
|
||||
/*background: #eeeeec;*/
|
||||
display: inline-block;
|
||||
padding: 0.15em 0.35em;
|
||||
}
|
||||
|
||||
.alectryon-io .goal-hyps > span {
|
||||
align-items: baseline;
|
||||
display: inline-flex;
|
||||
margin: 0.15em 0.25em;
|
||||
}
|
||||
|
||||
.alectryon-block var,
|
||||
.alectryon-inline var,
|
||||
.alectryon-io .goal-hyps > span > var {
|
||||
font-weight: 600;
|
||||
font-style: unset;
|
||||
}
|
||||
|
||||
.alectryon-io .goal-hyps > span > var {
|
||||
/* Shrink the list of names, but let it grow as long as space is available. */
|
||||
flex-basis: min-content;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.alectryon-io .goal-hyps > span b {
|
||||
font-weight: 600;
|
||||
margin: 0 0 0 0.5em;
|
||||
white-space: pre;
|
||||
}
|
||||
|
||||
.alectryon-io .hyp-body,
|
||||
.alectryon-io .hyp-type {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
}
|
||||
|
||||
.alectryon-io .goal-separator {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
height: 1em; /* Fixed height to ignore goal name and markers */
|
||||
margin-top: -0.5em; /* Compensated in .goal-hyps when shown */
|
||||
}
|
||||
|
||||
.alectryon-io .goal-separator hr {
|
||||
border: none;
|
||||
border-top: thin solid #555753;
|
||||
display: block;
|
||||
flex-grow: 1;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.alectryon-io .goal-separator .goal-name {
|
||||
font-size: 0.75em;
|
||||
margin-left: 0.5em;
|
||||
}
|
||||
|
||||
/**********/
|
||||
/* Banner */
|
||||
/**********/
|
||||
|
||||
.alectryon-banner {
|
||||
background: #eeeeec;
|
||||
border: 1px solid #babcbd;
|
||||
font-size: 0.75em;
|
||||
padding: 0.25em;
|
||||
text-align: center;
|
||||
margin: 1em 0;
|
||||
}
|
||||
|
||||
.alectryon-banner a {
|
||||
cursor: pointer;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.alectryon-banner kbd {
|
||||
background: #d3d7cf;
|
||||
border-radius: 0.15em;
|
||||
border: 1px solid #babdb6;
|
||||
box-sizing: border-box;
|
||||
display: inline-block;
|
||||
font-family: inherit;
|
||||
font-size: 0.9em;
|
||||
height: 1.3em;
|
||||
line-height: 1.2em;
|
||||
margin: -0.25em 0;
|
||||
padding: 0 0.25em;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
/**********/
|
||||
/* Toggle */
|
||||
/**********/
|
||||
|
||||
.alectryon-toggle-label {
|
||||
margin: 1rem 0;
|
||||
}
|
||||
|
||||
/******************/
|
||||
/* Floating style */
|
||||
/******************/
|
||||
|
||||
/* If there's space, display goals to the right of the code, not below it. */
|
||||
@media (min-width: 80rem) {
|
||||
/* Unlike the windowed case, we don't want to move output blocks to the side
|
||||
when they are both :checked and -targeted, since it gets confusing as
|
||||
things jump around; hence the commented-output part of the selector,
|
||||
which would otherwise increase specificity */
|
||||
.alectryon-floating .alectryon-sentence.alectryon-target /* > .alectryon-toggle ~ */ .alectryon-output,
|
||||
.alectryon-floating .alectryon-sentence:hover .alectryon-output {
|
||||
top: 0;
|
||||
left: 100%;
|
||||
right: -100%;
|
||||
padding: 0 0.5em;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.alectryon-floating .alectryon-output {
|
||||
min-height: 100%;
|
||||
}
|
||||
|
||||
.alectryon-floating .alectryon-sentence:hover .alectryon-output {
|
||||
background: white; /* Ensure that short goals hide long ones */
|
||||
}
|
||||
|
||||
/* This odd margin-bottom property prevents the sticky div from bumping
|
||||
against the bottom of its container (.alectryon-output). The alternative
|
||||
would be enlarging .alectryon-output, but that would cause overflows,
|
||||
enlarging scrollbars and yielding scrolling towards the bottom of the
|
||||
page. Doing things this way instead makes it possible to restrict
|
||||
.alectryon-output to a reasonable size (100%, through top = bottom = 0).
|
||||
See also https://stackoverflow.com/questions/43909940/. */
|
||||
/* See note on specificity above */
|
||||
.alectryon-floating .alectryon-sentence.alectryon-target /* > .alectryon-toggle ~ */ .alectryon-output > div,
|
||||
.alectryon-floating .alectryon-sentence:hover .alectryon-output > div {
|
||||
margin-bottom: -200%;
|
||||
position: sticky;
|
||||
top: 0;
|
||||
}
|
||||
|
||||
.alectryon-floating .alectryon-toggle:checked + label + .alectryon-container .alectryon-sentence .alectryon-output > div,
|
||||
.alectryon-floating .alectryon-io .alectryon-sentence > .alectryon-toggle:checked ~ .alectryon-output > div {
|
||||
margin-bottom: unset; /* Undo the margin */
|
||||
}
|
||||
|
||||
/* Float underneath the current fragment
|
||||
@media (max-width: 80rem) {
|
||||
.alectryon-floating .alectryon-output {
|
||||
top: 100%;
|
||||
}
|
||||
} */
|
||||
}
|
||||
|
||||
/********************/
|
||||
/* Multi-pane style */
|
||||
/********************/
|
||||
|
||||
.alectryon-windowed {
|
||||
border: 0 solid #2e3436;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.alectryon-windowed .alectryon-sentence:hover .alectryon-output {
|
||||
background: white; /* Ensure that short goals hide long ones */
|
||||
}
|
||||
|
||||
.alectryon-windowed .alectryon-output {
|
||||
position: fixed; /* Overwritten by the ‘:checked’ rules */
|
||||
}
|
||||
|
||||
/* See note about specificity below */
|
||||
.alectryon-windowed .alectryon-sentence:hover .alectryon-output,
|
||||
.alectryon-windowed .alectryon-sentence.alectryon-target > .alectryon-toggle ~ .alectryon-output {
|
||||
padding: 0.5em;
|
||||
overflow-y: auto; /* Windowed contents may need to scroll */
|
||||
}
|
||||
|
||||
.alectryon-windowed .alectryon-io .alectryon-sentence:hover .alectryon-output:not(:hover) .alectryon-messages,
|
||||
.alectryon-windowed .alectryon-io .alectryon-sentence.alectryon-target .alectryon-output .alectryon-messages,
|
||||
.alectryon-windowed .alectryon-io .alectryon-sentence:hover .alectryon-output:not(:hover) .alectryon-goals,
|
||||
.alectryon-windowed .alectryon-io .alectryon-sentence.alectryon-target .alectryon-output .alectryon-goals {
|
||||
box-shadow: none; /* A shadow is unnecessary here and incompatible with overflow-y set to auto */
|
||||
}
|
||||
|
||||
.alectryon-windowed .alectryon-io .alectryon-sentence.alectryon-target .alectryon-output .goal-hyps {
|
||||
/* Restated to override the :checked style */
|
||||
flex-flow: column nowrap;
|
||||
justify-content: space-around;
|
||||
}
|
||||
|
||||
|
||||
.alectryon-windowed .alectryon-sentence.alectryon-target .alectryon-extra-goals .alectryon-goal .goal-conclusion
|
||||
/* Like .alectryon-io .alectryon-extra-goal-toggle:checked + .alectryon-goal .goal-conclusion */ {
|
||||
max-height: unset;
|
||||
overflow-y: unset;
|
||||
}
|
||||
|
||||
.alectryon-windowed .alectryon-output > div {
|
||||
display: flex; /* Put messages after goals */
|
||||
flex-direction: column-reverse;
|
||||
}
|
||||
|
||||
/*********************/
|
||||
/* Standalone styles */
|
||||
/*********************/
|
||||
|
||||
.alectryon-standalone {
|
||||
font-family: 'IBM Plex Serif', 'PT Serif', 'Merriweather', 'DejaVu Serif', serif;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
@media screen and (min-width: 50rem) {
|
||||
html.alectryon-standalone {
|
||||
/* Prevent flickering when hovering a block causes scrollbars to appear. */
|
||||
margin-left: calc(100vw - 100%);
|
||||
margin-right: 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* Coqdoc */
|
||||
|
||||
.alectryon-coqdoc .doc .code,
|
||||
.alectryon-coqdoc .doc .inlinecode,
|
||||
.alectryon-coqdoc .doc .comment {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
.alectryon-coqdoc .doc .comment {
|
||||
color: #eeeeec;
|
||||
}
|
||||
|
||||
.alectryon-coqdoc .doc .paragraph {
|
||||
height: 0.75em;
|
||||
}
|
||||
|
||||
/* Centered, Floating */
|
||||
|
||||
.alectryon-standalone .alectryon-centered,
|
||||
.alectryon-standalone .alectryon-floating {
|
||||
max-width: 50rem;
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
@media (min-width: 80rem) {
|
||||
.alectryon-standalone .alectryon-floating {
|
||||
max-width: 80rem;
|
||||
}
|
||||
|
||||
.alectryon-standalone .alectryon-floating > * {
|
||||
width: 50%;
|
||||
margin-left: 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* Windowed */
|
||||
|
||||
.alectryon-standalone .alectryon-windowed {
|
||||
display: block;
|
||||
margin: 0;
|
||||
overflow-y: auto;
|
||||
position: absolute;
|
||||
padding: 0 1em;
|
||||
}
|
||||
|
||||
.alectryon-standalone .alectryon-windowed > * {
|
||||
/* Override properties of docutils_basic.css */
|
||||
margin-left: 0;
|
||||
max-width: unset;
|
||||
}
|
||||
|
||||
.alectryon-standalone .alectryon-windowed .alectryon-io {
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* No need to predicate the ‘:hover’ rules below on ‘:not(:checked)’, since ‘left’,
|
||||
‘right’, ‘top’, and ‘bottom’ will be inactived by the :checked rules setting
|
||||
‘position’ to ‘static’ */
|
||||
|
||||
|
||||
/* Specificity: We want the output to stay inline when hovered while unfolded
|
||||
(:checked), but we want it to move when it's targeted (i.e. when the user
|
||||
is browsing goals one by one using the keyboard, in which case we want to
|
||||
goals to appear in consistent locations). The selectors below ensure
|
||||
that :hover < :checked < -targeted in terms of specificity. */
|
||||
/* LATER: Reimplement this stuff with CSS variables */
|
||||
.alectryon-windowed .alectryon-sentence.alectryon-target > .alectryon-toggle ~ .alectryon-output {
|
||||
position: fixed;
|
||||
}
|
||||
|
||||
@media screen and (min-width: 60rem) {
|
||||
.alectryon-standalone .alectryon-windowed {
|
||||
border-right-width: thin;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 50%;
|
||||
top: 0;
|
||||
}
|
||||
|
||||
.alectryon-standalone .alectryon-windowed .alectryon-sentence:hover .alectryon-output,
|
||||
.alectryon-standalone .alectryon-windowed .alectryon-sentence.alectryon-target .alectryon-output {
|
||||
bottom: 0;
|
||||
left: 50%;
|
||||
right: 0;
|
||||
top: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 60rem) {
|
||||
.alectryon-standalone .alectryon-windowed {
|
||||
border-bottom-width: 1px;
|
||||
bottom: 40%;
|
||||
left: 0;
|
||||
right: 0;
|
||||
top: 0;
|
||||
}
|
||||
|
||||
.alectryon-standalone .alectryon-windowed .alectryon-sentence:hover .alectryon-output,
|
||||
.alectryon-standalone .alectryon-windowed .alectryon-sentence.alectryon-target .alectryon-output {
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
top: 60%;
|
||||
}
|
||||
}
|
||||
190
doc/alectryon.js
190
doc/alectryon.js
@@ -1,190 +0,0 @@
|
||||
var Alectryon;
|
||||
(function(Alectryon) {
|
||||
(function (slideshow) {
|
||||
function anchor(sentence) { return "#" + sentence.id; }
|
||||
|
||||
function current_sentence() { return slideshow.sentences[slideshow.pos]; }
|
||||
|
||||
function unhighlight() {
|
||||
var sentence = current_sentence();
|
||||
if (sentence) sentence.classList.remove("alectryon-target");
|
||||
slideshow.pos = -1;
|
||||
}
|
||||
|
||||
function highlight(sentence) {
|
||||
sentence.classList.add("alectryon-target");
|
||||
}
|
||||
|
||||
function scroll(sentence) {
|
||||
// Put the top of the current fragment close to the top of the
|
||||
// screen, but scroll it out of view if showing it requires pushing
|
||||
// the sentence past half of the screen. If sentence is already in
|
||||
// a reasonable position, don't move.
|
||||
var parent = sentence.parentElement;
|
||||
/* We want to scroll the whole document, so start at root… */
|
||||
while (parent && !parent.classList.contains("alectryon-root"))
|
||||
parent = parent.parentElement;
|
||||
/* … and work up from there to find a scrollable element.
|
||||
parent.scrollHeight can be greater than parent.clientHeight
|
||||
without showing scrollbars, so we add a 10px buffer. */
|
||||
while (parent && parent.scrollHeight <= parent.clientHeight + 10)
|
||||
parent = parent.parentElement;
|
||||
/* <body> and <html> elements can have their client rect overflow
|
||||
* the window if their height is unset, so scroll the window
|
||||
* instead */
|
||||
if (parent && (parent.nodeName == "BODY" || parent.nodeName == "HTML"))
|
||||
parent = null;
|
||||
|
||||
var rect = function(e) { return e.getBoundingClientRect(); };
|
||||
var parent_box = parent ? rect(parent) : { y: 0, height: window.innerHeight },
|
||||
sentence_y = rect(sentence).y - parent_box.y,
|
||||
fragment_y = rect(sentence.parentElement).y - parent_box.y;
|
||||
|
||||
// The assertion below sometimes fails for the first element in a block.
|
||||
// console.assert(sentence_y >= fragment_y);
|
||||
|
||||
if (sentence_y < 0.1 * parent_box.height ||
|
||||
sentence_y > 0.7 * parent_box.height) {
|
||||
(parent || window).scrollBy(
|
||||
0, Math.max(sentence_y - 0.5 * parent_box.height,
|
||||
fragment_y - 0.1 * parent_box.height));
|
||||
}
|
||||
}
|
||||
|
||||
function highlighted(pos) {
|
||||
return slideshow.pos == pos;
|
||||
}
|
||||
|
||||
function navigate(pos, inhibitScroll) {
|
||||
unhighlight();
|
||||
slideshow.pos = Math.min(Math.max(pos, 0), slideshow.sentences.length - 1);
|
||||
var sentence = current_sentence();
|
||||
highlight(sentence);
|
||||
if (!inhibitScroll)
|
||||
scroll(sentence);
|
||||
}
|
||||
|
||||
var keys = {
|
||||
PAGE_UP: 33,
|
||||
PAGE_DOWN: 34,
|
||||
ARROW_UP: 38,
|
||||
ARROW_DOWN: 40,
|
||||
h: 72, l: 76, p: 80, n: 78
|
||||
};
|
||||
|
||||
function onkeydown(e) {
|
||||
e = e || window.event;
|
||||
if (e.ctrlKey || e.metaKey) {
|
||||
if (e.keyCode == keys.ARROW_UP)
|
||||
slideshow.previous();
|
||||
else if (e.keyCode == keys.ARROW_DOWN)
|
||||
slideshow.next();
|
||||
else
|
||||
return;
|
||||
} else {
|
||||
// if (e.keyCode == keys.PAGE_UP || e.keyCode == keys.p || e.keyCode == keys.h)
|
||||
// slideshow.previous();
|
||||
// else if (e.keyCode == keys.PAGE_DOWN || e.keyCode == keys.n || e.keyCode == keys.l)
|
||||
// slideshow.next();
|
||||
// else
|
||||
return;
|
||||
}
|
||||
e.preventDefault();
|
||||
}
|
||||
|
||||
function start() {
|
||||
slideshow.navigate(0);
|
||||
}
|
||||
|
||||
function toggleHighlight(idx) {
|
||||
if (highlighted(idx))
|
||||
unhighlight();
|
||||
else
|
||||
navigate(idx, true);
|
||||
}
|
||||
|
||||
function handleClick(evt) {
|
||||
if (evt.ctrlKey || evt.metaKey) {
|
||||
var sentence = evt.currentTarget;
|
||||
|
||||
// Ensure that the goal is shown on the side, not inline
|
||||
var checkbox = sentence.getElementsByClassName("alectryon-toggle")[0];
|
||||
if (checkbox)
|
||||
checkbox.checked = false;
|
||||
|
||||
toggleHighlight(sentence.alectryon_index);
|
||||
evt.preventDefault();
|
||||
}
|
||||
}
|
||||
|
||||
function init() {
|
||||
document.onkeydown = onkeydown;
|
||||
slideshow.pos = -1;
|
||||
slideshow.sentences = Array.from(document.getElementsByClassName("alectryon-sentence"));
|
||||
slideshow.sentences.forEach(function (s, idx) {
|
||||
s.addEventListener('click', handleClick, false);
|
||||
s.alectryon_index = idx;
|
||||
});
|
||||
}
|
||||
|
||||
slideshow.start = start;
|
||||
slideshow.end = unhighlight;
|
||||
slideshow.navigate = navigate;
|
||||
slideshow.next = function() { navigate(slideshow.pos + 1); };
|
||||
slideshow.previous = function() { navigate(slideshow.pos + -1); };
|
||||
window.addEventListener('DOMContentLoaded', init);
|
||||
})(Alectryon.slideshow || (Alectryon.slideshow = {}));
|
||||
|
||||
(function (styles) {
|
||||
var styleNames = ["centered", "floating", "windowed"];
|
||||
|
||||
function className(style) {
|
||||
return "alectryon-" + style;
|
||||
}
|
||||
|
||||
function setStyle(style) {
|
||||
var root = document.getElementsByClassName("alectryon-root")[0];
|
||||
styleNames.forEach(function (s) {
|
||||
root.classList.remove(className(s)); });
|
||||
root.classList.add(className(style));
|
||||
}
|
||||
|
||||
function init() {
|
||||
var banner = document.getElementsByClassName("alectryon-banner")[0];
|
||||
if (banner) {
|
||||
banner.append(" Style: ");
|
||||
styleNames.forEach(function (styleName, idx) {
|
||||
var s = styleName;
|
||||
var a = document.createElement("a");
|
||||
a.onclick = function() { setStyle(s); };
|
||||
a.append(styleName);
|
||||
if (idx > 0) banner.append("; ");
|
||||
banner.appendChild(a);
|
||||
});
|
||||
banner.append(".");
|
||||
}
|
||||
}
|
||||
|
||||
window.addEventListener('DOMContentLoaded', init);
|
||||
|
||||
styles.setStyle = setStyle;
|
||||
})(Alectryon.styles || (Alectryon.styles = {}));
|
||||
})(Alectryon || (Alectryon = {}));
|
||||
|
||||
function setHidden(elements, isVisible, token) {
|
||||
for (let i = 0; i < elements.length; i++) {
|
||||
if (isVisible) {
|
||||
elements[i].classList.remove(token)
|
||||
} else {
|
||||
elements[i].classList.add(token)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function toggleShowTypes(checkbox) {
|
||||
setHidden(document.getElementsByClassName("alectryon-io"), checkbox.checked, "type-info-hidden")
|
||||
}
|
||||
|
||||
function toggleShowGoals(checkbox) {
|
||||
setHidden(document.getElementsByClassName("alectryon-io"), checkbox.checked, "output-hidden")
|
||||
}
|
||||
33
doc/array.md
33
doc/array.md
@@ -39,17 +39,16 @@ To create an array of size `n` in which all the elements are initialized to some
|
||||
|
||||
You can access array elements by using brackets (`[` and `]`).
|
||||
```lean
|
||||
def f (a : Array Nat) (i : Fin a.size) :=
|
||||
a[i] + a[i]
|
||||
```
|
||||
Note that the index `i` has type `Fin a.size`, i.e., it is natural number less than `a.size`.
|
||||
You can also write
|
||||
```lean
|
||||
def f (a : Array Nat) (i : Nat) (h : i < a.size) :=
|
||||
a[i] + a[i]
|
||||
#eval #['a', 'b', 'c'][1]
|
||||
-- 'b'
|
||||
|
||||
def getThird (xs : Array Nat) : Nat :=
|
||||
xs[2]
|
||||
|
||||
#eval getThird #[10, 20, 30, 40]
|
||||
-- 30
|
||||
```
|
||||
The bracket operator is whitespace sensitive.
|
||||
|
||||
```lean
|
||||
def f (xs : List Nat) : List Nat :=
|
||||
xs ++ xs
|
||||
@@ -57,21 +56,7 @@ def f (xs : List Nat) : List Nat :=
|
||||
def as : Array Nat :=
|
||||
#[1, 2, 3, 4]
|
||||
|
||||
def idx : Fin 4 :=
|
||||
2
|
||||
|
||||
#eval f [1, 2, 3] -- This is a function application
|
||||
|
||||
#eval as[idx] -- This is an array access
|
||||
```
|
||||
The notation `a[i]` has two variants: `a[i]!` and `a[i]?`. In both cases, `i` has type `Nat`. The first one
|
||||
produces a panic error message if the index `i` is out of bounds. The latter returns an `Option` type.
|
||||
|
||||
```lean
|
||||
#eval #['a', 'b', 'c'][1]?
|
||||
-- some 'b'
|
||||
#eval #['a', 'b', 'c'][5]?
|
||||
-- none
|
||||
#eval #['a', 'b', 'c'][1]!
|
||||
-- 'b!
|
||||
#eval as[2] -- This is an array access
|
||||
```
|
||||
|
||||
@@ -36,9 +36,9 @@ Note that, Lean inferred a more general type using `Sort` instead of `Type`.
|
||||
|
||||
Although we love this feature and use it extensively when implementing Lean,
|
||||
we realize some users may feel uncomfortable with it. Thus, you can disable it using
|
||||
the command `set_option autoImplicit false`.
|
||||
the command `set_option autoBoundImplicitLocal false`.
|
||||
```lean
|
||||
set_option autoImplicit false
|
||||
set_option autoBoundImplicitLocal false
|
||||
/- The following definition produces `unknown identifier` errors -/
|
||||
-- def compose (g : β → γ) (f : α → β) (x : α) : γ :=
|
||||
-- g (f x)
|
||||
|
||||
@@ -10,12 +10,6 @@ build-dir = "out"
|
||||
|
||||
[output.html]
|
||||
git-repository-url = "https://github.com/leanprover/lean4"
|
||||
additional-css = ["alectryon.css", "pygments.css"]
|
||||
additional-js = ["alectryon.js"]
|
||||
|
||||
[output.html.fold]
|
||||
enable = true
|
||||
level = 0
|
||||
|
||||
[output.html.playground.boring-prefixes]
|
||||
lean = "# "
|
||||
|
||||
@@ -74,7 +74,7 @@ Lean provides ways of adding new objects to the environment. The following provi
|
||||
* ``axiom c : α`` : declare a constant named ``c`` of type ``α``, it is postulating that `α` is not an empty type.
|
||||
* ``def c : α := v`` : defines ``c`` to denote ``v``, which should have type ``α``.
|
||||
* ``theorem c : p := v`` : similar to ``def``, but intended to be used when ``p`` is a proposition.
|
||||
* ``opaque c : α (:= v)?`` : declares a opaque constant named ``c`` of type ``α``, the optional value `v` is must have type `α`
|
||||
* ``constant c : α (:= v)?`` : declares a opaque constant named ``c`` of type ``α``, the optional value `v` is must have type `α`
|
||||
and can be viewed as a certificate that ``α`` is not an empty type. If the value is not provided, Lean tries to find one
|
||||
using a proceture based on type class resolution. The value `v` is hidden from the type checker. You can assume that
|
||||
Lean "forgets" `v` after type checking this kind of declaration.
|
||||
@@ -89,8 +89,8 @@ Any of ``def``, ``theorem``, ``axiom``, or ``example`` can take a list of argume
|
||||
is interpreted as ``def foo : (a : α) → β := fun a : α => t``. Similarly, a theorem ``theorem foo (a : α) : p := t`` is interpreted as ``theorem foo : ∀ a : α, p := fun a : α => t``.
|
||||
|
||||
```lean
|
||||
opaque c : Nat
|
||||
opaque d : Nat
|
||||
constant c : Nat
|
||||
constant d : Nat
|
||||
axiom cd_eq : c = d
|
||||
|
||||
def foo : Nat := 5
|
||||
@@ -165,70 +165,70 @@ Below are some common examples of inductive types, many of which are defined in
|
||||
|
||||
.. code-block:: lean
|
||||
|
||||
namespace Hide
|
||||
universe u v
|
||||
namespace hide
|
||||
universes u v
|
||||
|
||||
-- BEGIN
|
||||
inductive Empty : Type
|
||||
inductive empty : Type
|
||||
|
||||
inductive Unit : Type
|
||||
| unit : Unit
|
||||
inductive unit : Type
|
||||
| star : unit
|
||||
|
||||
inductive Bool : Type
|
||||
| false : Bool
|
||||
| true : Bool
|
||||
inductive bool : Type
|
||||
| ff : bool
|
||||
| tt : bool
|
||||
|
||||
inductive Prod (α : Type u) (β : Type v) : Type (max u v)
|
||||
| mk : α → β → Prod α β
|
||||
inductive prod (α : Type u) (β : Type v) : Type (max u v)
|
||||
| mk : α → β → prod
|
||||
|
||||
inductive Sum (α : Type u) (β : Type v)
|
||||
| inl : α → Sum α β
|
||||
| inr : β → Sum α β
|
||||
inductive sum (α : Type u) (β : Type v)
|
||||
| inl : α → sum
|
||||
| inr : β → sum
|
||||
|
||||
inductive Sigma (α : Type u) (β : α → Type v)
|
||||
| mk : (a : α) → β a → Sigma α β
|
||||
inductive sigma (α : Type u) (β : α → Type v)
|
||||
| mk : Π a : α, β a → sigma
|
||||
|
||||
inductive false : Prop
|
||||
|
||||
inductive True : Prop
|
||||
| trivial : True
|
||||
inductive true : Prop
|
||||
| trivial : true
|
||||
|
||||
inductive And (p q : Prop) : Prop
|
||||
| intro : p → q → And p q
|
||||
inductive and (p q : Prop) : Prop
|
||||
| intro : p → q → and
|
||||
|
||||
inductive Or (p q : Prop) : Prop
|
||||
| inl : p → Or p q
|
||||
| inr : q → Or p q
|
||||
inductive or (p q : Prop) : Prop
|
||||
| inl : p → or
|
||||
| inr : q → or
|
||||
|
||||
inductive Exists (α : Type u) (p : α → Prop) : Prop
|
||||
| intro : ∀ x : α, p x → Exists α p
|
||||
| intro : ∀ x : α, p x → Exists
|
||||
|
||||
inductive Subtype (α : Type u) (p : α → Prop) : Type u
|
||||
| intro : ∀ x : α, p x → Subtype α p
|
||||
inductive subtype (α : Type u) (p : α → Prop) : Type u
|
||||
| intro : ∀ x : α, p x → subtype
|
||||
|
||||
inductive Nat : Type
|
||||
| zero : Nat
|
||||
| succ : Nat → Nat
|
||||
inductive nat : Type
|
||||
| zero : nat
|
||||
| succ : nat → nat
|
||||
|
||||
inductive List (α : Type u)
|
||||
| nil : List α
|
||||
| cons : α → List α → List α
|
||||
inductive list (α : Type u)
|
||||
| nil : list
|
||||
| cons : α → list → list
|
||||
|
||||
-- full binary tree with nodes and leaves labeled from α
|
||||
inductive BinTree (α : Type u)
|
||||
| leaf : α → BinTree α
|
||||
| node : BinTree α → α → BinTree α → BinTree α
|
||||
inductive bintree (α : Type u)
|
||||
| leaf : α → bintree
|
||||
| node : bintree → α → bintree → bintree
|
||||
|
||||
-- every internal node has subtrees indexed by Nat
|
||||
inductive CBT (α : Type u)
|
||||
| leaf : α → CBT α
|
||||
| node : (Nat → CBT α) → CBT α
|
||||
inductive cbt (α : Type u)
|
||||
| leaf : α → cbt
|
||||
| node : (Nat → cbt) → cbt
|
||||
-- END
|
||||
end hide
|
||||
|
||||
Note that in the syntax of the inductive definition ``Foo``, the context ``(a : α)`` is left implicit. In other words, constructors and recursive arguments are written as though they have return type ``Foo`` rather than ``Foo a``.
|
||||
Note that in the syntax of the inductive definition ``foo``, the context ``(a : α)`` is left implicit. In other words, constructors and recursive arguments are written as though they have return type ``foo`` rather than ``foo a``.
|
||||
|
||||
Elements of the context ``(a : α)`` can be marked implicit as described in [Implicit Arguments](#implicit.md#implicit_arguments). These annotations bear only on the type former, ``Foo``. Lean uses a heuristic to determine which arguments to the constructors should be marked implicit, namely, an argument is marked implicit if it can be inferred from the type of a subsequent argument. If the annotation ``{}`` appears after the constructor, a argument is marked implicit if it can be inferred from the type of a subsequent argument *or the return type*. For example, it is useful to let ``nil`` denote the empty list of any type, since the type can usually be inferred in the context in which it appears. These heuristics are imperfect, and you may sometimes wish to define your own constructors in terms of the default ones. In that case, use the ``[matchPattern]`` [attribute](TODO: missing link) to ensure that these will be used appropriately by the [Equation Compiler](#the-equation-compiler).
|
||||
Elements of the context ``(a : α)`` can be marked implicit as described in [Implicit Arguments](#implicit.md#implicit_arguments). These annotations bear only on the type former, ``foo``. Lean uses a heuristic to determine which arguments to the constructors should be marked implicit, namely, an argument is marked implicit if it can be inferred from the type of a subsequent argument. If the annotation ``{}`` appears after the constructor, a argument is marked implicit if it can be inferred from the type of a subsequent argument *or the return type*. For example, it is useful to let ``nil`` denote the empty list of any type, since the type can usually be inferred in the context in which it appears. These heuristics are imperfect, and you may sometimes wish to define your own constructors in terms of the default ones. In that case, use the ``[pattern]`` [attribute](TODO: missing link) to ensure that these will be used appropriately by the [Equation Compiler](#the-equation-compiler).
|
||||
|
||||
There are restrictions on the universe ``u`` in the return type ``Sort u`` of the type former. There are also restrictions on the universe ``u`` in the return type ``Sort u`` of the motive of the eliminator. These will be discussed in the next section in the more general setting of inductive families.
|
||||
|
||||
@@ -236,69 +236,69 @@ Lean allows some additional syntactic conveniences. You can omit the return type
|
||||
|
||||
.. code-block:: lean
|
||||
|
||||
namespace Hide
|
||||
namespace hide
|
||||
universe u
|
||||
|
||||
-- BEGIN
|
||||
inductive Weekday
|
||||
inductive weekday
|
||||
| sunday | monday | tuesday | wednesday
|
||||
| thursday | friday | saturday
|
||||
|
||||
inductive Nat
|
||||
inductive nat
|
||||
| zero
|
||||
| succ (n : Nat) : Nat
|
||||
| succ (n : nat) : nat
|
||||
|
||||
inductive List (α : Type u)
|
||||
| nil : List α
|
||||
| cons (a : α) (l : List α) : List α
|
||||
inductive list (α : Type u)
|
||||
| nil {} : list
|
||||
| cons (a : α) (l : list) : list
|
||||
|
||||
@[matchPattern]
|
||||
def List.nil' (α : Type u) : List α := List.nil
|
||||
@[pattern]
|
||||
def list.nil' (α : Type u) : list α := list.nil
|
||||
|
||||
def length {α : Type u} : List α → Nat
|
||||
| (List.nil' _) => 0
|
||||
| (List.cons a l) => 1 + length l
|
||||
def length {α : Type u} : list α → Nat
|
||||
| (list.nil' .(α)) := 0
|
||||
| (list.cons a l) := 1 + length l
|
||||
-- END
|
||||
|
||||
end Hide
|
||||
end hide
|
||||
|
||||
The type former, constructors, and eliminator are all part of Lean's axiomatic foundation, which is to say, they are part of the trusted kernel. In addition to these axiomatically declared constants, Lean automatically defines some additional objects in terms of these, and adds them to the environment. These include the following:
|
||||
|
||||
- ``Foo.recOn`` : a variant of the eliminator, in which the major premise comes first
|
||||
- ``Foo.casesOn`` : a restricted version of the eliminator which omits any recursive calls
|
||||
- ``Foo.noConfusionType``, ``Foo.noConfusion`` : functions which witness the fact that the inductive type is freely generated, i.e. that the constructors are injective and that distinct constructors produce distinct objects
|
||||
- ``Foo.below``, ``Foo.ibelow`` : functions used by the equation compiler to implement structural recursion
|
||||
- ``instance : SizeOf Foo`` : a measure which can be used for well-founded recursion
|
||||
- ``foo.rec_on`` : a variant of the eliminator, in which the major premise comes first
|
||||
- ``foo.cases_on`` : a restricted version of the eliminator which omits any recursive calls
|
||||
- ``foo.no_confusion_type``, ``foo.no_confusion`` : functions which witness the fact that the inductive type is freely generated, i.e. that the constructors are injective and that distinct constructors produce distinct objects
|
||||
- ``foo.below``, ``foo.ibelow`` : functions used by the equation compiler to implement structural recursion
|
||||
- ``foo.sizeof`` : a measure which can be used for well-founded recursion
|
||||
|
||||
Note that it is common to put definitions and theorems related to a datatype ``foo`` in a namespace of the same name. This makes it possible to use projection notation described in [Structures](struct.md#structures) and [Namespaces](namespaces.md#namespaces).
|
||||
|
||||
.. code-block:: lean
|
||||
|
||||
namespace Hide
|
||||
namespace hide
|
||||
universe u
|
||||
|
||||
-- BEGIN
|
||||
inductive Nat
|
||||
inductive nat
|
||||
| zero
|
||||
| succ (n : Nat) : Nat
|
||||
| succ (n : nat) : nat
|
||||
|
||||
#check Nat
|
||||
#check @Nat.rec
|
||||
#check Nat.zero
|
||||
#check Nat.succ
|
||||
#check nat
|
||||
#check nat.rec
|
||||
#check nat.zero
|
||||
#check nat.succ
|
||||
|
||||
#check @Nat.recOn
|
||||
#check @Nat.casesOn
|
||||
#check @Nat.noConfusionType
|
||||
#check @Nat.noConfusion
|
||||
#check @Nat.brecOn
|
||||
#check Nat.below
|
||||
#check Nat.ibelow
|
||||
#check Nat._sizeOf_1
|
||||
#check nat.rec_on
|
||||
#check nat.cases_on
|
||||
#check nat.no_confusion_type
|
||||
#check @nat.no_confusion
|
||||
#check nat.brec_on
|
||||
#check nat.below
|
||||
#check nat.ibelow
|
||||
#check nat.sizeof
|
||||
|
||||
-- END
|
||||
|
||||
end Hide
|
||||
end hide
|
||||
|
||||
.. _inductive_families:
|
||||
|
||||
@@ -309,39 +309,39 @@ In fact, Lean implements a slight generalization of the inductive types describe
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
inductive Foo (a : α) : Π (c : γ), Sort u
|
||||
| constructor₁ : Π (b : β₁), Foo t₁
|
||||
| constructor₂ : Π (b : β₂), Foo t₂
|
||||
inductive foo (a : α) : Π (c : γ), Sort u
|
||||
| constructor₁ : Π (b : β₁), foo t₁
|
||||
| constructor₂ : Π (b : β₂), foo t₂
|
||||
...
|
||||
| constructorₙ : Π (b : βₙ), Foo tₙ
|
||||
| constructorₙ : Π (b : βₙ), foo tₙ
|
||||
|
||||
Here ``(a : α)`` is a context, ``(c : γ)`` is a telescope in context ``(a : α)``, each ``(b : βᵢ)`` is a telescope in the context ``(a : α)`` together with ``(Foo : Π (c : γ), Sort u)`` subject to the constraints below, and each ``tᵢ`` is a tuple of terms in the context ``(a : α) (b : βᵢ)`` having the types ``γ``. Instead of defining a single inductive type ``Foo a``, we are now defining a family of types ``Foo a c`` indexed by elements ``c : γ``. Each constructor, ``constructorᵢ``, places its result in the type ``Foo a tᵢ``, the member of the family with index ``tᵢ``.
|
||||
Here ``(a : α)`` is a context, ``(c : γ)`` is a telescope in context ``(a : α)``, each ``(b : βᵢ)`` is a telescope in the context ``(a : α)`` together with ``(foo : Π (c : γ), Sort u)`` subject to the constraints below, and each ``tᵢ`` is a tuple of terms in the context ``(a : α) (b : βᵢ)`` having the types ``γ``. Instead of defining a single inductive type ``foo a``, we are now defining a family of types ``foo a c`` indexed by elements ``c : γ``. Each constructor, ``constructorᵢ``, places its result in the type ``foo a tᵢ``, the member of the family with index ``tᵢ``.
|
||||
|
||||
The modifications to the scheme in the previous section are straightforward. Suppose the telescope ``(b : βᵢ)`` is ``(b₁ : βᵢ₁) ... (bᵤ : βᵢᵤ)``.
|
||||
|
||||
- As before, an argument ``(bⱼ : βᵢⱼ)`` is *nonrecursive* if ``βᵢⱼ`` does not refer to ``Foo,`` the inductive type being defined. In that case, ``βᵢⱼ`` can be any type, so long as it does not refer to any nonrecursive arguments.
|
||||
- As before, an argument ``(bⱼ : βᵢⱼ)`` is *nonrecursive* if ``βᵢⱼ`` does not refer to ``foo,`` the inductive type being defined. In that case, ``βᵢⱼ`` can be any type, so long as it does not refer to any nonrecursive arguments.
|
||||
|
||||
- An argument ``(bⱼ : βᵢⱼ)`` is *recursive* if ``βᵢⱼ`` is of the form ``Π (d : δ), Foo s`` where ``(d : δ)`` is a telescope which does not refer to ``Foo`` or any nonrecursive arguments and ``s`` is a tuple of terms in context ``(a : α)`` and the previous nonrecursive ``bⱼ``'s with types ``γ``.
|
||||
- An argument ``(bⱼ : βᵢⱼ)`` is *recursive* if ``βᵢⱼ`` is of the form ``Π (d : δ), foo s`` where ``(d : δ)`` is a telescope which does not refer to ``foo`` or any nonrecursive arguments and ``s`` is a tuple of terms in context ``(a : α)`` and the previous nonrecursive ``bⱼ``'s with types ``γ``.
|
||||
|
||||
The declaration of the type ``Foo`` as above results in the addition of the following constants to the environment:
|
||||
The declaration of the type ``foo`` as above results in the addition of the following constants to the environment:
|
||||
|
||||
- the *type former* ``Foo : Π (a : α) (c : γ), Sort u``
|
||||
- for each ``i``, the *constructor* ``Foo.constructorᵢ : Π (a : α) (b : βᵢ), Foo a tᵢ``
|
||||
- the *eliminator* ``Foo.rec``, which takes arguments
|
||||
- the *type former* ``foo : Π (a : α) (c : γ), Sort u``
|
||||
- for each ``i``, the *constructor* ``foo.constructorᵢ : Π (a : α) (b : βᵢ), foo a tᵢ``
|
||||
- the *eliminator* ``foo.rec``, which takes arguments
|
||||
|
||||
+ ``(a : α)`` (the parameters)
|
||||
+ ``{C : Π (c : γ), Foo a c → Type u}`` (the motive of the elimination)
|
||||
+ ``{C : Π (c : γ), foo a c → Type u}`` (the motive of the elimination)
|
||||
+ for each ``i``, the minor premise corresponding to ``constructorᵢ``
|
||||
+ ``(x : Foo a)`` (the major premise)
|
||||
+ ``(x : foo a)`` (the major premise)
|
||||
|
||||
and returns an element of ``C x``. Here, The ith minor premise is a function which takes
|
||||
|
||||
+ ``(b : βᵢ)`` (the arguments to the constructor)
|
||||
+ an argument of type ``Π (d : δ), C s (bⱼ d)`` corresponding to each recursive argument ``(bⱼ : βᵢⱼ)``, where ``βᵢⱼ`` is of the form ``Π (d : δ), Foo s``
|
||||
+ an argument of type ``Π (d : δ), C s (bⱼ d)`` corresponding to each recursive argument ``(bⱼ : βᵢⱼ)``, where ``βᵢⱼ`` is of the form ``Π (d : δ), foo s``
|
||||
|
||||
and returns an element of ``C tᵢ (constructorᵢ a b)``.
|
||||
|
||||
Suppose we set ``F := Foo.rec a C f₁ ... fₙ``. Then for each constructor, we have the definitional reduction, as before:
|
||||
Suppose we set ``F := foo.rec a C f₁ ... fₙ``. Then for each constructor, we have the definitional reduction, as before:
|
||||
|
||||
.. code-block :: text
|
||||
|
||||
@@ -353,24 +353,24 @@ The following are examples of inductive families.
|
||||
|
||||
.. code-block:: lean
|
||||
|
||||
namespace Hide
|
||||
namespace hide
|
||||
universe u
|
||||
|
||||
-- BEGIN
|
||||
inductive Vector (α : Type u) : Nat → Type u
|
||||
| nil : Vector 0
|
||||
| succ : Π n, Vector n → Vector (n + 1)
|
||||
inductive vector (α : Type u) : Nat → Type u
|
||||
| nil : vector 0
|
||||
| succ : Π n, vector n → vector (n + 1)
|
||||
|
||||
-- 'IsProd s n' means n is a product of elements of s
|
||||
inductive IsProd (s : Set Nat) : Nat → Prop
|
||||
| base : ∀ n ∈ s, IsProd n
|
||||
| step : ∀ m n, IsProd m → IsProd n → IsProd (m * n)
|
||||
-- 'is_prod s n' means n is a product of elements of s
|
||||
inductive is_prod (s : set Nat) : Nat → Prop
|
||||
| base : ∀ n ∈ s, is_prod n
|
||||
| step : ∀ m n, is_prod m → is_prod n → is_prod (m * n)
|
||||
|
||||
inductive Eq {α : Sort u} (a : α) : α → Prop
|
||||
| refl : Eq a
|
||||
inductive eq {α : Sort u} (a : α) : α → Prop
|
||||
| refl : eq a
|
||||
-- END
|
||||
|
||||
end Hide
|
||||
end hide
|
||||
|
||||
We can now describe the constraints on the return type of the type former, ``Sort u``. We can always take ``u`` to be ``0``, in which case we are defining an inductive family of propositions. If ``u`` is nonzero, however, it must satisfy the following constraint: for each type ``βᵢⱼ : Sort v`` occurring in the constructors, we must have ``u ≥ v``. In the set-theoretic interpretation, this ensures that the universe in which the resulting type resides is large enough to contain the inductively generated family, given the number of distinctly-labeled constructors. The restriction does not hold for inductively defined propositions, since these contain no data.
|
||||
|
||||
@@ -387,47 +387,42 @@ The first generalization allows for multiple inductive types to be defined simul
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
mutual
|
||||
|
||||
inductive Foo (a : α) : Π (c : γ₁), Sort u
|
||||
| constructor₁₁ : Π (b : β₁₁), Foo a t₁₁
|
||||
| constructor₁₂ : Π (b : β₁₂), Foo a t₁₂
|
||||
mutual inductive foo, bar (a : α)
|
||||
with foo : Π (c : γ), Sort u
|
||||
| constructor₁₁ : Π (b : β₁₁), foo t₁₁
|
||||
| constructor₁₂ : Π (b : β₁₂), foo t₁₂
|
||||
...
|
||||
| constructor₁ₙ : Π (b : β₁ₙ), Foo a t₁ₙ
|
||||
|
||||
inductive Bar (a : α) : Π (c : γ₂), Sort u
|
||||
| constructor₂₁ : Π (b : β₂₁), Bar a t₂₁
|
||||
| constructor₂₂ : Π (b : β₂₂), Bar a t₂₂
|
||||
| constructor₁ₙ : Π (b : β₁ₙ), foo t₁ₙ
|
||||
with bar :
|
||||
| constructor₂₁ : Π (b : β₂₁), bar t₂₁
|
||||
| constructor₂₂ : Π (b : β₂₂), bar t₂₂
|
||||
...
|
||||
| constructor₂ₘ : Π (b : β₂ₘ), Bar a t₂ₘ
|
||||
| constructor₂ₘ : Π (b : β₂ₘ), bar t₂ₘ
|
||||
|
||||
end
|
||||
Here the syntax is shown for defining two inductive families, ``foo`` and ``bar``, but any number is allowed. The restrictions are almost the same as for ordinary inductive families. For example, each ``(b : βᵢⱼ)`` is a telescope relative to the context ``(a : α)``. The difference is that the constructors can now have recursive arguments whose return types are any of the inductive families currently being defined, in this case ``foo`` and ``bar``. Note that all of the inductive definitions share the same parameters ``(a : α)``, though they may have different indices.
|
||||
|
||||
Here the syntax is shown for defining two inductive families, ``Foo`` and ``Bar``, but any number is allowed. The restrictions are almost the same as for ordinary inductive families. For example, each ``(b : βᵢⱼ)`` is a telescope relative to the context ``(a : α)``. The difference is that the constructors can now have recursive arguments whose return types are any of the inductive families currently being defined, in this case ``Foo`` and ``Bar``. Note that all of the inductive definitions share the same parameters ``(a : α)``, though they may have different indices.
|
||||
A mutual inductive definition is compiled down to an ordinary inductive definition using an extra finite-valued index to distinguish the components. The details of the internal construction are meant to be hidden from most users. Lean defines the expected type formers ``foo`` and ``bar`` and constructors ``constructorᵢⱼ`` from the internal inductive definition. There is no straightforward elimination principle, however. Instead, Lean defines an appropriate ``sizeof`` measure, meant for use with well-founded recursion, with the property that the recursive arguments to a constructor are smaller than the constructed value.
|
||||
|
||||
A mutual inductive definition is compiled down to an ordinary inductive definition using an extra finite-valued index to distinguish the components. The details of the internal construction are meant to be hidden from most users. Lean defines the expected type formers ``Foo`` and ``Bar`` and constructors ``constructorᵢⱼ`` from the internal inductive definition. There is no straightforward elimination principle, however. Instead, Lean defines an appropriate ``sizeOf`` measure, meant for use with well-founded recursion, with the property that the recursive arguments to a constructor are smaller than the constructed value.
|
||||
The second generalization relaxes the restriction that in the recursive definition of ``foo``, ``foo`` can only occur strictly positively in the type of any of its recursive arguments. Specifically, in a nested inductive definition, ``foo`` can appear as an argument to another inductive type constructor, so long as the corresponding parameter occurs strictly positively in the constructors for *that* inductive type. This process can be iterated, so that additional type constructors can be applied to those, and so on.
|
||||
|
||||
The second generalization relaxes the restriction that in the recursive definition of ``Foo``, ``Foo`` can only occur strictly positively in the type of any of its recursive arguments. Specifically, in a nested inductive definition, ``Foo`` can appear as an argument to another inductive type constructor, so long as the corresponding parameter occurs strictly positively in the constructors for *that* inductive type. This process can be iterated, so that additional type constructors can be applied to those, and so on.
|
||||
|
||||
A nested inductive definition is compiled down to an ordinary inductive definition using a mutual inductive definition to define copies of all the nested types simultaneously. Lean then constructs isomorphisms between the mutually defined nested types and their independently defined counterparts. Once again, the internal details are not meant to be manipulated by users. Rather, the type former and constructors are made available and work as expected, while an appropriate ``sizeOf`` measure is generated for use with well-founded recursion.
|
||||
A nested inductive definition is compiled down to an ordinary inductive definition using a mutual inductive definition to define copies of all the nested types simultaneously. Lean then constructs isomorphisms between the mutually defined nested types and their independently defined counterparts. Once again, the internal details are not meant to be manipulated by users. Rather, the type former and constructors are made available and work as expected, while an appropriate ``sizeof`` measure is generated for use with well-founded recursion.
|
||||
|
||||
.. code-block:: lean
|
||||
|
||||
universe u
|
||||
-- BEGIN
|
||||
mutual
|
||||
inductive Even : Nat → Prop
|
||||
| even_zero : Even 0
|
||||
| even_succ : ∀ n, Odd n → Even (n + 1)
|
||||
inductive Odd : Nat → Prop
|
||||
| odd_succ : ∀ n, Even n → Odd (n + 1)
|
||||
end
|
||||
mutual inductive even, odd
|
||||
with even : Nat → Prop
|
||||
| even_zero : even 0
|
||||
| even_succ : ∀ n, odd n → even (n + 1)
|
||||
with odd : Nat → Prop
|
||||
| odd_succ : ∀ n, even n → odd (n + 1)
|
||||
|
||||
inductive Tree (α : Type u)
|
||||
| mk : α → List (Tree α) → Tree α
|
||||
inductive tree (α : Type u)
|
||||
| mk : α → list tree → tree
|
||||
|
||||
inductive DoubleTree (α : Type u)
|
||||
| mk : α → List (DoubleTree α) × List (DoubleTree α) → DoubleTree α
|
||||
inductive double_tree (α : Type u)
|
||||
| mk : α → list double_tree × list double_tree → double_tree
|
||||
-- END
|
||||
|
||||
.. _the_equation_compiler:
|
||||
@@ -440,9 +435,9 @@ The equation compiler takes an equational description of a function or proof and
|
||||
.. code-block:: text
|
||||
|
||||
def foo (a : α) : Π (b : β), γ
|
||||
| [patterns₁] => t₁
|
||||
| [patterns₁] := t₁
|
||||
...
|
||||
| [patternsₙ] => tₙ
|
||||
| [patternsₙ] := tₙ
|
||||
|
||||
Here ``(a : α)`` is a telescope, ``(b : β)`` is a telescope in the context ``(a : α)``, and ``γ`` is an expression in the context ``(a : α) (b : β)`` denoting a ``Type`` or a ``Prop``.
|
||||
|
||||
@@ -455,78 +450,78 @@ Each ``patternsᵢ`` is a sequence of patterns of the same length as ``(b : β)`
|
||||
|
||||
In the last case, the pattern must be enclosed in parentheses.
|
||||
|
||||
Each term ``tᵢ`` is an expression in the context ``(a : α)`` together with the variables introduced on the left-hand side of the token ``=>``. The term ``tᵢ`` can also include recursive calls to ``foo``, as described below. The equation compiler does case splitting on the variables ``(b : β)`` as necessary to match the patterns, and defines ``foo`` so that it has the value ``tᵢ`` in each of the cases. In ideal circumstances (see below), the equations hold definitionally. Whether they hold definitionally or only propositionally, the equation compiler proves the relevant equations and assigns them internal names. They are accessible by the ``rewrite`` and ``simp`` tactics under the name ``foo`` (see [Rewrite](tactics.md#rewrite) and _[TODO: where is simplifier tactic documented?]_. If some of the patterns overlap, the equation compiler interprets the definition so that the first matching pattern applies in each case. Thus, if the last pattern is a variable, it covers all the remaining cases. If the patterns that are presented do not cover all possible cases, the equation compiler raises an error.
|
||||
Each term ``tᵢ`` is an expression in the context ``(a : α)`` together with the variables introduced on the left-hand side of the token ``:=``. The term ``tᵢ`` can also include recursive calls to ``foo``, as described below. The equation compiler does case splitting on the variables ``(b : β)`` as necessary to match the patterns, and defines ``foo`` so that it has the value ``tᵢ`` in each of the cases. In ideal circumstances (see below), the equations hold definitionally. Whether they hold definitionally or only propositionally, the equation compiler proves the relevant equations and assigns them internal names. They are accessible by the ``rewrite`` and ``simp`` tactics under the name ``foo`` (see [Rewrite](tactics.md#rewrite) and _[TODO: where is simplifier tactic documented?]_. If some of the patterns overlap, the equation compiler interprets the definition so that the first matching pattern applies in each case. Thus, if the last pattern is a variable, it covers all the remaining cases. If the patterns that are presented do not cover all possible cases, the equation compiler raises an error.
|
||||
|
||||
When identifiers are marked with the ``[matchPattern]`` attribute, the equation compiler unfolds them in the hopes of exposing a constructor. For example, this makes it possible to write ``n+1`` and ``0`` instead of ``Nat.succ n`` and ``Nat.zero`` in patterns.
|
||||
When identifiers are marked with the ``[pattern]`` attribute, the equation compiler unfolds them in the hopes of exposing a constructor. For example, this makes it possible to write ``n+1`` and ``0`` instead of ``nat.succ n`` and ``nat.zero`` in patterns.
|
||||
|
||||
For a nonrecursive definition involving case splits, the defining equations will hold definitionally. With inductive types like ``Char``, ``String``, and ``Fin n``, a case split would produce definitions with an inordinate number of cases. To avoid this, the equation compiler uses ``if ... then ... else`` instead of ``casesOn`` when defining the function. In this case, the defining equations hold definitionally as well.
|
||||
For a nonrecursive definition involving case splits, the defining equations will hold definitionally. With inductive types like ``char``, ``string``, and ``fin n``, a case split would produce definitions with an inordinate number of cases. To avoid this, the equation compiler uses ``if ... then ... else`` instead of ``cases_on`` when defining the function. In this case, the defining equations hold definitionally as well.
|
||||
|
||||
.. code-block:: lean
|
||||
|
||||
open Nat
|
||||
open nat
|
||||
|
||||
def sub2 : Nat → Nat
|
||||
| zero => 0
|
||||
| succ zero => 0
|
||||
| succ (succ a) => a
|
||||
| zero := 0
|
||||
| (succ zero) := 0
|
||||
| (succ (succ a)) := a
|
||||
|
||||
def bar : Nat → List Nat → Bool → Nat
|
||||
| 0, _, false => 0
|
||||
| 0, b :: _, _ => b
|
||||
| 0, [], true => 7
|
||||
| a+1, [], false => a
|
||||
| a+1, [], true => a + 1
|
||||
| a+1, b :: _, _ => a + b
|
||||
def bar : Nat → list Nat → bool → Nat
|
||||
| 0 _ ff := 0
|
||||
| 0 (b :: _) _ := b
|
||||
| 0 [] tt := 7
|
||||
| (a+1) [] ff := a
|
||||
| (a+1) [] tt := a + 1
|
||||
| (a+1) (b :: _) _ := a + b
|
||||
|
||||
def baz : Char → Nat
|
||||
| 'A' => 1
|
||||
| 'B' => 2
|
||||
| _ => 3
|
||||
def baz : char → Nat
|
||||
| 'A' := 1
|
||||
| 'B' := 2
|
||||
| _ := 3
|
||||
|
||||
If any of the terms ``tᵢ`` in the template above contain a recursive call to ``foo``, the equation compiler tries to interpret the definition as a structural recursion. In order for that to succeed, the recursive arguments must be subterms of the corresponding arguments on the left-hand side. The function is then defined using a *course of values* recursion, using automatically generated functions ``below`` and ``brec`` in the namespace corresponding to the inductive type of the recursive argument. In this case the defining equations hold definitionally, possibly with additional case splits.
|
||||
|
||||
.. code-block:: lean
|
||||
|
||||
namespace Hide
|
||||
namespace hide
|
||||
|
||||
-- BEGIN
|
||||
def fib : Nat → Nat
|
||||
| 0 => 1
|
||||
| 1 => 1
|
||||
| (n+2) => fib (n+1) + fib n
|
||||
def fib : nat → nat
|
||||
| 0 := 1
|
||||
| 1 := 1
|
||||
| (n+2) := fib (n+1) + fib n
|
||||
|
||||
def append {α : Type} : List α → List α → List α
|
||||
| [], l => l
|
||||
| h::t, l => h :: append t l
|
||||
def append {α : Type} : list α → list α → list α
|
||||
| [] l := l
|
||||
| (h::t) l := h :: append t l
|
||||
|
||||
example : append [(1 : Nat), 2, 3] [4, 5] = [1, 2, 3, 4, 5] => rfl
|
||||
example : append [(1 : Nat), 2, 3] [4, 5] = [1, 2, 3, 4, 5] := rfl
|
||||
-- END
|
||||
|
||||
end Hide
|
||||
end hide
|
||||
|
||||
If structural recursion fails, the equation compiler falls back on well-founded recursion. It tries to infer an instance of ``SizeOf`` for the type of each argument, and then show that each recursive call is decreasing under the lexicographic order of the arguments with respect to ``sizeOf`` measure. If it fails, the error message provides information as to the goal that Lean tried to prove. Lean uses information in the local context, so you can often provide the relevant proof manually using ``have`` in the body of the definition. In this case of well-founded recursion, the defining equations hold only propositionally, and can be accessed using ``simp`` and ``rewrite`` with the name ``foo``.
|
||||
If structural recursion fails, the equation compiler falls back on well-founded recursion. It tries to infer an instance of ``has_sizeof`` for the type of each argument, and then show that each recursive call is decreasing under the lexicographic order of the arguments with respect to ``sizeof`` measure. If it fails, the error message provides information as to the goal that Lean tried to prove. Lean uses information in the local context, so you can often provide the relevant proof manually using ``have`` in the body of the definition. In this case of well-founded recursion, the defining equations hold only propositionally, and can be accessed using ``simp`` and ``rewrite`` with the name ``foo``.
|
||||
|
||||
.. code-block:: lean
|
||||
|
||||
namespace Hide
|
||||
open Nat
|
||||
namespace hide
|
||||
open nat
|
||||
|
||||
-- BEGIN
|
||||
def div : Nat → Nat → Nat
|
||||
| x, y =>
|
||||
| x y :=
|
||||
if h : 0 < y ∧ y ≤ x then
|
||||
have : x - y < x :=
|
||||
sub_lt (Nat.lt_of_lt_of_le h.left h.right) h.left
|
||||
have x - y < x,
|
||||
from sub_lt (lt_of_lt_of_le h.left h.right) h.left,
|
||||
div (x - y) y + 1
|
||||
else
|
||||
0
|
||||
|
||||
example (x y : Nat) :
|
||||
div x y = if 0 < y ∧ y ≤ x then div (x - y) y + 1 else 0 :=
|
||||
by rw [div]; rfl
|
||||
by rw [div]
|
||||
-- END
|
||||
|
||||
end Hide
|
||||
end hide
|
||||
|
||||
Note that recursive definitions can in general require nested recursions, that is, recursion on different arguments of ``foo`` in the template above. The equation compiler handles this by abstracting later arguments, and recursively defining higher-order functions to meet the specification.
|
||||
|
||||
@@ -534,14 +529,13 @@ The equation compiler also allows mutual recursive definitions, with a syntax si
|
||||
|
||||
.. code-block:: lean
|
||||
|
||||
mutual
|
||||
def even : Nat → Bool
|
||||
| 0 => true
|
||||
| a+1 => odd a
|
||||
def odd : Nat → Bool
|
||||
| 0 => false
|
||||
| a+1 => even a
|
||||
end
|
||||
mutual def even, odd
|
||||
with even : Nat → bool
|
||||
| 0 := tt
|
||||
| (a+1) := odd a
|
||||
with odd : Nat → bool
|
||||
| 0 := ff
|
||||
| (a+1) := even a
|
||||
|
||||
example (a : Nat) : even (a + 1) = odd a :=
|
||||
by simp [even]
|
||||
@@ -553,39 +547,36 @@ Well-founded recursion is especially useful with [Mutual and Nested Inductive De
|
||||
|
||||
.. code-block:: lean
|
||||
|
||||
mutual
|
||||
inductive Even : Nat → Prop
|
||||
| even_zero : Even 0
|
||||
| even_succ : ∀ n, Odd n → Even (n + 1)
|
||||
inductive Odd : Nat → Prop
|
||||
| odd_succ : ∀ n, Even n → Odd (n + 1)
|
||||
end
|
||||
mutual inductive even, odd
|
||||
with even : Nat → Prop
|
||||
| even_zero : even 0
|
||||
| even_succ : ∀ n, odd n → even (n + 1)
|
||||
with odd : Nat → Prop
|
||||
| odd_succ : ∀ n, even n → odd (n + 1)
|
||||
|
||||
open Even Odd
|
||||
open even odd
|
||||
|
||||
theorem not_odd_zero : ¬ Odd 0 := fun x => nomatch x
|
||||
theorem not_odd_zero : ¬ odd 0.
|
||||
|
||||
mutual
|
||||
theorem even_of_odd_succ : ∀ n, Odd (n + 1) → Even n
|
||||
| _, odd_succ n h => h
|
||||
theorem odd_of_even_succ : ∀ n, Even (n + 1) → Odd n
|
||||
| _, even_succ n h => h
|
||||
end
|
||||
mutual theorem even_of_odd_succ, odd_of_even_succ
|
||||
with even_of_odd_succ : ∀ n, odd (n + 1) → even n
|
||||
| _ (odd_succ n h) := h
|
||||
with odd_of_even_succ : ∀ n, even (n + 1) → odd n
|
||||
| _ (even_succ n h) := h
|
||||
|
||||
inductive Term
|
||||
| const : String → Term
|
||||
| app : String → List Term → Term
|
||||
inductive term
|
||||
| const : string → term
|
||||
| app : string → list term → term
|
||||
|
||||
open Term
|
||||
open term
|
||||
|
||||
mutual
|
||||
def num_consts : Term → Nat
|
||||
| .const n => 1
|
||||
| .app n ts => num_consts_lst ts
|
||||
def num_consts_lst : List Term → Nat
|
||||
| [] => 0
|
||||
| t::ts => num_consts t + num_consts_lst ts
|
||||
end
|
||||
mutual def num_consts, num_consts_lst
|
||||
with num_consts : term → nat
|
||||
| (term.const n) := 1
|
||||
| (term.app n ts) := num_consts_lst ts
|
||||
with num_consts_lst : list term → nat
|
||||
| [] := 0
|
||||
| (t::ts) := num_consts t + num_consts_lst ts
|
||||
|
||||
The case where patterns are matched against an argument whose type is an inductive family is known as *dependent pattern matching*. This is more complicated, because the type of the function being defined can impose constraints on the patterns that are matched. In this case, the equation compiler will detect inconsistent cases and rule them out.
|
||||
|
||||
@@ -593,24 +584,52 @@ The case where patterns are matched against an argument whose type is an inducti
|
||||
|
||||
universe u
|
||||
|
||||
inductive Vector (α : Type u) : Nat → Type u
|
||||
| nil : Vector α 0
|
||||
| cons : α → Vector α n → Vector α (n+1)
|
||||
inductive vector (α : Type u) : Nat → Type u
|
||||
| nil {} : vector 0
|
||||
| cons : Π {n}, α → vector n → vector (n+1)
|
||||
|
||||
namespace Vector
|
||||
namespace vector
|
||||
|
||||
def head {α : Type} : Vector α (n+1) → α
|
||||
| cons h t => h
|
||||
def head {α : Type} : Π {n}, vector α (n+1) → α
|
||||
| n (cons h t) := h
|
||||
|
||||
def tail {α : Type} : Vector α (n+1) → Vector α n
|
||||
| cons h t => t
|
||||
def tail {α : Type} : Π {n}, vector α (n+1) → vector α n
|
||||
| n (cons h t) := t
|
||||
|
||||
def map {α β γ : Type} (f : α → β → γ) :
|
||||
∀ {n}, Vector α n → Vector β n → Vector γ n
|
||||
| 0, nil, nil => nil
|
||||
| n+1, cons a va, cons b vb => cons (f a b) (map f va vb)
|
||||
Π {n}, vector α n → vector β n → vector γ n
|
||||
| 0 nil nil := nil
|
||||
| (n+1) (cons a va) (cons b vb) := cons (f a b) (map va vb)
|
||||
|
||||
end Vector
|
||||
end vector
|
||||
|
||||
An expression of the form ``.(t)`` in a pattern is known as an *inaccessible term*. It is not viewed as part of the pattern; rather, it is explicit information that is used by the elaborator and equation compiler when interpreting the definition. Inaccessible terms do not participate in pattern matching. They are sometimes needed for a pattern to make sense, for example, when a constructor depends on a parameter that is not a pattern-matching variable. In other cases, they can be used to inform the equation compiler that certain arguments do not require a case split, and they can be used to make a definition more readable.
|
||||
|
||||
.. code-block:: lean
|
||||
|
||||
universe u
|
||||
|
||||
inductive vector (α : Type u) : Nat → Type u
|
||||
| nil {} : vector 0
|
||||
| cons : Π {n}, α → vector n → vector (n+1)
|
||||
|
||||
namespace vector
|
||||
|
||||
-- BEGIN
|
||||
variable {α : Type u}
|
||||
|
||||
def add [has_add α] :
|
||||
Π {n : Nat}, vector α n → vector α n → vector α n
|
||||
| ._ nil nil := nil
|
||||
| ._ (cons a v) (cons b w) := cons (a + b) (add v w)
|
||||
|
||||
def add' [has_add α] :
|
||||
Π {n : Nat}, vector α n → vector α n → vector α n
|
||||
| .(0) nil nil := nil
|
||||
| .(n+1) (@cons .(α) n a v) (cons b w) := cons (a + b) (add' v w)
|
||||
-- END
|
||||
|
||||
end vector
|
||||
|
||||
.. _match_expressions:
|
||||
|
||||
@@ -622,9 +641,9 @@ Lean supports a ``match ... with ...`` construct similar to ones found in most f
|
||||
.. code-block:: text
|
||||
|
||||
match t₁, ..., tₙ with
|
||||
| p₁₁, ..., p₁ₙ => s₁
|
||||
| p₁₁, ..., p₁ₙ := s₁
|
||||
...
|
||||
| pₘ₁, ..., pₘₙ => sₘ
|
||||
| pₘ₁, ..., pₘₙ := sₘ
|
||||
|
||||
Here ``t₁, ..., tₙ`` are any terms in the context in which the expression appears, the expressions ``pᵢⱼ`` are patterns, and the terms ``sᵢ`` are expressions in the local context together with variables introduced by the patterns on the left-hand side. Each ``sᵢ`` should have the expected type of the entire ``match`` expression.
|
||||
|
||||
@@ -632,42 +651,29 @@ Any ``match`` expression is interpreted using the equation compiler, which gener
|
||||
|
||||
.. code-block:: lean
|
||||
|
||||
def foo (n : Nat) (b c : Bool) :=
|
||||
def foo (n : Nat) (b c : bool) :=
|
||||
5 + match n - 5, b && c with
|
||||
| 0, true => 0
|
||||
| m+1, true => m + 7
|
||||
| 0, false => 5
|
||||
| m+1, false => m + 3
|
||||
| 0, tt := 0
|
||||
| m+1, tt := m + 7
|
||||
| 0, ff := 5
|
||||
| m+1, ff := m + 3
|
||||
end
|
||||
|
||||
When a ``match`` has only one line, Lean provides alternative syntax with a destructuring ``let``, as well as a destructuring lambda abstraction. Thus the following definitions all have the same net effect.
|
||||
When a ``match`` has only one line, the vertical bar may be left out. In that case, Lean provides alternative syntax with a destructuring ``let``, as well as a destructuring lambda abstraction. Thus the following definitions all have the same net effect.
|
||||
|
||||
.. code-block:: lean
|
||||
|
||||
def bar₁ : Nat × Nat → Nat
|
||||
| (m, n) => m + n
|
||||
| (m, n) := m + n
|
||||
|
||||
def bar₂ (p : Nat × Nat) : Nat :=
|
||||
match p with | (m, n) => m + n
|
||||
match p with (m, n) := m + n end
|
||||
|
||||
def bar₃ : Nat × Nat → Nat :=
|
||||
fun ⟨m, n⟩ => m + n
|
||||
|
||||
def bar₄ (p : Nat × Nat) : Nat :=
|
||||
let ⟨m, n⟩ := p; m + n
|
||||
|
||||
Information about the term being matched can be preserved in each branch using the syntax `match h : t with`. For example, a user may want to match a term `ns ++ ms : List Nat`, while tracking the hypothesis `ns ++ ms = []` or `ns ++ ms= h :: t` in the respective match arm:
|
||||
|
||||
```lean
|
||||
def foo (ns ms : List Nat) (h1 : ns ++ ms ≠ []) (k : Nat -> Char) : Char :=
|
||||
match h2 : ns ++ ms with
|
||||
-- in this arm, we have the hypothesis `h2 : ns ++ ms = []`
|
||||
| [] => absurd h2 h1
|
||||
-- in this arm, we have the hypothesis `h2 : ns ++ ms = h :: t`
|
||||
| h :: t => k h
|
||||
|
||||
-- '7'
|
||||
#eval foo [7, 8, 9] [] (by decide) Nat.digitChar
|
||||
```
|
||||
let ⟨m, n⟩ := p in m + n
|
||||
|
||||
.. _structures_and_records:
|
||||
|
||||
@@ -678,113 +684,114 @@ The ``structure`` command in Lean is used to define an inductive data type with
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
structure Foo (a : α) extends Bar, Baz : Sort u :=
|
||||
structure foo (a : α) extends bar, baz : Sort u :=
|
||||
constructor :: (field₁ : β₁) ... (fieldₙ : βₙ)
|
||||
|
||||
Here ``(a : α)`` is a telescope, that is, the parameters to the inductive definition. The name ``constructor`` followed by the double colon is optional; if it is not present, the name ``mk`` is used by default. The keyword ``extends`` followed by a list of previously defined structures is also optional; if it is present, an instance of each of these structures is included among the fields to ``Foo``, and the types ``βᵢ`` can refer to their fields as well. The output type, ``Sort u``, can be omitted, in which case Lean infers to smallest non-``Prop`` sort possible. Finally, ``(field₁ : β₁) ... (fieldₙ : βₙ)`` is a telescope relative to ``(a : α)`` and the fields in ``bar`` and ``baz``.
|
||||
Here ``(a : α)`` is a telescope, that is, the parameters to the inductive definition. The name ``constructor`` followed by the double colon is optional; if it is not present, the name ``mk`` is used by default. The keyword ``extends`` followed by a list of previously defined structures is also optional; if it is present, an instance of each of these structures is included among the fields to ``foo,`` and the types ``βᵢ`` can refer to their fields as well. The output type, ``Sort u``, can be omitted, in which case Lean infers to smallest non-``Prop`` sort possible. Finally, ``(field₁ : β₁) ... (fieldₙ : βₙ)`` is a telescope relative to ``(a : α)`` and the fields in ``bar`` and ``baz``.
|
||||
|
||||
The declaration above is syntactic sugar for an inductive type declaration, and so results in the addition of the following constants to the environment:
|
||||
|
||||
- the type former : ``Foo : Π (a : α), Sort u``
|
||||
- the type former : ``foo : Π (a : α), Sort u``
|
||||
- the single constructor :
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
Foo.constructor : Π (a : α) (toBar : Bar) (toBaz : Baz)
|
||||
(field₁ : β₁) ... (fieldₙ : βₙ), Foo a
|
||||
foo.constructor : Π (a : α) (_to_foo : foo) (_to_bar : bar)
|
||||
(field₁ : β₁) ... (fieldₙ : βₙ), foo a
|
||||
|
||||
- the eliminator ``Foo.rec`` for the inductive type with that constructor
|
||||
- the eliminator ``foo.rec`` for the inductive type with that constructor
|
||||
|
||||
In addition, Lean defines
|
||||
|
||||
- the projections : ``fieldᵢ : Π (a : α) (c : Foo) : βᵢ`` for each ``i``
|
||||
- the projections : ``fieldᵢ : Π (a : α) (c : foo) : βᵢ`` for each ``i``
|
||||
|
||||
where any other fields mentioned in ``βᵢ`` are replaced by the relevant projections from ``c``.
|
||||
|
||||
Given ``c : Foo``, Lean offers the following convenient syntax for the projection ``Foo.fieldᵢ c``:
|
||||
Given ``c : foo``, Lean offers the following convenient syntax for the projection ``foo.fieldᵢ c``:
|
||||
|
||||
- *anonymous projections* : ``c.fieldᵢ``
|
||||
- *numbered projections* : ``c.i``
|
||||
|
||||
These can be used in any situation where Lean can infer that the type of ``c`` is of the form ``Foo a``. The convention for anonymous projections is extended to any function ``f`` defined in the namespace ``Foo``, as described in [Namespaces](namespaces.md).
|
||||
These can be used in any situation where Lean can infer that the type of ``c`` is of the form ``foo a``. The convention for anonymous projections is extended to any function ``f`` defined in the namespace ``foo``, as described in [Namespaces](namespaces.md).
|
||||
|
||||
Similarly, Lean offers the following convenient syntax for constructing elements of ``Foo``. They are equivalent to ``Foo.constructor b₁ b₂ f₁ f₁ ... fₙ``, where ``b₁ : Bar``, ``b₂ : Baz``, and each ``fᵢ : βᵢ`` :
|
||||
Similarly, Lean offers the following convenient syntax for constructing elements of ``foo``. They are equivalent to ``foo.constructor b₁ b₂ f₁ f₁ ... fₙ``, where ``b₁ : foo``, ``b₂ : bar``, and each ``fᵢ : βᵢ`` :
|
||||
|
||||
- *anonymous constructor*: ``⟨ b₁, b₂, f₁, ..., fₙ ⟩``
|
||||
- *record notation*:
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
{ toBar := b₁, toBaz := b₂, field₁ := f₁, ...,
|
||||
fieldₙ := fₙ : Foo a }
|
||||
{ foo . to_bar := b₁, to_baz := b₂, field₁ := f₁, ...,
|
||||
fieldₙ := fₙ }
|
||||
|
||||
The anonymous constructor can be used in any context where Lean can infer that the expression should have a type of the form ``Foo a``. The unicode brackets are entered as ``\<`` and ``\>`` respectively.
|
||||
The anonymous constructor can be used in any context where Lean can infer that the expression should have a type of the form ``foo a``. The unicode brackets are entered as ``\<`` and ``\>`` respectively. The tokens ``(|`` and ``|)`` are ascii equivalents.
|
||||
|
||||
When using record notation, you can omit the annotation ``: Foo a`` when Lean can infer that the expression should have a type of the form ``Foo a``. You can replace either ``toBar`` or ``toBaz`` by assignments to *their* fields as well, essentially acting as though the fields of ``Bar`` and ``Baz`` are simply imported into ``Foo``. Finally, record notation also supports
|
||||
When using record notation, you can omit the annotation ``foo .`` when Lean can infer that the expression should have a type of the form ``foo a``. You can replace either ``to_bar`` or ``to_baz`` by assignments to *their* fields as well, essentially acting as though the fields of ``bar`` and ``baz`` are simply imported into ``foo``. Finally, record notation also supports
|
||||
|
||||
- *record updates*: ``{ t with ... fieldᵢ := fᵢ ...}``
|
||||
|
||||
Here ``t`` is a term of type ``Foo a`` for some ``a``. The notation instructs Lean to take values from ``t`` for any field assignment that is omitted from the list.
|
||||
Here ``t`` is a term of type ``foo a`` for some ``a``. The notation instructs Lean to take values from ``t`` for any field assignment that is omitted from the list.
|
||||
|
||||
Lean also allows you to specify a default value for any field in a structure by writing ``(fieldᵢ : βᵢ := t)``. Here ``t`` specifies the value to use when the field ``fieldᵢ`` is left unspecified in an instance of record notation.
|
||||
|
||||
.. code-block:: lean
|
||||
|
||||
universe u v
|
||||
universes u v
|
||||
|
||||
structure Vec (α : Type u) (n : Nat) :=
|
||||
(l : List α) (h : l.length = n)
|
||||
structure vec (α : Type u) (n : Nat) :=
|
||||
(l : list α) (h : l.length = n)
|
||||
|
||||
structure Foo (α : Type u) (β : Nat → Type v) : Type (max u v) :=
|
||||
structure foo (α : Type u) (β : Nat → Type v) : Type (max u v) :=
|
||||
(a : α) (n : Nat) (b : β n)
|
||||
|
||||
structure Bar :=
|
||||
structure bar :=
|
||||
(c : Nat := 8) (d : Nat)
|
||||
|
||||
structure Baz extends Foo Nat (Vec Nat), Bar :=
|
||||
(v : Vec Nat n)
|
||||
structure baz extends foo Nat (vec Nat), bar :=
|
||||
(v : vec Nat n)
|
||||
|
||||
#check Foo
|
||||
#check @Foo.mk
|
||||
#check @Foo.rec
|
||||
#check foo
|
||||
#check @foo.mk
|
||||
#check @foo.rec
|
||||
|
||||
#check Foo.a
|
||||
#check Foo.n
|
||||
#check Foo.b
|
||||
#check foo.a
|
||||
#check foo.n
|
||||
#check foo.b
|
||||
|
||||
#check Baz
|
||||
#check @Baz.mk
|
||||
#check @Baz.rec
|
||||
#check baz
|
||||
#check @baz.mk
|
||||
#check @baz.rec
|
||||
|
||||
#check Baz.toFoo
|
||||
#check Baz.toBar
|
||||
#check Baz.v
|
||||
#check baz.to_foo
|
||||
#check baz.to_bar
|
||||
#check baz.v
|
||||
|
||||
def bzz := Vec.mk [1, 2, 3] rfl
|
||||
def bzz := vec.mk [1, 2, 3] rfl
|
||||
|
||||
#check Vec.l bzz
|
||||
#check Vec.h bzz
|
||||
#check vec.l bzz
|
||||
#check vec.h bzz
|
||||
#check bzz.l
|
||||
#check bzz.h
|
||||
#check bzz.1
|
||||
#check bzz.2
|
||||
|
||||
example : Vec Nat 3 := Vec.mk [1, 2, 3] rfl
|
||||
example : Vec Nat 3 := ⟨[1, 2, 3], rfl⟩
|
||||
example : Vec Nat 3 := { l := [1, 2, 3], h := rfl : Vec Nat 3 }
|
||||
example : Vec Nat 3 := { l := [1, 2, 3], h := rfl }
|
||||
example : vec Nat 3 := vec.mk [1, 2, 3] rfl
|
||||
example : vec Nat 3 := ⟨[1, 2, 3], rfl⟩
|
||||
example : vec Nat 3 := (| [1, 2, 3], rfl |)
|
||||
example : vec Nat 3 := { vec . l := [1, 2, 3], h := rfl }
|
||||
example : vec Nat 3 := { l := [1, 2, 3], h := rfl }
|
||||
|
||||
example : Foo Nat (Vec Nat) := ⟨1, 3, bzz⟩
|
||||
example : foo Nat (vec Nat) := ⟨1, 3, bzz⟩
|
||||
|
||||
example : Baz := ⟨⟨1, 3, bzz⟩, ⟨5, 7⟩, bzz⟩
|
||||
example : Baz := { a := 1, n := 3, b := bzz, c := 5, d := 7, v := bzz}
|
||||
def fzz : Foo Nat (Vec Nat) := {a := 1, n := 3, b := bzz}
|
||||
example : baz := ⟨⟨1, 3, bzz⟩, ⟨5, 7⟩, bzz⟩
|
||||
example : baz := { a := 1, n := 3, b := bzz, c := 5, d := 7, v := bzz}
|
||||
def fzz : foo Nat (vec Nat) := {a := 1, n := 3, b := bzz}
|
||||
|
||||
example : Foo Nat (Vec Nat) := { fzz with a := 7 }
|
||||
example : Baz := { fzz with c := 5, d := 7, v := bzz }
|
||||
example : foo Nat (vec Nat) := { fzz with a := 7 }
|
||||
example : baz := { fzz with c := 5, d := 7, v := bzz }
|
||||
|
||||
example : Bar := { c := 8, d := 9 }
|
||||
example : Bar := { d := 9 } -- uses the default value for c
|
||||
example : bar := { c := 8, d := 9 }
|
||||
example : bar := { d := 9 } -- uses the default value for c
|
||||
|
||||
.. _type_classes:
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
|
||||
# Lean Build Bootstrapping
|
||||
|
||||
Since version 4, Lean is a partially bootstrapped program: most parts of the
|
||||
@@ -15,18 +16,17 @@ stage0/
|
||||
bin/lean
|
||||
stage1/
|
||||
include/
|
||||
config.h # config variables used to build `lean` such as used allocator
|
||||
runtime/lean.h # runtime header, used by extracted C code, uses `config.h`
|
||||
config.h # config variables used to build `lean` such as use allocator
|
||||
runtime/lean.h # runtime headers, used by extracted C code, uses `config.h`
|
||||
share/lean/
|
||||
lean.mk # used by `leanmake`
|
||||
Makefile # used by `leanmake`
|
||||
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/
|
||||
lean # the Lean compiler & server, a small executable that calls directly into libleanshared.so
|
||||
lean # the Lean compiler & server linked together from the above libraries
|
||||
leanc # a wrapper around a C compiler supplying search paths etc
|
||||
leanmake # a wrapper around `make` supplying the Makefile above
|
||||
stage2/...
|
||||
@@ -55,7 +55,7 @@ We are not aware of any "meta-meta" parts that influence more than two stages of
|
||||
compilation, so stage 3 should always be identical to stage 2 and only exists as
|
||||
a sanity check.
|
||||
|
||||
In summary, doing a standard build via `make` internally involves these steps:
|
||||
In summary, doing a standard build via `make` involves these steps:
|
||||
|
||||
1. compile the `stage0/src` archived sources into `stage0/bin/lean`
|
||||
1. use it to compile the current library (*including* your changes) into `stage1/lib`
|
||||
@@ -119,4 +119,4 @@ affect later stages. This is an issue in two specific cases.
|
||||
To modify either of these flags both for building and editing the stdlib, adjust
|
||||
the code in `stage0/src/stdlib_flags.h`. The flags will automatically be reset
|
||||
on the next `update-stage0` when the file is overwritten with the original
|
||||
version in `src/`.
|
||||
version in `src/`.
|
||||
155
doc/dev/cpp_coding_style.md
Normal file
155
doc/dev/cpp_coding_style.md
Normal file
@@ -0,0 +1,155 @@
|
||||
[google-style]: https://google.github.io/styleguide/cppguide.html
|
||||
[cpplint]: /src/cmake/Modules/cpplint.py
|
||||
|
||||
# Coding Style
|
||||
|
||||
The Lean project is moving away from using any C++ as more and more of
|
||||
the compiler is being bootstrapped in Lean itself. But the remaining
|
||||
C++ codebase is using modified version of [Google's C++ Style
|
||||
Guide][google-style].
|
||||
|
||||
## [C++11](http://en.wikipedia.org/wiki/C%2B%2B11) features
|
||||
|
||||
Lean makes extensive use of new features in the C++ 11 standard.
|
||||
Developers must be familiar with the standard to be able to understand
|
||||
the code. Here are some of the features that are extensively used.
|
||||
|
||||
- Type inference (aka `auto` keyword).
|
||||
- Initializer lists.
|
||||
- Lambda functions and expressions.
|
||||
- `nullptr` constant.
|
||||
- Strongly typed enumerations.
|
||||
- Right angle brackets with no space is now allowed in C++ 11.
|
||||
- Thread local storage.
|
||||
- Threading facilities.
|
||||
- Tuple types.
|
||||
- Smart pointers.
|
||||
- When using ``std::list`` make sure to include the `std::`
|
||||
qualifier so you do not accidentally use the ``lean::list`` type.
|
||||
- When using ``std::copy`` make sure to include the `std::`
|
||||
qualifier so you do not accidentally use the ``lean::copy`` type.
|
||||
- Small and focused functions are preferred: foo(). Try not to
|
||||
exceed 500 lines in a function, except in tests.
|
||||
- Do **not** use the `#ifndef-#define-#endif` idiom for header files.
|
||||
Instead use `#pragma once`.
|
||||
- Write `type const & v` instead of `const type & v`.
|
||||
- Use `const` extensively.
|
||||
- Use the macro `lean_assert` for assertions. The macro `lean_assert`
|
||||
is extensively used when writing unit tests.
|
||||
|
||||
## Naming
|
||||
|
||||
- Class, method, and function names are lower case
|
||||
Use `_` for composite names. Example: `type_checker`.
|
||||
- Class/struct fields should start with the prefix `m_`.
|
||||
|
||||
Example:
|
||||
```c++
|
||||
class point {
|
||||
int m_x;
|
||||
int m_y;
|
||||
public:
|
||||
...
|
||||
};
|
||||
```
|
||||
|
||||
## Namespaces
|
||||
|
||||
All code is in the `lean` namespace. Each frontend is stored in a
|
||||
separate nested namespace. For example, the SMT 2.0 frontend is stored
|
||||
in the `lean::smt` namespace.
|
||||
|
||||
Exception: some debugging functions are stored outside of the `lean`
|
||||
namespace. These functions are called `print` and are meant to be used
|
||||
when debugging Lean using `gdb`.
|
||||
|
||||
Do not use `using namespace` in a header file.
|
||||
|
||||
## Templates
|
||||
|
||||
Organize template source code using the approach described at http://www.codeproject.com/Articles/3515/How-To-Organize-Template-Source-Code
|
||||
|
||||
## Idioms
|
||||
|
||||
Use some popular C++ idioms:
|
||||
|
||||
- [Pimpl](http://c2.com/cgi/wiki?PimplIdiom)
|
||||
- [RAII](http://en.wikipedia.org/wiki/Resource_Acquisition_Is_Initialization) Resource Acquisition Is Initialization
|
||||
|
||||
## Formatting
|
||||
|
||||
* Use 4 spaces for indentation.
|
||||
|
||||
* `if-then-else` curly brackets not always required. The following
|
||||
forms are acceptable:
|
||||
|
||||
```c++
|
||||
if (cond) {
|
||||
...
|
||||
} else {
|
||||
...
|
||||
}
|
||||
```
|
||||
and
|
||||
|
||||
```c++
|
||||
if (cond)
|
||||
statement1;
|
||||
else
|
||||
statement2;
|
||||
```
|
||||
|
||||
In *exceptional cases*, we also use
|
||||
|
||||
```c++
|
||||
if (cond) statement;
|
||||
```
|
||||
|
||||
and
|
||||
|
||||
```c++
|
||||
if (cond) statement1; else stament2;
|
||||
```
|
||||
* `if-then-else-if-else`
|
||||
|
||||
The following forms are acceptable:
|
||||
|
||||
```c++
|
||||
if (cond) {
|
||||
...
|
||||
} else if (cond) {
|
||||
...
|
||||
} else {
|
||||
...
|
||||
}
|
||||
```c++
|
||||
and
|
||||
|
||||
```c++
|
||||
if (cond)
|
||||
statement1;
|
||||
else if (cond)
|
||||
statement2;
|
||||
else
|
||||
statement3;
|
||||
```
|
||||
|
||||
* Format code using extra spaces to make code more readable. For example:
|
||||
|
||||
```c++
|
||||
environment const & m_env;
|
||||
cache m_cache;
|
||||
normalizer m_normalizer;
|
||||
volatile bool m_interrupted;
|
||||
```
|
||||
instead of:
|
||||
|
||||
```c++
|
||||
environment const & m_env;
|
||||
cache m_cache;
|
||||
normalizer m_normalizer;
|
||||
volatile bool m_interrupted;
|
||||
```
|
||||
|
||||
* Spaces in expressions. Write `a == b` instead of `a==b`. Similarly,
|
||||
we write `x < y + 1` instead of `x<y+1`.
|
||||
@@ -20,9 +20,8 @@ Notable trace classes:
|
||||
* `Meta.isDefEq`: unification
|
||||
* `interpreter`: full execution trace of the interpreter. Only available in debug builds.
|
||||
|
||||
In pure contexts or when execution is aborted before the messages are finally printed, one can instead use the term `dbg_trace "msg with {interpolations}"; val` (`;` can also be replaced by a newline), which will print the message to stderr before evaluating `val`. `dbgTraceVal val` can be used as a shorthand for `dbg_trace "{val}"; val`.
|
||||
In pure contexts or when execution is aborted before the messages are finally printed, one can instead use the term `dbg_trace "msg with {interpolations}"; val` (`;` can also be replaced by a newline), which will print the message directly to stderr before evaluating `val`. `dbgTraceVal val` can be used as a shorthand for `dbg_trace "{val}"; val`.
|
||||
Note that if the return value is not actually used, the trace code is silently dropped as well.
|
||||
In the language server, stderr output is buffered and shown as messages after a command has been elaborated, unless the option `server.stderrAsMessages` is deactivated.
|
||||
|
||||
## Debuggers
|
||||
|
||||
|
||||
@@ -43,7 +43,6 @@ 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 `α`.
|
||||
@@ -67,45 +66,33 @@ Return values and `@[export]` parameters are always owned at the moment.
|
||||
## Initialization
|
||||
|
||||
When including Lean code as part of a larger program, modules must be *initialized* before accessing any of their declarations.
|
||||
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 `[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 `[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.
|
||||
Module initializers are idempotent, 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:
|
||||
```c
|
||||
void lean_initialize_runtime_module();
|
||||
void lean_initialize();
|
||||
lean_object * initialize_A_B(uint8_t builtin, lean_object *);
|
||||
lean_object * initialize_C(uint8_t builtin, lean_object *);
|
||||
lean_object * initialize_A_B(lean_object *);
|
||||
lean_object * initialize_C(lean_object *);
|
||||
...
|
||||
|
||||
lean_initialize_runtime_module();
|
||||
//lean_initialize(); // necessary if you (indirectly) access the `Lean` package
|
||||
|
||||
lean_object * res;
|
||||
// use same default as for Lean executables
|
||||
uint8_t builtin = 1;
|
||||
res = initialize_A_B(builtin, lean_io_mk_world());
|
||||
res = initialize_A_B(lean_io_mk_world());
|
||||
if (lean_io_result_is_ok(res)) {
|
||||
lean_dec_ref(res);
|
||||
} else {
|
||||
lean_io_result_show_error(res);
|
||||
lean_dec(res);
|
||||
return ...; // do not access Lean declarations if initialization failed
|
||||
return ...; // do not access Lean declarations if initialization failed
|
||||
}
|
||||
res = initialize_C(builtin, lean_io_mk_world());
|
||||
res = initialize_C(lean_io_mk_world());
|
||||
if (lean_io_result_is_ok(res)) {
|
||||
...
|
||||
|
||||
//lean_init_task_manager(); // necessary if you (indirectly) use `Task`
|
||||
//lean_init_task_manager(); // necessary if you (indirectly) use `Task`
|
||||
lean_io_mark_end_initialization();
|
||||
```
|
||||
|
||||
|
||||
39
doc/dev/fixing_tests.md
Normal file
39
doc/dev/fixing_tests.md
Normal file
@@ -0,0 +1,39 @@
|
||||
Fixing Tests
|
||||
============
|
||||
|
||||
The test suite contains some tests that compare the produced output
|
||||
with the expected output. For example, the directory `tests/lean`
|
||||
contains files such as [`bad_class.lean`](../tests/lean/bad_class.lean) and
|
||||
[`bad_class.lean.expected.out`](../tests/lean/bad_class.lean.expected.out).
|
||||
The later contains the expected output for the test file `bad_class.lean`.
|
||||
|
||||
When the Lean source code or the standard library are modified, some of these
|
||||
tests break because the produced output is slightly different, and we have
|
||||
to reflect the changes in the `.lean.expected.out` files.
|
||||
We should not blindly copy the new produced output since we may accidentally
|
||||
miss a bug introduced by recent changes.
|
||||
The test suite contains commands that allow us to see what changed in a convenient way.
|
||||
First, we must install [meld](http://meldmerge.org/). On Ubuntu, we can do it by simply executing
|
||||
|
||||
```
|
||||
sudo apt-get install meld
|
||||
```
|
||||
|
||||
Now, suppose `bad_class.lean` test is broken. We can see the problem by going to `test/lean` directory and
|
||||
executing
|
||||
|
||||
```
|
||||
./test_single.sh -i bad_class.lean
|
||||
```
|
||||
|
||||
When the `-i` option is provided, `meld` is automatically invoked
|
||||
whenever there is discrepancy between the produced and expected
|
||||
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,30 +1,48 @@
|
||||
# Development Workflow
|
||||
- [Commit Convention](./commit_convention.md)
|
||||
- [Building Lean](../make/index.md)
|
||||
- [Ubuntu Setup](../make/ubuntu.md)
|
||||
- [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)
|
||||
- [Unit Testing](./testing.md)
|
||||
- [Building This Manual](./mdbook.md)
|
||||
- [Fixing Tests](./fixing_tests.md)
|
||||
- [Debugging](./debugging.md)
|
||||
- [C++ Coding Style](./dev/cpp_coding_style.md)
|
||||
|
||||
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).
|
||||
You will notice there is a `stage0` folder. This is for bootstrapping
|
||||
the compiler development. Generally you do not change any code in
|
||||
`stage0` manually. It is important that you read [bootstrapping
|
||||
pipeline](bootstrap.md) so you understand how this works.
|
||||
|
||||
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).
|
||||
You should not edit the `stage0` directory except using the commands described in that section when necessary.
|
||||
The dev team uses `elan` to manage which `lean` toolchain to use
|
||||
locally and `elan` can be used to setup the version of Lean you are
|
||||
manually building. This means you generally do not use `make
|
||||
install`. You use `elan` instead.
|
||||
|
||||
## Development Setup
|
||||
|
||||
You can use any of the [supported editors](../setup.md) for editing the Lean source code.
|
||||
If you set up `elan` as below, opening `src/` as a *workspace folder* should ensure that stage 0 (i.e. the stage that first compiles `src/`) will be used for files in that directory.
|
||||
You can use any of the [supported editors](../setup.md) for editing
|
||||
the Lean source code. If you set up `elan` as below, opening `src/` as
|
||||
a *workspace folder* should ensure that stage 0 will be used for file
|
||||
in that directory.
|
||||
|
||||
### Dev setup using elan
|
||||
## Dev setup using elan
|
||||
|
||||
You can use [`elan`](https://github.com/leanprover/elan) to easily
|
||||
switch between stages and build configurations based on the current
|
||||
directory, both for the `lean`, `leanc`, and `leanmake` binaries in your shell's
|
||||
PATH and inside your editor.
|
||||
|
||||
To install elan, you can do so, without installing a default version of Lean, using (Unix)
|
||||
To install elan, you can do so, without installing a default version of Lean, using
|
||||
|
||||
```bash
|
||||
[Unix]
|
||||
curl https://raw.githubusercontent.com/leanprover/elan/master/elan-init.sh -sSf | sh -s -- --default-toolchain none
|
||||
```
|
||||
or (Windows)
|
||||
```
|
||||
|
||||
[Windows]
|
||||
curl -O --location https://raw.githubusercontent.com/leanprover/elan/master/elan-init.ps1
|
||||
powershell -f elan-init.ps1 --default-toolchain none
|
||||
del elan-init.ps1
|
||||
@@ -45,7 +63,6 @@ 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
|
||||
toolchains on the spot. `lean4-mode` will automatically use the `lean` executable
|
||||
associated with the directory of the current file as long as `lean4-rootdir` is
|
||||
|
||||
@@ -1,12 +1,3 @@
|
||||
# Documentation
|
||||
|
||||
The Lean `doc` folder contains the [Lean Manual](https://leanprover.github.io/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,84 +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 copy the resulting
|
||||
executable to your `$HOME/.elan/bin` folder or `%USERPROFILE%\.elan\bin` so Alectryon can find it
|
||||
there.
|
||||
Run `mdbook test` to test all `lean` code blocks.
|
||||
|
||||
1. Create a Python 3.10 environment.
|
||||
|
||||
1. Install the following packages:
|
||||
```
|
||||
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
|
||||
```
|
||||
|
||||
And 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://leanprover.github.io/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
|
||||
```
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
# Test Suite
|
||||
# Unit Testing
|
||||
|
||||
After [building Lean](../make/index.md) you can run all the tests using
|
||||
You can run the unit tests after completing a build using the following:
|
||||
|
||||
After [building lean](../make/index.md) you can run all the tests using
|
||||
```
|
||||
cd build/release
|
||||
make test ARGS=-j4
|
||||
@@ -27,11 +29,10 @@ ctest -j 4 --output-on-failure --timeout 300
|
||||
|
||||
To get verbose output from ctest pass the `--verbose` command line
|
||||
option. Test output is normally suppressed and only summary
|
||||
information is displayed. This option will show all test output.
|
||||
information is displayed. This option will show all test output
|
||||
|
||||
## Test Suite Organization
|
||||
|
||||
All these tests are included by [src/shell/CMakeLists.txt](https://github.com/leanprover/lean4/blob/master/src/shell/CMakeLists.txt):
|
||||
Here is the summary of the test source code organization.
|
||||
All these tests are included by [/src/shell/CMakeLists.txt](https://github.com/leanprover/lean4/blob/master/src/shell/CMakeLists.txt):
|
||||
|
||||
- `tests/lean`: contains tests that come equipped with a
|
||||
.lean.expected.out file. The driver script `test_single.sh` runs
|
||||
@@ -87,35 +88,11 @@ All these tests are included by [src/shell/CMakeLists.txt](https://github.com/le
|
||||
- `tests/plugin`: tests that compiled Lean code can be loaded into
|
||||
`lean` via the `--plugin` command line option.
|
||||
|
||||
## Fixing Tests
|
||||
|
||||
When the Lean source code or the standard library are modified, some of the
|
||||
tests break because the produced output is slightly different, and we have
|
||||
to reflect the changes in the `.lean.expected.out` files.
|
||||
We should not blindly copy the new produced output since we may accidentally
|
||||
miss a bug introduced by recent changes.
|
||||
The test suite contains commands that allow us to see what changed in a convenient way.
|
||||
First, we must install [meld](http://meldmerge.org/). On Ubuntu, we can do it by simply executing
|
||||
|
||||
```
|
||||
sudo apt-get install meld
|
||||
```
|
||||
|
||||
Now, suppose `bad_class.lean` test is broken. We can see the problem by going to `test/lean` directory and
|
||||
executing
|
||||
|
||||
```
|
||||
./test_single.sh -i bad_class.lean
|
||||
```
|
||||
|
||||
When the `-i` option is provided, `meld` is automatically invoked
|
||||
whenever there is discrepancy between the produced and expected
|
||||
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`.
|
||||
- `tests/leanpkg`: tests the `leanpkg` program, where each sub-folder
|
||||
is a complete "lean package", including:
|
||||
- `cyclic`
|
||||
- `user_ext`
|
||||
- `user_attr`
|
||||
- `user_opt`
|
||||
- `prv`
|
||||
- `user_attr_app`
|
||||
|
||||
40
doc/do.md
40
doc/do.md
@@ -348,46 +348,6 @@ TODO: describe `forIn`
|
||||
|
||||
TODO
|
||||
|
||||
## Returning early from a failed match
|
||||
|
||||
Inside a `do` block, the pattern `let _ ← <success> | <fail>` will continue with the rest of the block if the match on the left hand side succeeds, but will execute the right hand side and exit the block on failure:
|
||||
|
||||
```lean
|
||||
def showUserInfo (getUsername getFavoriteColor : IO (Option String)) : IO Unit := do
|
||||
let some n ← getUsername | IO.println "no username!"
|
||||
IO.println s!"username: {n}"
|
||||
let some c ← getFavoriteColor | IO.println "user didn't provide a favorite color!"
|
||||
IO.println s!"favorite color: {c}"
|
||||
|
||||
-- username: JohnDoe
|
||||
-- favorite color: red
|
||||
#eval showUserInfo (pure <| some "JohnDoe") (pure <| some "red")
|
||||
|
||||
-- no username
|
||||
#eval showUserInfo (pure none) (pure <| some "purple")
|
||||
|
||||
-- username: JaneDoe
|
||||
-- user didn't provide a favorite color
|
||||
#eval showUserInfo (pure <| some "JaneDoe") (pure none)
|
||||
```
|
||||
|
||||
## If-let
|
||||
|
||||
Inside a `do` block, users can employ the `if let` pattern to destructure actions:
|
||||
|
||||
```lean
|
||||
def tryIncrement (getInput : IO (Option Nat)) : IO (Except String Nat) := do
|
||||
if let some n ← getInput
|
||||
then return Except.ok n.succ
|
||||
else return Except.error "argument was `none`"
|
||||
|
||||
-- Except.ok 2
|
||||
#eval tryIncrement (pure <| some 1)
|
||||
|
||||
-- Except.error "argument was `none`"
|
||||
#eval tryIncrement (pure <| none)
|
||||
```
|
||||
|
||||
## Pattern matching
|
||||
|
||||
TODO
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
## Elaborators
|
||||
|
||||
TODO. See [Lean Together 2021: Metaprogramming in Lean
|
||||
4](https://youtu.be/hxQ1vvhYN_U) for an overview as well [the
|
||||
continuation](https://youtu.be/vy4JWIiiXSY) about tactic programming.
|
||||
For more information on antiquotations, see also §4.1 of [Beyond
|
||||
Notations: Hygienic Macro Expansion for Theorem Proving
|
||||
Languages](https://arxiv.org/pdf/2001.10490.pdf#page=11).
|
||||
@@ -1,9 +0,0 @@
|
||||
Examples
|
||||
========
|
||||
|
||||
- [Palindromes](examples/palindromes.lean.md)
|
||||
- [Binary Search Trees](examples/bintree.lean.md)
|
||||
- [A Certified Type Checker](examples/tc.lean.md)
|
||||
- [The Well-Typed Interpreter](examples/interp.lean.md)
|
||||
- [Dependent de Bruijn Indices](examples/deBruijn.lean.md)
|
||||
- [Parametric Higher-Order Abstract Syntax](examples/phoas.lean.md)
|
||||
@@ -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)
|
||||
|
||||
|
||||
|
||||
@@ -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 x 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,295 +0,0 @@
|
||||
/-!
|
||||
# Binary Search Trees
|
||||
|
||||
If the type of keys can be totally ordered -- that is, it supports a well-behaved `≤` comparison --
|
||||
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 ["Sofware Foundations"](https://softwarefoundations.cis.upenn.edu/vfa-current/SearchTree.html)
|
||||
book (volume 3).
|
||||
-/
|
||||
|
||||
/-!
|
||||
We use `Nat` as the key type in our implementation of BSTs,
|
||||
since it has a convenient total order with lots of theorems and automation available.
|
||||
We leave as an exercise to the reader the generalization to arbitrary types.
|
||||
-/
|
||||
|
||||
inductive Tree (β : Type v) where
|
||||
| leaf
|
||||
| node (left : Tree β) (key : Nat) (value : β) (right : Tree β)
|
||||
deriving Repr
|
||||
|
||||
/-!
|
||||
The function `contains` returns `true` iff the given tree contains the key `k`.
|
||||
-/
|
||||
def Tree.contains (t : Tree β) (k : Nat) : Bool :=
|
||||
match t with
|
||||
| leaf => false
|
||||
| node left key value right =>
|
||||
if k < key then
|
||||
left.contains k
|
||||
else if key < k then
|
||||
right.contains k
|
||||
else
|
||||
true
|
||||
|
||||
/-!
|
||||
`t.find? k` returns `some v` if `v` is the value bound to key `k` in the tree `t`. It returns `none` otherwise.
|
||||
-/
|
||||
def Tree.find? (t : Tree β) (k : Nat) : Option β :=
|
||||
match t with
|
||||
| leaf => none
|
||||
| node left key value right =>
|
||||
if k < key then
|
||||
left.find? k
|
||||
else if key < k then
|
||||
right.find? k
|
||||
else
|
||||
some value
|
||||
|
||||
/-!
|
||||
`t.insert k v` is the map containing all the bindings of `t` along with a binding of `k` to `v`.
|
||||
-/
|
||||
def Tree.insert (t : Tree β) (k : Nat) (v : β) : Tree β :=
|
||||
match t with
|
||||
| leaf => node leaf k v leaf
|
||||
| node left key value right =>
|
||||
if k < key then
|
||||
node (left.insert k v) key value right
|
||||
else if key < k then
|
||||
node left key value (right.insert k v)
|
||||
else
|
||||
node left k v right
|
||||
/-!
|
||||
Let's add a new operation to our tree: converting it to an association list that contains the key--value bindings from the tree stored as pairs.
|
||||
If that list is sorted by the keys, then any two trees that represent the same map would be converted to the same list.
|
||||
Here's a function that does so with an in-order traversal of the tree.
|
||||
-/
|
||||
def Tree.toList (t : Tree β) : List (Nat × β) :=
|
||||
match t with
|
||||
| leaf => []
|
||||
| node l k v r => l.toList ++ [(k, v)] ++ r.toList
|
||||
|
||||
#eval Tree.leaf.insert 2 "two"
|
||||
|>.insert 3 "three"
|
||||
|>.insert 1 "one"
|
||||
|
||||
#eval Tree.leaf.insert 2 "two"
|
||||
|>.insert 3 "three"
|
||||
|>.insert 1 "one"
|
||||
|>.toList
|
||||
|
||||
/-!
|
||||
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
|
||||
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 × β) :=
|
||||
go t []
|
||||
where
|
||||
go (t : Tree β) (acc : List (Nat × β)) : List (Nat × β) :=
|
||||
match t with
|
||||
| leaf => acc
|
||||
| node l k v r => go l ((k, v) :: go r acc)
|
||||
|
||||
/-!
|
||||
We now prove that `t.toList` and `t.toListTR` return the same list.
|
||||
The proof is on induction, and as we used the auxiliary function `go`
|
||||
to define `Tree.toListTR`, we use the auxiliary theorem `go` to prove the theorem.
|
||||
|
||||
The proof of the auxiliary theorem is by induction on `t`.
|
||||
The `generalizing acc` modifier instructs Lean to revert `acc`, apply the
|
||||
induction theorem for `Tree`s, and then reintroduce `acc` in each case.
|
||||
By using `generalizing`, we obtain the more general induction hypotheses
|
||||
|
||||
- `left_ih : ∀ acc, toListTR.go left acc = toList left ++ acc`
|
||||
|
||||
- `right_ih : ∀ acc, toListTR.go right acc = toList right ++ acc`
|
||||
|
||||
Recall that the combinator `tac <;> tac'` runs `tac` on the main goal and `tac'` on each produced goal,
|
||||
concatenating all goals produced by `tac'`. In this theorem, we use it to apply
|
||||
`simp` and close each subgoal produced by the `induction` tactic.
|
||||
|
||||
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 `*` 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` intructs the simplifier to use the
|
||||
`List.append_assoc` theorem as a rewriting rule.
|
||||
-/
|
||||
theorem Tree.toList_eq_toListTR (t : Tree β)
|
||||
: t.toList = t.toListTR := by
|
||||
simp [toListTR, go t []]
|
||||
where
|
||||
go (t : Tree β) (acc : List (Nat × β))
|
||||
: toListTR.go t acc = t.toList ++ acc := by
|
||||
induction t generalizing acc <;>
|
||||
simp [toListTR.go, toList, *, List.append_assoc]
|
||||
|
||||
/-!
|
||||
The `[csimp]` annotation instructs the Lean code generator to replace
|
||||
any `Tree.toList` with `Tree.toListTR` when generating code.
|
||||
-/
|
||||
@[csimp] theorem Tree.toList_eq_toListTR_csimp
|
||||
: @Tree.toList = @Tree.toListTR := by
|
||||
funext β t
|
||||
apply toList_eq_toListTR
|
||||
|
||||
/-!
|
||||
The implementations of `Tree.find?` and `Tree.insert` assume that values of type tree obey the BST invariant:
|
||||
for any non-empty node with key `k`, all the values of the `left` subtree are less than `k` and all the values
|
||||
of the right subtree are greater than `k`. But that invariant is not part of the definition of tree.
|
||||
|
||||
So, let's formalize the BST invariant. Here's one way to do so. First, we define a helper `ForallTree`
|
||||
to express that idea that a predicate holds at every node of a tree:
|
||||
-/
|
||||
inductive ForallTree (p : Nat → β → Prop) : Tree β → Prop
|
||||
| leaf : ForallTree p .leaf
|
||||
| node :
|
||||
ForallTree p left →
|
||||
p key value →
|
||||
ForallTree p right →
|
||||
ForallTree p (.node left key value right)
|
||||
|
||||
/-!
|
||||
Second, we define the BST invariant:
|
||||
An empty tree is a BST.
|
||||
A non-empty tree is a BST if all its left nodes have a lesser key, its right nodes have a greater key, and the left and right subtrees are themselves BSTs.
|
||||
-/
|
||||
inductive BST : Tree β → Prop
|
||||
| leaf : BST .leaf
|
||||
| node :
|
||||
ForallTree (fun k v => k < key) left →
|
||||
ForallTree (fun k v => key < k) right →
|
||||
BST left → BST right →
|
||||
BST (.node left key value right)
|
||||
|
||||
/-!
|
||||
We can use the `macro` command to create helper tactics for organizing our proofs.
|
||||
The macro `have_eq x y` tries to prove `x = y` using linear arithmetic, and then
|
||||
immediately uses the new equality to substitute `x` with `y` everywhere in the goal.
|
||||
|
||||
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 =>
|
||||
`((have h : $lhs = $rhs :=
|
||||
-- TODO: replace with linarith
|
||||
by simp_arith at *; apply Nat.le_antisymm <;> assumption
|
||||
try subst $lhs))
|
||||
|
||||
/-!
|
||||
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 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 =>
|
||||
`(by_cases $e <;> simp [*])
|
||||
|
||||
|
||||
/-!
|
||||
We can use the attribute `[simp]` to instruct the simplifier to reduce given definitions or
|
||||
apply rewrite theorems. The `local` modifier limits the scope of this modification to this file.
|
||||
-/
|
||||
attribute [local simp] Tree.insert
|
||||
|
||||
/-!
|
||||
We now prove that `Tree.insert` preserves the BST invariant using induction and case analysis.
|
||||
Recall that the tactic `. tac` focuses on the main goal and tries to solve it using `tac`, or else fails.
|
||||
It is used to structure proofs in Lean.
|
||||
The notation `‹e›` is just syntax sugar for `(by assumption : e)`. That is, it tries to find a hypothesis `h : e`.
|
||||
It is useful to access hypothesis that have auto generated names (aka "inaccessible") names.
|
||||
-/
|
||||
|
||||
theorem Tree.forall_insert_of_forall
|
||||
(h₁ : ForallTree p t) (h₂ : p key value)
|
||||
: ForallTree p (t.insert key value) := by
|
||||
induction h₁ with
|
||||
| leaf => exact .node .leaf h₂ .leaf
|
||||
| node hl hp hr ihl ihr =>
|
||||
rename Nat => k
|
||||
by_cases' key < k
|
||||
. exact .node ihl hp hr
|
||||
. by_cases' k < key
|
||||
. exact .node hl hp ihr
|
||||
. have_eq key k
|
||||
exact .node hl h₂ hr
|
||||
|
||||
theorem Tree.bst_insert_of_bst
|
||||
{t : Tree β} (h : BST t) (key : Nat) (value : β)
|
||||
: BST (t.insert key value) := by
|
||||
induction h with
|
||||
| leaf => exact .node .leaf .leaf .leaf .leaf
|
||||
| node h₁ h₂ b₁ b₂ ih₁ ih₂ =>
|
||||
rename Nat => k
|
||||
simp
|
||||
by_cases' key < k
|
||||
. exact .node (forall_insert_of_forall h₁ ‹key < k›) h₂ ih₁ b₂
|
||||
. by_cases' k < key
|
||||
. exact .node h₁ (forall_insert_of_forall h₂ ‹k < key›) b₁ ih₂
|
||||
. have_eq key k
|
||||
exact .node h₁ h₂ b₁ b₂
|
||||
|
||||
/-!
|
||||
Now, we define the type `BinTree` using a `Subtype` that states that only trees satisfying the BST invariant are `BinTree`s.
|
||||
-/
|
||||
def BinTree (β : Type u) := { t : Tree β // BST t }
|
||||
|
||||
def BinTree.mk : BinTree β :=
|
||||
⟨.leaf, .leaf⟩
|
||||
|
||||
def BinTree.contains (b : BinTree β) (k : Nat) : Bool :=
|
||||
b.val.contains k
|
||||
|
||||
def BinTree.find? (b : BinTree β) (k : Nat) : Option β :=
|
||||
b.val.find? k
|
||||
|
||||
def BinTree.insert (b : BinTree β) (k : Nat) (v : β) : BinTree β :=
|
||||
⟨b.val.insert k v, b.val.bst_insert_of_bst b.property k v⟩
|
||||
|
||||
/-!
|
||||
Finally, we prove that `BinTree.find?` and `BinTree.insert` satisfy the map properties.
|
||||
-/
|
||||
|
||||
attribute [local simp]
|
||||
BinTree.mk BinTree.contains BinTree.find?
|
||||
BinTree.insert Tree.find? Tree.contains Tree.insert
|
||||
|
||||
theorem BinTree.find_mk (k : Nat)
|
||||
: BinTree.mk.find? k = (none : Option β) := by
|
||||
simp
|
||||
|
||||
theorem BinTree.find_insert (b : BinTree β) (k : Nat) (v : β)
|
||||
: (b.insert k v).find? k = some v := by
|
||||
let ⟨t, h⟩ := b; simp
|
||||
induction t with simp
|
||||
| node left key value right ihl ihr =>
|
||||
by_cases' k < key
|
||||
. cases h; apply ihl; assumption
|
||||
. by_cases' key < k
|
||||
cases h; apply ihr; assumption
|
||||
|
||||
theorem BinTree.find_insert_of_ne (b : BinTree β) (h : k ≠ k') (v : β)
|
||||
: (b.insert k v).find? k' = b.find? k' := by
|
||||
let ⟨t, h⟩ := b; simp
|
||||
induction t with simp
|
||||
| leaf =>
|
||||
split <;> simp <;> split <;> simp
|
||||
have_eq k k'
|
||||
contradiction
|
||||
| node left key value right ihl ihr =>
|
||||
let .node hl hr bl br := h
|
||||
specialize ihl bl
|
||||
specialize ihr br
|
||||
by_cases' k < key; by_cases' key < k
|
||||
have_eq key k
|
||||
by_cases' k' < k; by_cases' k < k'
|
||||
have_eq k k'
|
||||
contradiction
|
||||
@@ -1,5 +0,0 @@
|
||||
(this example is rendered by Alectryon in the CI)
|
||||
|
||||
```lean
|
||||
{{#include bintree.lean}}
|
||||
```
|
||||
@@ -1,163 +0,0 @@
|
||||
/-!
|
||||
# Dependent de Bruijn Indices
|
||||
|
||||
In this example, we represent program syntax terms in a type family parameterized by a list of types,
|
||||
representing the typing context, or information on which free variables are in scope and what
|
||||
their types are.
|
||||
|
||||
|
||||
Remark: this example is based on an example in the book [Certified Programming with Dependent Types](http://adam.chlipala.net/cpdt/) by Adam Chlipala.
|
||||
-/
|
||||
|
||||
/-!
|
||||
Programmers who move to statically typed functional languages from scripting languages
|
||||
often complain about the requirement that every element of a list have the same type. With
|
||||
fancy type systems, we can partially lift this requirement. We can index a list type with a
|
||||
“type-level” list that explains what type each element of the list should have. This has been
|
||||
done in a variety of ways in Haskell using type classes, and we can do it much more cleanly
|
||||
and directly in Lean.
|
||||
|
||||
We parameterize our heterogeneous lists by at type `α` and an `α`-indexed type `β`.
|
||||
-/
|
||||
|
||||
inductive HList {α : Type v} (β : α → Type u) : List α → Type (max u v)
|
||||
| nil : HList β []
|
||||
| cons : β i → HList β is → HList β (i::is)
|
||||
|
||||
/-!
|
||||
We overload the `List.cons` notation `::` so we can also use it to create
|
||||
heterogeneous lists.
|
||||
-/
|
||||
infix:67 " :: " => HList.cons
|
||||
|
||||
/-! We similarly overload the `List` notation `[]` for the empty heterogeneous list. -/
|
||||
notation "[" "]" => HList.nil
|
||||
|
||||
/-!
|
||||
Variables are represented in a way isomorphic to the natural numbers, where
|
||||
number 0 represents the first element in the context, number 1 the second element, and so
|
||||
on. Actually, instead of numbers, we use the `Member` inductive family.
|
||||
|
||||
The value of type `Member a as` can be viewed as a certificate that `a` is
|
||||
an element of the list `as`. The constructor `Member.head` says that `a`
|
||||
is in the list if the list begins with it. The constructor `Member.tail`
|
||||
says that if `a` is in the list `bs`, it is also in the list `b::bs`.
|
||||
-/
|
||||
inductive Member : α → List α → Type
|
||||
| head : Member a (a::as)
|
||||
| tail : Member a bs → Member a (b::bs)
|
||||
|
||||
/-!
|
||||
Given a heterogeneous list `HList β is` and value of type `Member i is`, `HList.get`
|
||||
retrieves an element of type `β i` from the list.
|
||||
The pattern `.head` and `.tail h` are sugar for `Member.head` and `Member.tail h` respectively.
|
||||
Lean can infer the namespace using the expected type.
|
||||
-/
|
||||
def HList.get : HList β is → Member i is → β i
|
||||
| a::as, .head => a
|
||||
| a::as, .tail h => as.get h
|
||||
|
||||
/-!
|
||||
Here is the definition of the simple type system for our programming language, a simply typed
|
||||
lambda calculus with natural numbers as the base type.
|
||||
-/
|
||||
inductive Ty where
|
||||
| nat
|
||||
| fn : Ty → Ty → Ty
|
||||
|
||||
/-!
|
||||
We can write a function to translate `Ty` values to a Lean type
|
||||
— remember that types are first class, so can be calculated just like any other value.
|
||||
We mark `Ty.denote` as `[reducible]` to make sure the typeclass resolution procedure can
|
||||
unfold/reduce it. For example, suppose Lean is trying to synthesize a value for the instance
|
||||
`Add (Ty.denote Ty.nat)`. Since `Ty.denote` is marked as `[reducible]`,
|
||||
the typeclass resolution procedure can reduce `Ty.denote Ty.nat` to `Nat`, and use
|
||||
the builtin instance for `Add Nat` as the solution.
|
||||
|
||||
Recall that the term `a.denote` is sugar for `denote a` where `denote` is the function being defined.
|
||||
We call it the "dot notation".
|
||||
-/
|
||||
@[reducible] def Ty.denote : Ty → Type
|
||||
| nat => Nat
|
||||
| fn a b => a.denote → b.denote
|
||||
|
||||
/-!
|
||||
Here is the definition of the `Term` type, including variables, constants, addition,
|
||||
function application and abstraction, and let binding of local variables.
|
||||
Since `let` is a keyword in Lean, we use the "escaped identifier" `«let»`.
|
||||
You can input the unicode (French double quotes) using `\f<<` (for `«`) and `\f>>` (for `»`).
|
||||
The term `Term ctx .nat` is sugar for `Term ctx Ty.nat`, Lean infers the namespace using the expected type.
|
||||
-/
|
||||
inductive Term : List Ty → Ty → Type
|
||||
| var : Member ty ctx → Term ctx ty
|
||||
| const : Nat → Term ctx .nat
|
||||
| plus : Term ctx .nat → Term ctx .nat → Term ctx .nat
|
||||
| app : Term ctx (.fn dom ran) → Term ctx dom → Term ctx ran
|
||||
| lam : Term (dom :: ctx) ran → Term ctx (.fn dom ran)
|
||||
| «let» : Term ctx ty₁ → Term (ty₁ :: ctx) ty₂ → Term ctx ty₂
|
||||
|
||||
/-!
|
||||
Here are two example terms encoding, the first addition packaged as a two-argument
|
||||
curried function, and the second of a sample application of addition to constants.
|
||||
|
||||
The command `open Ty Term Member` opens the namespaces `Ty`, `Term`, and `Member`. Thus,
|
||||
you can write `lam` instead of `Term.lam`.
|
||||
-/
|
||||
open Ty Term Member
|
||||
def add : Term [] (fn nat (fn nat nat)) :=
|
||||
lam (lam (plus (var (tail head)) (var head)))
|
||||
|
||||
def three_the_hard_way : Term [] nat :=
|
||||
app (app add (const 1)) (const 2)
|
||||
|
||||
/-!
|
||||
Since dependent typing ensures that any term is well-formed in its context and has a particular type,
|
||||
it is easy to translate syntactic terms into Lean values.
|
||||
|
||||
The attribute `[simp]` instructs Lean to always try to unfold `Term.denote` applications when one applies
|
||||
the `simp` tactic. We also say this is a hint for the Lean term simplifier.
|
||||
-/
|
||||
@[simp] def Term.denote : Term ctx ty → HList Ty.denote ctx → ty.denote
|
||||
| var h, env => env.get h
|
||||
| const n, _ => n
|
||||
| plus a b, env => a.denote env + b.denote env
|
||||
| app f a, env => f.denote env (a.denote env)
|
||||
| lam b, env => fun x => b.denote (x :: env)
|
||||
| «let» a b, env => b.denote (a.denote env :: env)
|
||||
|
||||
/-!
|
||||
You can show that the denotation of `three_the_hard_way` is indeed `3` using reflexivity.
|
||||
-/
|
||||
example : three_the_hard_way.denote [] = 3 :=
|
||||
rfl
|
||||
|
||||
/-!
|
||||
We now define the constant folding optimization that traverses a term if replaces subterms such as
|
||||
`plus (const m) (const n)` with `const (n+m)`.
|
||||
-/
|
||||
@[simp] def Term.constFold : Term ctx ty → Term ctx ty
|
||||
| const n => const n
|
||||
| var h => var h
|
||||
| app f a => app f.constFold a.constFold
|
||||
| lam b => lam b.constFold
|
||||
| «let» a b => «let» a.constFold b.constFold
|
||||
| plus a b =>
|
||||
match a.constFold, b.constFold with
|
||||
| const n, const m => const (n+m)
|
||||
| a', b' => plus a' b'
|
||||
|
||||
/-!
|
||||
The correctness of the `Term.constFold` is proved using induction, case-analysis, and the term simplifier.
|
||||
We prove all cases but the one for `plus` using `simp [*]`. This tactic instructs the term simplifier to
|
||||
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 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
|
||||
induction e with simp [*]
|
||||
| plus a b iha ihb =>
|
||||
split
|
||||
next he₁ he₂ => simp [← iha, ← ihb, he₁, he₂]
|
||||
next => simp [iha, ihb]
|
||||
@@ -1,5 +0,0 @@
|
||||
(this example is rendered by Alectryon in the CI)
|
||||
|
||||
```lean
|
||||
{{#include deBruijn.lean}}
|
||||
```
|
||||
@@ -1,152 +0,0 @@
|
||||
/-!
|
||||
# The Well-Typed Interpreter
|
||||
|
||||
In this example, we build an interpreter for a simple functional programming language,
|
||||
with variables, function application, binary operators and an `if...then...else` construct.
|
||||
We will use the dependent type system to ensure that any programs which can be represented are well-typed.
|
||||
|
||||
Remark: this example is based on an example found in the Idris manual.
|
||||
-/
|
||||
|
||||
/-!
|
||||
Vectors
|
||||
--------
|
||||
|
||||
A `Vector` is a list of size `n` whose elements belong to a type `α`.
|
||||
-/
|
||||
|
||||
inductive Vector (α : Type u) : Nat → Type u
|
||||
| nil : Vector α 0
|
||||
| cons : α → Vector α n → Vector α (n+1)
|
||||
|
||||
/-!
|
||||
We can overload the `List.cons` notation `::` and use it to create `Vector`s.
|
||||
-/
|
||||
infix:67 " :: " => Vector.cons
|
||||
|
||||
/-!
|
||||
Now, we define the types of our simple functional language.
|
||||
We have integers, booleans, and functions, represented by `Ty`.
|
||||
-/
|
||||
inductive Ty where
|
||||
| int
|
||||
| bool
|
||||
| fn (a r : Ty)
|
||||
|
||||
/-!
|
||||
We can write a function to translate `Ty` values to a Lean type
|
||||
— remember that types are first class, so can be calculated just like any other value.
|
||||
We mark `Ty.interp` as `[reducible]` to make sure the typeclass resolution procedure can
|
||||
unfold/reduce it. For example, suppose Lean is trying to synthesize a value for the instance
|
||||
`Add (Ty.interp Ty.int)`. Since `Ty.interp` is marked as `[reducible]`,
|
||||
the typeclass resolution procedure can reduce `Ty.interp Ty.int` to `Int`, and use
|
||||
the builtin instance for `Add Int` as the solution.
|
||||
-/
|
||||
@[reducible] def Ty.interp : Ty → Type
|
||||
| int => Int
|
||||
| bool => Bool
|
||||
| fn a r => a.interp → r.interp
|
||||
|
||||
/-!
|
||||
Expressions are indexed by the types of the local variables, and the type of the expression itself.
|
||||
-/
|
||||
inductive HasType : Fin n → Vector Ty n → Ty → Type where
|
||||
| stop : HasType 0 (ty :: ctx) ty
|
||||
| pop : HasType k ctx ty → HasType k.succ (u :: ctx) ty
|
||||
|
||||
inductive Expr : Vector Ty n → Ty → Type where
|
||||
| var : HasType i ctx ty → Expr ctx ty
|
||||
| val : Int → Expr ctx Ty.int
|
||||
| lam : Expr (a :: ctx) ty → Expr ctx (Ty.fn a ty)
|
||||
| app : Expr ctx (Ty.fn a ty) → Expr ctx a → Expr ctx ty
|
||||
| op : (a.interp → b.interp → c.interp) → Expr ctx a → Expr ctx b → Expr ctx c
|
||||
| ife : Expr ctx Ty.bool → Expr ctx a → Expr ctx a → Expr ctx a
|
||||
| delay : (Unit → Expr ctx a) → Expr ctx a
|
||||
|
||||
/-!
|
||||
We use the command `open` to create the aliases `stop` and `pop` for `HasType.stop` and `HasType.pop` respectively.
|
||||
-/
|
||||
open HasType (stop pop)
|
||||
|
||||
/-!
|
||||
Since expressions are indexed by their type, we can read the typing rules of the language from the definitions of the constructors.
|
||||
Let us look at each constructor in turn.
|
||||
|
||||
We use a nameless representation for variables — they are de Bruijn indexed.
|
||||
Variables are represented by a proof of their membership in the context, `HasType i ctx ty`,
|
||||
which is a proof that variable `i` in context `ctx` has type `ty`.
|
||||
|
||||
We can treat `stop` as a proof that the most recently defined variable is well-typed,
|
||||
and `pop n` as a proof that, if the `n`th most recently defined variable is well-typed, so is the `n+1`th.
|
||||
In practice, this means we use `stop` to refer to the most recently defined variable,
|
||||
`pop stop` to refer to the next, and so on, via the `Expr.var` constructor.
|
||||
|
||||
A value `Expr.val` carries a concrete representation of an integer.
|
||||
|
||||
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`.
|
||||
|
||||
The constructor `Expr.op` allows us to use arbitrary binary operators, where the type of the operator informs what the types of the arguments must be.
|
||||
|
||||
Finally, the constructor `Exp.ife` represents a `if-then-else` expression. The condition is a Boolean, and each branch must have the same type.
|
||||
|
||||
The auxiliary constructor `Expr.delay` is used to delay evaluation.
|
||||
-/
|
||||
|
||||
|
||||
/-!
|
||||
When we evaluate an `Expr`, we’ll need to know the values in scope, as well as their types. `Env` is an environment,
|
||||
indexed over the types in scope. Since an environment is just another form of list, albeit with a strongly specified connection
|
||||
to the vector of local variable types, we overload again the notation `::` so that we can use the usual list syntax.
|
||||
Given a proof that a variable is defined in the context, we can then produce a value from the environment.
|
||||
-/
|
||||
inductive Env : Vector Ty n → Type where
|
||||
| nil : Env Vector.nil
|
||||
| cons : Ty.interp a → Env ctx → Env (a :: ctx)
|
||||
|
||||
infix:67 " :: " => Env.cons
|
||||
|
||||
def Env.lookup : HasType i ctx ty → Env ctx → ty.interp
|
||||
| stop, x :: xs => x
|
||||
| pop k, x :: xs => lookup k xs
|
||||
|
||||
/-!
|
||||
Given this, an interpreter is a function which translates an `Expr` into a Lean value with respect to a specific environment.
|
||||
-/
|
||||
def Expr.interp (env : Env ctx) : Expr ctx ty → ty.interp
|
||||
| var i => env.lookup i
|
||||
| val x => x
|
||||
| lam b => fun x => b.interp (Env.cons x env)
|
||||
| app f a => f.interp env (a.interp env)
|
||||
| op o x y => o (x.interp env) (y.interp env)
|
||||
| ife c t e => if c.interp env then t.interp env else e.interp env
|
||||
| delay a => (a ()).interp env
|
||||
|
||||
open Expr
|
||||
|
||||
/-!
|
||||
We can make some simple test functions. Firstly, adding two inputs `fun x y => y + x` is written as follows.
|
||||
-/
|
||||
|
||||
def add : Expr ctx (Ty.fn Ty.int (Ty.fn Ty.int Ty.int)) :=
|
||||
lam (lam (op (·+·) (var stop) (var (pop stop))))
|
||||
|
||||
#eval add.interp Env.nil 10 20
|
||||
|
||||
/-!
|
||||
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 viwed as
|
||||
"trust me, this recursive definition makes sense". Recall that `sorry` is an unsound axiom in Lean.
|
||||
-/
|
||||
|
||||
def fact : Expr ctx (Ty.fn Ty.int Ty.int) :=
|
||||
lam (ife (op (·==·) (var stop) (val 0))
|
||||
(val 1)
|
||||
(op (·*·) (delay fun _ => app fact (op (·-·) (var stop) (val 1))) (var stop)))
|
||||
decreasing_by sorry
|
||||
|
||||
#eval fact.interp Env.nil 10
|
||||
@@ -1,5 +0,0 @@
|
||||
(this example is rendered by Alectryon in the CI)
|
||||
|
||||
```lean
|
||||
{{#include interp.lean}}
|
||||
```
|
||||
@@ -1,122 +0,0 @@
|
||||
/-!
|
||||
# Palindromes
|
||||
|
||||
Palindromes are lists that read the same from left to right and from right to left.
|
||||
For example, `[a, b, b, a]` and `[a, h, a]` are palindromes.
|
||||
|
||||
We use an inductive predicate to specify whether a list is a palindrome or not.
|
||||
Recall that inductive predicates, or inductively defined propositions, are a convenient
|
||||
way to specify functions of type `... → Prop`.
|
||||
|
||||
This example is a based on an example from the book "The Hitchhiker's Guide to Logical Verification".
|
||||
-/
|
||||
|
||||
inductive Palindrome : List α → Prop where
|
||||
| nil : Palindrome []
|
||||
| single : (a : α) → Palindrome [a]
|
||||
| sandwich : (a : α) → Palindrome as → Palindrome ([a] ++ as ++ [a])
|
||||
|
||||
/-!
|
||||
The definition distinguishes three cases: (1) `[]` is a palindrome; (2) for any element
|
||||
`a`, the singleton list `[a]` is a palindrome; (3) for any element `a` and any palindrome
|
||||
`[b₁, . . ., bₙ]`, the list `[a, b₁, . . ., bₙ, a]` is a palindrome.
|
||||
-/
|
||||
|
||||
/-!
|
||||
We now prove that the reverse of a palindrome is a palindrome using induction on the inductive predicate `h : Palindrome as`.
|
||||
-/
|
||||
theorem palindrome_reverse (h : Palindrome as) : Palindrome as.reverse := by
|
||||
induction h with
|
||||
| nil => exact Palindrome.nil
|
||||
| single a => exact Palindrome.single a
|
||||
| sandwich a h ih => simp; exact Palindrome.sandwich _ ih
|
||||
|
||||
/-! If a list `as` is a palindrome, then the reverse of `as` is equal to itself. -/
|
||||
theorem reverse_eq_of_palindrome (h : Palindrome as) : as.reverse = as := by
|
||||
induction h with
|
||||
| nil => rfl
|
||||
| single a => rfl
|
||||
| sandwich a h ih => simp [ih]
|
||||
|
||||
/-! Note that you can also easily prove `palindrome_reverse` using `reverse_eq_of_palindrome`. -/
|
||||
example (h : Palindrome as) : Palindrome as.reverse := by
|
||||
simp [reverse_eq_of_palindrome h, h]
|
||||
|
||||
/-!
|
||||
Given a nonempty list, the function `List.last` returns its element.
|
||||
Note that we use `(by simp)` to prove that `a₂ :: as ≠ []` in the recursive application.
|
||||
-/
|
||||
def List.last : (as : List α) → as ≠ [] → α
|
||||
| [a], _ => a
|
||||
| _::a₂:: as, _ => (a₂::as).last (by simp)
|
||||
|
||||
/-!
|
||||
We use the function `List.last` to prove the following theorem that says that if a list `as` is not empty,
|
||||
then removing the last element from `as` and appending it back is equal to `as`.
|
||||
We use the attribute `@[simp]` to instruct the `simp` tactic to use this theorem as a simplification rule.
|
||||
-/
|
||||
@[simp] theorem List.dropLast_append_last (h : as ≠ []) : as.dropLast ++ [as.last h] = as := by
|
||||
match as with
|
||||
| [] => contradiction
|
||||
| [a] => simp_all [last, dropLast]
|
||||
| a₁ :: a₂ :: as =>
|
||||
simp [last, dropLast]
|
||||
exact dropLast_append_last (as := a₂ :: as) (by simp)
|
||||
|
||||
/-!
|
||||
We now define the following auxiliary induction principle for lists using well-founded recursion on `as.length`.
|
||||
We can read it as follows, to prove `motive as`, it suffices to show that: (1) `motive []`; (2) `motive [a]` for any `a`;
|
||||
(3) if `motive as` holds, then `motive ([a] ++ as ++ [b])` also holds for any `a`, `b`, and `as`.
|
||||
Note that the structure of this induction principle is very similar to the `Palindrome` inductive predicate.
|
||||
-/
|
||||
theorem List.palindrome_ind (motive : List α → Prop)
|
||||
(h₁ : motive [])
|
||||
(h₂ : (a : α) → motive [a])
|
||||
(h₃ : (a b : α) → (as : List α) → motive as → motive ([a] ++ as ++ [b]))
|
||||
(as : List α)
|
||||
: motive as :=
|
||||
match as with
|
||||
| [] => h₁
|
||||
| [a] => h₂ a
|
||||
| a₁::a₂::as' =>
|
||||
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 => as.length
|
||||
|
||||
/-!
|
||||
We use our new induction principle to prove that if `as.reverse = as`, then `Palindrome as` holds.
|
||||
Note that we use the `using` modifier to instruct the `induction` tactic to use this induction principle
|
||||
instead of the default one for lists.
|
||||
-/
|
||||
theorem List.palindrome_of_eq_reverse (h : as.reverse = as) : Palindrome as := by
|
||||
induction as using palindrome_ind
|
||||
next => exact Palindrome.nil
|
||||
next a => exact Palindrome.single a
|
||||
next a b as ih =>
|
||||
have : a = b := by simp_all
|
||||
subst this
|
||||
have : as.reverse = as := by simp_all
|
||||
exact Palindrome.sandwich a (ih this)
|
||||
|
||||
/-!
|
||||
We now define a function that returns `true` iff `as` is a palindrome.
|
||||
The function assumes that the type `α` has decidable equality. We need this assumption
|
||||
because we need to compare the list elements.
|
||||
-/
|
||||
def List.isPalindrome [DecidableEq α] (as : List α) : Bool :=
|
||||
as.reverse = as
|
||||
|
||||
/-!
|
||||
It is straightforward to prove that `isPalindrome` is correct using the previously proved theorems.
|
||||
-/
|
||||
theorem List.isPalindrome_correct [DecidableEq α] (as : List α) : as.isPalindrome ↔ Palindrome as := by
|
||||
simp [isPalindrome]
|
||||
exact Iff.intro (fun h => palindrome_of_eq_reverse h) (fun h => reverse_eq_of_palindrome h)
|
||||
|
||||
#eval [1, 2, 1].isPalindrome
|
||||
#eval [1, 2, 3, 1].isPalindrome
|
||||
|
||||
example : [1, 2, 1].isPalindrome := rfl
|
||||
example : [1, 2, 2, 1].isPalindrome := rfl
|
||||
example : ![1, 2, 3, 1].isPalindrome := rfl
|
||||
@@ -1,5 +0,0 @@
|
||||
(this example is rendered by Alectryon in the CI)
|
||||
|
||||
```lean
|
||||
{{#include palindromes.lean}}
|
||||
```
|
||||
@@ -1,239 +0,0 @@
|
||||
/-!
|
||||
# Parametric Higher-Order Abstract Syntax
|
||||
|
||||
In contrast to first-order encodings, higher-order encodings avoid explicit modeling of variable identity.
|
||||
Instead, the binding constructs of an object language (the language being
|
||||
formalized) can be represented using the binding constructs of the meta language (the language in which the formalization is done).
|
||||
The best known higher-order encoding is called higher-order abstract syntax (HOAS),
|
||||
and we can start by attempting to apply it directly in Lean.
|
||||
|
||||
Remark: this example is based on an example in the book [Certified Programming with Dependent Types](http://adam.chlipala.net/cpdt/) by Adam Chlipala.
|
||||
-/
|
||||
|
||||
/-!
|
||||
Here is the definition of the simple type system for our programming language, a simply typed
|
||||
lambda calculus with natural numbers as the base type.
|
||||
-/
|
||||
inductive Ty where
|
||||
| nat
|
||||
| fn : Ty → Ty → Ty
|
||||
|
||||
/-!
|
||||
We can write a function to translate `Ty` values to a Lean type
|
||||
— remember that types are first class, so can be calculated just like any other value.
|
||||
We mark `Ty.denote` as `[reducible]` to make sure the typeclass resolution procedure can
|
||||
unfold/reduce it. For example, suppose Lean is trying to synthesize a value for the instance
|
||||
`Add (Ty.denote Ty.nat)`. Since `Ty.denote` is marked as `[reducible]`,
|
||||
the typeclass resolution procedure can reduce `Ty.denote Ty.nat` to `Nat`, and use
|
||||
the builtin instance for `Add Nat` as the solution.
|
||||
|
||||
Recall that the term `a.denote` is sugar for `denote a` where `denote` is the function being defined.
|
||||
We call it the "dot notation".
|
||||
-/
|
||||
@[reducible] def Ty.denote : Ty → Type
|
||||
| nat => Nat
|
||||
| fn a b => a.denote → b.denote
|
||||
|
||||
/-!
|
||||
With HOAS, each object language binding construct is represented with a function of
|
||||
the meta language. Here is what we get if we apply that idea within an inductive definition
|
||||
of term syntax. However a naive encondig in Lean fails to meet the strict positivity restrictions
|
||||
imposed by the Lean kernel. An alternate higher-order encoding is parametric HOAS, as introduced by Washburn
|
||||
and Weirich for Haskell and tweaked by Adam Chlipala for use in Coq. The key idea is to parameterize the
|
||||
declaration by a type family `rep` standing for a "representation of variables."
|
||||
-/
|
||||
inductive Term' (rep : Ty → Type) : Ty → Type
|
||||
| var : rep ty → Term' rep ty
|
||||
| const : Nat → Term' rep .nat
|
||||
| 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₂
|
||||
|
||||
/-!
|
||||
Lean accepts this definition because our embedded functions now merely take variables as
|
||||
arguments, instead of arbitrary terms. One might wonder whether there is an easy loophole
|
||||
to exploit here, instantiating the parameter `rep` as term itself. However, to do that, we
|
||||
would need to choose a variable representation for this nested mention of term, and so on
|
||||
through an infinite descent into term arguments.
|
||||
|
||||
We write the final type of a closed term using polymorphic quantification over all possible
|
||||
choices of `rep` type family
|
||||
-/
|
||||
|
||||
open Ty (nat fn)
|
||||
|
||||
namespace FirstTry
|
||||
|
||||
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 three_the_hard_way : Term nat := fun rep =>
|
||||
.app (.app (add rep) (.const 1)) (.const 2)
|
||||
|
||||
end FirstTry
|
||||
|
||||
/-!
|
||||
The argument `rep` does not even appear in the function body for `add`. How can that be?
|
||||
By giving our terms expressive types, we allow Lean to infer many arguments for us. In fact,
|
||||
we do not even need to name the `rep` argument! By using Lean implicit arguments and lambdas,
|
||||
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)
|
||||
|
||||
def three_the_hard_way : Term nat :=
|
||||
.app (.app add (.const 1)) (.const 2)
|
||||
|
||||
/-!
|
||||
It may not be at all obvious that the PHOAS representation admits the crucial computable
|
||||
operations. The key to effective deconstruction of PHOAS terms is one principle: treat
|
||||
the `rep` parameter as an unconstrained choice of which data should be annotated on each
|
||||
variable. We will begin with a simple example, that of counting how many variable nodes
|
||||
appear in a PHOAS term. This operation requires no data annotated on variables, so we
|
||||
simply annotate variables with `Unit` values. Note that, when we go under binders in the
|
||||
cases for `lam` and `let`, we must provide the data value to annotate on the new variable we
|
||||
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 ())
|
||||
|
||||
/-! We can now easily prove that `add` has two variables by using reflexivity -/
|
||||
|
||||
example : countVars add = 2 :=
|
||||
rfl
|
||||
|
||||
/-!
|
||||
Here is another example, translating PHOAS terms into strings giving a first-order rendering.
|
||||
To implement this translation, the key insight is to tag variables with strings, giving their names.
|
||||
The function takes as an additional input `i` which is used to create variable names for binders.
|
||||
We also use the string interpolation available in Lean. For example, `s!"x_{i}"` is expanded to
|
||||
`"x_" ++ toString 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 =>
|
||||
let x := s!"x_{i}"
|
||||
s!"(fun {x} => {pretty (f x) (i+1)})"
|
||||
| .let a b =>
|
||||
let x := s!"x_{i}"
|
||||
s!"(let {x} := {pretty a i}; => {pretty (b x) (i+1)}"
|
||||
|
||||
#eval pretty three_the_hard_way
|
||||
|
||||
/-!
|
||||
It is not necessary to convert to a different representation to support many common
|
||||
operations on terms. For instance, we can implement substitution of terms for variables.
|
||||
The key insight here is to tag variables with terms, so that, on encountering a variable, we
|
||||
can simply replace it by the term in its tag. We will call this function initially on a term
|
||||
with exactly one free variable, tagged with the appropriate substitute. During recursion,
|
||||
new variables are added, but they are only tagged with their own term equivalents. Note
|
||||
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))
|
||||
|
||||
/-!
|
||||
To define the final substitution function over terms with single free variables, we define
|
||||
`Term1`, an analogue to Term that we defined before for closed terms.
|
||||
-/
|
||||
def Term1 (ty1 ty2 : Ty) := {rep : Ty → Type} → rep ty1 → Term' rep ty2
|
||||
|
||||
/-!
|
||||
Substitution is defined by (1) instantiating a `Term1` to tag variables with terms and (2)
|
||||
applying the result to a specific term to be substituted. Note how the parameter `rep` of
|
||||
`squash` is instantiated: the body of `subst` is itself a polymorphic quantification over `rep`,
|
||||
standing for a variable tag choice in the output term; and we use that input to compute a
|
||||
tag choice for the input term.
|
||||
-/
|
||||
|
||||
def subst (e : Term1 ty1 ty2) (e' : Term ty1) : Term ty2 :=
|
||||
squash (e e')
|
||||
|
||||
/-!
|
||||
We can view `Term1` as a term with hole. In the following example,
|
||||
`(fun x => plus (var x) (const 5))` can be viewed as the term `plus _ (const 5)` where
|
||||
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
|
||||
|
||||
/-!
|
||||
One further development, which may seem surprising at first,
|
||||
is that we can also implement a usual term denotation function,
|
||||
when we tag variables with their denotations.
|
||||
|
||||
The attribute `[simp]` instructs Lean to always try to unfold `denote` applications when one applies
|
||||
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))
|
||||
|
||||
example : denote three_the_hard_way = 3 :=
|
||||
rfl
|
||||
|
||||
/-!
|
||||
To summarize, the PHOAS representation has all the expressive power of more
|
||||
standard encodings (e.g., using de Bruijn indices), and a variety of translations are actually much more pleasant to
|
||||
implement than usual, thanks to the novel ability to tag variables with data.
|
||||
-/
|
||||
|
||||
/-!
|
||||
We now define the constant folding optimization that traverses a term if replaces subterms such as
|
||||
`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 =>
|
||||
match constFold a, constFold b with
|
||||
| .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.
|
||||
We prove all cases but the one for `plus` using `simp [*]`. This tactic instructs the term simplifier to
|
||||
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 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
|
||||
induction e with simp [*]
|
||||
| plus a b iha ihb =>
|
||||
split
|
||||
next he₁ he₂ => simp [← iha, ← ihb, he₁, he₂]
|
||||
next => simp [iha, ihb]
|
||||
@@ -1,5 +0,0 @@
|
||||
(this example is rendered by Alectryon in the CI)
|
||||
|
||||
```lean
|
||||
{{#include phoas.lean}}
|
||||
```
|
||||
@@ -1,119 +0,0 @@
|
||||
/-!
|
||||
# A Certified Type Checker
|
||||
|
||||
In this example, we build a certified type checker for a simple expression
|
||||
language.
|
||||
|
||||
Remark: this example is based on an example in the book [Certified Programming with Dependent Types](http://adam.chlipala.net/cpdt/) by Adam Chlipala.
|
||||
-/
|
||||
inductive Expr where
|
||||
| nat : Nat → Expr
|
||||
| plus : Expr → Expr → Expr
|
||||
| bool : Bool → Expr
|
||||
| and : Expr → Expr → Expr
|
||||
|
||||
/-!
|
||||
We define a simple language of types using the inductive datatype `Ty`, and
|
||||
its typing rules using the inductive predicate `HasType`.
|
||||
-/
|
||||
inductive Ty where
|
||||
| nat
|
||||
| bool
|
||||
deriving DecidableEq
|
||||
|
||||
inductive HasType : Expr → Ty → Prop
|
||||
| nat : HasType (.nat v) .nat
|
||||
| plus : HasType a .nat → HasType b .nat → HasType (.plus a b) .nat
|
||||
| bool : HasType (.bool v) .bool
|
||||
| and : HasType a .bool → HasType b .bool → HasType (.and a b) .bool
|
||||
|
||||
/-!
|
||||
We can easily show that if `e` has type `t₁` and type `t₂`, then `t₁` and `t₂` must be equal
|
||||
by using the the `cases` tactic. This tactic creates a new subgoal for every constructor,
|
||||
and automatically discharges unreachable cases. The tactic combinator `tac₁ <;> tac₂` applies
|
||||
`tac₂` to each subgoal produced by `tac₁`. Then, the tactic `rfl` is used to close all produced
|
||||
goals using reflexivity.
|
||||
-/
|
||||
theorem HasType.det (h₁ : HasType e t₁) (h₂ : HasType e t₂) : t₁ = t₂ := by
|
||||
cases h₁ <;> cases h₂ <;> rfl
|
||||
|
||||
/-!
|
||||
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".
|
||||
-/
|
||||
|
||||
inductive Maybe (p : α → Prop) where
|
||||
| found : (a : α) → p a → Maybe p
|
||||
| unknown
|
||||
|
||||
/-!
|
||||
We define a notation for `Maybe` that is similar to the builtin notation for the Lean builtin type `Subtype`.
|
||||
-/
|
||||
notation "{{ " x " | " p " }}" => Maybe (fun x => p)
|
||||
|
||||
/-!
|
||||
The function `Expr.typeCheck e` returns a type `ty` and a proof that `e` has type `ty`,
|
||||
or `unknown`.
|
||||
Recall that, `def Expr.typeCheck ...` in Lean is notation for `namespace Expr def typeCheck ... end Expr`.
|
||||
The term `.found .nat .nat` is sugar for `Maybe.found Ty.nat HasType.nat`. Lean can infer the namespaces using
|
||||
the expected types.
|
||||
-/
|
||||
def Expr.typeCheck (e : Expr) : {{ ty | HasType e ty }} :=
|
||||
match e with
|
||||
| nat .. => .found .nat .nat
|
||||
| bool .. => .found .bool .bool
|
||||
| plus a b =>
|
||||
match a.typeCheck, b.typeCheck with
|
||||
| .found .nat h₁, .found .nat h₂ => .found .nat (.plus h₁ h₂)
|
||||
| _, _ => .unknown
|
||||
| and a b =>
|
||||
match a.typeCheck, b.typeCheck with
|
||||
| .found .bool h₁, .found .bool h₂ => .found .bool (.and h₁ h₂)
|
||||
| _, _ => .unknown
|
||||
|
||||
theorem Expr.typeCheck_correct (h₁ : HasType e ty) (h₂ : e.typeCheck ≠ .unknown)
|
||||
: e.typeCheck = .found ty h := by
|
||||
revert h₂
|
||||
cases typeCheck e with
|
||||
| found ty' h' => intro; have := HasType.det h₁ h'; subst this; rfl
|
||||
| unknown => intros; contradiction
|
||||
|
||||
/-!
|
||||
Now, we prove that if `Expr.typeCheck e` returns `Maybe.unknown`, then forall `ty`, `HasType e ty` does not hold.
|
||||
The notation `e.typeCheck` is sugar for `Expr.typeCheck e`. Lean can infer this because we explicitly said that `e` has type `Expr`.
|
||||
The proof is by induction on `e` and case analysis. The tactic `rename_i` is used to to rename "inaccessible" variables.
|
||||
We say a variable is inaccessible if it is introduced by a tactic (e.g., `cases`) or has been shadowed by another variable introduced
|
||||
by the user. Note that the tactic `simp [typeCheck]` is applied to all goal generated by the `induction` tactic, and closes
|
||||
the cases corresponding to the constructors `Expr.nat` and `Expr.bool`.
|
||||
-/
|
||||
theorem Expr.typeCheck_complete {e : Expr} : e.typeCheck = .unknown → ¬ HasType e ty := by
|
||||
induction e with simp [typeCheck]
|
||||
| plus a b iha ihb =>
|
||||
split
|
||||
next => intros; contradiction
|
||||
next ra rb hnp =>
|
||||
-- Recall that `hnp` is a hypothesis generated by the `split` tactic
|
||||
-- that asserts the previous case was not taken
|
||||
intro h ht
|
||||
cases ht with
|
||||
| plus h₁ h₂ => exact hnp h₁ h₂ (typeCheck_correct h₁ (iha · h₁)) (typeCheck_correct h₂ (ihb · h₂))
|
||||
| and a b iha ihb =>
|
||||
split
|
||||
next => intros; contradiction
|
||||
next ra rb hnp =>
|
||||
intro h ht
|
||||
cases ht with
|
||||
| and h₁ h₂ => exact hnp h₁ h₂ (typeCheck_correct h₁ (iha · h₁)) (typeCheck_correct h₂ (ihb · h₂))
|
||||
|
||||
/-!
|
||||
Finally, we show that type checking for `e` can be decided using `Expr.typeCheck`.
|
||||
-/
|
||||
instance (e : Expr) (t : Ty) : Decidable (HasType e t) :=
|
||||
match h' : e.typeCheck with
|
||||
| .found t' ht' =>
|
||||
if heq : t = t' then
|
||||
isTrue (heq ▸ ht')
|
||||
else
|
||||
isFalse fun ht => heq (HasType.det ht ht')
|
||||
| .unknown => isFalse (Expr.typeCheck_complete h')
|
||||
@@ -1,5 +0,0 @@
|
||||
(this example is rendered by Alectryon in the CI)
|
||||
|
||||
```lean
|
||||
{{#include tc.lean}}
|
||||
```
|
||||
@@ -1,4 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
source ../../tests/common.sh
|
||||
|
||||
exec_check lean -j 0 -Dlinter.all=false "$f"
|
||||
@@ -1,186 +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]
|
||||
def helloWidget : UserWidgetDefinition where
|
||||
name := "Hello"
|
||||
javascript := "
|
||||
import * as React from 'react';
|
||||
export default function(props) {
|
||||
const name = props.name || 'world'
|
||||
return React.createElement('p', {}, name + '!')
|
||||
}"
|
||||
|
||||
#widget helloWidget .null
|
||||
|
||||
/-!
|
||||
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:
|
||||
-/
|
||||
|
||||
#widget helloWidget (Json.mkObj [("name", "<your name here>")])
|
||||
|
||||
/-!
|
||||
💡 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
|
||||
@[serverRpcMethod]
|
||||
def getType (params : GetTypeParams) : RequestM (RequestTask CodeWithInfos) :=
|
||||
withWaitFindSnapAtPos params.pos fun snap => do
|
||||
runTermElabM snap do
|
||||
let name ← resolveGlobalConstNoOverloadCore params.name
|
||||
let some c ← Meta.getConst? name
|
||||
| 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 a `status` enum, the returned `val`ue in case
|
||||
the call was successful, and otherwise an `err`or. Based on the `status` we either display
|
||||
an `InteractiveCode`, or `mapRpcError` the error in order to turn it into a readable message.
|
||||
-/
|
||||
|
||||
@[widget]
|
||||
def checkWidget : UserWidgetDefinition where
|
||||
name := "#check as a service"
|
||||
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 [status, val, err] = useAsync(() =>
|
||||
rs.call('getType', { name, pos: props.pos }), [name, rs, props.pos])
|
||||
|
||||
const type = status === 'fulfilled' ? val && e(InteractiveCode, {fmt: val})
|
||||
: status === 'rejected' ? e('p', null, mapRpcError(err).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 .null
|
||||
|
||||
/-!
|
||||

|
||||
|
||||
## 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).
|
||||
-/
|
||||
@@ -1,5 +0,0 @@
|
||||
(this chapter is rendered by Alectryon in the CI)
|
||||
|
||||
```lean
|
||||
{{#include widgets.lean}}
|
||||
```
|
||||
@@ -219,10 +219,10 @@ def f2 (a b c : Bool) : Bool :=
|
||||
|
||||
#eval (1, 2)
|
||||
|
||||
def p : Nat × Bool := (1, false)
|
||||
def p : Nat × Bool := (1,
|
||||
|
||||
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
|
||||
@@ -419,31 +419,33 @@ Every computable definition in Lean is compiled to bytecode at definition time.
|
||||
|
||||
.. code-block:: lean
|
||||
|
||||
#reduce (fun x => x + 3) 5
|
||||
#eval (fun x => x + 3) 5
|
||||
#reduce (λ x, x + 3) 5
|
||||
#eval (λ x, x + 3) 5
|
||||
|
||||
#reduce let x := 5; x + 3
|
||||
#eval let x := 5; x + 3
|
||||
#reduce let x := 5 in x + 3
|
||||
#eval let x := 5 in x + 3
|
||||
|
||||
def f x := x + 3
|
||||
|
||||
#reduce f 5
|
||||
#eval f 5
|
||||
|
||||
#reduce @Nat.rec (λ n => Nat) (0 : Nat)
|
||||
(λ n recval : Nat => recval + n + 1) (5 : Nat)
|
||||
#reduce @nat.rec (λ n, Nat) (0 : Nat)
|
||||
(λ n recval : Nat, recval + n + 1) (5 : Nat)
|
||||
#eval @nat.rec (λ n, Nat) (0 : Nat)
|
||||
(λ n recval : Nat, recval + n + 1) (5 : Nat)
|
||||
|
||||
def g : Nat → Nat
|
||||
| 0 => 0
|
||||
| (n+1) => g n + n + 1
|
||||
| 0 := 0
|
||||
| (n+1) := g n + n + 1
|
||||
|
||||
#reduce g 5
|
||||
#eval g 5
|
||||
|
||||
#eval g 5000
|
||||
#eval g 50000
|
||||
|
||||
example : (fun x => x + 3) 5 = 8 := rfl
|
||||
example : (fun x => f x) = f := rfl
|
||||
example : (λ x, x + 3) 5 = 8 := rfl
|
||||
example : (λ x, f x) = f := rfl
|
||||
example (p : Prop) (h₁ h₂ : p) : h₁ = h₂ := rfl
|
||||
|
||||
Note: the combination of proof irrelevance and singleton ``Prop`` elimination in ι-reduction renders the ideal version of definitional equality, as described above, undecidable. Lean's procedure for checking definitional equality is only an approximation to the ideal. It is not transitive, as illustrated by the example below. Once again, this does not compromise the consistency or soundness of Lean; it only means that Lean is more conservative in the terms it recognizes as well typed, and this does not cause problems in practice. Singleton elimination will be discussed in greater detail in [Inductive Types](inductive.md).
|
||||
|
||||
214
doc/flake.lock
generated
214
doc/flake.lock
generated
@@ -1,214 +0,0 @@
|
||||
{
|
||||
"nodes": {
|
||||
"alectryon": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
"lastModified": 1654613606,
|
||||
"narHash": "sha256-IGCn1PzTyw8rrwmyWUiw3Jo/dyZVGkMslnHYW7YB8yk=",
|
||||
"owner": "Kha",
|
||||
"repo": "alectryon",
|
||||
"rev": "c3b16f650665745e1da4ddfcc048d3bd639f71d5",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "Kha",
|
||||
"ref": "typeid",
|
||||
"repo": "alectryon",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"flake-utils": {
|
||||
"locked": {
|
||||
"lastModified": 1644229661,
|
||||
"narHash": "sha256-1YdnJAsNy69bpcjuoKdOYQX0YxZBiCYZo4Twxerqv7k=",
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"rev": "3cecb5b042f7f209c56ffd8371b2711a290ec797",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"lean": {
|
||||
"inputs": {
|
||||
"flake-utils": "flake-utils",
|
||||
"lean-stage0": "lean-stage0",
|
||||
"lean4-mode": "lean4-mode",
|
||||
"nix": "nix",
|
||||
"nixpkgs": "nixpkgs_2"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 0,
|
||||
"narHash": "sha256-AfBkKX6Ahb9YbZke+eWLmsUk1Z9BwdJ1CpIoPY8Msx8=",
|
||||
"path": "../.",
|
||||
"type": "path"
|
||||
},
|
||||
"original": {
|
||||
"path": "../.",
|
||||
"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": 1647694750,
|
||||
"narHash": "sha256-0rV61KhevG9IAjZDN2Ts2VS65fiUAPAezbf282u7yy8=",
|
||||
"owner": "leanprover",
|
||||
"repo": "lean4-mode",
|
||||
"rev": "c016c7aeee92564836355083664c49ed57024427",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "leanprover",
|
||||
"repo": "lean4-mode",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"leanInk": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
"lastModified": 1656863690,
|
||||
"narHash": "sha256-9tmynTTeJGhYZaltS4xhSJgLTpe7Ta1ofV6U1SA/5V4=",
|
||||
"owner": "leanprover",
|
||||
"repo": "LeanInk",
|
||||
"rev": "4b5e606ea8cc54c2447ce48706f8ec1d133d19e9",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "leanprover",
|
||||
"repo": "LeanInk",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"lowdown-src": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
"lastModified": 1633514407,
|
||||
"narHash": "sha256-Dw32tiMjdK9t3ETl5fzGrutQTzh2rufgZV4A/BbxuD4=",
|
||||
"owner": "kristapsdz",
|
||||
"repo": "lowdown",
|
||||
"rev": "d2c2b44ff6c27b936ec27358a2653caaef8f73b8",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "kristapsdz",
|
||||
"repo": "lowdown",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"mdBook": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
"lastModified": 1660074464,
|
||||
"narHash": "sha256-W30G7AeWBjdJE/CQZJU5vJjaDGZtpmxEKNMEvaYtuF8=",
|
||||
"owner": "leanprover",
|
||||
"repo": "mdBook",
|
||||
"rev": "9321c10c502cd59eea8afc4325a84eab3ddf9391",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "leanprover",
|
||||
"repo": "mdBook",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nix": {
|
||||
"inputs": {
|
||||
"lowdown-src": "lowdown-src",
|
||||
"nixpkgs": "nixpkgs",
|
||||
"nixpkgs-regression": "nixpkgs-regression"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1648022028,
|
||||
"narHash": "sha256-HtwmifW6STPcym+3uJ4YavgTKTYVIoiQHg3f0wXOm+Q=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nix",
|
||||
"rev": "98ce1a21b7d959c5575fac566c8699e91703a9f7",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "NixOS",
|
||||
"repo": "nix",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1632864508,
|
||||
"narHash": "sha256-d127FIvGR41XbVRDPVvozUPQ/uRHbHwvfyKHwEt5xFM=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "82891b5e2c2359d7e58d08849e4c89511ab94234",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"id": "nixpkgs",
|
||||
"ref": "nixos-21.05-small",
|
||||
"type": "indirect"
|
||||
}
|
||||
},
|
||||
"nixpkgs-regression": {
|
||||
"locked": {
|
||||
"lastModified": 1643052045,
|
||||
"narHash": "sha256-uGJ0VXIhWKGXxkeNnq4TvV3CIOkUJ3PAoLZ3HMzNVMw=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "215d4d0fd80ca5163643b03a33fde804a29cc1e2",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"id": "nixpkgs",
|
||||
"rev": "215d4d0fd80ca5163643b03a33fde804a29cc1e2",
|
||||
"type": "indirect"
|
||||
}
|
||||
},
|
||||
"nixpkgs_2": {
|
||||
"locked": {
|
||||
"lastModified": 1648219316,
|
||||
"narHash": "sha256-Ctij+dOi0ZZIfX5eMhgwugfvB+WZSrvVNAyAuANOsnQ=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "30d3d79b7d3607d56546dd2a6b49e156ba0ec634",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "NixOS",
|
||||
"ref": "nixpkgs-unstable",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"root": {
|
||||
"inputs": {
|
||||
"alectryon": "alectryon",
|
||||
"flake-utils": [
|
||||
"lean",
|
||||
"flake-utils"
|
||||
],
|
||||
"lean": "lean",
|
||||
"leanInk": "leanInk",
|
||||
"mdBook": "mdBook"
|
||||
}
|
||||
}
|
||||
},
|
||||
"root": "root",
|
||||
"version": 7
|
||||
}
|
||||
101
doc/flake.nix
101
doc/flake.nix
@@ -1,101 +0,0 @@
|
||||
{
|
||||
description = "Lean documentation";
|
||||
|
||||
inputs.lean.url = path:../.;
|
||||
inputs.flake-utils.follows = "lean/flake-utils";
|
||||
inputs.mdBook = {
|
||||
url = github:leanprover/mdBook;
|
||||
flake = false;
|
||||
};
|
||||
inputs.alectryon = {
|
||||
url = github:Kha/alectryon/typeid;
|
||||
flake = false;
|
||||
};
|
||||
inputs.leanInk = {
|
||||
url = github:leanprover/LeanInk;
|
||||
flake = false;
|
||||
};
|
||||
|
||||
outputs = inputs@{ self, ... }: inputs.flake-utils.lib.eachDefaultSystem (system:
|
||||
with inputs.lean.packages.${system}; with nixpkgs;
|
||||
let
|
||||
doc-src = lib.sourceByRegex ../. ["doc.*" "tests(/lean(/beginEndAsMacro.lean)?)?"];
|
||||
in {
|
||||
packages = rec {
|
||||
lean-mdbook = mdbook.overrideAttrs (drv: rec {
|
||||
name = "lean-${mdbook.name}";
|
||||
src = inputs.mdBook;
|
||||
cargoDeps = drv.cargoDeps.overrideAttrs (_: {
|
||||
inherit src;
|
||||
outputHash = "sha256-mhTWHs/bsmm3FH59SkUxBTl5lEH2Rlz/aF9CuBTu1TE=";
|
||||
});
|
||||
doCheck = false;
|
||||
});
|
||||
book = stdenv.mkDerivation {
|
||||
name ="lean-doc";
|
||||
src = doc-src;
|
||||
buildInputs = [ lean-mdbook ];
|
||||
buildCommand = ''
|
||||
mkdir $out
|
||||
# necessary for `additional-css`...?
|
||||
cp -r --no-preserve=mode $src/doc/* .
|
||||
# overwrite stub .lean.md files
|
||||
cp -r ${inked}/* .
|
||||
mdbook build -d $out
|
||||
'';
|
||||
};
|
||||
# We use a separate derivation instead of `checkPhase` so we can push it but not `doc` to the binary cache
|
||||
test = stdenv.mkDerivation {
|
||||
name ="lean-doc-test";
|
||||
src = doc-src;
|
||||
buildInputs = [ lean-mdbook stage1.Lean.lean-package strace ];
|
||||
patchPhase = ''
|
||||
cd doc
|
||||
patchShebangs test
|
||||
'';
|
||||
buildPhase = ''
|
||||
mdbook test
|
||||
touch $out
|
||||
'';
|
||||
dontInstall = true;
|
||||
};
|
||||
leanInk = (buildLeanPackage {
|
||||
name = "Main";
|
||||
src = inputs.leanInk;
|
||||
deps = [ (buildLeanPackage {
|
||||
name = "LeanInk";
|
||||
src = inputs.leanInk;
|
||||
}) ];
|
||||
executableName = "leanInk";
|
||||
linkFlags = ["-rdynamic"];
|
||||
}).executable;
|
||||
alectryon = python3Packages.buildPythonApplication {
|
||||
name = "alectryon";
|
||||
src = inputs.alectryon;
|
||||
propagatedBuildInputs =
|
||||
[ leanInk lean-all ] ++
|
||||
# https://github.com/cpitclaudel/alectryon/blob/master/setup.cfg
|
||||
(with python3Packages; [ pygments dominate beautifulsoup4 docutils ]);
|
||||
doCheck = false;
|
||||
};
|
||||
renderLean = name: file: runCommandNoCC "${name}.md" { buildInputs = [ alectryon ]; } ''
|
||||
mkdir -p $(basename $out/${name})
|
||||
alectryon --frontend lean4+markup ${file} --backend webpage -o $out/${name}.md
|
||||
'';
|
||||
listFilesRecursiveRel = root: dir: lib.flatten (lib.mapAttrsToList (name: type:
|
||||
if type == "directory" then
|
||||
listFilesRecursiveRel root ("${dir}/${name}")
|
||||
else
|
||||
dir + "/${name}"
|
||||
) (builtins.readDir "${root}/${dir}"));
|
||||
renderDir = dir: let
|
||||
inputs = builtins.filter (n: builtins.match ".*\.lean" n != null) (listFilesRecursiveRel dir ".");
|
||||
outputs = lib.genAttrs inputs (n: renderLean n "${dir}/${n}");
|
||||
in
|
||||
outputs // symlinkJoin { inherit name; paths = lib.attrValues outputs; };
|
||||
inked = renderDir ./.;
|
||||
doc = book;
|
||||
};
|
||||
defaultPackage = self.packages.${system}.doc;
|
||||
});
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
Functional Programming in Lean
|
||||
=======================
|
||||
|
||||
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.
|
||||
@@ -46,25 +46,6 @@ partial def g (x : Nat) (p : Nat -> Bool) : Nat :=
|
||||
In the previous example, `g x p` only terminates if there is a `y >= x` such that `p y` returns `true`.
|
||||
Of course, `g 0 (fun x => false)` never terminates.
|
||||
|
||||
However, the use of `partial` is restricted to functions whose return type is not empty so the soundness
|
||||
of the system is not compromised.
|
||||
|
||||
```lean,ignore
|
||||
partial def loop? : α := -- failed to compile partial definition 'loop?', failed to
|
||||
loop? -- show that type is inhabited and non empty
|
||||
|
||||
partial def loop [Inhabited α] : α := -- compiles
|
||||
loop
|
||||
|
||||
example : True := -- accepted
|
||||
loop
|
||||
|
||||
example : False :=
|
||||
loop -- failed to synthesize instance Inhabited False
|
||||
```
|
||||
|
||||
If we were able to partially define `loop?`, we could prove `False` with it.
|
||||
|
||||
# Lambda expressions
|
||||
|
||||
A lambda expression is an unnamed function.
|
||||
@@ -89,25 +70,28 @@ def twice (f : Nat -> Nat) (x : Nat) : Nat :=
|
||||
|
||||
# Syntax sugar for simple lambda expressions
|
||||
|
||||
Simple functions can be defined using parentheses and `·` as a placeholder.
|
||||
Simple functions can be defined using parentheses and `.` (or `·`) as a placeholder.
|
||||
```lean
|
||||
#check (. + 1)
|
||||
-- fun a => a + 1
|
||||
#check (· + 1)
|
||||
-- fun a => a + 1
|
||||
#check (2 - ·)
|
||||
#check (2 - .)
|
||||
-- fun a => 2 - a
|
||||
#eval [1, 2, 3, 4, 5].foldl (· * ·) 1
|
||||
#eval [1, 2, 3, 4, 5].foldl (. * .) 1
|
||||
-- 120
|
||||
|
||||
def h (x y z : Nat) :=
|
||||
x + y + z
|
||||
|
||||
#check (h · 1 ·)
|
||||
#check (h . 1 .)
|
||||
-- fun a b => h a 1 b
|
||||
|
||||
#eval [(1, 2), (3, 4), (5, 6)].map (·.1)
|
||||
-- [1, 3, 5]
|
||||
```
|
||||
In the previous example, the term `(·.1)` is syntax sugar for `fun x => x.1`.
|
||||
In the previous example, the term `(·.1)` is syntax sugar for `fun x => x.1`. Note that, the ASCII version `(..1)`
|
||||
is not supported because `..` is a reserved symbol.
|
||||
|
||||
# Pipelining
|
||||
|
||||
@@ -127,14 +111,14 @@ In contrast, the backward pipeline `<|` operator takes an argument and a functio
|
||||
These operators are useful for minimizing the number of parentheses.
|
||||
```lean
|
||||
def add1Times3FilterEven (xs : List Nat) :=
|
||||
List.filter (· % 2 == 0) (List.map (· * 3) (List.map (· + 1) xs))
|
||||
List.filter (. % 2 == 0) (List.map (. * 3) (List.map (. + 1) xs))
|
||||
|
||||
#eval add1Times3FilterEven [1, 2, 3, 4]
|
||||
-- [6, 12]
|
||||
|
||||
-- Define the same function using pipes
|
||||
def add1Times3FilterEven' (xs : List Nat) :=
|
||||
xs |> List.map (· + 1) |> List.map (· * 3) |> List.filter (· % 2 == 0)
|
||||
xs |> List.map (. + 1) |> List.map (. * 3) |> List.filter (. % 2 == 0)
|
||||
|
||||
#eval add1Times3FilterEven' [1, 2, 3, 4]
|
||||
-- [6, 12]
|
||||
@@ -143,7 +127,7 @@ Lean also supports the operator `|>.` which combines forward pipeline `|>` opera
|
||||
```lean
|
||||
-- Define the same function using pipes
|
||||
def add1Times3FilterEven'' (xs : List Nat) :=
|
||||
xs.map (· + 1) |>.map (· * 3) |>.filter (· % 2 == 0)
|
||||
xs.map (. + 1) |>.map (. * 3) |>.filter (. % 2 == 0)
|
||||
|
||||
#eval add1Times3FilterEven'' [1, 2, 3, 4]
|
||||
-- [6, 12]
|
||||
|
||||
@@ -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 ' +
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 18 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 5.8 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: 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://leanprover.github.io/theorem_proving_in_lean4/inductive_types.html) has a chapter about inductive datatypes.
|
||||
TODO
|
||||
@@ -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',
|
||||
|
||||
@@ -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},
|
||||
|
||||
@@ -14,7 +14,7 @@ to separate the elements of a list, and in the lambda expression itself. We now
|
||||
as an example, `fun x => x` is the identity function. One may still use the symbol `λ` as a shorthand for `fun`.
|
||||
The lambda expression notation has many new features that are not supported in Lean 3.
|
||||
|
||||
## Pattern matching
|
||||
* Pattern matching
|
||||
|
||||
In Lean 4, one can easily create new notation that abbreviates commonly used idioms. One of them is a
|
||||
`fun` followed by a `match`. In the following examples, we define a few functions using `fun`+`match` notation.
|
||||
@@ -39,7 +39,7 @@ def Sum.str : Option Nat → String :=
|
||||
# end ex1
|
||||
```
|
||||
|
||||
## Implicit lambdas
|
||||
* 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 we the expected type is a function type with implicit arguments,
|
||||
@@ -83,7 +83,7 @@ def id5 : {α : Type} → α → α :=
|
||||
# end ex2
|
||||
```
|
||||
|
||||
## Sugar for simple functions
|
||||
* 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:
|
||||
|
||||
@@ -230,10 +230,11 @@ restriction imposed by ordinary type theory. Metadefinitions may also use unsafe
|
||||
The keyword `meta` has been currently removed from Lean 4. However, we may re-introduce it in the future,
|
||||
but with a much more limited purpose: marking meta code that should not be included in the executables produced by Lean.
|
||||
|
||||
The keyword `constant` has been deleted in Lean 4, and `axiom` should be used instead. In Lean 4, the new command `opaque` is used to define an opaque definition. Here are two simple examples:
|
||||
The keywords `axiom` and `constant` are not equivalent in Lean 4. In Lean 4, `constant` is used to define
|
||||
an opaque definition. Here are two simple examples:
|
||||
```lean
|
||||
# namespace meta1
|
||||
opaque x : Nat := 1
|
||||
constant x : Nat := 1
|
||||
-- The following example will not type check since `x` is opaque
|
||||
-- example : x = 1 := rfl
|
||||
|
||||
@@ -243,11 +244,11 @@ opaque x : Nat := 1
|
||||
|
||||
-- When no value is provided, the elaborator tries to build one automatically for us
|
||||
-- using the `Inhabited` type class
|
||||
opaque y : Nat
|
||||
constant y : Nat
|
||||
# end meta1
|
||||
```
|
||||
|
||||
We can instruct Lean to use a foreign function as the implementation for any definition
|
||||
We can instruct Lean to use a foreign function as the implementation for any constant or definition
|
||||
using the attribute `@[extern "foreign_function"]`. It is the user's responsibility to ensure the
|
||||
foreign implementation is correct.
|
||||
However, a user mistake here will only impact the code generated by Lean, and
|
||||
@@ -335,34 +336,3 @@ partial def f (x : Nat) : IO Unit := do
|
||||
#eval f 98
|
||||
# end partial1
|
||||
```
|
||||
|
||||
## Library changes
|
||||
|
||||
These are changes to the library which may trip up Lean 3 users:
|
||||
|
||||
- `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
|
||||
|
||||
Coding style changes have also been made:
|
||||
|
||||
- Term constants and variables are now `lowerCamelCase` rather than `snake_case`
|
||||
- Type constants are now `UpperCamelCase`, eg `Nat`, `List`. Type variables are still lower case greek letters. Functors are still lower case latin `(m : Type → Type) [Monad m]`.
|
||||
- When defining typeclasses, prefer not to use "has". Eg `ToString` or `Add` instead of `HasToString` or `HasAdd`.
|
||||
- Prefer `return` to `pure` in monad expressions.
|
||||
- Pipes `<|` are preferred to dollars `$` for function application.
|
||||
- Declaration bodies should always be indented:
|
||||
```lean
|
||||
inductive Hello where
|
||||
| foo
|
||||
| bar
|
||||
|
||||
structure Point where
|
||||
x : Nat
|
||||
y : Nat
|
||||
|
||||
def Point.addX : Point → Point → Nat :=
|
||||
fun { x := a, .. } { x := b, .. } => a + b
|
||||
```
|
||||
- In structures and typeclass definitions, prefer `where` to `:=` and don't surround fields with parentheses. (Shown in `Point` above)
|
||||
|
||||
@@ -158,7 +158,7 @@ Output:
|
||||
[Elab.definition.body] binderterm.quot : Lean.ParserDescr :=
|
||||
Lean.ParserDescr.node `Lean.Parser.Term.quot 1024
|
||||
(Lean.ParserDescr.binary `andthen (Lean.ParserDescr.symbol "`(binderterm|")
|
||||
(Lean.ParserDescr.binary `andthen (Lean.ParserDescr.cat `binderterm 0)
|
||||
(Lean.ParserDescr.binary `andthen (Lean.ParserDescr.unary `incQuotDepth (Lean.ParserDescr.cat `binderterm 0))
|
||||
(Lean.ParserDescr.symbol ")")))
|
||||
-/
|
||||
```
|
||||
|
||||
@@ -46,8 +46,9 @@ For example, on an AMD Ryzen 9 `make` takes 00:04:55, whereas `make -j 10`
|
||||
takes 00:01:38. Your results may vary depending on the speed of your hard
|
||||
drive.
|
||||
|
||||
You should not usually run `make install` after a successful build.
|
||||
See [Dev setup using elan](../dev/index.md#dev-setup-using-elan) on how to properly set up your editor to use the correct stage depending on the source directory.
|
||||
To install the build, see [Dev setup using
|
||||
elan](../dev/index.md#dev-setup-using-elan).
|
||||
|
||||
|
||||
Useful CMake Configuration Settings
|
||||
-----------------------------------
|
||||
|
||||
@@ -18,7 +18,8 @@ the stdlib.
|
||||
## Installing dependencies
|
||||
|
||||
[The official webpage of MSYS2][msys2] provides one-click installers.
|
||||
Once installed, you should run the "MSYS2 MinGW 64-bit shell" from the start menu (the one that runs `mingw64.exe`).
|
||||
Once installed, you should run the "MSYS2 MinGW 64-bit shell" from the start menu.
|
||||
(The one that runs `mingw64.exe`)
|
||||
Do not run "MSYS2 MSYS" instead!
|
||||
MSYS2 has a package management system, [pacman][pacman], which is used in Arch Linux.
|
||||
|
||||
@@ -73,11 +74,6 @@ The following linux command will do that:
|
||||
cp $(ldd lean.exe | cut -f3 -d' ' | grep mingw) .
|
||||
```
|
||||
|
||||
However, if you plan to use this build to compile lean programs
|
||||
to executable binaries using `lake build` in normal Windows command
|
||||
prompt outside of msys2 environment you will also need to add a windows
|
||||
version clang to your path.
|
||||
|
||||
## Trouble shooting
|
||||
|
||||
**-bash: gcc: command not found**
|
||||
|
||||
@@ -10,7 +10,7 @@ Follow the setup in the link above; to open the Lean shell inside a Lean checkou
|
||||
$ 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.
|
||||
On top of the local and remote Nix cache, it helps to 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
|
||||
@@ -35,7 +35,7 @@ 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
|
||||
nix build # dito
|
||||
```
|
||||
|
||||
## Build Process Description
|
||||
@@ -52,14 +52,11 @@ This is because modules are discovered not from a directory listing anymore but
|
||||
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:
|
||||
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):
|
||||
@@ -89,7 +86,7 @@ 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)
|
||||
nix build .#stage0-from-input
|
||||
```
|
||||
This setup will inadvertently change your `flake.lock` file, which you can revert when you are done.
|
||||
|
||||
|
||||
@@ -1,58 +0,0 @@
|
||||
inductive Arith : Type
|
||||
| add : Arith → Arith → Arith -- e + f
|
||||
| mul : Arith → Arith → Arith -- e * f
|
||||
| int : Int → Arith -- constant
|
||||
| symbol : String → Arith -- variable
|
||||
|
||||
declare_syntax_cat arith
|
||||
|
||||
syntax num : arith -- int for Arith.int
|
||||
syntax str : arith -- strings for Arith.symbol
|
||||
syntax:60 arith:60 "+" arith:61 : arith -- Arith.add
|
||||
syntax:70 arith:70 "*" arith:71 : arith -- Arith.mul
|
||||
syntax "(" arith ")" : arith -- parenthesized expressions
|
||||
|
||||
-- auxiliary notation for translating `arith` into `term`
|
||||
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])
|
||||
|
||||
#check `[Arith| "x" * "y"] -- mul
|
||||
-- Arith.mul (Arith.symbol "x") (Arith.symbol "y")
|
||||
|
||||
#check `[Arith| "x" + "y"] -- add
|
||||
-- Arith.add (Arith.symbol "x") (Arith.symbol "y")
|
||||
|
||||
#check `[Arith| "x" + 20] -- symbol + int
|
||||
-- Arith.add (Arith.symbol "x") (Arith.int 20)
|
||||
|
||||
#check `[Arith| "x" + "y" * "z"] -- precedence
|
||||
-- Arith.add (Arith.symbol "x") (Arith.mul (Arith.symbol "y") (Arith.symbol "z"))
|
||||
|
||||
#check `[Arith| "x" * "y" + "z"] -- precedence
|
||||
-- Arith.add (Arith.mul (Arith.symbol "x") (Arith.symbol "y")) (Arith.symbol "z")
|
||||
|
||||
#check `[Arith| ("x" + "y") * "z"] -- parentheses
|
||||
-- Arith.mul (Arith.add (Arith.symbol "x") (Arith.symbol "y")) (Arith.symbol "z")
|
||||
|
||||
syntax ident : arith
|
||||
|
||||
macro_rules
|
||||
| `(`[Arith| $x:ident]) => `(Arith.symbol $(Lean.quote (toString x.getId)))
|
||||
|
||||
#check `[Arith| x] -- Arith.symbol "x"
|
||||
|
||||
def xPlusY := `[Arith| x + y]
|
||||
#print xPlusY -- def xPlusY : Arith := Arith.add (Arith.symbol "x") (Arith.symbol "y")
|
||||
|
||||
syntax "<[" term "]>" : arith -- escape for embedding terms into `Arith`
|
||||
|
||||
macro_rules
|
||||
| `(`[Arith| <[ $e:term ]>]) => pure e
|
||||
|
||||
#check `[Arith| <[ xPlusY ]> + z] -- Arith.add xPlusY (Arith.symbol "z")
|
||||
@@ -1,134 +0,0 @@
|
||||
# Arithmetic as an embedded domain-specific language
|
||||
|
||||
Let's parse another classic grammar, the grammar of arithmetic expressions with
|
||||
addition, multiplication, integers, and variables. In the process, we'll learn
|
||||
how to:
|
||||
|
||||
- Convert identifiers such as `x` into strings within a macro.
|
||||
- add the ability to "escape" the macro context from within the macro. This is useful to interpret identifiers with their _original_ meaning (predefined values)
|
||||
instead of their new meaning within a macro (treat as a symbol).
|
||||
|
||||
Let's begin with the simplest thing possible. We'll define an AST, and use operators `+` and `*` to denote
|
||||
building an arithmetic AST.
|
||||
|
||||
|
||||
Here's the AST that we will be parsing:
|
||||
|
||||
```lean,ignore
|
||||
{{#include metaprogramming-arith.lean:1:5}}
|
||||
```
|
||||
|
||||
We declare a syntax category to describe the grammar that we will be parsing.
|
||||
See that we control the precedence of `+` and `*` by writing `syntax:50` for addition and `syntax:60` for multiplication,
|
||||
indicating that multiplication binds tighter than addition (higher the number, tighter the binding).
|
||||
This allows us to declare _precedence_ when defining new syntax.
|
||||
|
||||
```lean,ignore
|
||||
{{#include metaprogramming-arith.lean:7:13}}
|
||||
```
|
||||
|
||||
Further, if we look at `syntax:60 arith:60 "+" arith:61 : arith`, the
|
||||
precedence declarations at `arith:60 "+" arith:61` conveys that the left
|
||||
argument must have precedence at least `60` or greater, and the right argument
|
||||
must have precedence at least`61` or greater. Note that this forces left
|
||||
associativity. To understand this, let's compare two hypothetical parses:
|
||||
|
||||
```
|
||||
-- syntax:60 arith:60 "+" arith:61 : arith -- Arith.add
|
||||
-- a + b + c
|
||||
(a:60 + b:61):60 + c
|
||||
a + (b:60 + c:61):60
|
||||
```
|
||||
|
||||
In the parse tree of `a + (b:60 + c:61):60`, we see that the right argument `(b + c)` is given the precedence `60`. However,
|
||||
the rule for addition expects the right argument to have a precedence of **at least** 61, as witnessed by the `arith:61` at
|
||||
the right-hand-side of `syntax:60 arith:60 "+" arith:61 : arith`. Thus, the rule `syntax:60 arith:60 "+" arith:61 : arith`
|
||||
ensures that addition is left associative.
|
||||
|
||||
Since addition is declared arguments of precedence `60/61` and multiplication with `70/71`, this causes multiplication to bind
|
||||
tighter than addition. Once again, let's compare two hypothetical parses:
|
||||
|
||||
```
|
||||
-- syntax:60 arith:60 "+" arith:61 : arith -- Arith.add
|
||||
-- syntax:70 arith:70 "*" arith:71 : arith -- Arith.mul
|
||||
-- a * b + c
|
||||
a * (b:60 + c:61):60
|
||||
(a:70 * b:71):70 + c
|
||||
```
|
||||
|
||||
While parsing `a * (b + c)`, `(b + c)` is assigned a precedence `60` by the addition rule. However, multiplication expects
|
||||
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](../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
|
||||
lives in `term`:
|
||||
|
||||
|
||||
```lean,ignore
|
||||
{{#include metaprogramming-arith.lean:15:16}}
|
||||
```
|
||||
|
||||
Our macro rules perform the "obvious" translation:
|
||||
|
||||
```lean,ignore
|
||||
{{#include metaprogramming-arith.lean:18:23}}
|
||||
```
|
||||
|
||||
And some examples:
|
||||
|
||||
```lean,ignore
|
||||
{{#include metaprogramming-arith.lean:25:41}}
|
||||
```
|
||||
|
||||
Writing variables as strings, such as `"x"` gets old; wouldn't it be so much
|
||||
prettier if we could write `x * y`, and have the macro translate this into `Arith.mul (Arith.Symbol "x") (Arith.mul "y")`?
|
||||
We can do this, and this will be our first taste of manipulating macro variables --- we'll use `x.getId` instead of directly evaluating `$x`.
|
||||
We also write a macro rule for `Arith|` that translates an identifier into
|
||||
a string, using `$(Lean.quote (toString x.getId))`:
|
||||
|
||||
```lean,ignore
|
||||
{{#include metaprogramming-arith.lean:43:46}}
|
||||
```
|
||||
|
||||
Let's test and see that we can now write expressions such as `x * y` directly instead of having to write `"x" * "y"`:
|
||||
|
||||
```lean,ignore
|
||||
{{#include metaprogramming-arith.lean:48:51}}
|
||||
```
|
||||
|
||||
We now show an unfortunate consequence of the above definitions. Suppose we want to build `(x + y) + z`.
|
||||
Since we already have defined `xPlusY` as `x + y`, perhaps we should reuse it! Let's try:
|
||||
|
||||
```lean,ignore
|
||||
#check `[Arith| xPlusY + z] -- Arith.add (Arith.symbol "xPlusY") (Arith.symbol "z")
|
||||
```
|
||||
|
||||
Whoops, that didn't work! What happened? Lean treats `xPlusY` _itself_ as an identifier! So we need to add some syntax
|
||||
to be able to "escape" the `Arith|` context. Let's use the syntax `<[ $e:term ]>` to mean: evaluate `$e` as a real term,
|
||||
not an identifier. The macro looks like follows:
|
||||
|
||||
```lean,ignore
|
||||
{{#include metaprogramming-arith.lean:53:56}}
|
||||
```
|
||||
|
||||
Let's try our previous example:
|
||||
|
||||
```lean,ignore
|
||||
{{#include metaprogramming-arith.lean:58:58}}
|
||||
```
|
||||
|
||||
Perfect!
|
||||
|
||||
In this tutorial, we expanded on the previous tutorial to parse a more
|
||||
realistic grammar with multiple levels of precedence, how to parse identifiers directly
|
||||
within a macro, and how to provide an escape from within the macro context.
|
||||
|
||||
#### Full code listing
|
||||
|
||||
```lean
|
||||
{{#include metaprogramming-arith.lean}}
|
||||
```
|
||||
|
||||
10
doc/metaprogramming.md
Normal file
10
doc/metaprogramming.md
Normal file
@@ -0,0 +1,10 @@
|
||||
# Metaprogramming
|
||||
|
||||
Macros are a language feature that allows writing code that writes other code (metaprogramming).
|
||||
|
||||
In Lean 4, macros are used pervasively. So much so that core language features such as `do` notation
|
||||
is implemented via macros! As a language user, macros are useful to easily
|
||||
embed domain-specific languages and to generate code at compile-time, to name a few uses.
|
||||
|
||||
## References
|
||||
- [Hygenic Macro Expansion for Theorem Proving Languages](https://arxiv.org/abs/2001.10490)
|
||||
1
doc/monads/.gitignore
vendored
1
doc/monads/.gitignore
vendored
@@ -1 +0,0 @@
|
||||
*.lean.md
|
||||
@@ -1,326 +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 is an 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`:
|
||||
|
||||
```lean,ignore
|
||||
class Applicative (f : Type u → Type v) extends Functor f, Pure f, Seq f, SeqLeft f, SeqRight f where
|
||||
```
|
||||
|
||||
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.
|
||||
|
||||
```lean,ignore
|
||||
class Pure (f : Type u → Type v) where
|
||||
pure {α : Type u} : α → f α
|
||||
```
|
||||
|
||||
You can think of it as lifing 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 `<*>`.
|
||||
|
||||
```lean,ignore
|
||||
class Seq (f : Type u → Type v) : Type (max (u+1) v) where
|
||||
seq : {α β : Type u} → f (α → β) → (Unit → f α) → f β
|
||||
```
|
||||
|
||||
|
||||
## 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 couse, 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 `*>` repsectively. 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 somthing interesting:
|
||||
|
||||
-/
|
||||
#eval Seq.seq ((.*.) <$> some 4) (fun (_ : Unit) => some 5) -- some 20
|
||||
/-!
|
||||
|
||||
This may look a bit combersome, 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,166 +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 pretty much ignored in that case.
|
||||
|
||||
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 try 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.
|
||||
|
||||
## 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 fuunction. 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"]
|
||||
#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 Functors has a more specialized variant than this which 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 it to reused 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 lean the specifics of how
|
||||
to actually use monads with some examples using the `Option` monad and the all important `IO` monad.
|
||||
|
||||
## [Reader Monads](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 makes available 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 of 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 monad. After all, can't you just implement these type
|
||||
classes any way you want and write a "monad" instance? 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,318 +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. Many structures have similar laws, including monads.
|
||||
|
||||
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
|
||||
|
||||
#eval ["apple", "orange"] >>= pure -- ["apple", "orange"]
|
||||
#eval [1,2,3] >>= pure -- [1,2,3]
|
||||
/-!
|
||||
|
||||
### 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 x -- 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 bahavior of your
|
||||
new functors, applicatives and monads.
|
||||
|
||||
-/
|
||||
@@ -1,300 +0,0 @@
|
||||
/-!
|
||||
# Monads
|
||||
|
||||
Building on [Functors](functors.lean.md) and [Applicatives](applicatives.lean.md) we can now
|
||||
introduce [monads](https://en.wikipedia.org/wiki/Monad_%28category_theory%29).
|
||||
|
||||
A monad is another type of abstract, functional structure. Let's explore what makes it different
|
||||
from the first two structures.
|
||||
|
||||
## What is a Monad?
|
||||
|
||||
A monad is a computational context. It provides a structure that allows you to chain together
|
||||
operations that have some kind of shared state or similar effect. Whereas pure functional code can
|
||||
only operate on explicit input parameters and affect the program through explicit return values,
|
||||
operations in a monad can affect other computations in the chain implicitly through side effects,
|
||||
especially modification of an implicitly shared value.
|
||||
|
||||
## How are monads represented in Lean?
|
||||
|
||||
Like functors and applicatives, monads are represented with a type class in Lean:
|
||||
|
||||
```lean,ignore
|
||||
class Monad (m : Type u → Type v) extends Applicative m, Bind m where
|
||||
```
|
||||
|
||||
Just as every applicative is a functor, every monad is also an applicative and there's one more new
|
||||
base type class used here that you need to understand, namely, `Bind`.
|
||||
|
||||
```lean,ignore
|
||||
class Bind (f : Type u → Type v) where
|
||||
bind : {α β : Type u} → f α → (α → f β) → f β
|
||||
```
|
||||
|
||||
The `bind` operator also has infix notation `>>=` where `x >>= g` represents the result of executing
|
||||
`x` to get a value of type `f α` then unwrapping the value `α` from that and passing it to function
|
||||
`g` of type `α → f β` returning the result of type `f β` where `f` is the target structure type
|
||||
(like `Option` or List)
|
||||
|
||||
This `bind` operation looks similar to the other ones you've seen so far, if you put them all
|
||||
together `Monad` has the following operations:
|
||||
|
||||
```lean,ignore
|
||||
class Monad (f : Type u → Type v) extends Applicative f, Bind f where
|
||||
pure {α : Type u} : α → f α
|
||||
map : {α β : Type u} → (α → β) → f α → f β
|
||||
seq : {α β : Type u} → f (α → β) → (Unit → f α) → f β
|
||||
bind : {α β : Type u} → f α → (α → f β) → f β
|
||||
...
|
||||
```
|
||||
|
||||
Notice `Monad` also contains `pure` it must also have a "default" way to wrap a value in the
|
||||
structure.
|
||||
|
||||
The `bind` operator is similar to the applicative `seq` operator in that it chains two operations,
|
||||
with one of them being function related. Notice that `bind`, `seq` and `map` all take a function of
|
||||
some kind. Let's examine those function types:
|
||||
|
||||
- map: `(α → β)`
|
||||
- seq: `f (α → β)`
|
||||
- bind: `(α → f β)`
|
||||
|
||||
So `map` is a pure function, `seq` is a pure function wrapped in the structure, and `bind` takes a
|
||||
pure input but produces an output wrapped in the structure.
|
||||
|
||||
Note: we are ignoring the `(Unit → f α)` function also used by `seq` in this comparison, since that
|
||||
was explained in [Applicatives Lazy Evaluation](applicatives.lean.md#lazy-evaluation).
|
||||
|
||||
## Basic Monad Example
|
||||
|
||||
Just as `Option` is a functor and an applicative functor, it is also a monad! Let's start with how
|
||||
`Option` implements the Monad type class.
|
||||
|
||||
-/
|
||||
instance : Monad Option where
|
||||
pure := Option.some
|
||||
bind := Option.bind
|
||||
/-!
|
||||
|
||||
where:
|
||||
|
||||
```lean,ignore
|
||||
def Option.bind : Option α → (α → Option β) → Option β
|
||||
| none, _ => none
|
||||
| some a, f => f a
|
||||
```
|
||||
|
||||
> **Side note**: this function definition is using a special shorthand syntax in Lean where the `:=
|
||||
match a, b with` code can be collapsed away. To make this more clear consider the following simpler
|
||||
example, where `Option.bind` is using the second form like `bar`:
|
||||
|
||||
-/
|
||||
def foo (x : Option Nat) (y : Nat) : Option Nat :=
|
||||
match x, y with
|
||||
| none, _ => none
|
||||
| some x, y => some (x + y)
|
||||
|
||||
def bar : Option Nat → Nat → Option Nat
|
||||
| none, _ => none
|
||||
| some x, y => some (x + y)
|
||||
|
||||
#eval foo (some 1) 2 -- some 3
|
||||
#eval bar (some 1) 2 -- some 3
|
||||
/-!
|
||||
What is important is that `Option.bind` is using a `match` statement to unwrap the input value
|
||||
`Option α`, if it is `none` then it does nothing and returns `none`, if it has a value of type `α`
|
||||
then it applies the function in the second argument `(α → Option β)` to this value, which is
|
||||
the expression `f a` that you see in the line ` | some a, f => f a` above. The function
|
||||
returns a result of type `Option β` which then becomes the return value for `bind`. So there
|
||||
is no structure wrapping required on the return value since the input function already did that.
|
||||
|
||||
But let's bring in the definition of a monad. What does it mean to describe `Option` as a
|
||||
computational context?
|
||||
|
||||
The `Option` monad encapsulates the context of failure. Essentially, the `Option` monad lets us
|
||||
abort a series of operations whenever one of them fails. This allows future operations to assume
|
||||
that all previous operations have succeeded. Here's some code to motivate this idea:
|
||||
|
||||
-/
|
||||
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 runOptionFuncs (input : String) : Option (List Nat) :=
|
||||
match optionFunc1 input with
|
||||
| none => none
|
||||
| some i => match optionFunc2 i with
|
||||
| none => none
|
||||
| some f => optionFunc3 f
|
||||
|
||||
#eval runOptionFuncs "big" -- some [9, 10]
|
||||
/-!
|
||||
|
||||
Here you see three different functions that could fail. These are then combined in `runOptionFuncs`.
|
||||
But then you have to use nested `match` expressions to check if the previous result succeeded. It
|
||||
would be very tedious to continue this pattern much longer.
|
||||
|
||||
The `Option` monad helps you fix this. Here's what this function looks like using the `bind`
|
||||
operator.
|
||||
|
||||
-/
|
||||
|
||||
def runOptionFuncsBind (input : String) : Option (List Nat) :=
|
||||
optionFunc1 input >>= optionFunc2 >>= optionFunc3
|
||||
|
||||
#eval runOptionFuncsBind "big" -- some [9, 10]
|
||||
/-!
|
||||
|
||||
It's much cleaner now! You take the first result and pass it into the second and third functions
|
||||
using the `bind` operation. The monad instance handles all the failure cases so you don't have to!
|
||||
|
||||
Let's see why the types work out. The result of `optionFunc1` input is simply `Option Nat`. Then the
|
||||
bind operator allows you to take this `Option Nat` value and combine it with `optionFunc2`, whose type
|
||||
is `Nat → Option Float` The **bind operator resolves** these to an `Option Float`. Then you pass this
|
||||
similarly through the bind operator to `optionFunc3`, resulting in the final type, `Option (List Nat)`.
|
||||
|
||||
Your functions will not always combine so cleanly though. This is where `do` notation comes into play.
|
||||
This notation allows you to write monadic operations one after another, line-by-line. It almost makes
|
||||
your code look like imperative programming. You can rewrite the above as:
|
||||
-/
|
||||
|
||||
def runOptionFuncsDo (input : String) : Option (List Nat) := do
|
||||
let i ← optionFunc1 input
|
||||
let f ← optionFunc2 i
|
||||
optionFunc3 f
|
||||
|
||||
#eval runOptionFuncsDo "big" -- some [9, 10]
|
||||
/-!
|
||||
|
||||
The `←` operator used here is special. It effectively unwraps the value on the right-hand side from
|
||||
the monad. This means the value `i` has type `Nat`, _even though_ the result of `optionFunc1` is
|
||||
`Option Nat`. This is done using a `bind` operation under the hood.
|
||||
|
||||
> Note you can use `<-` or the nice unicode symbol `←` which you can type into VS code by typing
|
||||
these characters `\l `. When you type the final space, `\l` is replaced with `←`.
|
||||
|
||||
Observe that we do not unwrap the final line of the computation. The function result is `Option
|
||||
(List Nat)` which matches what `optionFunc3` returns. At first glance, this may look more complicated
|
||||
than the `bind` example. However, it gives you a lot more flexibility, like mixing monadic and
|
||||
non-monadic statements, using if then/else structures with their own local do blocks and so on. It
|
||||
is particularly helpful when one monadic function depends on multiple previous functions.
|
||||
|
||||
## Example using List
|
||||
|
||||
You can easily make `List` into a monad with the following, since List already provides an
|
||||
implementation of `pure` and `bind`.
|
||||
|
||||
-/
|
||||
instance : Monad List where
|
||||
pure := List.pure
|
||||
bind := List.bind
|
||||
/-!
|
||||
|
||||
Like you saw with the applicative `seq` operator, the `bind` operator applies the given function
|
||||
to every element of the list. It is useful to look at the bind implementation for List:
|
||||
|
||||
-/
|
||||
open List
|
||||
def bind (a : List α) (b : α → List β) : List β := join (map b a)
|
||||
/-!
|
||||
|
||||
So `Functor.map` is used to apply the function `b` to every element of `a` but this would
|
||||
return a whole bunch of little lists, so `join` is used to turn those back into a single list.
|
||||
|
||||
Here's an example where you use `bind` to convert a list of strings into a combined list of chars:
|
||||
|
||||
-/
|
||||
|
||||
#eval "apple".toList -- ['a', 'p', 'p', 'l', 'e']
|
||||
|
||||
#eval ["apple", "orange"] >>= String.toList
|
||||
-- ['a', 'p', 'p', 'l', 'e', 'o', 'r', 'a', 'n', 'g', 'e']
|
||||
|
||||
/-!
|
||||
|
||||
|
||||
## The IO Monad
|
||||
|
||||
The `IO Monad` is perhaps the most important monad in Lean. It is also one of the hardest monads to
|
||||
understand starting out. Its actual implementation is too intricate to discuss when first learning
|
||||
monads. So it is best to learn by example.
|
||||
|
||||
What is the **computational context** that describes the IO monad? IO operations can read
|
||||
information from or write information to the terminal, file system, operating system, and/or
|
||||
network. They interact with systems outside of your program. If you want to get user input, print a
|
||||
message to the user, read information from a file, or make a network call, you'll need to do so
|
||||
within the IO Monad.
|
||||
|
||||
The state of the world outside your program can change at virtually any moment, and so this IO
|
||||
context is particularly special. So these IO operations are "side effects" which means you cannot
|
||||
perform them from "pure" Lean functions.
|
||||
|
||||
Now, the most important job of pretty much any computer program is precisely to perform this
|
||||
interaction with the outside world. For this reason, the root of all executable Lean code is a
|
||||
function called main, with the type `IO Unit`. So every program starts in the IO monad!
|
||||
|
||||
When your function is `IO` monadic, you can get any input you need, call into "pure" code with the
|
||||
inputs, and then output the result in some way. The reverse does not work. You cannot call into IO
|
||||
code from pure code like you can call into a function that takes `Option` as input. Another way to
|
||||
say this is you cannot invent an `IO` context out of thin air, it has to be given to you in your
|
||||
`main` function.
|
||||
|
||||
Let's look at a simple program showing a few of the basic IO functions. It also uses `do` notation
|
||||
to make the code read nicely:
|
||||
-/
|
||||
def main : IO Unit := do
|
||||
IO.println "enter a line of text:"
|
||||
let stdin ← IO.getStdin -- IO IO.FS.Stream (monadic)
|
||||
let input ← stdin.getLine -- IO.FS.Stream → IO String (monadic)
|
||||
let uppercased := input.toUpper -- String → String (pure)
|
||||
IO.println uppercased -- IO Unit (monadic)
|
||||
/-!
|
||||
|
||||
So, once again you can see that the `do` notation lets you chain a series of monadic actions.
|
||||
`IO.getStdin` is of type `IO IO.FS.Stream` and `stdin.getLine` is of type `IO String`
|
||||
and `IO.println` is of type `IO Unit`.
|
||||
|
||||
In between you see a non-monadic expression `let uppercased := input.toUpper` which is fine too.
|
||||
A let statement can occur in any monad. Just as you could unwrap `i` from `Option Nat` to get the
|
||||
inner Nat, you can use `←` to unwrap the result of `getLine` to get a String. You can then manipulate
|
||||
this value using normal pure string functions like `toUpper`, and then you can pass the result to the
|
||||
`IO.println` function.
|
||||
|
||||
This is a simple echo program. It reads a line from the terminal, and then prints the line back out
|
||||
capitalized to the terminal. Hopefully it gives you a basic understanding of how IO works.
|
||||
|
||||
You can test this program using `lean --run` as follows:
|
||||
|
||||
```
|
||||
> lean --run Main.lean
|
||||
enter a line of text:
|
||||
the quick brown fox
|
||||
THE QUICK BROWN FOX
|
||||
```
|
||||
|
||||
Here the user entered the string `the quick brown fox` and got back the uppercase result.
|
||||
|
||||
## What separates Monads from Applicatives?
|
||||
|
||||
The key that separates these is **context**. You cannot really determine the structure of
|
||||
"future" operations without knowing the results of "past" operations, because the past can alter the
|
||||
context in which the future operations work. With applicatives, you can't get the final function
|
||||
result without evaluating everything, but you can determine the structure of how the operation will
|
||||
take place. This allows some degree of parallelism with applicatives that is not generally possible
|
||||
with monads.
|
||||
|
||||
|
||||
## Conclusion
|
||||
|
||||
Hopefully you now have a basic level understanding of what a monad is. But perhaps some more
|
||||
examples of what a "computational context" means would be useful to you. The Reader, State and
|
||||
Except monads each provide a concrete and easily understood context that can be compared easily to
|
||||
function parameters. You can learn more about those in [Reader monads](readers.lean.md),
|
||||
[State monads](states.lean.md), and the [Except monad](except.lean.md).
|
||||
-/
|
||||
@@ -1,194 +0,0 @@
|
||||
/-!
|
||||
# Readers
|
||||
|
||||
In the [previous section](monads.lean.md) you learned about the conceptual idea of monads. You learned
|
||||
what they are, and saw how some common types like `IO` and `Option` work as monads. Now in this
|
||||
part, you will be looking at some other useful monads. In particular, the `ReaderM` monad.
|
||||
|
||||
## How to do Global Variables in Lean?
|
||||
|
||||
In Lean, your code is generally "pure", meaning functions can only interact with the arguments
|
||||
passed to them. This effectively means you cannot have global variables. You can have global
|
||||
definitions, but these are fixed at compile time. If some user behavior might change them, you have
|
||||
to wrap them in the `IO` monad, which means they can't be used from pure code.
|
||||
|
||||
Consider this example. Here, you want to have an `Environment` containing different parameters as a
|
||||
global variable. However, you want to load these parameters from the process environment variables,
|
||||
which requires the `IO` monad.
|
||||
-/
|
||||
|
||||
structure Environment where
|
||||
path : String
|
||||
home : String
|
||||
user : String
|
||||
deriving Repr
|
||||
|
||||
def getEnvDefault (name : String): IO String := do
|
||||
let val? ← IO.getEnv name
|
||||
pure <| match val? with
|
||||
| none => ""
|
||||
| some s => s
|
||||
|
||||
def loadEnv : IO Environment := do
|
||||
let path ← getEnvDefault "PATH"
|
||||
let home ← getEnvDefault "HOME"
|
||||
let user ← getEnvDefault "USER"
|
||||
pure { path, home, user }
|
||||
|
||||
def func1 (e : Environment) : Float :=
|
||||
let l1 := e.path.length
|
||||
let l2 := e.home.length * 2
|
||||
let l3 := e.user.length * 3
|
||||
(l1 + l2 + l3).toFloat * 2.1
|
||||
|
||||
def func2 (env : Environment) : Nat :=
|
||||
2 + (func1 env).floor.toUInt32.toNat
|
||||
|
||||
def func3 (env : Environment) : String :=
|
||||
"Result: " ++ (toString (func2 env))
|
||||
|
||||
def main : IO Unit := do
|
||||
let env ← loadEnv
|
||||
let str := func3 env
|
||||
IO.println str
|
||||
|
||||
#eval main -- Result: 7538
|
||||
|
||||
/-!
|
||||
The only function actually using the environment is func1. However func1 is a pure function. This
|
||||
means it cannot directly call loadEnv, an impure function in the IO monad. This means the
|
||||
environment has to be passed through as a variable to the other functions, just so they can
|
||||
ultimately pass it to func1. In a language with global variables, you could save env as a global
|
||||
value in main. Then func1 could access it directly. There would be no need to have it as a parameter
|
||||
to func1, func2 and func3. In larger programs, these "pass-through" variables can cause a lot of
|
||||
headaches.
|
||||
|
||||
## The Reader Solution
|
||||
|
||||
The `ReaderM` monad solves this problem. It effectively creates a global read-only value of a
|
||||
specified type. All functions within the monad can "read" the type. Let's look at how the `ReaderM`
|
||||
monad changes the shape of this code. Now the functions **no longer need** to be given the
|
||||
`Environment` as an explicit parameter, as they can access it through the monad.
|
||||
-/
|
||||
|
||||
def readerFunc1 : ReaderM Environment Float := do
|
||||
let env ← read
|
||||
let l1 := env.path.length
|
||||
let l2 := env.home.length * 2
|
||||
let l3 := env.user.length * 3
|
||||
return (l1 + l2 + l3).toFloat * 2.1
|
||||
|
||||
def readerFunc2 : ReaderM Environment Nat :=
|
||||
readerFunc1 >>= (fun x => return 2 + (x.floor.toUInt32.toNat))
|
||||
|
||||
def readerFunc3 : ReaderM Environment String := do
|
||||
let x ← readerFunc2
|
||||
return "Result: " ++ toString x
|
||||
|
||||
def main2 : IO Unit := do
|
||||
let env ← loadEnv
|
||||
let str := readerFunc3.run env
|
||||
IO.println str
|
||||
|
||||
#eval main2 -- Result: 7538
|
||||
/-!
|
||||
The `ReaderM` monad provides a `run` method and it is the `ReaderM` run method that takes the initial
|
||||
`Environment` context. So here you see `main2` loads the environment as before, and estabilishes
|
||||
the `ReaderM` context by passing `env` to the `run` method.
|
||||
|
||||
> **Side note 1**: The `return` statement used above also needs some explanation. The `return`
|
||||
statement in Lean is closely related to `pure`, but a little different. First the similarity is that
|
||||
`return` and `pure` both lift a pure value up to the Monad type. But `return` is a keyword so you do
|
||||
not need to parenthesize the expression like you do when using `pure`. (Note: you can avoid
|
||||
parentheses when using `pure` by using the `<|` operator like we did above in the initial
|
||||
`getEnvDefault` function). Furthermore, `return` can also cause an early `return` in a monadic
|
||||
function similar to how it can in an imperative language while `pure` cannot.
|
||||
|
||||
> So technically if `return` is the last statement in a function it could be replaced with `pure <|`,
|
||||
but one could argue that `return` is still a little easier for most folks to read, just so long as
|
||||
you understand that `return` is doing more than other languages, it is also wrapping pure values in
|
||||
the monadic container type.
|
||||
|
||||
> **Side note 2**: If the function `readerFunc3` also took some explicit arguments then you would have
|
||||
to write `(readerFunc3 args).run env` and this is a bit ugly, so Lean provides an infix operator
|
||||
`|>` that eliminiates those parens so you can write `readerFunc3 args |>.run env` and then you can
|
||||
chain multiple monadic actions like this `m1 args1 |>.run args2 |>.run args3` and this is the
|
||||
recommended style. You will see this patten used heavily in Lean code.
|
||||
|
||||
The `let env ← read` expression in `readerFunc1` unwraps the environment from the `ReaderM` so we
|
||||
can use it. Each type of monad might provide one or more extra functions like this, functions that
|
||||
become available only when you are in the context of that monad.
|
||||
|
||||
Here the `readerFunc2` function uses the `bind` operator `>>=` just to show you that there are bind
|
||||
operations happening here. The `readerFunc3` function uses the `do` notation you learned about in
|
||||
[Monads](monads.lean.md) which hides that bind operation and can make the code look cleaner.
|
||||
|
||||
The `do` notation with `let x ← readerFunc2` is also calling the `bind` function under the covers,
|
||||
so that you can access the unwrapped value `x` needed for the `toString x` conversion.
|
||||
|
||||
The important difference here to the earlier code is that `readerFunc3` and `readerFunc2` no longer
|
||||
have an **explicit** Environment input parameter that needs to be passed along all the way to
|
||||
`readerFunc1`. Instead, the `ReaderM` monad is taking care of that for you, which gives you the
|
||||
illusion of something like global context where the context is now available to all functions that use
|
||||
the `ReaderM` monad.
|
||||
|
||||
The above code also introduces an important idea. Whenever you learn about a monad "X", there's
|
||||
often (but not always) a `run` function to execute that monad, and sometimes some additional
|
||||
functions like `read` that interact with the monad context.
|
||||
|
||||
You might be wondering, how does the context actually move through the `ReaderM` monad? How can you
|
||||
add an input argument to a function by modifying its return type? There is a special command in
|
||||
Lean that will show you the reduced types:
|
||||
-/
|
||||
#reduce ReaderM Environment String -- Environment → String
|
||||
/-!
|
||||
And you can see here that this type is actually a function! It's a function that takes an
|
||||
`Environment` as input and returns a `String`.
|
||||
|
||||
Now, remember in Lean that a function that takes an argument of type `Nat` and returns a `String`
|
||||
like `def f (a : Nat) : String` is the same as this function `def f : Nat → String`. These are
|
||||
exactly equal as types. Well this is being used by the `ReaderM` Monad to add an input argument to
|
||||
all the functions that use the `ReaderM` monad and this is why `main` is able to start things off by
|
||||
simply passing that new input argument in `readerFunc3 env`. So now that you know the implementation
|
||||
details of the `ReaderM` monad you can see that what it is doing looks very much like the original
|
||||
code we wrote at the beginning of this section, only it's taking a lot of the tedious work off your
|
||||
plate and it is creating a nice clean separation between what your pure functions are doing, and the
|
||||
global context idea that the `ReaderM` adds.
|
||||
|
||||
## withReader
|
||||
|
||||
One `ReaderM` function can call another with a modified version of the `ReaderM` context. You can
|
||||
use the `withReader` function from the `MonadWithReader` typeclass to do this:
|
||||
|
||||
-/
|
||||
def readerFunc3WithReader : ReaderM Environment String := do
|
||||
let x ← withReader (λ env => { env with user := "new user" }) readerFunc2
|
||||
return "Result: " ++ toString x
|
||||
|
||||
/-!
|
||||
Here we changed the `user` in the `Environment` context to "new user" and then we
|
||||
passed that modified context to `readerFunc2`.
|
||||
|
||||
So `withReader f m` executes monad `m` in the `ReaderM` context modified by `f`.
|
||||
|
||||
## Handy shortcut with (← e)
|
||||
|
||||
If you use the operator `←` in a let expression and the variable is only used once you can
|
||||
eliminate the let expression and place the `←` operator in parentheses like this
|
||||
call to loadEnv:
|
||||
-/
|
||||
def main3 : IO Unit := do
|
||||
let str := readerFunc3 (← loadEnv)
|
||||
IO.println str
|
||||
/-!
|
||||
|
||||
## Conclusion
|
||||
|
||||
It might not seem like much has been accomplished with this `ReaderM Environment` monad, but you will
|
||||
find that in larger code bases, with many different types of monads all composed together this
|
||||
greatly cleans up the code. Monads provide a beautiful functional way of managing cross-cutting
|
||||
concerns that would otherwise make your code very messy.
|
||||
|
||||
Now it's time to move on to [StateM Monad](states.lean.md) which is like a `ReaderM` that is
|
||||
also updatable.
|
||||
-/
|
||||
@@ -1,252 +0,0 @@
|
||||
import Lean.Data.HashMap
|
||||
/-!
|
||||
# State
|
||||
|
||||
In the [previous section](readers.lean.md), you learned about the `ReaderM` monad. Hopefully this gave you
|
||||
a new perspective on Lean. It showed that, in fact, you _can_ have global variables of some sort;
|
||||
you just need to encode them in the type signature somehow, and this is what monads are for! In this
|
||||
part, you will explore the `StateM` monad, which is like a `ReaderM` only the state can also be updated.
|
||||
|
||||
## Motivating example: Tic Tac Toe
|
||||
|
||||
For this part, let's build a simple model for a Tic Tace Toe game. The main object is the `GameState`
|
||||
data type containing several important pieces of information. First and foremost, it has the
|
||||
"board", a map from 2D tile indices to the "Tile State" (X, O or empty). Then it also knows the
|
||||
current player, and it has a random generator.
|
||||
-/
|
||||
|
||||
open Std (HashMap)
|
||||
abbrev TileIndex := Nat × Nat -- a 2D index
|
||||
|
||||
inductive TileState where
|
||||
| TileEmpty | TileX | TileO
|
||||
deriving Repr, BEq
|
||||
|
||||
inductive Player where
|
||||
| XPlayer | OPlayer
|
||||
deriving Repr, BEq
|
||||
|
||||
abbrev Board := HashMap TileIndex TileState
|
||||
|
||||
structure GameState where
|
||||
board : Board
|
||||
currentPlayer : Player
|
||||
generator : StdGen
|
||||
|
||||
/-!
|
||||
Let's think at a high level about how some of the game functions would work. You could, for
|
||||
instance, have a function for selecting a random move. This would output a `TileIndex` to play and
|
||||
alter the game's number generator. You would then make a move based on the selected move and the
|
||||
current player. This would change the board state as well as swap the current player. In other
|
||||
words, you have operations that depend on the current state of the game, but also need to **update
|
||||
that state**.
|
||||
|
||||
## The StateM Monad to the Rescue
|
||||
|
||||
This is exactly the situation the `StateM` monad deals with. The `StateM` monad wraps computations in
|
||||
the context of reading and modifying a global state object.
|
||||
|
||||
It is parameterized by a single type parameter `s`, the state type in use. So just like the `ReaderM`
|
||||
has a single type you read from, the `StateM` has a single type you can both **read from and write
|
||||
to**. There are three primary actions you can take within the `StateM`monad:
|
||||
|
||||
- **get** - retrieves the state, like Reader.read
|
||||
- **set** - updates the state
|
||||
- **modifyGet** - retrieves the state, then updates it
|
||||
|
||||
There is also a `run` function, similar to `run` on `ReaderM`. Like the `ReaderM` monad, you must
|
||||
provide an initial state, in addition to the computation to run. `StateM` then produces two outputs:
|
||||
the result of the computation combined with the final updated state.
|
||||
|
||||
If you wish to discard the final state and just get the computation's result, you can use
|
||||
`run'` method instead. Yes in Lean, the apostraphe can be part of a name, you read this "run
|
||||
prime", and the general naming convention is that the prime method discards something.
|
||||
|
||||
So for your Tic Tac Toe game, many of your functions will have a signature like `State GameState a`.
|
||||
|
||||
## Stateful Functions
|
||||
|
||||
Now you can examine some of the different functions mentioned above and determine their types.
|
||||
You can, for instance, pick a random move:
|
||||
|
||||
-/
|
||||
open TileState
|
||||
|
||||
def findOpen : StateM GameState (List TileIndex) := do
|
||||
let game ← get
|
||||
return game.board.toList.filterMap fun (i, x) => guard (x == TileEmpty) *> pure i
|
||||
|
||||
def chooseRandomMove : StateM GameState TileIndex := do
|
||||
let game ← get
|
||||
let openSpots ← findOpen
|
||||
let gen := game.generator
|
||||
let (i, gen') := randNat gen 0 (openSpots.length - 1)
|
||||
set { game with generator := gen' }
|
||||
return openSpots[i]!
|
||||
|
||||
/-!
|
||||
This returns a `TileIndex` and modifies the random number generator stored in the `GameState`!
|
||||
Notice you have a fun little use of the `Applicative.seqRight` operator `*>` in `findOpen`
|
||||
as described in [Applicatives](applicatives.lean.md).
|
||||
|
||||
Now you can create the function that can make a move:
|
||||
-/
|
||||
open Player
|
||||
|
||||
def tileStateForPlayer : Player → TileState
|
||||
| XPlayer => TileX
|
||||
| OPlayer => TileO
|
||||
|
||||
def nextPlayer : Player → Player
|
||||
| XPlayer => OPlayer
|
||||
| OPlayer => XPlayer
|
||||
|
||||
def applyMove (i : TileIndex): StateM GameState Unit := do
|
||||
let game ← get
|
||||
let p := game.currentPlayer
|
||||
let newBoard := game.board.insert i (tileStateForPlayer p)
|
||||
set { game with currentPlayer := nextPlayer p, board := newBoard }
|
||||
|
||||
/-!
|
||||
This updates the board in the `GameState` with the new tile, and then changes the current player,
|
||||
providing no output (`Unit` return type).
|
||||
|
||||
So finally, you can combine these functions together with `do` notation, and it actually looks quite
|
||||
clean! You don't need to worry about the side effects. The different monadic functions handle them.
|
||||
Here's a sample of what your function might look like to play one turn of the game. At the end, it
|
||||
returns a boolean determining if all the spaces have been filled.
|
||||
-/
|
||||
|
||||
def isGameDone : StateM GameState Bool := do
|
||||
return (← findOpen).isEmpty
|
||||
|
||||
def nextTurn : StateM GameState Bool := do
|
||||
let i ← chooseRandomMove
|
||||
applyMove i
|
||||
isGameDone
|
||||
|
||||
/-!
|
||||
To give you a quick test harness that runs all moves for both players you can run this:
|
||||
-/
|
||||
|
||||
def initBoard : Board := Id.run do
|
||||
let mut board := HashMap.empty
|
||||
for i in [0:3] do
|
||||
for j in [0:3] do
|
||||
let t : TileIndex := (i, j)
|
||||
board := board.insert t TileEmpty
|
||||
board
|
||||
|
||||
def printBoard (board : Board) : IO Unit := do
|
||||
let mut row : List String := []
|
||||
for i in board.toList do
|
||||
let s := match i.2 with
|
||||
| TileEmpty => " "
|
||||
| TileX => "X"
|
||||
| TileO => "O"
|
||||
row := row.append [s]
|
||||
if row.length == 3 then
|
||||
IO.println row
|
||||
row := []
|
||||
|
||||
def playGame : StateM GameState Unit := do
|
||||
while true do
|
||||
let finished ← nextTurn
|
||||
if finished then return
|
||||
|
||||
def main : IO Unit := do
|
||||
let gen ← IO.stdGenRef.get
|
||||
let (x, gen') := randNat gen 0 1
|
||||
let gs := {
|
||||
board := initBoard,
|
||||
currentPlayer := if x = 0 then XPlayer else OPlayer,
|
||||
generator := gen' }
|
||||
let (_, g) := playGame |>.run gs
|
||||
printBoard g.board
|
||||
|
||||
#eval main
|
||||
-- [X, X, O]
|
||||
-- [X, O, O]
|
||||
-- [O, O, X]
|
||||
|
||||
/-!
|
||||
|
||||
Note that when you run the above code interactively the random number generator always starts in the
|
||||
same place. But if you run `lean --run states.lean` then you will see randomness in the result.
|
||||
|
||||
## Implementation
|
||||
|
||||
It may be helpful to see how the `StateM` monad adds the input state and output state. If you look
|
||||
at the reduced Type for `nextTurn`:
|
||||
-/
|
||||
#reduce StateM GameState Bool
|
||||
-- GameState → Bool × GameState
|
||||
/-!
|
||||
|
||||
So a function like `nextTurn` that might have just returned a `Bool` has been modified by the
|
||||
`StateM` monad such that the initial `GameState` is passed in as a new input argument, and the output
|
||||
value has been changed to the pair `Bool × GameState` so that it can return the pure `Bool` and the
|
||||
updated `GameState`. This is why the call to `nextTurn` looks like this: `let (_, g) := nextTurn gs`.
|
||||
This expression `(_, g)` conveniently breaks the pair up into 2 values, it doesn't care what the first
|
||||
value is (hence the underscore `_`), but it does need the updated state `g` which you can then assign
|
||||
back to the mutable `gs` variable to use next time around this loop.
|
||||
|
||||
It is also interesting to see how much work the `do` and `←` notation are doing for you. To
|
||||
implement the `nextTurn` function without these you would have to write this, manually plumbing
|
||||
the state all the way through:
|
||||
-/
|
||||
def nextTurnManually : StateM GameState Bool
|
||||
| state =>
|
||||
let (i, gs) := chooseRandomMove |>.run state
|
||||
let (_, gs') := applyMove i |>.run gs
|
||||
let (result, gs'') := isGameDone |>.run gs'
|
||||
(result, gs'')
|
||||
|
||||
/-!
|
||||
|
||||
|
||||
## StateM vs ReaderM
|
||||
|
||||
While `ReaderM` functions can use `withReader` to modify the context before calling another function,
|
||||
`StateM` functions are a little more powerful, let's look at this function again:
|
||||
```
|
||||
def nextTurn : StateM GameState Bool := do
|
||||
let i ← chooseRandomMove
|
||||
applyMove i
|
||||
isGameDone
|
||||
```
|
||||
|
||||
In this function `chooseRandomMove` is modifying the state that `applyMove` is getting
|
||||
and `chooseRandomMove` knows nothing about `applyMove`. So `StateM` functions can have this
|
||||
kind of downstream effect outside their own scope, whereas, `withReader` cannot do that.
|
||||
|
||||
## State, IO and other languages
|
||||
|
||||
When thinking about Lean, it is often seen as a restriction that you can't have global variables or
|
||||
`static` variables like you can with other languages like Python or C++. However, hopefully you see
|
||||
now this isn't true. You can have a data type with exactly the same functionality as a Python class.
|
||||
You would simply have many functions that can modify some global state using the `StateM` monad.
|
||||
|
||||
The difference is in Lean you simply put a label on these types of functions. You don't allow it to
|
||||
happen for free anywhere in an uncontrolled fashion because that results in too many sleepless
|
||||
nights debugging nasty code. You want to know when side effects can potentially happen, because
|
||||
knowing when they can happen makes your code easier to reason about. In a Python class, many of the
|
||||
methods won't actually need to modify the global state. But they could, which makes it harder to
|
||||
debug them. In Lean you can simply make these pure functions, and the compiler will ensure they stay
|
||||
pure and cannot modify any global state.
|
||||
|
||||
IO is the same way. It's not like you can't perform IO in Lean. Instead, you want to label the areas
|
||||
where you can, to increase your certainty about the areas where you don't need to. When you know part of
|
||||
your code cannot communicate with the outside world, you can be far more certain of its behavior.
|
||||
|
||||
The `StateM` monad is also a more disciplined way of managing side effects. Top level code could
|
||||
call a `StateM` function multiple times with different independent initial states, even doing that
|
||||
across multiple tasks in parallel and each of these cannot clobber the state belonging to other
|
||||
tasks. Monadic code is more reusable than code that uses global variables.
|
||||
|
||||
## Summary
|
||||
|
||||
That wraps it up for the `StateM` monad! There is one more very useful monad that can be used to do
|
||||
exception handling which will be covered in the [next section](except.lean.md).
|
||||
|
||||
-/
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user