mirror of
https://github.com/ilyakooo0/Idris-dev.git
synced 2024-11-13 07:26:59 +03:00
Merge
This commit is contained in:
commit
26dba597f7
1
.gitignore
vendored
1
.gitignore
vendored
@ -20,6 +20,7 @@ custom.mk
|
||||
libs/base/base_doc/
|
||||
libs/effects/effects_doc/
|
||||
libs/prelude/prelude_doc/
|
||||
libs/contrib/contrib_doc/
|
||||
test/output
|
||||
test/*[0-9][0-9][0-9]/output
|
||||
test/*[0-9][0-9][0-9]/*.exe
|
||||
|
@ -16,6 +16,7 @@ before_install:
|
||||
- sudo apt-get install hscolour
|
||||
# test dependency
|
||||
- sudo apt-get install -qq expect
|
||||
- sudo apt-get install -qq cppcheck
|
||||
- cabal install alex-3.1.3
|
||||
install:
|
||||
- cabal install -f FFI --enable-tests --dependencies-only
|
||||
@ -30,6 +31,7 @@ script:
|
||||
- if [[ "$TESTS" != "doc" ]]; then cabal build; fi
|
||||
- if [[ "$TESTS" != "doc" ]]; then cabal copy; fi
|
||||
- if [[ "$TESTS" != "doc" ]]; then cabal register; fi
|
||||
- if [[ "$TESTS" == "test_c" ]]; then cppcheck -j 2 --error-exitcode=1 ./rts/idris_bitstring.c ./rts/idris_bitstring.h ./rts/idris_gc.h ./rts/idris_gc.c ./rts/idris_gmp.h ./rts/idris_gmp.c ./rts/idris_heap.h ./rts/idris_heap.c ./rts/idris_main.c ./rts/idris_net.h ./rts/idris_net.c ./rts/idris_opts.h ./rts/idris_opts.c ./rts/idris_rts.h ./rts/idris_rts.c ./rts/idris_stats.h ./rts/idris_stats.c ./rts/idris_stdfgn.h ./rts/idris_stdfgn.c ./rts/libtest.c ; fi
|
||||
- if [[ "$TESTS" == "test_llvm" ]]; then git clone --depth 1 https://github.com/idris-hackers/idris-llvm.git ; cd idris-llvm ; cabal install ; cd .. ; fi
|
||||
- make -j2 $TESTS
|
||||
env:
|
||||
|
19
CHANGELOG
19
CHANGELOG
@ -1,3 +1,19 @@
|
||||
New in 0.9.18:
|
||||
--------------
|
||||
* Strings are now UTF8 encoded in the default back end
|
||||
* Some reorganisation of primitives:
|
||||
+ Buffer and BitVector primitives have been removed (they were not
|
||||
tested sufficiently, and lack a maintainer)
|
||||
+ Float has been renamed 'Double' (Float is defined in the Prelude for
|
||||
compatibility)
|
||||
+ Externally defined primitives and operations now supported with
|
||||
'%extern' directive, allowing back ends to define their own special
|
||||
purpose primitives
|
||||
+ Ptr and ManagedPtr have been removed and replaced with external primitives
|
||||
* Syntax rules no longer perform variable capture. Users of effects will
|
||||
need to explicitly name results in dependent effect signatures instead
|
||||
of using the default name "result".
|
||||
|
||||
New in 0.9.17:
|
||||
--------------
|
||||
* The --ideslave command line option has been replaced with a --ide-mode
|
||||
@ -42,6 +58,9 @@ New in 0.9.17:
|
||||
* Classes can now be annotated with 'determining parameters' to say which
|
||||
must be available before resolving instances. Only determining parameters
|
||||
are checked when checking for overlapping instances.
|
||||
* New package 'contrib' containing things that are less mature or less used
|
||||
than the contents of 'base'. 'contrib' is not available by default, so you
|
||||
may need to add '-p contrib' to your .ipkg file or Idris command line.
|
||||
* Arguments to class instances are now checked for injectivity.
|
||||
Unification assumes this, so we need to check when instances are defined.
|
||||
|
||||
|
@ -9,7 +9,7 @@ Here are a few guidelines that we would like contributors to follow so that we c
|
||||
1. Make sure you are familiar with [Git](http://git-scm.com/book).
|
||||
1. Make sure you have a [GitHub account](https://github.com/signup/free).
|
||||
1. Make sure you are familiar with: [Idris](http://eb.host.cs.st-andrews.ac.uk/writings/idris-tutorial.pdf).
|
||||
1. Make sure you can install `Idris`:
|
||||
1. Make sure you can install Idris:
|
||||
* [Mac OS X](https://github.com/idris-lang/Idris-dev/wiki/Idris-on-OS-X-using-Homebrew)
|
||||
* [Ubuntu](https://github.com/idris-lang/Idris-dev/wiki/Idris-on-Ubuntu)
|
||||
* [Debian](https://github.com/idris-lang/Idris-dev/wiki/Idris-on-Debian)
|
||||
@ -27,18 +27,20 @@ Please clean up any messes that you find, and don't leave behind new messes for
|
||||
|
||||
## Contributing to the default libraries.
|
||||
|
||||
`Idris` ships with a set of packages in `libs/` that is provided as a default library.
|
||||
Idris ships with a set of packages in `libs/` that is provided as a default library.
|
||||
These packages should not be seen as the *standard* as when working with dependent types we do not necessarily know how best to work with dependent types.
|
||||
These packages offer functionality that can be built on top of when constructing `Idris` programs.
|
||||
These packages offer functionality that can be built on top of when constructing Idris programs.
|
||||
|
||||
One major point to make is that everything in prelude will be imported automatically, unless given
|
||||
the `--noprelude` option.
|
||||
A central idea within the `Idris` Community is that what counts as `Idris` is: **the compiler plus the Standard Prelude**.
|
||||
Other libraries (base, effects, etc) are still part of the distribution, but not necessarily standard.
|
||||
Likewise, the contents of base are available with no special options.
|
||||
The other two packages that ship with Idris, contrib and effects, require a the use of the `-p` command-line argument to bring their contents into the include path.
|
||||
New contributions should probably be sent to contrib first, so that they can get maintained with the Idris distribution.
|
||||
If they turn out to be widely applicable and useful, they may later be moved into base.
|
||||
|
||||
As `Idris` is still being developed we are open to suggestions and changes that make improvements to these default packages.
|
||||
Major changes to the library (or `Idris` itself) should ideally be discussed first through the projects official channels of communication i.e. the mailing list, github wiki, or IRC, or as a [Dragon Egg](https://github.com/idris-lang/Idris-dev/wiki/Feature-proposals).
|
||||
Developers then seeking to add content to `Idris`s prelude and default library, should do so through a PR where more discussion's and refinements can be made.
|
||||
As Idris is still being developed we are open to suggestions and changes that make improvements to these default packages.
|
||||
Major changes to the library (or Idris itself) should ideally be discussed first through the projects official channels of communication i.e. the mailing list, github wiki, or IRC, or as a [Dragon Egg](https://github.com/idris-lang/Idris-dev/wiki/Feature-proposals).
|
||||
Developers then seeking to add content to Idris's prelude and default library, should do so through a PR where more discussion's and refinements can be made.
|
||||
|
||||
We do not want you wasting your time nor duplicating somebody's work!
|
||||
|
||||
@ -140,7 +142,7 @@ To help increase the chance of your pull request being accepted:
|
||||
|
||||
1. Run the tests.
|
||||
1. Update the documentation, the surrounding one, examples elsewhere, guides, whatever is affected by your contribution
|
||||
1. Use appropriate code formatting for both `Idris` and `Haskell`.
|
||||
1. Use appropriate code formatting for both Idris and Haskell.
|
||||
|
||||
## Additional Resources
|
||||
|
||||
|
13
Makefile
13
Makefile
@ -1,4 +1,4 @@
|
||||
.PHONY: build configure doc install linecount nodefault pinstall lib_clean relib test_c test lib_doc lib_doc_clean
|
||||
.PHONY: build configure doc install linecount nodefault pinstall lib_clean relib test_c test lib_doc lib_doc_clean user_doc_html user_doc_pdf user_docs
|
||||
|
||||
include config.mk
|
||||
-include custom.mk
|
||||
@ -55,5 +55,16 @@ lib_doc:
|
||||
lib_doc_clean:
|
||||
$(MAKE) -C libs IDRIS=../../dist/build/idris/idris doc_clean
|
||||
|
||||
user_docs: user_doc_html user_doc_pdf
|
||||
|
||||
user_doc_clean:
|
||||
$(MAKE) -C docs clean
|
||||
|
||||
user_doc_html:
|
||||
$(MAKE) -C docs html
|
||||
|
||||
user_doc_pdf:
|
||||
$(MAKE) -C docs latexpdf
|
||||
|
||||
dist/setup-config:
|
||||
$(CABAL) configure $(CABALFLAGS)
|
||||
|
@ -1,6 +1,7 @@
|
||||
# Idris
|
||||
|
||||
[![Build Status](https://travis-ci.org/idris-lang/Idris-dev.svg?branch=master)](https://travis-ci.org/idris-lang/Idris-dev)
|
||||
[![Documentation Status](https://readthedocs.org/projects/idris/badge/?version=latest)](https://readthedocs.org/projects/idris/?badge=latest)
|
||||
[![Hackage](https://budueba.com/hackage/idris)](https://hackage.haskell.org/package/idris)
|
||||
|
||||
Idris (http://idris-lang.org/) is a general-purpose functional programming
|
||||
|
3
docs/.gitignore
vendored
Normal file
3
docs/.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
*~
|
||||
UnicodeData.txt
|
||||
_build/
|
10
docs/LICENSE
Normal file
10
docs/LICENSE
Normal file
@ -0,0 +1,10 @@
|
||||
#+TITLE: Licensing Information
|
||||
|
||||
The documentation for Idris has been published under the Creative
|
||||
Commons CC0 License. As such to the extent possible under law, /The
|
||||
Idris Community/ has waived all copyright and related or neighboring
|
||||
rights to Documentation for Idris.
|
||||
|
||||
More information concerning the CC0 can be found online at:
|
||||
|
||||
http://creativecommons.org/publicdomain/zero/1.0/
|
183
docs/Makefile
Normal file
183
docs/Makefile
Normal file
@ -0,0 +1,183 @@
|
||||
# Makefile for Sphinx documentation
|
||||
#
|
||||
|
||||
# You can set these variables from the command line.
|
||||
SPHINXOPTS =
|
||||
SPHINXBUILD = sphinx-build
|
||||
PAPER =
|
||||
BUILDDIR = _build
|
||||
|
||||
# User-friendly check for sphinx-build
|
||||
ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1)
|
||||
$(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/)
|
||||
endif
|
||||
|
||||
# Internal variables.
|
||||
PAPEROPT_a4 = -D latex_paper_size=a4
|
||||
PAPEROPT_letter = -D latex_paper_size=letter
|
||||
ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
|
||||
# the i18n builder cannot share the environment and doctrees with the others
|
||||
I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
|
||||
|
||||
.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest coverage gettext
|
||||
|
||||
help:
|
||||
@echo "Please use \`make <target>' where <target> is one of"
|
||||
@echo " html to make standalone HTML files"
|
||||
@echo " dirhtml to make HTML files named index.html in directories"
|
||||
@echo " singlehtml to make a single large HTML file"
|
||||
@echo " pickle to make pickle files"
|
||||
@echo " json to make JSON files"
|
||||
@echo " htmlhelp to make HTML files and a HTML help project"
|
||||
@echo " qthelp to make HTML files and a qthelp project"
|
||||
@echo " devhelp to make HTML files and a Devhelp project"
|
||||
@echo " epub to make an epub"
|
||||
@echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
|
||||
@echo " latexpdf to make LaTeX files and run them through pdflatex"
|
||||
@echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx"
|
||||
@echo " text to make text files"
|
||||
@echo " man to make manual pages"
|
||||
@echo " texinfo to make Texinfo files"
|
||||
@echo " info to make Texinfo files and run them through makeinfo"
|
||||
@echo " gettext to make PO message catalogs"
|
||||
@echo " changes to make an overview of all changed/added/deprecated items"
|
||||
@echo " xml to make Docutils-native XML files"
|
||||
@echo " pseudoxml to make pseudoxml-XML files for display purposes"
|
||||
@echo " linkcheck to check all external links for integrity"
|
||||
@echo " doctest to run all doctests embedded in the documentation (if enabled)"
|
||||
@echo " coverage to run coverage check of the documentation (if enabled)"
|
||||
|
||||
clean:
|
||||
rm -rf $(BUILDDIR)/*
|
||||
|
||||
html:
|
||||
$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
|
||||
@echo
|
||||
@echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
|
||||
|
||||
dirhtml:
|
||||
$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
|
||||
@echo
|
||||
@echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
|
||||
|
||||
singlehtml:
|
||||
$(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
|
||||
@echo
|
||||
@echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
|
||||
|
||||
pickle:
|
||||
$(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
|
||||
@echo
|
||||
@echo "Build finished; now you can process the pickle files."
|
||||
|
||||
json:
|
||||
$(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
|
||||
@echo
|
||||
@echo "Build finished; now you can process the JSON files."
|
||||
|
||||
htmlhelp:
|
||||
$(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
|
||||
@echo
|
||||
@echo "Build finished; now you can run HTML Help Workshop with the" \
|
||||
".hhp project file in $(BUILDDIR)/htmlhelp."
|
||||
|
||||
qthelp:
|
||||
$(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
|
||||
@echo
|
||||
@echo "Build finished; now you can run "qcollectiongenerator" with the" \
|
||||
".qhcp project file in $(BUILDDIR)/qthelp, like this:"
|
||||
@echo "# qcollectiongenerator $(BUILDDIR)/qthelp/IdrisManual.qhcp"
|
||||
@echo "To view the help file:"
|
||||
@echo "# assistant -collectionFile $(BUILDDIR)/qthelp/IdrisManual.qhc"
|
||||
|
||||
devhelp:
|
||||
$(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
|
||||
@echo
|
||||
@echo "Build finished."
|
||||
@echo "To view the help file:"
|
||||
@echo "# mkdir -p $$HOME/.local/share/devhelp/IdrisManual"
|
||||
@echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/IdrisManual"
|
||||
@echo "# devhelp"
|
||||
|
||||
epub:
|
||||
$(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
|
||||
@echo
|
||||
@echo "Build finished. The epub file is in $(BUILDDIR)/epub."
|
||||
|
||||
latex:
|
||||
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
|
||||
@echo
|
||||
@echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
|
||||
@echo "Run \`make' in that directory to run these through (pdf)latex" \
|
||||
"(use \`make latexpdf' here to do that automatically)."
|
||||
|
||||
latexpdf:
|
||||
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
|
||||
@echo "Running LaTeX files through pdflatex..."
|
||||
$(MAKE) -C $(BUILDDIR)/latex all-pdf
|
||||
@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
|
||||
|
||||
latexpdfja:
|
||||
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
|
||||
@echo "Running LaTeX files through platex and dvipdfmx..."
|
||||
$(MAKE) -C $(BUILDDIR)/latex all-pdf-ja
|
||||
@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
|
||||
|
||||
text:
|
||||
$(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
|
||||
@echo
|
||||
@echo "Build finished. The text files are in $(BUILDDIR)/text."
|
||||
|
||||
man:
|
||||
$(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
|
||||
@echo
|
||||
@echo "Build finished. The manual pages are in $(BUILDDIR)/man."
|
||||
|
||||
texinfo:
|
||||
$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
|
||||
@echo
|
||||
@echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo."
|
||||
@echo "Run \`make' in that directory to run these through makeinfo" \
|
||||
"(use \`make info' here to do that automatically)."
|
||||
|
||||
info:
|
||||
$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
|
||||
@echo "Running Texinfo files through makeinfo..."
|
||||
make -C $(BUILDDIR)/texinfo info
|
||||
@echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo."
|
||||
|
||||
gettext:
|
||||
$(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale
|
||||
@echo
|
||||
@echo "Build finished. The message catalogs are in $(BUILDDIR)/locale."
|
||||
|
||||
changes:
|
||||
$(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
|
||||
@echo
|
||||
@echo "The overview file is in $(BUILDDIR)/changes."
|
||||
|
||||
linkcheck:
|
||||
$(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
|
||||
@echo
|
||||
@echo "Link check complete; look for any errors in the above output " \
|
||||
"or in $(BUILDDIR)/linkcheck/output.txt."
|
||||
|
||||
doctest:
|
||||
$(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
|
||||
@echo "Testing of doctests in the sources finished, look at the " \
|
||||
"results in $(BUILDDIR)/doctest/output.txt."
|
||||
|
||||
coverage:
|
||||
$(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(BUILDDIR)/coverage
|
||||
@echo "Testing of coverage in the sources finished, look at the " \
|
||||
"results in $(BUILDDIR)/coverage/python.txt."
|
||||
|
||||
xml:
|
||||
$(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml
|
||||
@echo
|
||||
@echo "Build finished. The XML files are in $(BUILDDIR)/xml."
|
||||
|
||||
pseudoxml:
|
||||
$(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml
|
||||
@echo
|
||||
@echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml."
|
45
docs/README.md
Normal file
45
docs/README.md
Normal file
@ -0,0 +1,45 @@
|
||||
# Documentation for the Idris Language.
|
||||
|
||||
|
||||
This manual has been prepared using ReStructured Text and the [Sphinx Documentation Generator](http://sphinx-doc.org) for future inclusion on [Read The Docs](http://www.readthedocs.org).
|
||||
|
||||
## Dependencies
|
||||
|
||||
To build the manual the following dependencies must be met. We assume that you have standard build automation tools already install i.e. `make`.
|
||||
|
||||
### Sphinx-Doc
|
||||
|
||||
Python should be installed by default on most systems.
|
||||
Sphinx can be installed either through your hosts package manager or using pip/easy_install.
|
||||
|
||||
*Note* [ReadTheDocs](http://www.readthedocs.org) works with Sphinx
|
||||
`v1.2.2`. If you install a more recent version of sphinx then
|
||||
'incorrectly' marked up documentation may get passed the build system
|
||||
of readthedocs and be ignored. In the past we had several code-blocks
|
||||
disappear because of that.
|
||||
|
||||
### LaTeX
|
||||
|
||||
LaTeX can be install either using your systems package manager or direct from TeXLive.
|
||||
|
||||
|
||||
## Build Instructions
|
||||
|
||||
```sh
|
||||
make html
|
||||
make latexpdf
|
||||
```
|
||||
|
||||
## Contributing
|
||||
|
||||
The documentation for Idris has been published under the Creative
|
||||
Commons CC0 License. As such to the extent possible under law, /The
|
||||
Idris Community/ has waived all copyright and related or neighboring
|
||||
rights to Documentation for Idris.
|
||||
|
||||
More information concerning the CC0 can be found online at:
|
||||
|
||||
http://creativecommons.org/publicdomain/zero/1.0/
|
||||
|
||||
|
||||
When contributing material to the manual please bear in mind that the work will be licensed as above.
|
357
docs/conf.py
Normal file
357
docs/conf.py
Normal file
@ -0,0 +1,357 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Idris Manual documentation build configuration file, created by
|
||||
# sphinx-quickstart on Sat Feb 28 20:41:47 2015.
|
||||
#
|
||||
# This file is execfile()d with the current directory set to its
|
||||
# containing dir.
|
||||
#
|
||||
# Note that not all possible configuration values are present in this
|
||||
# autogenerated file.
|
||||
#
|
||||
# All configuration values have a default; values that are commented out
|
||||
# serve to show the default.
|
||||
|
||||
import sys
|
||||
import os
|
||||
|
||||
# If extensions (or modules to document with autodoc) are in another directory,
|
||||
# add these directories to sys.path here. If the directory is relative to the
|
||||
# documentation root, use os.path.abspath to make it absolute, like shown here.
|
||||
#sys.path.insert(0, os.path.abspath('.'))
|
||||
|
||||
# -- General configuration ------------------------------------------------
|
||||
|
||||
# If your documentation needs a minimal Sphinx version, state it here.
|
||||
#needs_sphinx = '1.0'
|
||||
|
||||
# Add any Sphinx extension module names here, as strings. They can be
|
||||
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
|
||||
# ones.
|
||||
extensions = [
|
||||
'sphinx.ext.todo',
|
||||
'sphinx.ext.pngmath',
|
||||
'sphinx.ext.ifconfig',
|
||||
]
|
||||
|
||||
# Add any paths that contain templates here, relative to this directory.
|
||||
templates_path = ['_templates']
|
||||
|
||||
# The suffix of source filenames.
|
||||
source_suffix = '.rst'
|
||||
|
||||
# The encoding of source files.
|
||||
#source_encoding = 'utf-8-sig'
|
||||
|
||||
# The master toctree document.
|
||||
master_doc = 'index'
|
||||
|
||||
# General information about the project.
|
||||
project = u'Idris'
|
||||
copyright = u'2015, The Idris Community'
|
||||
author = u'The Idris Community'
|
||||
|
||||
# The version info for the project you're documenting, acts as replacement for
|
||||
# |version| and |release|, also used in various other places throughout the
|
||||
# built documents.
|
||||
#
|
||||
# The short X.Y version.
|
||||
version = '0.9.17'
|
||||
# The full version, including alpha/beta/rc tags.
|
||||
release = '0.9.17'
|
||||
|
||||
# The language for content autogenerated by Sphinx. Refer to documentation
|
||||
# for a list of supported languages.
|
||||
#
|
||||
# This is also used if you do content translation via gettext catalogs.
|
||||
# Usually you set "language" from the command line for these cases.
|
||||
language = None
|
||||
|
||||
# There are two options for replacing |today|: either, you set today to some
|
||||
# non-false value, then it is used:
|
||||
#today = ''
|
||||
# Else, today_fmt is used as the format for a strftime call.
|
||||
#today_fmt = '%B %d, %Y'
|
||||
|
||||
# List of patterns, relative to source directory, that match files and
|
||||
# directories to ignore when looking for source files.
|
||||
exclude_patterns = ['_build']
|
||||
|
||||
# The reST default role (used for this markup: `text`) to use for all
|
||||
# documents.
|
||||
#default_role = None
|
||||
|
||||
# If true, '()' will be appended to :func: etc. cross-reference text.
|
||||
#add_function_parentheses = True
|
||||
|
||||
# If true, the current module name will be prepended to all description
|
||||
# unit titles (such as .. function::).
|
||||
#add_module_names = True
|
||||
|
||||
# If true, sectionauthor and moduleauthor directives will be shown in the
|
||||
# output. They are ignored by default.
|
||||
#show_authors = False
|
||||
|
||||
# The name of the Pygments (syntax highlighting) style to use.
|
||||
pygments_style = 'sphinx'
|
||||
|
||||
# A list of ignored prefixes for module index sorting.
|
||||
#modindex_common_prefix = []
|
||||
|
||||
# If true, keep warnings as "system message" paragraphs in the built documents.
|
||||
#keep_warnings = False
|
||||
|
||||
# If true, `todo` and `todoList` produce output, else they produce nothing.
|
||||
todo_include_todos = True
|
||||
|
||||
|
||||
# -- Options for HTML output ----------------------------------------------
|
||||
|
||||
# The theme to use for HTML and HTML Help pages. See the documentation for
|
||||
# a list of builtin themes.
|
||||
html_theme = "default"
|
||||
|
||||
# Theme options are theme-specific and customize the look and feel of a theme
|
||||
# further. For a list of options available for each theme, see the
|
||||
# documentation.
|
||||
#html_theme_options = {}
|
||||
|
||||
# Add any paths that contain custom themes here, relative to this directory.
|
||||
#html_theme_path = []
|
||||
|
||||
# The name for this set of Sphinx documents. If None, it defaults to
|
||||
# "<project> v<release> documentation".
|
||||
#html_title = None
|
||||
|
||||
# A shorter title for the navigation bar. Default is the same as html_title.
|
||||
#html_short_title = None
|
||||
|
||||
# The name of an image file (relative to this directory) to place at the top
|
||||
# of the sidebar.
|
||||
#html_logo = None
|
||||
|
||||
# The name of an image file (within the static path) to use as favicon of the
|
||||
# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
|
||||
# pixels large.
|
||||
#html_favicon = None
|
||||
|
||||
# Add any paths that contain custom static files (such as style sheets) here,
|
||||
# relative to this directory. They are copied after the builtin static files,
|
||||
# so a file named "default.css" will overwrite the builtin "default.css".
|
||||
html_static_path = ['_static']
|
||||
|
||||
# Add any extra paths that contain custom files (such as robots.txt or
|
||||
# .htaccess) here, relative to this directory. These files are copied
|
||||
# directly to the root of the documentation.
|
||||
#html_extra_path = []
|
||||
|
||||
# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
|
||||
# using the given strftime format.
|
||||
#html_last_updated_fmt = '%b %d, %Y'
|
||||
|
||||
# If true, SmartyPants will be used to convert quotes and dashes to
|
||||
# typographically correct entities.
|
||||
#html_use_smartypants = True
|
||||
|
||||
# Custom sidebar templates, maps document names to template names.
|
||||
#html_sidebars = {}
|
||||
|
||||
# Additional templates that should be rendered to pages, maps page names to
|
||||
# template names.
|
||||
#html_additional_pages = {}
|
||||
|
||||
# If false, no module index is generated.
|
||||
#html_domain_indices = True
|
||||
|
||||
# If false, no index is generated.
|
||||
#html_use_index = True
|
||||
|
||||
# If true, the index is split into individual pages for each letter.
|
||||
#html_split_index = False
|
||||
|
||||
# If true, links to the reST sources are added to the pages.
|
||||
#html_show_sourcelink = True
|
||||
|
||||
# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
|
||||
#html_show_sphinx = True
|
||||
|
||||
# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
|
||||
#html_show_copyright = True
|
||||
|
||||
# If true, an OpenSearch description file will be output, and all pages will
|
||||
# contain a <link> tag referring to it. The value of this option must be the
|
||||
# base URL from which the finished HTML is served.
|
||||
#html_use_opensearch = ''
|
||||
|
||||
# This is the file name suffix for HTML files (e.g. ".xhtml").
|
||||
#html_file_suffix = None
|
||||
|
||||
# Language to be used for generating the HTML full-text search index.
|
||||
# Sphinx supports the following languages:
|
||||
# 'da', 'de', 'en', 'es', 'fi', 'fr', 'hu', 'it', 'ja'
|
||||
# 'nl', 'no', 'pt', 'ro', 'ru', 'sv', 'tr'
|
||||
#html_search_language = 'en'
|
||||
|
||||
# A dictionary with options for the search language support, empty by default.
|
||||
# Now only 'ja' uses this config value
|
||||
#html_search_options = {'type': 'default'}
|
||||
|
||||
# The name of a javascript file (relative to the configuration directory) that
|
||||
# implements a search results scorer. If empty, the default will be used.
|
||||
#html_search_scorer = 'scorer.js'
|
||||
|
||||
# Output file base name for HTML help builder.
|
||||
htmlhelp_basename = 'IdrisManualdoc'
|
||||
|
||||
# -- Options for LaTeX output ---------------------------------------------
|
||||
|
||||
latex_elements = {
|
||||
# The paper size ('letterpaper' or 'a4paper').
|
||||
'papersize': 'a4paper',
|
||||
|
||||
# The font size ('10pt', '11pt' or '12pt').
|
||||
'pointsize': '10pt',
|
||||
|
||||
# Additional stuff for the LaTeX preamble.
|
||||
#'preamble': '',
|
||||
|
||||
# Latex figure (float) alignment
|
||||
#'figure_align': 'htbp',
|
||||
}
|
||||
|
||||
# Grouping the document tree into LaTeX files. List of tuples
|
||||
# (source start file, target name, title,
|
||||
# author, documentclass [howto, manual, or own class]).
|
||||
latex_documents = [
|
||||
('guides/index', 'idris-guides.tex', u'Idris Tutorial Series', u'The Idris Community', 'manual'),
|
||||
('reference/index', 'idris-reference.tex', u'The Idris Reference', u'The Idris Community', 'manual'),
|
||||
('tutorial/index', 'idris-tutorial.tex', u'The Idris Tutorial', u'The Idris Community', 'manual'),
|
||||
('effects/index', 'eff-tutorial.tex', u'The Effects Tutorial', u'The Idris Community', 'manual')
|
||||
]
|
||||
|
||||
# The name of an image file (relative to this directory) to place at the top of
|
||||
# the title page.
|
||||
#latex_logo = None
|
||||
|
||||
# For "manual" documents, if this is true, then toplevel headings are parts,
|
||||
# not chapters.
|
||||
#latex_use_parts = True
|
||||
|
||||
# If true, show page references after internal links.
|
||||
#latex_show_pagerefs = False
|
||||
|
||||
# If true, show URL addresses after external links.
|
||||
#latex_show_urls = False
|
||||
|
||||
# Documents to append as an appendix to all manuals.
|
||||
#latex_appendices = []
|
||||
|
||||
# If false, no module index is generated.
|
||||
#latex_domain_indices = True
|
||||
|
||||
|
||||
# -- Options for manual page output ---------------------------------------
|
||||
|
||||
# One entry per manual page. List of tuples
|
||||
# (source start file, name, description, authors, manual section).
|
||||
man_pages = [
|
||||
(master_doc, 'idrismanual', u'Idris Manual Documentation',
|
||||
[author], 1)
|
||||
]
|
||||
|
||||
# If true, show URL addresses after external links.
|
||||
#man_show_urls = False
|
||||
|
||||
|
||||
# -- Options for Texinfo output -------------------------------------------
|
||||
|
||||
# Grouping the document tree into Texinfo files. List of tuples
|
||||
# (source start file, target name, title, author,
|
||||
# dir menu entry, description, category)
|
||||
texinfo_documents = [
|
||||
(master_doc, 'IdrisManual', u'Idris Manual Documentation',
|
||||
author, 'IdrisManual', 'One line description of project.',
|
||||
'Miscellaneous'),
|
||||
]
|
||||
|
||||
# Documents to append as an appendix to all manuals.
|
||||
#texinfo_appendices = []
|
||||
|
||||
# If false, no module index is generated.
|
||||
#texinfo_domain_indices = True
|
||||
|
||||
# How to display URL addresses: 'footnote', 'no', or 'inline'.
|
||||
#texinfo_show_urls = 'footnote'
|
||||
|
||||
# If true, do not generate a @detailmenu in the "Top" node's menu.
|
||||
#texinfo_no_detailmenu = False
|
||||
|
||||
|
||||
# -- Options for Epub output ----------------------------------------------
|
||||
|
||||
# Bibliographic Dublin Core info.
|
||||
epub_title = project
|
||||
epub_author = author
|
||||
epub_publisher = author
|
||||
epub_copyright = copyright
|
||||
|
||||
# The basename for the epub file. It defaults to the project name.
|
||||
#epub_basename = project
|
||||
|
||||
# The HTML theme for the epub output. Since the default themes are not optimized
|
||||
# for small screen space, using the same theme for HTML and epub output is
|
||||
# usually not wise. This defaults to 'epub', a theme designed to save visual
|
||||
# space.
|
||||
#epub_theme = 'epub'
|
||||
|
||||
# The language of the text. It defaults to the language option
|
||||
# or 'en' if the language is not set.
|
||||
#epub_language = ''
|
||||
|
||||
# The scheme of the identifier. Typical schemes are ISBN or URL.
|
||||
#epub_scheme = ''
|
||||
|
||||
# The unique identifier of the text. This can be a ISBN number
|
||||
# or the project homepage.
|
||||
#epub_identifier = ''
|
||||
|
||||
# A unique identification for the text.
|
||||
#epub_uid = ''
|
||||
|
||||
# A tuple containing the cover image and cover page html template filenames.
|
||||
#epub_cover = ()
|
||||
|
||||
# A sequence of (type, uri, title) tuples for the guide element of content.opf.
|
||||
#epub_guide = ()
|
||||
|
||||
# HTML files that should be inserted before the pages created by sphinx.
|
||||
# The format is a list of tuples containing the path and title.
|
||||
#epub_pre_files = []
|
||||
|
||||
# HTML files shat should be inserted after the pages created by sphinx.
|
||||
# The format is a list of tuples containing the path and title.
|
||||
#epub_post_files = []
|
||||
|
||||
# A list of files that should not be packed into the epub file.
|
||||
epub_exclude_files = ['search.html']
|
||||
|
||||
# The depth of the table of contents in toc.ncx.
|
||||
#epub_tocdepth = 3
|
||||
|
||||
# Allow duplicate toc entries.
|
||||
#epub_tocdup = True
|
||||
|
||||
# Choose between 'default' and 'includehidden'.
|
||||
#epub_tocscope = 'default'
|
||||
|
||||
# Fix unsupported image types using the Pillow.
|
||||
#epub_fix_images = False
|
||||
|
||||
# Scale large images.
|
||||
#epub_max_image_width = 0
|
||||
|
||||
# How to display URL addresses: 'footnote', 'no', or 'inline'.
|
||||
#epub_show_urls = 'inline'
|
||||
|
||||
# If false, no index is generated.
|
||||
#epub_use_index = True
|
76
docs/effects/conclusions.rst
Normal file
76
docs/effects/conclusions.rst
Normal file
@ -0,0 +1,76 @@
|
||||
.. _sect-further:
|
||||
|
||||
===============
|
||||
Further Reading
|
||||
===============
|
||||
|
||||
This tutorial has given an introduction to writing and reasoning about
|
||||
side-effecting programs in Idris, using the ``Effects`` library.
|
||||
More details about the *implementation* of the library, such as how
|
||||
``run`` works, how handlers are invoked, etc, are given in a separate
|
||||
paper [1]_.
|
||||
|
||||
Some libraries and programs which use ``Effects`` can be found in the
|
||||
following places:
|
||||
|
||||
- http://github.com/edwinb/SDL-idris — some bindings for the SDL media
|
||||
library, supporting graphics in particular.
|
||||
|
||||
- http://github.com/edwinb/idris-demos — various demonstration
|
||||
programs, including several examples from this tutorial, and a “Space
|
||||
Invaders” game.
|
||||
|
||||
- https://github.com/SimonJF/IdrisNet2 — networking and socket
|
||||
libraries.
|
||||
|
||||
- http://github.com/edwinb/Protocols — a high level communication
|
||||
protocol description language.
|
||||
|
||||
The inspiration for the ``Effects`` library was Bauer and Pretnar’s
|
||||
Eff language [2]_, which describes a langauge based on algebraic
|
||||
effects and handlers. Other recent languages and libraries have also
|
||||
been built on this ideas, for example [3]_ and [4]_. The theoretical
|
||||
foundations are also well-studied see [5]_, [6]_, [7]_, [8]_.
|
||||
|
||||
|
||||
|
||||
.. [1] Edwin Brady. 2013. Programming and reasoning with algebraic
|
||||
effects and dependent types. SIGPLAN Not. 48, 9 (September
|
||||
2013), 133-144. DOI=10.1145/2544174.2500581
|
||||
http://doi.acm.org/10.1145/2544174.2500581
|
||||
|
||||
.. [2] Andrej Bauer, Matija Pretnar, Programming with algebraic
|
||||
effects and handlers, Journal of Logical and Algebraic Methods
|
||||
in Programming, Volume 84, Issue 1, January 2015, Pages
|
||||
108-123, ISSN 2352-2208,
|
||||
http://dx.doi.org/10.1016/j.jlamp.2014.02.001.
|
||||
(http://www.sciencedirect.com/science/article/pii/S2352220814000194)
|
||||
|
||||
.. [3] Ben Lippmeier. 2009. Witnessing Purity, Constancy and
|
||||
Mutability. In Proceedings of the 7th Asian Symposium on
|
||||
Programming Languages and Systems (APLAS '09), Zhenjiang Hu
|
||||
(Ed.). Springer-Verlag, Berlin, Heidelberg,
|
||||
95-110. DOI=10.1007/978-3-642-10672-9_9
|
||||
http://dx.doi.org/10.1007/978-3-642-10672-9_9
|
||||
|
||||
.. [4] Ohad Kammar, Sam Lindley, and Nicolas Oury. 2013. Handlers in
|
||||
action. SIGPLAN Not. 48, 9 (September 2013),
|
||||
145-158. DOI=10.1145/2544174.2500590
|
||||
http://doi.acm.org/10.1145/2544174.2500590
|
||||
|
||||
.. [5] Martin Hyland, Gordon Plotkin, John Power, Combining effects:
|
||||
Sum and tensor, Theoretical Computer Science, Volume 357,
|
||||
Issues 1–3, 25 July 2006, Pages 70-99, ISSN 0304-3975,
|
||||
http://dx.doi.org/10.1016/j.tcs.2006.03.013.
|
||||
(http://www.sciencedirect.com/science/article/pii/S0304397506002659)
|
||||
|
||||
.. [6] Paul Blain Levy. 2004. Call-By-Push-Value: A
|
||||
Functional/Imperative Synthesis (Semantics Structures in
|
||||
Computation, V. 2). Kluwer Academic Publishers, Norwell, MA,
|
||||
USA.
|
||||
|
||||
.. [7] Plotkin, Gordon, and Matija Pretnar. "Handlers of algebraic
|
||||
effects." Programming Languages and Systems. Springer Berlin
|
||||
Heidelberg, 2009. 80-94.
|
||||
|
||||
.. [8] Pretnar, Matija. "Logic and handling of algebraic effects." (2010).
|
328
docs/effects/depeff.rst
Normal file
328
docs/effects/depeff.rst
Normal file
@ -0,0 +1,328 @@
|
||||
.. _sect-depeff:
|
||||
|
||||
=================
|
||||
Dependent Effects
|
||||
=================
|
||||
|
||||
In the programs we have seen so far, the available effects have remained
|
||||
constant. Sometimes, however, an operation can *change* the available
|
||||
effects. The simplest example occurs when we have a state with a
|
||||
dependent type—adding an element to a vector also changes its type, for
|
||||
example, since its length is explicit in the type. In this section, we
|
||||
will see how the library supports this. Firstly, we will see how states
|
||||
with dependent types can be implemented. Secondly, we will see how the
|
||||
effects can depend on the *result* of an effectful operation. Finally,
|
||||
we will see how this can be used to implement a type-safe and
|
||||
resource-safe protocol for file management.
|
||||
|
||||
Dependent States
|
||||
----------------
|
||||
|
||||
Suppose we have a function which reads input from the console, converts
|
||||
it to an integer, and adds it to a list which is stored in a ``STATE``.
|
||||
It might look something like the following:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
readInt : { [STATE (List Int), STDIO] } Eff ()
|
||||
readInt = do let x = trim !getStr
|
||||
put (cast x :: !get)
|
||||
|
||||
But what if, instead of a list of integers, we would like to store a
|
||||
``Vect``, maintaining the length in the type?
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
readInt : { [STATE (Vect n Int), STDIO] } Eff ()
|
||||
readInt = do let x = trim !getStr
|
||||
put (cast x :: !get)
|
||||
|
||||
This will not type check! Although the vector has length ``n`` on entry
|
||||
to ``readInt``, it has length ``S n`` on exit. The library allows us to
|
||||
express this as follows:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
readInt : { [STATE (Vect n Int), STDIO] ==>
|
||||
[STATE (Vect (S n) Int), STDIO] } Eff ()
|
||||
readInt = do let x = trim !getStr
|
||||
putM (cast x :: !get)
|
||||
|
||||
The notation ``{ xs ==> xs’ } Eff a`` in a type means that the operation
|
||||
begins with effects ``xs`` available, and ends with effects ``xs’``
|
||||
available. We have used ``putM`` to update the state, where the ``M``
|
||||
suffix indicates that the *type* is being updated as well as the value.
|
||||
It has the following type:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
putM : y -> { [STATE x] ==> [STATE y] } Eff ()
|
||||
|
||||
Result-dependent Effects
|
||||
------------------------
|
||||
|
||||
Often, whether a state is updated could depend on the success or
|
||||
otherwise of an operation. In our ``readInt`` example, we might wish to
|
||||
update the vector only if the input is a valid integer (i.e. all
|
||||
digits). As a first attempt, we could try the following, returning a
|
||||
``Bool`` which indicates success:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
readInt : { [STATE (Vect n Int), STDIO] ==>
|
||||
[STATE (Vect (S n) Int), STDIO] } Eff Bool
|
||||
readInt = do let x = trim !getStr
|
||||
case all isDigit (unpack x) of
|
||||
False => pure False
|
||||
True => do putM (cast x :: !get)
|
||||
pure True
|
||||
|
||||
Unfortunately, this will not type check because the vector does not get
|
||||
extended in both branches of the ``case``!
|
||||
|
||||
::
|
||||
|
||||
MutState.idr:18:19:When elaborating right hand side of Main.case
|
||||
block in readInt:
|
||||
Unifying n and S n would lead to infinite value
|
||||
|
||||
Clearly, the size of the resulting vector depends on whether or not the
|
||||
value read from the user was valid. We can express this in the type:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
readInt : { [STATE (Vect n Int), STDIO] ==>
|
||||
{ok} if ok then [STATE (Vect (S n) Int), STDIO]
|
||||
else [STATE (Vect n Int), STDIO] } Eff Bool
|
||||
readInt = do let x = trim !getStr
|
||||
case all isDigit (unpack x) of
|
||||
False => pure False
|
||||
True => do putM (cast x :: !get)
|
||||
pure True
|
||||
|
||||
The notation ``{ xs ==> res xs’ } Eff a`` in a type means that the
|
||||
effects available are updated from ``xs`` to ``xs’``, *and* the
|
||||
resulting effects ``xs’`` may depend on the result of the operation
|
||||
``res``, of type ``a``. Here, the resulting effects are computed from
|
||||
the result ``ok``—if ``True``, the vector is extended, otherwise it
|
||||
remains the same.
|
||||
|
||||
When using the function, we will naturally have to check its return
|
||||
value in order to know what the new set of effects is. For example, to
|
||||
read a set number of values into a vector, we could write the following:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
readN : (n : Nat) ->
|
||||
{ [STATE (Vect m Int), STDIO] ==>
|
||||
[STATE (Vect (n + m) Int), STDIO] } Eff ()
|
||||
readN Z = pure ()
|
||||
readN {m} (S k) = case !readInt of
|
||||
True => rewrite plusSuccRightSucc k m in readN k
|
||||
False => readN (S k)
|
||||
|
||||
The ``case`` analysis on the result of ``readInt`` means that we know in
|
||||
each branch whether reading the integer succeeded, and therefore how
|
||||
many values still need to be read into the vector. What this means in
|
||||
practice is that the type system has verified that a necessary dynamic
|
||||
check (i.e. whether reading a value succeeded) has indeed been done.
|
||||
|
||||
.. note::
|
||||
Only ``case`` will work here. We cannot use ``if/then/else``
|
||||
because the ``then`` and ``else`` branches must have the same
|
||||
type. The ``case`` construct, however, abstracts over the value
|
||||
being inspected in the type of each branch.
|
||||
|
||||
File Management
|
||||
---------------
|
||||
|
||||
A practical use for dependent effects is in specifying resource usage
|
||||
protocols and verifying that they are executed correctly. For example,
|
||||
file management follows a resource usage protocol with the following
|
||||
(informally specified) requirements:
|
||||
|
||||
- It is necessary to open a file for reading before reading it
|
||||
|
||||
- Opening may fail, so the programmer should check whether opening was
|
||||
successful
|
||||
|
||||
- A file which is open for reading must not be written to, and vice
|
||||
versa
|
||||
|
||||
- When finished, an open file handle should be closed
|
||||
|
||||
- When a file is closed, its handle should no longer be used
|
||||
|
||||
These requirements can be expressed formally in , by creating a
|
||||
``FILE_IO`` effect parameterised over a file handle state, which is
|
||||
either empty, open for reading, or open for writing. The ``FILE_IO``
|
||||
effect’s definition is given below. Note that this
|
||||
effect is mainly for illustrative purposes—typically we would also like
|
||||
to support random access files and better reporting of error conditions.
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
module Effect.File
|
||||
|
||||
import Effects
|
||||
import Control.IOExcept
|
||||
|
||||
FILE_IO : Type -> EFFECT
|
||||
|
||||
data OpenFile : Mode -> Type
|
||||
|
||||
open : String -> (m : Mode) ->
|
||||
{ [FILE_IO ()] ==>
|
||||
{ok} [FILE_IO (if ok then OpenFile m else ())] } Eff Bool
|
||||
close : { [FILE_IO (OpenFile m)] ==> [FILE_IO ()] } Eff ()
|
||||
|
||||
readLine : { [FILE_IO (OpenFile Read)] } Eff String
|
||||
writeLine : { [FILE_IO (OpenFile Write)] } Eff ()
|
||||
eof : { [FILE_IO (OpenFile Read)] } Eff Bool
|
||||
|
||||
instance Handler FileIO IO
|
||||
|
||||
In particular, consider the type of ``open``:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
open : String -> (m : Mode) ->
|
||||
{ [FILE_IO ()] ==>
|
||||
{ok} [FILE_IO (if ok then OpenFile m else ())] } Eff Bool
|
||||
|
||||
This returns a ``Bool`` which indicates whether opening the file was
|
||||
successful. The resulting state depends on whether the operation was
|
||||
successful; if so, we have a file handle open for the stated purpose,
|
||||
and if not, we have no file handle. By ``case`` analysis on the result,
|
||||
we continue the protocol accordingly.
|
||||
|
||||
.. _eff-readfile:
|
||||
.. code-block:: idris
|
||||
|
||||
readFile : { [FILE_IO (OpenFile Read)] } Eff (List String)
|
||||
readFile = readAcc [] where
|
||||
readAcc : List String -> { [FILE_IO (OpenFile Read)] }
|
||||
Eff (List String)
|
||||
readAcc acc = if (not !eof)
|
||||
then readAcc (!readLine :: acc)
|
||||
else pure (reverse acc)
|
||||
|
||||
Given a function ``readFile``, above, which reads from
|
||||
an open file until reaching the end, we can write a program which opens
|
||||
a file, reads it, then displays the contents and closes it, as follows,
|
||||
correctly following the protocol:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
dumpFile : String -> { [FILE_IO (), STDIO] } Eff ()
|
||||
dumpFile name = case !(open name Read) of
|
||||
True => do putStrLn (show !readFile)
|
||||
close
|
||||
False => putStrLn ("Error!")
|
||||
|
||||
The type of ``dumpFile``, with ``FILE_IO ()`` in its effect list,
|
||||
indicates that any use of the file resource will follow the protocol
|
||||
correctly (i.e. it both begins and ends with an empty resource). If we
|
||||
fail to follow the protocol correctly (perhaps by forgetting to close
|
||||
the file, failing to check that ``open`` succeeded, or opening the file
|
||||
for writing) then we will get a compile-time error. For example,
|
||||
changing ``open name Read`` to ``open name Write`` yields a compile-time
|
||||
error of the following form:
|
||||
|
||||
::
|
||||
|
||||
FileTest.idr:16:18:When elaborating right hand side of Main.case
|
||||
block in testFile:
|
||||
Can't solve goal
|
||||
SubList [(FILE_IO (OpenFile Read))]
|
||||
[(FILE_IO (OpenFile Write)), STDIO]
|
||||
|
||||
In other words: when reading a file, we need a file which is open for
|
||||
reading, but the effect list contains a ``FILE_IO`` effect carrying a
|
||||
file open for writing.
|
||||
|
||||
Pattern-matching bind
|
||||
---------------------
|
||||
|
||||
It might seem that having to test each potentially failing operation
|
||||
with a ``case`` clause could lead to ugly code, with lots of
|
||||
nested case blocks. Many languages support exceptions to improve this,
|
||||
but unfortunately exceptions may not allow completely clean resource
|
||||
management—for example, guaranteeing that any ``open`` which did succeed
|
||||
has a corresponding close.
|
||||
|
||||
Idris supports *pattern-matching* bindings, such as the following:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
dumpFile : String -> { [FILE_IO (), STDIO] } Eff ()
|
||||
dumpFile name = do True <- open name Read
|
||||
putStrLn (show !readFile)
|
||||
close
|
||||
|
||||
This also has a problem: we are no longer dealing with the case where
|
||||
opening a file failed! The solution is to extend the pattern-matching
|
||||
binding syntax to give brief clauses for failing matches. Here, for
|
||||
example, we could write:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
dumpFile : String -> { [FILE_IO (), STDIO] } Eff ()
|
||||
dumpFile name = do True <- open name Read | False => putStrLn "Error"
|
||||
putStrLn (show !readFile)
|
||||
close
|
||||
|
||||
This is exactly equivalent to the definition with the explicit ``case``.
|
||||
In general, in a ``do``-block, the syntax:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
do pat <- val | <alternatives>
|
||||
p
|
||||
|
||||
is desugared to
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
do x <- val
|
||||
case x of
|
||||
pat => p
|
||||
<alternatives>
|
||||
|
||||
There can be several ``alternatives``, separated by a vertical bar
|
||||
``|``. For example, there is a ``SYSTEM`` effect which supports
|
||||
reading command line arguments, among other things (see Appendix
|
||||
:ref:`sect-appendix`). To read command line arguments, we can use
|
||||
``getArgs``:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
getArgs : { [SYSTEM] } Eff (List String)
|
||||
|
||||
A main program can read command line arguments as follows, where in the
|
||||
list which is returned, the first element ``prog`` is the executable
|
||||
name and the second is an expected argument:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
emain : { [SYSTEM, STDIO] } Eff ()
|
||||
emain = do [prog, arg] <- getArgs
|
||||
putStrLn $ "Argument is " ++ arg
|
||||
{- ... rest of function ... -}
|
||||
|
||||
Unfortunately, this will not fail gracefully if no argument is given, or
|
||||
if too many arguments are given. We can use pattern matching bind
|
||||
alternatives to give a better (more informative) error:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
emain : { [SYSTEM, STDIO] } Eff ()
|
||||
emain = do [prog, arg] <- getArgs | [] => putStrLn "Can't happen!"
|
||||
| [prog] => putStrLn "No arguments!"
|
||||
| _ => putStrLn "Too many arguments!"
|
||||
putStrLn $ "Argument is " ++ arg
|
||||
{- ... rest of function ... -}
|
||||
|
||||
If ``getArgs`` does not return something of the form ``[prog, arg]`` the
|
||||
alternative which does match is executed instead, and that value
|
||||
returned.
|
333
docs/effects/hangman.rst
Normal file
333
docs/effects/hangman.rst
Normal file
@ -0,0 +1,333 @@
|
||||
.. _sect-hangman:
|
||||
|
||||
=======================================
|
||||
Example: A “Mystery Word” Guessing Game
|
||||
=======================================
|
||||
|
||||
In this section, we will use the techniques and specific effects
|
||||
discussed in the tutorial so far to implement a larger example, a simple
|
||||
text-based word-guessing game. In the game, the computer chooses a word,
|
||||
which the player must guess letter by letter. After a limited number of
|
||||
wrong guesses, the player loses [1]_.
|
||||
|
||||
We will implement the game by following these steps:
|
||||
|
||||
#. Define the game state, in enough detail to express the rules
|
||||
|
||||
#. Define the rules of the game (i.e. what actions the player may take,
|
||||
and how these actions affect the game state)
|
||||
|
||||
#. Implement the rules of the game (i.e. implement state updates for
|
||||
each action)
|
||||
|
||||
#. Implement a user interface which allows a player to direct actions
|
||||
|
||||
Step 2 may be achieved by defining an effect which depends on the state
|
||||
defined in step 1. Then step 3 involves implementing a ``Handler`` for
|
||||
this effect. Finally, step 4 involves implementing a program in ``Eff``
|
||||
using the newly defined effect (and any others required to implement the
|
||||
interface).
|
||||
|
||||
Step 1: Game State
|
||||
------------------
|
||||
|
||||
First, we categorise the game states as running games (where there are a
|
||||
number of guesses available, and a number of letters still to guess), or
|
||||
non-running games (i.e. games which have not been started, or games
|
||||
which have been won or lost).
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
data GState = Running Nat Nat | NotRunning
|
||||
|
||||
Notice that at this stage, we say nothing about what it means to make a
|
||||
guess, what the word to be guessed is, how to guess letters, or any
|
||||
other implementation detail. We are only interested in what is necessary
|
||||
to describe the game rules.
|
||||
|
||||
We will, however, parameterise a concrete game state ``Mystery`` over
|
||||
this data:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
data Mystery : GState -> Type
|
||||
|
||||
Step 2: Game Rules
|
||||
------------------
|
||||
|
||||
We describe the game rules as a dependent effect, where each action has
|
||||
a *precondition* (i.e. what the game state must be before carrying out
|
||||
the action) and a *postcondition* (i.e. how the action affects the game
|
||||
state). Informally, these actions with the pre- and postconditions are:
|
||||
|
||||
Guess
|
||||
Guess a letter in the word.
|
||||
|
||||
- Precondition: The game must be running, and there must be both
|
||||
guesses still available, and letters still to be guessed.
|
||||
|
||||
- Postcondition: If the guessed letter is in the word and not yet
|
||||
guessed, reduce the number of letters, otherwise reduce the
|
||||
number of guesses.
|
||||
|
||||
Won
|
||||
Declare victory
|
||||
|
||||
- Precondition: The game must be running, and there must be no
|
||||
letters still to be guessed.
|
||||
|
||||
- Postcondition: The game is no longer running.
|
||||
|
||||
Lost
|
||||
Accept defeat
|
||||
|
||||
- Precondition: The game must be running, and there must be no
|
||||
guesses left.
|
||||
|
||||
- Postcondition: The game is no longer running.
|
||||
|
||||
NewWord
|
||||
Set a new word to be guessed
|
||||
|
||||
- Precondition: The game must not be running.
|
||||
|
||||
- Postcondition: The game is running, with 6 guesses available (the
|
||||
choice of 6 is somewhat arbitrary here) and the number of unique
|
||||
letters in the word still to be guessed.
|
||||
|
||||
StrState
|
||||
Get a string representation of the game state. This is for display
|
||||
purposes; there are no pre- or postconditions.
|
||||
|
||||
We can make these rules precise by declaring them more formally in an
|
||||
effect signature:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
data MysteryRules : Effect where
|
||||
Guess : (x : Char) ->
|
||||
{ Mystery (Running (S g) (S w)) ==>
|
||||
{inword} if inword then Mystery (Running (S g) w)
|
||||
else Mystery (Running g (S w)) }
|
||||
MysteryRules Bool
|
||||
Won : { Mystery (Running g 0) ==> Mystery NotRunning } MysteryRules ()
|
||||
Lost : { Mystery (Running 0 g) ==> Mystery NotRunning } MysteryRules ()
|
||||
NewWord : (w : String) ->
|
||||
{ Mystery NotRunning ==>
|
||||
Mystery (Running 6 (length (letters w))) } MysteryRules ()
|
||||
StrState : { Mystery h } MysteryRules String
|
||||
|
||||
This description says nothing about how the rules are implemented. In
|
||||
particular, it does not specify *how* to tell whether a guessed letter
|
||||
was in a word, just that the result of ``Guess`` depends on it.
|
||||
|
||||
Nevertheless, we can still create an ``EFFECT`` from this, and use it in
|
||||
an ``Eff`` program. Implementing a ``Handler`` for ``MysteryRules`` will
|
||||
then allow us to play the game.
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
MYSTERY : GState -> EFFECT
|
||||
MYSTERY h = MkEff (Mystery h) MysteryRules
|
||||
|
||||
Step 3: Implement Rules
|
||||
-----------------------
|
||||
|
||||
To *implement* the rules, we begin by giving a concrete definition of
|
||||
game state:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
data Mystery : GState -> Type where
|
||||
Init : Mystery NotRunning
|
||||
GameWon : (word : String) -> Mystery NotRunning
|
||||
GameLost : (word : String) -> Mystery NotRunning
|
||||
MkG : (word : String) ->
|
||||
(guesses : Nat) ->
|
||||
(got : List Char) ->
|
||||
(missing : Vect m Char) ->
|
||||
Mystery (Running guesses m)
|
||||
|
||||
If a game is ``NotRunning``, that is either because it has not yet
|
||||
started (``Init``) or because it is won or lost (``GameWon`` and
|
||||
``GameLost``, each of which carry the word so that showing the game
|
||||
state will reveal the word to the player). Finally, ``MkG`` captures a
|
||||
running game’s state, including the target word, the letters
|
||||
successfully guessed, and the missing letters. Using a ``Vect`` for the
|
||||
missing letters is convenient since its length is used in the type.
|
||||
|
||||
To initialise the state, we implement the following functions:
|
||||
``letters``, which returns a list of unique letters in a ``String``
|
||||
(ignoring spaces) and ``initState`` which sets up an initial state
|
||||
considered valid as a postcondition for ``NewWord``.
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
letters : String -> List Char
|
||||
initState : (x : String) -> Mystery (Running 6 (length (letters x)))
|
||||
|
||||
When checking if a guess is in the vector of missing letters, it is
|
||||
convenient to return a *proof* that the guess is in the vector, using
|
||||
``isElem`` below, rather than merely a ``Bool``:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
data IsElem : a -> Vect n a -> Type where
|
||||
First : IsElem x (x :: xs)
|
||||
Later : IsElem x xs -> IsElem x (y :: xs)
|
||||
|
||||
isElem : DecEq a => (x : a) -> (xs : Vect n a) -> Maybe (IsElem x xs)
|
||||
|
||||
The reason for returning a proof is that we can use it to remove an
|
||||
element from the correct position in a vector:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
shrink : (xs : Vect (S n) a) -> IsElem x xs -> Vect n a
|
||||
|
||||
We leave the definitions of ``letters``, ``init``, ``isElem`` and
|
||||
``shrink`` as exercises. Having implemented these, the ``Handler``
|
||||
implementation for ``MysteryRules`` is surprisingly straightforward:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
instance Handler MysteryRules m where
|
||||
handle (MkG w g got []) Won k = k () (GameWon w)
|
||||
handle (MkG w Z got m) Lost k = k () (GameLost w)
|
||||
|
||||
handle st StrState k = k (show st) st
|
||||
handle st (NewWord w) k = k () (initState w)
|
||||
|
||||
handle (MkG w (S g) got m) (Guess x) k =
|
||||
case isElem x m of
|
||||
Nothing => k False (MkG w _ got m)
|
||||
(Just p) => k True (MkG w _ (x :: got) (shrink m p))
|
||||
|
||||
Each case simply involves directly updating the game state in a way
|
||||
which is consistent with the declared rules. In particular, in
|
||||
``Guess``, if the handler claims that the guessed letter is in the word
|
||||
(by passing ``True`` to ``k``), there is no way to update the state in
|
||||
such a way that the number of missing letters or number of guesses does
|
||||
not follow the rules.
|
||||
|
||||
Step 4: Implement Interface
|
||||
---------------------------
|
||||
|
||||
Having described the rules, and implemented state transitions which
|
||||
follow those rules as an effect handler, we can now write an interface
|
||||
for the game which uses the ``MYSTERY`` effect:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
game : { [MYSTERY (Running (S g) w), STDIO] ==>
|
||||
[MYSTERY NotRunning, STDIO] } Eff ()
|
||||
|
||||
The type indicates that the game must start in a running state, with
|
||||
some guesses available, and eventually reach a not-running state (i.e.
|
||||
won or lost). The only way to achieve this is by correctly following the
|
||||
stated rules.
|
||||
|
||||
Note that the type of ``game`` makes no assumption that there are
|
||||
letters to be guessed in the given word (i.e. it is ``w`` rather than
|
||||
``S w``). This is because we will be choosing a word at random from a
|
||||
vector of ``String``, and at no point have we made it explicit that
|
||||
those ``String`` are non-empty.
|
||||
|
||||
Finally, we need to initialise the game by picking a word at random from
|
||||
a list of candidates, setting it as the target using ``NewWord``, then
|
||||
running ``game``:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
runGame : { [MYSTERY NotRunning, RND, SYSTEM, STDIO] } Eff ()
|
||||
runGame = do srand (cast !time)
|
||||
let w = index !(rndFin WEWEWE) words
|
||||
NewWord w
|
||||
game
|
||||
putStrLn !StrState
|
||||
|
||||
We use the system time (``time`` from the ``SYSTEM`` effect; see
|
||||
Appendix :ref:`sect-appendix`) to initialise the random number
|
||||
generator, then pick a random ``Fin`` to index into a list of
|
||||
``words``. For example, we could initialise a word list as follows:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
words : ?wtype
|
||||
words = with Vect ["idris","agda","haskell","miranda",
|
||||
"java","javascript","fortran","basic",
|
||||
"coffeescript","rust"]
|
||||
|
||||
wtype = proof search
|
||||
|
||||
.. note::
|
||||
Rather than have to explicitly declare a type with the vector’s
|
||||
length, it is convenient to give a metavariable ``?wtype`` and let
|
||||
Idris’s proof search mechanism find the type. This is a
|
||||
limited form of type inference, but very useful in practice.
|
||||
|
||||
A possible complete implementation of ``game`` is
|
||||
presented below:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
game : { [MYSTERY (Running (S g) w), STDIO] ==>
|
||||
[MYSTERY NotRunning, STDIO] } Eff ()
|
||||
game {w=Z} = Won
|
||||
game {w=S _}
|
||||
= do putStrLn !StrState
|
||||
putStr "Enter guess: "
|
||||
let guess = trim !getStr
|
||||
case choose (not (guess == "")) of
|
||||
(Left p) => processGuess (strHead' guess p)
|
||||
(Right p) => do putStrLn "Invalid input!"
|
||||
game
|
||||
where
|
||||
processGuess : Char -> { [MYSTERY (Running (S g) (S w)), STDIO] ==>
|
||||
[MYSTERY NotRunning, STDIO] }
|
||||
Eff ()
|
||||
processGuess {g} {w} c
|
||||
= case !(Main.Guess c) of
|
||||
True => do putStrLn "Good guess!"
|
||||
case w of
|
||||
Z => Won
|
||||
(S k) => game
|
||||
False => do putStrLn "No, sorry"
|
||||
case g of
|
||||
Z => Lost
|
||||
(S k) => game
|
||||
|
||||
Discussion
|
||||
----------
|
||||
|
||||
Writing the rules separately as an effect, then an implementation
|
||||
which uses that effect, ensures that the implementation must follow
|
||||
the rules. This has practical applications in more serious contexts;
|
||||
``MysteryRules`` for example can be though of as describing a
|
||||
*protocol* that a game player most follow, or alternative a
|
||||
*precisely-typed API*.
|
||||
|
||||
In practice, we wouldn’t really expect to write rules first then
|
||||
implement the game once the rules were complete. Indeed, I didn’t do
|
||||
so when constructing this example! Rather, I wrote down a set of
|
||||
likely rules making any assumptions *explicit* in the state
|
||||
transitions for ``MysteryRules``. Then, when implementing ``game`` at
|
||||
first, any incorrect assumption was caught as a type error. The
|
||||
following errors were caught during development:
|
||||
|
||||
- Not realising that allowing ``NewWord`` to be an arbitrary string
|
||||
would mean that ``game`` would have to deal with a zero-length word
|
||||
as a starting state.
|
||||
|
||||
- Forgetting to check whether a game was won before recursively
|
||||
calling ``processGuess``, thus accidentally continuing a finished
|
||||
game.
|
||||
|
||||
- Accidentally checking the number of missing letters, rather than the
|
||||
number of remaining guesses, when checking if a game was lost.
|
||||
|
||||
These are, of course, simple errors, but were caught by the type
|
||||
checker before any testing of the game.
|
||||
|
||||
.. [1]
|
||||
Readers may recognise this game by the name “Hangman.”
|
323
docs/effects/impleff.rst
Normal file
323
docs/effects/impleff.rst
Normal file
@ -0,0 +1,323 @@
|
||||
.. _sect-impleff:
|
||||
|
||||
====================
|
||||
Creating New Effects
|
||||
====================
|
||||
|
||||
We have now seen several side-effecting operations provided by the
|
||||
``Effects`` library, and examples of their use in Section
|
||||
:ref:`sect-simpleff`. We have also seen how operations may *modify*
|
||||
the available effects by changing state in Section
|
||||
:ref:`sect-depeff`. We have not, however, yet seen how these
|
||||
operations are implemented. In this section, we describe how a
|
||||
selection of the available effects are implemented, and show how new
|
||||
effectful operations may be provided.
|
||||
|
||||
State
|
||||
-----
|
||||
|
||||
Effects are described by *algebraic data types*, where the
|
||||
constructors describe the operations provided when the effect is
|
||||
available. Stateful operations are described as follows:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
data State : Effect where
|
||||
Get : { a } State a
|
||||
Put : b -> { a ==> b } State ()
|
||||
|
||||
Each effect is associated with a *resource*, the type of which is
|
||||
given with the notation ``{ x ==> x’ }``. This notation gives the
|
||||
resource type expected by each operation, and how it updates when the
|
||||
operation is run. Here, it means:
|
||||
|
||||
- ``Get`` takes no arguments. It has a resource of type ``a``, which
|
||||
is not updated, and running the ``Get`` operation returns something
|
||||
of type ``a``.
|
||||
|
||||
- ``Put`` takes a ``b`` as an argument. It has a resource of type
|
||||
``a`` on input, which is updated to a resource of type
|
||||
``b``. Running the ``Put`` operation returns the element of the
|
||||
unit type.
|
||||
|
||||
``Effect`` itself is a type synonym, declared as follows:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
Effect : Type
|
||||
Effect = (result : Type) ->
|
||||
(input_resource : Type) ->
|
||||
(output_resource : result -> Type) -> Type
|
||||
|
||||
That is, an effectful operation returns something of type ``result``,
|
||||
has an input resource of type ``input_resource``, and a function
|
||||
``output_resource`` which computes the output resource type from the
|
||||
result. We use the same syntactic sugar as with ``Eff`` to make effect
|
||||
declarations more readable. It is defined as follows in the library:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
syntax "{" [inst] "}" [eff] = eff inst (\result => inst)
|
||||
syntax "{" [inst] "==>" "{" {b} "}" [outst] "}" [eff]
|
||||
= eff inst (\b => outst)
|
||||
syntax "{" [inst] "==>" [outst] "}" [eff] = eff inst (\result => outst)
|
||||
|
||||
In order to convert ``State`` (of type ``Effect``) into something
|
||||
usable in an effects list, of type ``EFFECT``, we write the following:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
STATE : Type -> EFFECT
|
||||
STATE t = MkEff t State
|
||||
|
||||
``MkEff`` constructs an ``EFFECT`` by taking the resource type (here,
|
||||
the ``t`` which parameterises ``STATE``) and the effect signature
|
||||
(here, ``State``). For reference, ``EFFECT`` is declared as follows:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
data EFFECT : Type where
|
||||
MkEff : Type -> Effect -> EFFECT
|
||||
|
||||
Recall that to run an effectful program in ``Eff``, we use one of the
|
||||
``run`` family of functions to run the program in a particular
|
||||
computation context ``m``. For each effect, therefore, we must explain
|
||||
how it is executed in a particular computation context for ``run`` to
|
||||
work in that context. This is achieved with the following type class:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
class Handler (e : Effect) (m : Type -> Type) where
|
||||
handle : resource -> (eff : e t resource resource') ->
|
||||
((x : t) -> resource' x -> m a) -> m a
|
||||
|
||||
We have already seen some instance declarations in the effect
|
||||
summaries in Section :ref:`sect-simpleff`. An instance of ``Handler e
|
||||
m`` means that the effect declared with signature ``e`` can be run in
|
||||
computation context ``m``. The ``handle`` function takes:
|
||||
|
||||
- The ``resource`` on input (so, the current value of the state for
|
||||
``State``)
|
||||
|
||||
- The effectful operation (either ``Get`` or ``Put x`` for ``State``)
|
||||
|
||||
- A *continuation*, which we conventionally call ``k``, and should be
|
||||
passed the result value of the operation, and an updated resource.
|
||||
|
||||
There are two reasons for taking a continuation here: firstly, this is
|
||||
neater because there are multiple return values (a new resource and
|
||||
the result of the operation); secondly, and more importantly, the
|
||||
continuation can be called zero or more times.
|
||||
|
||||
A ``Handler`` for ``State`` simply passes on the value of the state,
|
||||
in the case of ``Get``, or passes on a new state, in the case of
|
||||
``Put``. It is defined the same way for all computation contexts:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
instance Handler State m where
|
||||
handle st Get k = k st st
|
||||
handle st (Put n) k = k () n
|
||||
|
||||
This gives enough information for ``Get`` and ``Put`` to be used
|
||||
directly in ``Eff`` programs. It is tidy, however, to define top level
|
||||
functions in ``Eff``, as follows:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
get : { [STATE x] } Eff x
|
||||
get = call Get
|
||||
|
||||
put : x -> { [STATE x] } Eff ()
|
||||
put val = call (Put val)
|
||||
|
||||
putM : y -> { [STATE x] ==> [STATE y] } Eff ()
|
||||
putM val = call (Put val)
|
||||
|
||||
**An implementation detail (aside):** The ``call`` function converts
|
||||
an ``Effect`` to a function in ``Eff``, given a proof that the effect
|
||||
is available. This proof can be constructed automatically by , since
|
||||
it is essentially an index into a statically known list of effects:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
call : {e : Effect} ->
|
||||
(eff : e t a b) -> {auto prf : EffElem e a xs} ->
|
||||
Eff t xs (\v => updateResTy v xs prf eff)
|
||||
|
||||
This is the reason for the ``Can’t solve goal`` error when an effect
|
||||
is not available: the implicit proof ``prf`` has not been solved
|
||||
automatically because the required effect is not in the list of
|
||||
effects ``xs``.
|
||||
|
||||
Such details are not important for using the library, or even writing
|
||||
new effects, however.
|
||||
|
||||
Summary
|
||||
~~~~~~~
|
||||
|
||||
The following listing summarises what is required to define the
|
||||
``STATE`` effect:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
data State : Effect where
|
||||
Get : { a } State a
|
||||
Put : b -> { a ==> b } State ()
|
||||
|
||||
STATE : Type -> EFFECT
|
||||
STATE t = MkEff t State
|
||||
|
||||
instance Handler State m where
|
||||
handle st Get k = k st st
|
||||
handle st (Put n) k = k () n
|
||||
|
||||
get : { [STATE x] } Eff x
|
||||
get = call Get
|
||||
|
||||
put : x -> { [STATE x] } Eff ()
|
||||
put val = call (Put val)
|
||||
|
||||
putM : y -> { [STATE x] ==> [STATE y] } Eff ()
|
||||
putM val = call (Put val)
|
||||
|
||||
|
||||
Console I/O
|
||||
-----------
|
||||
|
||||
Then listing below gives the definition of the ``STDIO``
|
||||
effect, including handlers for ``IO`` and ``IOExcept``. We omit the
|
||||
definition of the top level ``Eff`` functions, as this merely invoke
|
||||
the effects ``PutStr``, ``GetStr``, ``PutCh`` and ``GetCh`` directly.
|
||||
|
||||
Note that in this case, the resource is the unit type in every case,
|
||||
since the handlers merely apply the ``IO`` equivalents of the effects
|
||||
directly.
|
||||
|
||||
.. _eff-stdiodef:
|
||||
.. code-block:: idris
|
||||
|
||||
data StdIO : Effect where
|
||||
PutStr : String -> { () } StdIO ()
|
||||
GetStr : { () } StdIO String
|
||||
PutCh : Char -> { () } StdIO ()
|
||||
GetCh : { () } StdIO Char
|
||||
|
||||
instance Handler StdIO IO where
|
||||
handle () (PutStr s) k = do putStr s; k () ()
|
||||
handle () GetStr k = do x <- getLine; k x ()
|
||||
handle () (PutCh c) k = do putChar c; k () ()
|
||||
handle () GetCh k = do x <- getChar; k x ()
|
||||
|
||||
instance Handler StdIO (IOExcept a) where
|
||||
handle () (PutStr s) k = do ioe_lift $ putStr s; k () ()
|
||||
handle () GetStr k = do x <- ioe_lift $ getLine; k x ()
|
||||
handle () (PutCh c) k = do ioe_lift $ putChar c; k () ()
|
||||
handle () GetCh k = do x <- ioe_lift $ getChar; k x ()
|
||||
|
||||
STDIO : EFFECT
|
||||
STDIO = MkEff () StdIO
|
||||
|
||||
Exceptions
|
||||
----------
|
||||
|
||||
The listing below gives the definition of the ``Exception``
|
||||
effect, including two of its handlers for ``Maybe`` and ``List``. The
|
||||
only operation provided is ``Raise``. The key point to note in the
|
||||
definitions of these handlers is that the continuation ``k`` is not
|
||||
used. Running ``Raise`` therefore means that computation stops with an
|
||||
error.
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
data Exception : Type -> Effect where
|
||||
Raise : a -> { () } Exception a b
|
||||
|
||||
instance Handler (Exception a) Maybe where
|
||||
handle _ (Raise e) k = Nothing
|
||||
|
||||
instance Handler (Exception a) List where
|
||||
handle _ (Raise e) k = []
|
||||
|
||||
EXCEPTION : Type -> EFFECT
|
||||
EXCEPTION t = MkEff () (Exception t)
|
||||
|
||||
|
||||
Non-determinism
|
||||
---------------
|
||||
|
||||
The following listing gives the definition of the ``Select``
|
||||
effect for writing non-deterministic programs, including a handler for
|
||||
``List`` context which returns all possible successful values, and a
|
||||
handler for ``Maybe`` context which returns the first successful
|
||||
value.
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
data Selection : Effect where
|
||||
Select : List a -> { () } Selection a
|
||||
|
||||
instance Handler Selection Maybe where
|
||||
handle _ (Select xs) k = tryAll xs where
|
||||
tryAll [] = Nothing
|
||||
tryAll (x :: xs) = case k x () of
|
||||
Nothing => tryAll xs
|
||||
Just v => Just v
|
||||
|
||||
instance Handler Selection List where
|
||||
handle r (Select xs) k = concatMap (\x => k x r) xs
|
||||
|
||||
SELECT : EFFECT
|
||||
SELECT = MkEff () Selection
|
||||
|
||||
|
||||
Here, the continuation is called multiple times in each handler, for
|
||||
each value in the list of possible values. In the ``List`` handler, we
|
||||
accumulate all successful results, and in the ``Maybe`` handler we try
|
||||
the first value in the last, and try later values only if that fails.
|
||||
|
||||
File Management
|
||||
---------------
|
||||
|
||||
Result-dependent effects are no different from non-dependent effects
|
||||
in the way they are implemented. The listing below
|
||||
illustrates this for the ``FILE_IO`` effect. The syntax for state
|
||||
transitions ``{ x ==> {res} x’ }``, where the result state ``x’`` is
|
||||
computed from the result of the operation ``res``, follows that for
|
||||
the equivalent ``Eff`` programs.
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
data FileIO : Effect where
|
||||
Open : String -> (m : Mode) ->
|
||||
{() ==> {res} if res then OpenFile m else ()} FileIO Bool
|
||||
Close : {OpenFile m ==> ()} FileIO ()
|
||||
|
||||
ReadLine : {OpenFile Read} FileIO String
|
||||
WriteLine : String -> {OpenFile Write} FileIO ()
|
||||
EOF : {OpenFile Read} FileIO Bool
|
||||
|
||||
instance Handler FileIO IO where
|
||||
handle () (Open fname m) k = do h <- openFile fname m
|
||||
if !(validFile h)
|
||||
then k True (FH h)
|
||||
else k False ()
|
||||
handle (FH h) Close k = do closeFile h
|
||||
k () ()
|
||||
|
||||
handle (FH h) ReadLine k = do str <- fread h
|
||||
k str (FH h)
|
||||
handle (FH h) (WriteLine str) k = do fwrite h str
|
||||
k () (FH h)
|
||||
handle (FH h) EOF k = do e <- feof h
|
||||
k e (FH h)
|
||||
|
||||
FILE_IO : Type -> EFFECT
|
||||
FILE_IO t = MkEff t FileIO
|
||||
|
||||
Note that in the handler for ``Open``, the types passed to the
|
||||
continuation ``k`` are different depending on whether the result is
|
||||
``True`` (opening succeeded) or ``False`` (opening failed). This uses
|
||||
``validFile``, defined in the ``Prelude``, to test whether a file
|
||||
handler refers to an open file or not.
|
||||
|
28
docs/effects/index.rst
Normal file
28
docs/effects/index.rst
Normal file
@ -0,0 +1,28 @@
|
||||
.. _eff-tutorial-index:
|
||||
|
||||
####################
|
||||
The Effects Tutorial
|
||||
####################
|
||||
|
||||
A tutorial on the `Effects` package in `Idris`.
|
||||
|
||||
.. note::
|
||||
|
||||
The documentation for Idris has been published under the Creative
|
||||
Commons CC0 License. As such to the extent possible under law, *The
|
||||
Idris Community* has waived all copyright and related or neighboring
|
||||
rights to Documentation for Idris.
|
||||
|
||||
More information concerning the CC0 can be found online at: http://creativecommons.org/publicdomain/zero/1.0/
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
|
||||
introduction
|
||||
state
|
||||
simpleeff
|
||||
depeff
|
||||
impleff
|
||||
hangman
|
||||
conclusions
|
||||
summary
|
113
docs/effects/introduction.rst
Normal file
113
docs/effects/introduction.rst
Normal file
@ -0,0 +1,113 @@
|
||||
============
|
||||
Introduction
|
||||
============
|
||||
|
||||
Pure functional languages with dependent types such as `Idris
|
||||
<http://idris-lang.org/>`_ support reasoning about programs directly
|
||||
in the type system, promising that we can *know* a program will run
|
||||
correctly (i.e. according to the specification in its type) simply
|
||||
because it compiles. Realistically, though, things are not so simple:
|
||||
programs have to interact with the outside world, with user input,
|
||||
input from a network, mutable state, and so on. In this tutorial I
|
||||
will introduce the library, which is included with the distribution
|
||||
and supports programming and reasoning with side-effecting programs,
|
||||
supporting mutable state, interaction with the outside world,
|
||||
exceptions, and verified resource management.
|
||||
|
||||
This tutorial assumes familiarity with pure programming in Idris,
|
||||
as described in Sections 1–6 of the main tutorial [1]_. The examples
|
||||
are presented are tested with Idris and can be found in the
|
||||
examples directory of the Idris repository.
|
||||
|
||||
Consider, for example, the following introductory function which
|
||||
illustrates the kind of properties which can be expressed in the type
|
||||
system:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
vadd : Vect n Int -> Vect n Int -> Vect n Int
|
||||
vadd [] [] = []
|
||||
vadd (x :: xs) (y :: ys) = x + y :: vadd xs ys
|
||||
|
||||
This function adds corresponding elements in a pair of vectors. The type
|
||||
guarantees that the vectors will contain only elements of type ``Int``,
|
||||
and that the input lengths and the output length all correspond. A
|
||||
natural question to ask here, which is typically neglected by
|
||||
introductory tutorials, is “How do I turn this into a program?” That is,
|
||||
given some lists entered by a user, how do we get into a position to be
|
||||
able to apply the ``vadd`` function? Before doing so, we will have to:
|
||||
|
||||
- Read user input, either from the keyboard, a file, or some other
|
||||
input device.
|
||||
|
||||
- Check that the user inputs are valid, i.e. contain only ``Int`` s
|
||||
and are the same length, and report an error if not.
|
||||
|
||||
- Write output
|
||||
|
||||
The complete program will include side-effects for I/O and error
|
||||
handling, before we can get to the pure core functionality. In this
|
||||
tutorial, we will see how Idris supports side-effects.
|
||||
Furthermore, we will see how we can use the dependent type system to
|
||||
*reason* about stateful and side-effecting programs. We will return to
|
||||
this specific example later.
|
||||
|
||||
Hello world
|
||||
===========
|
||||
|
||||
To give an idea of how programs with effects look in , here is the
|
||||
ubiquitous “Hello world” program, written using the ``Effects``
|
||||
library:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
module Main
|
||||
|
||||
import Effects
|
||||
import Effect.StdIO
|
||||
|
||||
hello : [STDIO] Eff ()
|
||||
hello = putStrLn “Hello world!”
|
||||
|
||||
main : IO ()
|
||||
main = run hello
|
||||
|
||||
As usual, the entry point is ``main``. All ``main`` has to do is invoke the
|
||||
``hello`` function which supports the ``STDIO`` effect for console I/O, and
|
||||
returns the unit value. All programs using the ``Effects`` library must
|
||||
``import Effects``. The details of the ``Eff`` type will be presented in the
|
||||
remainder of this tutorial.
|
||||
|
||||
To compile and run this program, Idris needs to be told to include
|
||||
the ``Effects`` package, using the ``-p effects`` flag (this flag is
|
||||
required for all examples in this tutorial):
|
||||
|
||||
.. code-block:: sh
|
||||
|
||||
idris hello.idr -o hello -p effects
|
||||
./hello Hello world!
|
||||
|
||||
Outline
|
||||
=======
|
||||
|
||||
The tutorial is structured as follows: first, in Section
|
||||
:ref:`sect-state`, we will discuss state management, describing why it
|
||||
is important and introducing the ``effects`` library to show how it
|
||||
can be used to manage state. This section also gives an overview of
|
||||
the syntax of effectful programs. Section :ref:`sect-simpleff` then
|
||||
introduces a number of other effects a program may have: I/O;
|
||||
Exceptions; Random Numbers; and Non-determinism, giving examples for
|
||||
each, and an extended example combining several effects in one
|
||||
complete program. Section :ref:`sect-depeff` introduces *dependent*
|
||||
effects, showing how states and resources can be managed in
|
||||
types. Section :ref:`sect-impleff` shows how new effects can be
|
||||
implemented. Section :ref:`sect-hangman` gives an extended example
|
||||
showing how to implement a “mystery word” guessing game, using effects
|
||||
to describe the rules of the game and ensure they are implemented
|
||||
accurately. References to further reading are given in Section
|
||||
:ref:`sect-further`.
|
||||
|
||||
.. [1]
|
||||
You do not, however, need to know what a monad is. A correctness
|
||||
property of this tutorial is that the word “monad” should appear
|
||||
exactly twice, both in this footnote.
|
592
docs/effects/simpleeff.rst
Normal file
592
docs/effects/simpleeff.rst
Normal file
@ -0,0 +1,592 @@
|
||||
.. _sect-simpleff:
|
||||
|
||||
==============
|
||||
Simple Effects
|
||||
==============
|
||||
|
||||
So far we have seen how to write programs with locally mutable state
|
||||
using the ``STATE`` effect. To recap, we have the definitions below
|
||||
in a module ``Effect.State``
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
module Effect.State
|
||||
|
||||
STATE : Type -> EFFECT
|
||||
|
||||
get : { [STATE x] } Eff x
|
||||
put : x -> { [STATE x] } Eff ()
|
||||
putM : y -> { [STATE x] ==> [STATE y] } Eff ()
|
||||
update : (x -> x) -> { [STATE x] } Eff ()
|
||||
|
||||
instance Handler State m
|
||||
|
||||
The last line, ``instance Handler State m``, means that the ``STATE``
|
||||
effect is usable in any computation context ``m``. That is, a program
|
||||
which uses this effect and returns something of type ``a`` can be
|
||||
evaluated to something of type ``m a`` using ``run``, for any
|
||||
``m``. The lower case ``State`` is a data type describing the
|
||||
operations which make up the ``STATE`` effect itself—we will go into
|
||||
more detail about this in Section :ref:`sect-impleff`.
|
||||
|
||||
In this section, we will introduce some other supported effects,
|
||||
allowing console I/O, exceptions, random number generation and
|
||||
non-deterministic programming. For each effect we introduce, we will
|
||||
begin with a summary of the effect, its supported operations, and the
|
||||
contexts in which it may be used, like that above for ``STATE``, and
|
||||
go on to present some simple examples. At the end, we will see some
|
||||
examples of programs which combine multiple effects.
|
||||
|
||||
All of the effects in the library, including those described in this
|
||||
section, are summarised in Appendix :ref:`sect-appendix`.
|
||||
|
||||
Console I/O
|
||||
-----------
|
||||
|
||||
Console I/O is supported with the ``STDIO``
|
||||
effect, which allows reading and writing characters and strings to and
|
||||
from standard input and standard output. Notice that there is a
|
||||
constraint here on the computation context ``m``, because it only
|
||||
makes sense to support console I/O operations in a context where we
|
||||
can perform (or at the very least simulate) console I/O:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
module Effect.StdIO
|
||||
|
||||
STDIO : EFFECT
|
||||
|
||||
putChar : Char -> { [STDIO] } Eff ()
|
||||
putStr : String -> { [STDIO] } Eff ()
|
||||
putStrLn : String -> { [STDIO] } Eff ()
|
||||
|
||||
getStr : { [STDIO] } Eff String
|
||||
getChar : { [STDIO] } Eff Char
|
||||
|
||||
instance Handler StdIO IO
|
||||
instance Handler StdIO (IOExcept a)
|
||||
|
||||
Examples
|
||||
~~~~~~~~
|
||||
|
||||
A program which reads the user’s name, then says hello, can be written
|
||||
as follows:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
hello : { [STDIO] } Eff ()
|
||||
hello = do putStr "Name? "
|
||||
x <- getStr
|
||||
putStrLn ("Hello " ++ trim x ++ "!")
|
||||
|
||||
We use ``trim`` here to remove the trailing newline from the
|
||||
input. The resource associated with ``STDIO`` is simply the empty
|
||||
tuple, which has a default value ``()``, so we can run this as
|
||||
follows:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
main : IO ()
|
||||
main = run hello
|
||||
|
||||
In ``hello`` we could also use ``!``-notation instead of ``x <-
|
||||
getStr``, since we only use the string that is read once:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
hello : { [STDIO] } Eff ()
|
||||
hello = do putStr "Name? "
|
||||
putStrLn ("Hello " ++ trim !getStr ++ "!")
|
||||
|
||||
More interestingly, we can combine multiple effects in one
|
||||
program. For example, we can loop, counting the number of people we’ve
|
||||
said hello to:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
hello : { [STATE Int, STDIO] } Eff ()
|
||||
hello = do putStr "Name? "
|
||||
putStrLn ("Hello " ++ trim !getStr ++ "!")
|
||||
update (+1)
|
||||
putStrLn ("I've said hello to: " ++ show !get ++ " people")
|
||||
hello
|
||||
|
||||
The list of effects given in ``hello`` means that the function can
|
||||
call ``get`` and ``put`` on an integer state, and any functions which
|
||||
read and write from the console. To run this, ``main`` does not need
|
||||
to be changed.
|
||||
|
||||
Aside: Resource Types
|
||||
~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
To find out the resource type of an effect, if necessary (for example
|
||||
if we want to initialise a resource explicitiy with ``runInit`` rather
|
||||
than using a default value with ``run``) we can run the
|
||||
``resourceType`` function at the REPL:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
*ConsoleIO> resourceType STDIO
|
||||
() : Type
|
||||
*ConsoleIO> resourceType (STATE Int)
|
||||
Int : Type
|
||||
|
||||
Exceptions
|
||||
----------
|
||||
|
||||
The ``EXCEPTION``
|
||||
effect is declared in module ``Effect.Exception``. This allows programs
|
||||
to exit immediately with an error, or errors to be handled more
|
||||
generally:
|
||||
|
||||
.. _eff-exception:
|
||||
.. code-block:: idris
|
||||
|
||||
module Effect.Exception
|
||||
|
||||
EXCEPTION : Type -> EFFECT
|
||||
|
||||
raise : a -> { [EXCEPTION a ] } Eff b
|
||||
|
||||
instance Handler (Exception a) Maybe
|
||||
instance Handler (Exception a) List
|
||||
instance Handler (Exception a) (Either a)
|
||||
instance Handler (Exception a) (IOExcept a)
|
||||
instance Show a => Handler (Exception a) IO
|
||||
|
||||
Example
|
||||
~~~~~~~
|
||||
|
||||
Suppose we have a ``String`` which is expected to represent an integer
|
||||
in the range ``0`` to ``n``. We can write a function ``parseNumber``
|
||||
which returns an ``Int`` if parsing the string returns a number in the
|
||||
appropriate range, or throws an exception otherwise. Exceptions are
|
||||
paramaterised by an error type:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
data Err = NotANumber | OutOfRange
|
||||
|
||||
parseNumber : Int -> String -> { [EXCEPTION Err] } Eff Int
|
||||
parseNumber num str
|
||||
= if all isDigit (unpack str)
|
||||
then let x = cast str in
|
||||
if (x >=0 && x <= num)
|
||||
then pure x
|
||||
else raise OutOfRange
|
||||
else raise NotANumber
|
||||
|
||||
Programs which support the ``EXCEPTION`` effect can be run in any
|
||||
context which has some way of throwing errors, for example, we can run
|
||||
``parseNumber`` in the ``Either Err`` context. It returns a value of
|
||||
the form ``Right x`` if successful:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
*Exception> the (Either Err Int) $ run (parseNumber 42 "20")
|
||||
Right 20 : Either Err Int
|
||||
|
||||
Or ``Left e`` on failure, carrying the appropriate exception:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
*Exception> the (Either Err Int) $ run (parseNumber 42 "50")
|
||||
Left OutOfRange : Either Err Int
|
||||
|
||||
*Exception> the (Either Err Int) $ run (parseNumber 42 "twenty")
|
||||
Left NotANumber : Either Err Int
|
||||
|
||||
In fact, we can do a little bit better with ``parseNumber``, and have
|
||||
it return a *proof* that the integer is in the required range along
|
||||
with the integer itself. One way to do this is define a type of
|
||||
bounded integers, ``Bounded``:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
Bounded : Int -> Type
|
||||
Bounded x = (n : Int ** So (n >= 0 && n <= x))
|
||||
|
||||
Recall that ``So`` is parameterised by a ``Bool``, and only ``So
|
||||
True`` is inhabited. We can use ``choose`` to construct such a value
|
||||
from the result of a dynamic check:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
data So : Bool -> Type = Oh : So True
|
||||
|
||||
choose : (b : Bool) -> Either (So b) (So (not b))
|
||||
|
||||
We then write ``parseNumber`` using ``choose`` rather than an
|
||||
``if/then/else`` construct, passing the proof it returns on success as
|
||||
the boundedness proof:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
parseNumber : (x : Int) -> String -> { [EXCEPTION Err] } Eff (Bounded x)
|
||||
parseNumber x str
|
||||
= if all isDigit (unpack str)
|
||||
then let num = cast str in
|
||||
case choose (num >=0 && num <= x) of
|
||||
Left p => pure (num ** p)
|
||||
Right p => raise OutOfRange
|
||||
else raise NotANumber
|
||||
|
||||
Random Numbers
|
||||
--------------
|
||||
|
||||
Random number generation is also implemented by the library, in module
|
||||
``Effect.Random``:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
module Effect.Random
|
||||
|
||||
RND : EFFECT
|
||||
|
||||
srand : Integer -> { [RND] } Eff ()
|
||||
rndInt : Integer -> Integer -> { [RND] } Eff Integer
|
||||
rndFin : (k : Nat) -> { [RND] } Eff (Fin (S k))
|
||||
|
||||
instance Handler Random m
|
||||
|
||||
Random number generation is considered side-effecting because its
|
||||
implementation generally relies on some external source of randomness.
|
||||
The default implementation here relies on an integer *seed*, which can
|
||||
be set with ``srand``. A specific seed will lead to a predictable,
|
||||
repeatable sequence of random numbers. There are two functions which
|
||||
produce a random number:
|
||||
|
||||
- ``rndInt``, which returns a random integer between the given lower
|
||||
and upper bounds.
|
||||
|
||||
- ``rndFin``, which returns a random element of a finite set
|
||||
(essentially a number with an upper bound given in its type).
|
||||
|
||||
Example
|
||||
~~~~~~~
|
||||
|
||||
We can use the ``RND`` effect to implement a simple guessing game. The
|
||||
``guess`` function, given a target number, will repeatedly ask the
|
||||
user for a guess, and state whether the guess is too high, too low, or
|
||||
correct:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
guess : Int -> { [STDIO] } Eff ()
|
||||
|
||||
For reference, the code for ``guess`` is given below:
|
||||
|
||||
.. _eff-game:
|
||||
.. code-block:: idris
|
||||
|
||||
guess : Int -> { [STDIO] } Eff ()
|
||||
guess target
|
||||
= do putStr "Guess: "
|
||||
case run {m=Maybe} (parseNumber 100 (trim !getStr)) of
|
||||
Nothing => do putStrLn "Invalid input"
|
||||
guess target
|
||||
Just (v ** _) =>
|
||||
case compare v target of
|
||||
LT => do putStrLn "Too low"
|
||||
guess target
|
||||
EQ => putStrLn "You win!"
|
||||
GT => do putStrLn "Too high"
|
||||
guess target
|
||||
|
||||
Note that we use ``parseNumber`` as defined previously to read user input, but
|
||||
we don’t need to list the ``EXCEPTION`` effect because we use a nested ``run``
|
||||
to invoke ``parseNumber``, independently of the calling effectful program.
|
||||
|
||||
To invoke this, we pick a random number within the range 0–100,
|
||||
having set up the random number generator with a seed, then run
|
||||
``guess``:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
game : { [RND, STDIO] } Eff ()
|
||||
game = do srand 123456789
|
||||
guess (fromInteger !(rndInt 0 100))
|
||||
|
||||
main : IO ()
|
||||
main = run game
|
||||
|
||||
If no seed is given, it is set to the ``default`` value. For a less
|
||||
predictable game, some better source of randomness would be required,
|
||||
for example taking an initial seed from the system time. To see how to
|
||||
do this, see the ``SYSTEM`` effect described in :ref:`sect-appendix`.
|
||||
|
||||
|
||||
Non-determinism
|
||||
---------------
|
||||
|
||||
The listing below gives the definition of the non-determinism
|
||||
effect, which allows a program to choose a value non-deterministically
|
||||
from a list of possibilities in such a way that the entire computation
|
||||
succeeds:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
import Effects
|
||||
import Effect.Select
|
||||
|
||||
SELECT : EFFECT
|
||||
|
||||
select : List a -> { [SELECT] } Eff a
|
||||
|
||||
instance Handler Selection Maybe
|
||||
instance Handler Selection List
|
||||
|
||||
Example
|
||||
~~~~~~~
|
||||
|
||||
The ``SELECT`` effect can be used to solve constraint problems, such
|
||||
as finding Pythagorean triples. The idea is to use ``select`` to give
|
||||
a set of candidate values, then throw an exception for any combination
|
||||
of values which does not satisfy the constraint:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
triple : Int -> { [SELECT, EXCEPTION String] } Eff (Int, Int, Int)
|
||||
triple max = do z <- select [1..max]
|
||||
y <- select [1..z]
|
||||
x <- select [1..y]
|
||||
if (x * x + y * y == z * z)
|
||||
then pure (x, y, z)
|
||||
else raise "No triple"
|
||||
|
||||
This program chooses a value for ``z`` between ``1`` and ``max``, then
|
||||
values for ``y`` and ``x``. In operation, after a ``select``, the
|
||||
program executes the rest of the ``do``-block for every possible
|
||||
assignment, effectively searching depth-first. If the list is empty
|
||||
(or an exception is thrown) execution fails.
|
||||
|
||||
There are handlers defined for ``Maybe`` and ``List`` contexts, i.e.
|
||||
contexts which can capture failure. Depending on the context ``m``,
|
||||
``triple`` will either return the first triple it finds (if in
|
||||
``Maybe`` context) or all triples in the range (if in ``List``
|
||||
context). We can try this as follows:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
main : IO ()
|
||||
main = do print $ the (Maybe _) $ run (triple 100)
|
||||
print $ the (List _) $ run (triple 100)
|
||||
|
||||
``vadd`` revisited
|
||||
------------------
|
||||
|
||||
We now return to the ``vadd`` program from the introduction. Recall the
|
||||
definition:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
vadd : Vect n Int -> Vect n Int -> Vect n Int
|
||||
vadd [] [] = []
|
||||
vadd (x :: idris xs) (y :: ys) = x + y :: vadd xs ys
|
||||
|
||||
Using , we can set up a program so that it reads input from a user,
|
||||
checks that the input is valid (i.e both vectors contain integers, and
|
||||
are the same length) and if so, pass it on to ``vadd``. First, we
|
||||
write a wrapper for ``vadd`` which checks the lengths and throw an
|
||||
exception if they are not equal. We can do this for input vectors of
|
||||
length ``n`` and ``m`` by matching on the implicit arguments ``n`` and
|
||||
``m`` and using ``decEq`` to produce a proof of their equality, if
|
||||
they are equal:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
vadd_check : Vect n Int -> Vect m Int ->
|
||||
{ [EXCEPTION String] } Eff (Vect m Int)
|
||||
vadd_check {n} {m} xs ys with (decEq n m)
|
||||
vadd_check {n} {m=n} xs ys | (Yes Refl) = pure (vadd xs ys)
|
||||
vadd_check {n} {m} xs ys | (No contra) = raise "Length mismatch"
|
||||
|
||||
To read a vector from the console, we implement a function of the
|
||||
following type:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
read_vec : { [STDIO] } Eff (p ** Vect p Int)
|
||||
|
||||
This returns a dependent pair of a length, and a vector of that
|
||||
length, because we cannot know in advance how many integers the user
|
||||
is going to input. One way to implement this function, using ``-1`` to
|
||||
indicate the end of input, is shown in Listing [readvec]. This uses a
|
||||
variation on ``parseNumber`` which does not require a number to be
|
||||
within range.
|
||||
|
||||
Finally, we write a program which reads two vectors and prints the
|
||||
result of pairwise addition of them, throwing an exception if the
|
||||
inputs are of differing lengths:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
do_vadd : { [STDIO, EXCEPTION String] } Eff ()
|
||||
do_vadd = do putStrLn "Vector 1"
|
||||
(_ ** xs) <- read_vec
|
||||
putStrLn "Vector 2"
|
||||
(_ ** ys) <- read_vec
|
||||
putStrLn (show !(vadd_check xs ys))
|
||||
|
||||
By having explicit lengths in the type, we can be sure that ``vadd``
|
||||
is only being used where the lengths of inputs are guaranteed to be
|
||||
equal. This does not stop us reading vectors from user input, but it
|
||||
does require that the lengths are checked and any discrepancy is dealt
|
||||
with gracefully.
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
read_vec : { [STDIO] } Eff (p ** Vect p Int)
|
||||
read_vec = do putStr "Number (-1 when done): "
|
||||
case run (parseNumber (trim !getStr)) of
|
||||
Nothing => do putStrLn "Input error"
|
||||
read_vec
|
||||
Just v => if (v /= -1)
|
||||
then do (_ ** xs) <- read_vec
|
||||
pure (_ ** v :: xs)
|
||||
else pure (_ ** [])
|
||||
where
|
||||
parseNumber : String -> { [EXCEPTION String] } Eff Int
|
||||
parseNumber str
|
||||
= if all (\x => isDigit x || x == '-') (unpack str)
|
||||
then pure (cast str)
|
||||
else raise "Not a number"
|
||||
|
||||
Example: An Expression Calculator
|
||||
---------------------------------
|
||||
|
||||
To show how these effects can fit together, let us consider an
|
||||
evaluator for a simple expression language, with addition and integer
|
||||
values.
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
data Expr = Val Integer
|
||||
| Add Expr Expr
|
||||
|
||||
An evaluator for this language always returns an ``Integer``, and
|
||||
there are no situations in which it can fail!
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
eval : Expr -> Integer
|
||||
eval (Val x) = x
|
||||
eval (Add l r) = eval l + eval r
|
||||
|
||||
If we add variables, however, things get more interesting. The
|
||||
evaluator will need to be able to access the values stored in
|
||||
variables, and variables may be undefined.
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
data Expr = Val Integer
|
||||
| Var String
|
||||
| Add Expr Expr
|
||||
|
||||
To start, we will change the type of ``eval`` so that it is effectful,
|
||||
and supports an exception effect for throwing errors, and a state
|
||||
containing a mapping from variable names (as ``String``) to their
|
||||
values:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
Env : Type
|
||||
Env = List (String, Integer)
|
||||
|
||||
eval : Expr -> { [EXCEPTION String, STATE Env] } Eff Integer
|
||||
eval (Val x) = return x
|
||||
eval (Add l r) = return $ !(eval l) + !(eval r)
|
||||
|
||||
Note that we are using ``!``-notation to avoid having to bind
|
||||
subexpressions in a ``do`` block. Next, we add a case for evaluating
|
||||
``Var``:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
eval (Var x) = case lookup x !get of
|
||||
Nothing => raise $ "No such variable " ++ x
|
||||
Just val => return val
|
||||
|
||||
This retrieves the state (with ``get``, supported by the ``STATE Env``
|
||||
effect) and raises an exception if the variable is not in the
|
||||
environment (with ``raise``, supported by the ``EXCEPTION String``
|
||||
effect).
|
||||
|
||||
To run the evaluator on a particular expression in a particular
|
||||
environment of names and their values, we can write a function which
|
||||
sets the state then invokes ``eval``:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
runEval : List (String, Integer) -> Expr -> Maybe Integer
|
||||
runEval args expr = run (eval' expr)
|
||||
where eval' : Expr -> { [EXCEPTION String, STATE Env] } Eff Integer
|
||||
eval' e = do put args
|
||||
eval e
|
||||
|
||||
We have picked ``Maybe`` as a computation context here; it needs to be
|
||||
a context which is available for every effect supported by
|
||||
``eval``. In particular, because we have exceptions, it needs to be a
|
||||
context which supports exceptions. Alternatively, ``Either String`` or
|
||||
``IO`` would be fine, for example.
|
||||
|
||||
What if we want to extend the evaluator further, with random number
|
||||
generation? To achieve this, we add a new constructor to ``Expr``,
|
||||
which gives a random number up to a maximum value:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
data Expr = Val Integer
|
||||
| Var String
|
||||
| Add Expr Expr
|
||||
| Random Integer
|
||||
|
||||
Then, we need to deal with the new case, making sure that we extend
|
||||
the list of events to include ``RND``. It doen’t matter where ``RND``
|
||||
appears in the list, as long as it is present:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
eval : Expr -> { [EXCEPTION String, RND, STATE Env] } Eff Integer
|
||||
|
||||
eval (Random upper) = rndInt 0 upper
|
||||
|
||||
For test purposes, we might also want to print the random number which
|
||||
has been generated:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
eval (Random upper) = do val <- rndInt 0 upper
|
||||
putStrLn (show val)
|
||||
return val
|
||||
|
||||
If we try this without extending the effects list, we would see an
|
||||
error something like the following:
|
||||
|
||||
::
|
||||
|
||||
Expr.idr:28:6:When elaborating right hand side of eval:
|
||||
Can't solve goal
|
||||
SubList [STDIO]
|
||||
[(EXCEPTION String), RND, (STATE (List (String, Integer)))]
|
||||
|
||||
In other words, the ``STDIO`` effect is not available. We can correct
|
||||
this simply by updating the type of ``eval`` to include ``STDIO``.
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
eval : Expr -> { [STDIO, EXCEPTION String, RND, STATE Env] } Eff Integer
|
||||
|
||||
Note that using ``STDIO`` will restrict the number of contexts in
|
||||
which ``eval`` can be ``run`` to those which support ``STDIO``, such
|
||||
as ``IO``. Once effect lists get longer, it can be a good idea instead
|
||||
to encapsulate sets of effects in a type synonym. This is achieved as
|
||||
follows, simply by defining a function which computes a type, since
|
||||
types are first class in Idris:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
EvalEff : Type -> Type
|
||||
EvalEff t = { [STDIO, EXCEPTION String, RND, STATE Env] } Eff t
|
||||
|
||||
eval : Expr -> EvalEff Integer
|
519
docs/effects/state.rst
Normal file
519
docs/effects/state.rst
Normal file
@ -0,0 +1,519 @@
|
||||
.. _sect-state:
|
||||
|
||||
=====
|
||||
State
|
||||
=====
|
||||
|
||||
Many programs, even pure programs, can benefit from locally mutable
|
||||
state. For example, consider a program which tags binary tree nodes
|
||||
with a counter, by an inorder traversal (i.e. counting depth first,
|
||||
left to right). This would perform something like the following:
|
||||
|
||||
|image|
|
||||
|
||||
We can describe binary trees with the following data type ``BTree``
|
||||
and ``testTree`` to represent the example input above:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
data BTree a = Leaf
|
||||
| Node (BTree a) a (BTree a)
|
||||
|
||||
testTree : BTree String
|
||||
testTree = Node (Node Leaf "Jim" Leaf)
|
||||
"Fred"
|
||||
(Node (Node Leaf "Alice" Leaf)
|
||||
"Sheila"
|
||||
(Node Leaf "Bob" Leaf))
|
||||
|
||||
Then our function to implement tagging, beginning to tag with a
|
||||
specific value ``i``, has the following type:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
treeTag : (i : Int) -> BTree a -> BTree (Int, a)
|
||||
|
||||
First attempt
|
||||
-------------
|
||||
|
||||
Naïvely, we can implement ``treeTag`` by implementing a helper
|
||||
function which propagates a counter, returning the result of the count
|
||||
for each subtree:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
treeTagAux : (i : Int) -> BTree a -> (Int, BTree (Int, a))
|
||||
treeTagAux i Leaf = (i, Leaf)
|
||||
treeTagAux i (Node l x r)
|
||||
= let (i', l') = treeTagAux i l in
|
||||
let x' = (i', x) in
|
||||
let (i'', r') = treeTagAux (i' + 1) r in
|
||||
(i'', Node l' x' r')
|
||||
|
||||
treeTag : (i : Int) -> BTree a -> BTree (Int, a)
|
||||
treeTag i x = snd (treeTagAux i x)
|
||||
|
||||
This gives the expected result when run at the REPL prompt:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
*TreeTag> treeTag 1 testTree
|
||||
Node (Node Leaf (1, "Jim") Leaf)
|
||||
(2, "Fred")
|
||||
(Node (Node Leaf (3, "Alice") Leaf)
|
||||
(4, "Sheila")
|
||||
(Node Leaf (5, "Bob") Leaf)) : BTree (Int, String)
|
||||
|
||||
This works as required, but there are several problems when we try to
|
||||
scale this to larger programs. It is error prone, because we need to
|
||||
ensure that state is propagated correctly to the recursive calls (i.e.
|
||||
passing the appropriate ``i`` or ``i’``). It is hard to read, because
|
||||
the functional details are obscured by the state propagation. Perhaps
|
||||
most importantly, there is a common programming pattern here which
|
||||
should be abstracted but instead has been implemented by hand. There
|
||||
is local mutable state (the counter) which we have had to make
|
||||
explicit.
|
||||
|
||||
Introducing ``Effects``
|
||||
-----------------------
|
||||
|
||||
Idris provides a library, ``Effects`` [3]_, which captures this
|
||||
pattern and many others involving effectful computation [1]_. An
|
||||
effectful program ``f`` has a type of the following form:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
f : (x1 : a1) -> (x2 : a2) -> ... -> { effs } Eff t
|
||||
|
||||
That is, the return type gives the effects that ``f`` supports
|
||||
(``effs``, of type ``List EFFECT``) and the type the computation
|
||||
returns ``t``. So, our ``treeTagAux`` helper could be written with the
|
||||
following type:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
treeTagAux : BTree a -> { [STATE Int] } Eff (BTree (Int, a))
|
||||
|
||||
That is, ``treeTagAux`` has access to an integer state, because the
|
||||
list of available effects includes ``STATE Int``. ``STATE`` is
|
||||
declared as follows in the module ``Effect.State`` (that is, we must
|
||||
``import Effect.State`` to be able to use it):
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
STATE : Type -> EFFECT
|
||||
|
||||
It is an effect parameterised by a type (by convention, we write
|
||||
effects in all capitals). The ``treeTagAux`` function is an effectful
|
||||
program which builds a new tree tagged with ``Ints``, and is
|
||||
implemented as follows:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
treeTagAux Leaf = pure Leaf
|
||||
treeTagAux (Node l x r)
|
||||
= do l' <- treeTagAux l
|
||||
i <- get
|
||||
put (i + 1)
|
||||
r' <- treeTagAux r
|
||||
pure (Node l' (i, x) r')
|
||||
|
||||
There are several remarks to be made about this implementation.
|
||||
Essentially, it hides the state, which can be accessed using ``get``
|
||||
and updated using ``put``, but it introduces several new features.
|
||||
Specifically, it uses ``do``-notation, binding variables with ``<-``,
|
||||
and a ``pure`` function. There is much to be said about these
|
||||
features, but for our purposes, it suffices to know the following:
|
||||
|
||||
- ``do`` blocks allow effectful operations to be sequenced.
|
||||
|
||||
- ``x <- e`` binds the result of an effectful operation ``e`` to a
|
||||
variable ``x``. For example, in the above code, ``treeTagAux l`` is
|
||||
an effectful operation returning ``BTree (Int, a)``, so ``l’`` has
|
||||
type ``BTree (Int, a)``.
|
||||
|
||||
- ``pure e`` turns a pure value ``e`` into the result of an effectful
|
||||
operation.
|
||||
|
||||
The ``get`` and ``put`` functions read and write a state ``t``,
|
||||
assuming that the ``STATE t`` effect is available. They have the
|
||||
following types, polymorphic in the state ``t`` they manage:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
get : { [STATE t] } Eff t
|
||||
put : t -> { [STATE t] } Eff ()
|
||||
|
||||
A program in ``Eff`` can call any other function in ``Eff`` provided
|
||||
that the calling function supports at least the effects required by
|
||||
the called function. In this case, it is valid for ``treeTagAux`` to
|
||||
call both ``get`` and ``put`` because all three functions support the
|
||||
``STATE Int`` effect.
|
||||
|
||||
Programs in ``Eff`` are run in some underlying *computation context*,
|
||||
using the ``run`` or ``runPure`` function. Using ``runPure``, which
|
||||
runs an effectful program in the identity context, we can write the
|
||||
``treeTag`` function as follows, using ``put`` to initialise the
|
||||
state:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
treeTag : (i : Int) -> BTree a -> BTree (Int, a)
|
||||
treeTag i x = runPure (do put i
|
||||
treeTagAux x)
|
||||
|
||||
We could also run the program in an impure context such as ``IO``,
|
||||
without changing the definition of ``treeTagAux``, by using ``run``
|
||||
instead of ``runPure``:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
treeTagAux : BTree a -> { [STATE Int] } Eff (BTree (Int, a))
|
||||
...
|
||||
|
||||
treeTag : (i : Int) -> BTree a -> IO (BTree (Int, a))
|
||||
treeTag i x = run (do put i
|
||||
treeTagAux x)
|
||||
|
||||
Note that the definition of ``treeTagAux`` is exactly as before. For
|
||||
reference, this complete program (including a ``main`` to run it) is
|
||||
shown in Listing [introprog].
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
module Main
|
||||
|
||||
import Effects
|
||||
import Effect.State
|
||||
|
||||
data BTree a = Leaf
|
||||
| Node (BTree a) a (BTree a)
|
||||
|
||||
instance Show a => Show (BTree a) where
|
||||
show Leaf = "[]"
|
||||
show (Node l x r) = "[" ++ show l ++ " "
|
||||
++ show x ++ " "
|
||||
++ show r ++ "]"
|
||||
|
||||
testTree : BTree String
|
||||
testTree = Node (Node Leaf "Jim" Leaf)
|
||||
"Fred"
|
||||
(Node (Node Leaf "Alice" Leaf)
|
||||
"Sheila"
|
||||
(Node Leaf "Bob" Leaf))
|
||||
|
||||
treeTagAux : BTree a -> { [STATE Int] } Eff (BTree (Int, a))
|
||||
treeTagAux Leaf = pure Leaf
|
||||
treeTagAux (Node l x r) = do l' <- treeTagAux l
|
||||
i <- get
|
||||
put (i + 1)
|
||||
r' <- treeTagAux r
|
||||
pure (Node l' (i, x) r')
|
||||
|
||||
treeTag : (i : Int) -> BTree a -> BTree (Int, a)
|
||||
treeTag i x = runPure (do put i; treeTagAux x)
|
||||
|
||||
main : IO ()
|
||||
main = print (treeTag 1 testTree)
|
||||
|
||||
Effects and Resources
|
||||
---------------------
|
||||
|
||||
Each effect is associated with a *resource*, which is initialised
|
||||
before an effectful program can be run. For example, in the case of
|
||||
``STATE Int`` the corresponding resource is the integer state itself.
|
||||
The types of ``runPure`` and ``run`` show this (slightly simplified
|
||||
here for illustrative purposes):
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
runPure : {env : Env id xs} -> { xs } Eff a -> a
|
||||
run : Applicative m => {env : Env m xs} -> { xs } Eff a -> m a
|
||||
|
||||
The ``env`` argument is implicit, and initialised automatically where
|
||||
possible using default values given by instances of the following type
|
||||
class:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
class Default a where
|
||||
default : a
|
||||
|
||||
Instances of ``Default`` are defined for all primitive types, and many
|
||||
library types such as ``List``, ``Vect``, ``Maybe``, pairs, etc.
|
||||
However, where no default value exists for a resource type (for
|
||||
example, you may want a ``STATE`` type for which there is no
|
||||
``Default`` instance) the resource environment can be given explicitly
|
||||
using one of the following functions:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
runPureInit : Env id xs -> { xs } Eff a -> a
|
||||
runInit : Applicative m => Env m xs -> { xs } Eff a -> m a
|
||||
|
||||
To be well-typed, the environment must contain resources corresponding
|
||||
exactly to the effects in ``xs``. For example, we could also have
|
||||
implemented ``treeTag`` by initialising the state as follows:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
treeTag : (i : Int) -> BTree a -> BTree (Int, a)
|
||||
treeTag i x = runPureInit [i] (treeTagAux x)
|
||||
|
||||
Labelled Effects
|
||||
----------------
|
||||
|
||||
What if we have more than one state, especially more than one state of
|
||||
the same type? How would ``get`` and ``put`` know which state they
|
||||
should be referring to? For example, how could we extend the tree
|
||||
tagging example such that it additionally counts the number of leaves
|
||||
in the tree? One possibility would be to change the state so that it
|
||||
captured both of these values, e.g.:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
treeTagAux : BTree a
|
||||
-> { [STATE (Int, Int)] } Eff (BTree (Int, a))
|
||||
|
||||
Doing this, however, ties the two states together throughout (as well
|
||||
as not indicating which integer is which). It would be nice to be able
|
||||
to call effectful programs which guaranteed only to access one of the
|
||||
states, for example. In a larger application, this becomes
|
||||
particularly important.
|
||||
|
||||
The library therefore allows effects in general to be *labelled* so
|
||||
that they can be referred to explicitly by a particular name. This
|
||||
allows multiple effects of the same type to be included. We can count
|
||||
leaves and update the tag separately, by labelling them as follows:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
treeTagAux : BTree a
|
||||
-> {['Tag ::: STATE Int,
|
||||
'Leaves ::: STATE Int]} Eff (BTree (Int, a))
|
||||
|
||||
The ``:::`` operator allows an arbitrary label to be given to an
|
||||
effect. This label can be any type—it is simply used to identify an
|
||||
effect uniquely. Here, we have used a symbol type. In general
|
||||
``’name`` introduces a new symbol, the only purpose of which is to
|
||||
disambiguate values [2]_.
|
||||
|
||||
When an effect is labelled, its operations are also labelled using the
|
||||
``:-`` operator. In this way, we can say explicitly which state we
|
||||
mean when using ``get`` and ``put``. The tree tagging program which
|
||||
also counts leaves can be written as follows:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
treeTagAux Leaf = do
|
||||
'Leaves :- update (+1)
|
||||
pure Leaf
|
||||
treeTagAux (Node l x r) = do
|
||||
l' <- treeTagAux l
|
||||
i <- 'Tag :- get
|
||||
'Tag :- put (i + 1)
|
||||
r' <- treeTagAux r
|
||||
pure (Node l' (i, x) r')
|
||||
|
||||
The ``update`` function here is a combination of ``get`` and ``put``,
|
||||
applying a function to the current state.
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
update : (x -> x) -> { [STATE x] } Eff ()
|
||||
|
||||
Finally, our top level ``treeTag`` function now returns a pair of the
|
||||
number of leaves, and the new tree. Resources for labelled effects are
|
||||
intialised using the ``:=`` operator (reminisicent of assignment in an
|
||||
imperative language):
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
treeTag : (i : Int) -> BTree a -> (Int, BTree (Int, a))
|
||||
treeTag i x = runPureInit ['Tag := i, 'Leaves := 0]
|
||||
(do x' <- treeTagAux x
|
||||
leaves <- 'Leaves :- get
|
||||
pure (leaves, x'))
|
||||
|
||||
To summarise, we have:
|
||||
|
||||
- ``:::`` to convert an effect to a labelled effect.
|
||||
|
||||
- ``:-`` to convert an effectful operation to a labelled effectful
|
||||
operation.
|
||||
|
||||
- ``:=`` to initialise a resource for a labelled effect.
|
||||
|
||||
Or, more formally with their types (slightly simplified to account
|
||||
only for the situation where available effects are not updated):
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
(:::) : lbl -> EFFECT -> EFFECT
|
||||
(:-) : (l : lbl) -> { [x] } Eff a -> { [l ::: x] } Eff a
|
||||
(:=) : (l : lbl) -> res -> LRes l res
|
||||
|
||||
Here, ``LRes`` is simply the resource type associated with a labelled
|
||||
effect. Note that labels are polymorphic in the label type ``lbl``.
|
||||
Hence, a label can be anything—a string, an integer, a type, etc.
|
||||
|
||||
``!``-notation
|
||||
--------------
|
||||
|
||||
In many cases, using ``do``-notation can make programs unnecessarily
|
||||
verbose, particularly in cases where the value bound is used once,
|
||||
immediately. The following program returns the length of the
|
||||
``String`` stored in the state, for example:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
stateLength : { [STATE String] } Eff Nat
|
||||
stateLength = do x <- get
|
||||
pure (length x)
|
||||
|
||||
This seems unnecessarily verbose, and it would be nice to program in a
|
||||
more direct style in these cases. provides ``!``-notation to help with
|
||||
this. The above program can be written instead as:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
stateLength : { [STATE String] } Eff Nat
|
||||
stateLength = pure (length !get)
|
||||
|
||||
The notation ``!expr`` means that the expression ``expr`` should be
|
||||
evaluated and then implicitly bound. Conceptually, we can think of
|
||||
``!`` as being a prefix function with the following type:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
(!) : { xs } Eff a -> a
|
||||
|
||||
Note, however, that it is not really a function, merely syntax! In
|
||||
practice, a subexpression ``!expr`` will lift ``expr`` as high as
|
||||
possible within its current scope, bind it to a fresh name ``x``, and
|
||||
replace ``!expr`` with ``x``. Expressions are lifted depth first, left
|
||||
to right. In practice, ``!``-notation allows us to program in a more
|
||||
direct style, while still giving a notational clue as to which
|
||||
expressions are effectful.
|
||||
|
||||
For example, the expression:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
let y = 42 in f !(g !(print y) !x)
|
||||
|
||||
is lifted to:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
let y = 42 in do y' <- print y
|
||||
x' <- x
|
||||
g' <- g y' x'
|
||||
f g'
|
||||
|
||||
Syntactic Sugar and ``Eff``
|
||||
---------------------------
|
||||
|
||||
By now, you may be wondering about the syntax we are using for
|
||||
``Eff``, because it doesn’t look like a normal type! (If not, you may
|
||||
safely skip this section and return to it later.) In fact, the type of
|
||||
``Eff`` is the following:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
Eff : (x : Type) ->
|
||||
List EFFECT -> (x -> List EFFECT) -> Type
|
||||
|
||||
This is more general than the types we have been writing so far. It is
|
||||
parameterised over a result type ``x``, as we have already seen, but
|
||||
also a ``List EFFECT`` and a function type ``x -> List EFFECT``.
|
||||
|
||||
These additional parameters are the list of *input* effects, and a
|
||||
list of *output* effects, computed from the result of an effectful
|
||||
operation. That is: running an effectful program can change the set
|
||||
of effects available! This is a particularly powerful idea, and we
|
||||
will see its consequences in more detail later. Some examples of
|
||||
operations which can change the set of available effects are:
|
||||
|
||||
- Updating a state containing a dependent type (for example adding an
|
||||
element to a vector).
|
||||
|
||||
- Opening a file for reading is an effect, but whether the file really
|
||||
*is* open afterwards depends on whether the file was successfully
|
||||
opened.
|
||||
|
||||
- Closing a file means that reading from the file should no longer be
|
||||
possible.
|
||||
|
||||
While powerful, this can make uses of the ``Eff`` type hard to read.
|
||||
Therefore, the library provides syntactic sugar which is translated
|
||||
such that:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
{ xs } Eff a
|
||||
|
||||
is expanded to
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
Eff a xs (\_ => xs)
|
||||
|
||||
i.e. the set of effects remains the same on output. This suffices for
|
||||
the ``STATE`` example we have seen so far, and for many useful
|
||||
side-effecting programs. We could also have written ``treeTagAux``
|
||||
with the expanded type:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
treeTagAux : BTree a ->
|
||||
Eff (BTree (Int, a)) [STATE Int] (\x => [STATE Int])
|
||||
|
||||
Later, we will see programs which update effects:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
{ xs ==> xs' } Eff a
|
||||
|
||||
which is expanded to
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
Eff a xs (\_ => xs')
|
||||
|
||||
i.e. the set of effects is updated to ``xs’`` (think of a transition
|
||||
in a state machine). There is, for example, a version of ``put`` which
|
||||
updates the type of the state:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
putM : y -> { [STATE x] ==> [STATE y] } Eff ()
|
||||
|
||||
Also, we have:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
{ xs ==> {res} xs' } Eff a
|
||||
|
||||
which is expanded to
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
Eff a xs (\res => xs')
|
||||
|
||||
i.e. the set of effects is updated according to the result of the
|
||||
operation ``res``.
|
||||
|
||||
.. [1] The earlier paper [3]_ describes the essential implementation
|
||||
details, although the library presented there is an earlier version
|
||||
which is less powerful than that presented in this tutorial.
|
||||
|
||||
.. [2] In practice, ``’name`` simply introduces a new empty type
|
||||
|
||||
.. [3] Edwin Brady. 2013. Programming and reasoning with algebraic
|
||||
effects and dependent types. SIGPLAN Not. 48, 9 (September
|
||||
2013), 133-144. DOI=10.1145/2544174.2500581
|
||||
http://doi.acm.org/10.1145/2544174.2500581
|
||||
|
||||
.. |image| image:: ../image/effects-tree.png
|
||||
:width: 500px
|
153
docs/effects/summary.rst
Normal file
153
docs/effects/summary.rst
Normal file
@ -0,0 +1,153 @@
|
||||
.. _sect-appendix:
|
||||
|
||||
===============
|
||||
Effects Summary
|
||||
===============
|
||||
|
||||
This appendix gives interfaces for the core effects provided by the
|
||||
library.
|
||||
|
||||
EXCEPTION
|
||||
---------
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
module Effect.Exception
|
||||
|
||||
import Effects
|
||||
import System
|
||||
import Control.IOExcept
|
||||
|
||||
EXCEPTION : Type -> EFFECT
|
||||
|
||||
raise : a -> { [EXCEPTION a ] } Eff m b
|
||||
|
||||
instance Handler (Exception a) Maybe
|
||||
instance Handler (Exception a) List
|
||||
instance Handler (Exception a) (Either a)
|
||||
instance Handler (Exception a) (IOExcept a)
|
||||
instance Show a => Handler (Exception a) IO
|
||||
|
||||
FILE\_IO
|
||||
--------
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
module Effect.File
|
||||
|
||||
import Effects
|
||||
import Control.IOExcept
|
||||
|
||||
FILE_IO : Type -> EFFECT
|
||||
|
||||
data OpenFile : Mode -> Type
|
||||
|
||||
open : Handler FileIO e => String -> (m : Mode) ->
|
||||
{ [FILE_IO ()] ==>
|
||||
{ok} [FILE_IO (if ok then OpenFile m else ())] } Eff e Bool
|
||||
close : Handler FileIO e =>
|
||||
{ [FILE_IO (OpenFile m)] ==> [FILE_IO ()] } Eff e ()
|
||||
|
||||
readLine : Handler FileIO e =>
|
||||
{ [FILE_IO (OpenFile Read)] } Eff e String
|
||||
writeLine : Handler FileIO e => String ->
|
||||
{ [FILE_IO (OpenFile Write)] } Eff e ()
|
||||
eof : Handler FileIO e =>
|
||||
{ [FILE_IO (OpenFile Read)] } Eff e Bool
|
||||
|
||||
instance Handler FileIO IO
|
||||
|
||||
RND
|
||||
---
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
module Effect.Random
|
||||
|
||||
import Effects
|
||||
import Data.Vect
|
||||
import Data.Fin
|
||||
|
||||
RND : EFFECT
|
||||
|
||||
srand : Integer -> { [RND] } Eff m ()
|
||||
rndInt : Integer -> Integer -> { [RND] } Eff m Integer
|
||||
rndFin : (k : Nat) -> { [RND] } Eff m (Fin (S k))
|
||||
|
||||
instance Handler Random m
|
||||
|
||||
SELECT
|
||||
------
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
module Effect.Select
|
||||
|
||||
import Effects
|
||||
|
||||
SELECT : EFFECT
|
||||
|
||||
select : List a -> { [SELECT] } Eff m a
|
||||
|
||||
instance Handler Selection Maybe
|
||||
instance Handler Selection List
|
||||
|
||||
STATE
|
||||
-----
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
module Effect.State
|
||||
|
||||
import Effects
|
||||
|
||||
STATE : Type -> EFFECT
|
||||
|
||||
get : { [STATE x] } Eff m x
|
||||
put : x -> { [STATE x] } Eff m ()
|
||||
putM : y -> { [STATE x] ==> [STATE y] } Eff m ()
|
||||
update : (x -> x) -> { [STATE x] } Eff m ()
|
||||
|
||||
instance Handler State m
|
||||
|
||||
STDIO
|
||||
-----
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
module Effect.StdIO
|
||||
|
||||
import Effects
|
||||
import Control.IOExcept
|
||||
|
||||
STDIO : EFFECT
|
||||
|
||||
putChar : Handler StdIO m => Char -> { [STDIO] } Eff m ()
|
||||
putStr : Handler StdIO m => String -> { [STDIO] } Eff m ()
|
||||
putStrLn : Handler StdIO m => String -> { [STDIO] } Eff m ()
|
||||
|
||||
getStr : Handler StdIO m => { [STDIO] } Eff m String
|
||||
getChar : Handler StdIO m => { [STDIO] } Eff m Char
|
||||
|
||||
instance Handler StdIO IO
|
||||
instance Handler StdIO (IOExcept a)
|
||||
|
||||
SYSTEM
|
||||
------
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
module Effect.System
|
||||
|
||||
import Effects
|
||||
import System
|
||||
import Control.IOExcept
|
||||
|
||||
SYSTEM : EFFECT
|
||||
|
||||
getArgs : Handler System e => { [SYSTEM] } Eff e (List String)
|
||||
time : Handler System e => { [SYSTEM] } Eff e Int
|
||||
getEnv : Handler System e => String -> { [SYSTEM] } Eff e (Maybe String)
|
||||
|
||||
instance Handler System IO
|
||||
instance Handler System (IOExcept a)
|
130
docs/faq/faq.rst
Normal file
130
docs/faq/faq.rst
Normal file
@ -0,0 +1,130 @@
|
||||
==========================
|
||||
Frequently Asked Questions
|
||||
==========================
|
||||
|
||||
What are the differences between Agda and Idris?
|
||||
------------------------------------------------
|
||||
|
||||
The main difference is that Idris has been designed from the start to support
|
||||
verified systems programming through easy interoperability with C and high
|
||||
level language constructs to support domain specific language implementation.
|
||||
Idris emphasises general-purpose programming, rather than theorem proving, and
|
||||
as such includes higher level programming constructs such as type classes and
|
||||
do notation. Idris also supports tactic based theorem proving, and has a
|
||||
lightweight Hugs/GHCI style interface.
|
||||
|
||||
Is Idris production ready?
|
||||
--------------------------
|
||||
|
||||
Idris is primarily a research tool for exploring the possibilities of software
|
||||
development with dependent types, meaning that the primary goal is not (yet) to
|
||||
make a system which could be used in production. As such, there are a few rough
|
||||
corners, and lots of missing libraries. Nobody is working on Idris full time,
|
||||
and we don't have the resources at the moment to polish the system on our own.
|
||||
Therefore, we don't recommend building your business around it!
|
||||
|
||||
Having said that, contributions which help towards making Idris suitable
|
||||
for use in production would be very welcome - this includes (but is not
|
||||
limited to) extra library support, polishing the run-time system (and ensuring
|
||||
it is robust), providing and maintaining a JVM back end, etc.
|
||||
|
||||
Why does Idris use eager evaluation rather than lazy?
|
||||
-----------------------------------------------------
|
||||
|
||||
Idris uses eager evaluation for more predictable performance, in particular
|
||||
because one of the longer term goals is to be able to write efficient and
|
||||
verified low level code such as device drivers and network infrastructure.
|
||||
Furthermore, the Idris type system allows us to state precisely the type
|
||||
of each value, and therefore the run-time form of each value. In a lazy
|
||||
language, consider a value of type ``Int``:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
thing : Int
|
||||
|
||||
What is the representation of ``thing`` at run-time? Is it a bit pattern
|
||||
representing an integer, or is it a pointer to some code which will compute
|
||||
an integer? In Idris, we have decided that we would like to make this
|
||||
distinction precise, in the type:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
thing_val : Int
|
||||
thing_comp : Lazy Int
|
||||
|
||||
Here, it is clear from the type that ``thing_val`` is guaranteed to be a
|
||||
concrete ``Int``, whereas ``thing_comp`` is a computation which will produce an
|
||||
``Int``.
|
||||
|
||||
How can I make lazy control structures?
|
||||
---------------------------------------
|
||||
|
||||
You can make control structures using the special Lazy type. For example,
|
||||
``if...then...else`` is defined as follows in the library:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
boolElim : Bool -> (t : Lazy a) -> (e : Lazy a) -> a
|
||||
boolElim True t e = t
|
||||
boolElim False t e = e
|
||||
|
||||
syntax if [test] then [t] else [e] = boolElim test t e
|
||||
|
||||
The type ``Lazy a`` for ``t`` and ``f`` indicates that those arguments will
|
||||
only be evaluated if they are used, that is, they are evaluated lazily.
|
||||
|
||||
Evaluation at the REPL doesn't behave as I expect. What's going on?
|
||||
-------------------------------------------------------------------
|
||||
|
||||
Being a fully dependently typed language, Idris has two phases where it
|
||||
evaluates things, compile-time and run-time. At compile-time it will only
|
||||
evaluate things which it knows to be total (i.e. terminating and covering all
|
||||
possible inputs) in order to keep type checking decidable. The compile-time
|
||||
evaluator is part of the Idris kernel, and is implemented in Haskell using a
|
||||
HOAS (higher order abstract syntax) style representation of values. Since
|
||||
everything is known to have a normal form here, the evaluation strategy doesn't
|
||||
actually matter because either way it will get the same answer, and in practice
|
||||
it will do whatever the Haskell run-time system chooses to do.
|
||||
|
||||
The REPL, for convenience, uses the compile-time notion of evaluation. As well
|
||||
as being easier to implement (because we have the evaluator available) this can
|
||||
be very useful to show how terms evaluate in the type checker. So you can see
|
||||
the difference between:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
Idris> \n, m => (S n) + m
|
||||
\n => \m => S (plus n m) : Nat -> Nat -> Nat
|
||||
|
||||
Idris> \n, m => n + (S m)
|
||||
\n => \m => plus n (S m) : Nat -> Nat -> Nat
|
||||
|
||||
When will Idris be self-hosting?
|
||||
--------------------------------
|
||||
|
||||
It’s not a priority, though not a bad idea in the long run. It would be a
|
||||
worthwhile effort in the short term to implement libraries to support
|
||||
self-hosting, such as a good parsing library.
|
||||
|
||||
Does Idris have Universe Polymorphism? What is the type of ``Type``?
|
||||
--------------------------------------------------------------------
|
||||
|
||||
Rather than Universe polymorphism, Idris has a cumulative hierarchy of
|
||||
universes; ``Type : Type 1``, ``Type 1 : Type 2``, etc.
|
||||
Cumulativity means that if ``x : Type n`` then also ``x : Type m``,
|
||||
provided that ``n <= m``.
|
||||
|
||||
What does the name ‘Idris’ mean?
|
||||
--------------------------------
|
||||
|
||||
British people of a certain age may be familiar with this
|
||||
`singing dragon <https://www.youtube.com/watch?v=G5ZMNyscPcg>`_. If
|
||||
that doesn’t help, maybe you can invent a suitable acronym :-) .
|
||||
|
||||
Where can I find more answers?
|
||||
------------------------------
|
||||
|
||||
There is an `Unofficial FAQ
|
||||
<https://github.com/idris-lang/Idris-dev/wiki/Unofficial-FAQ>`_ on the wiki on
|
||||
github which answers more technical questions and may be updated more often.
|
||||
|
19
docs/faq/index.rst
Normal file
19
docs/faq/index.rst
Normal file
@ -0,0 +1,19 @@
|
||||
.. _faq-index:
|
||||
|
||||
##########################
|
||||
Frequently Asked Questions
|
||||
##########################
|
||||
|
||||
.. note::
|
||||
The documentation for Idris has been published under the Creative
|
||||
Commons CC0 License. As such to the extent possible under law, *The
|
||||
Idris Community* has waived all copyright and related or neighboring
|
||||
rights to Documentation for Idris.
|
||||
|
||||
More information concerning the CC0 can be found online at: http://creativecommons.org/publicdomain/zero/1.0/
|
||||
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
|
||||
faq
|
21
docs/guides/index.rst
Normal file
21
docs/guides/index.rst
Normal file
@ -0,0 +1,21 @@
|
||||
.. _guides-index:
|
||||
|
||||
################################
|
||||
Tutorials on the Idris Language
|
||||
################################
|
||||
|
||||
Tutorials submitted by community members.
|
||||
|
||||
.. note::
|
||||
The documentation for Idris has been published under the Creative
|
||||
Commons CC0 License. As such to the extent possible under law, *The
|
||||
Idris Community* has waived all copyright and related or neighboring
|
||||
rights to Documentation for Idris.
|
||||
|
||||
More information concerning the CC0 can be found online at: http://creativecommons.org/publicdomain/zero/1.0/
|
||||
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
|
||||
type-providers-ffi
|
290
docs/guides/type-providers-ffi.rst
Normal file
290
docs/guides/type-providers-ffi.rst
Normal file
@ -0,0 +1,290 @@
|
||||
Type Providers in Idris
|
||||
=======================
|
||||
|
||||
`Type providers in Idris
|
||||
<http://www.itu.dk/people/drc/pubs/dependent-type-providers.pdf>`__
|
||||
are simple enough, but there are a few caveats to using them that it
|
||||
would be worthwhile to go through the basic steps. We also go over
|
||||
foreign functions, because these will often be used with type
|
||||
providers.
|
||||
|
||||
The use case
|
||||
------------
|
||||
|
||||
First, let's talk about *why* we might want type providers. There are
|
||||
a number of reasons to use them and there are other examples available
|
||||
around the net, but in this tutorial we'll be using them to port C's
|
||||
``struct stat`` to Idris.
|
||||
|
||||
Why do we need type providers? Well, Idris's FFI needs to know the
|
||||
types of the things it passes to and from C, but the fields of a
|
||||
``struct stat`` are implementation-dependent types that cannot be
|
||||
relied upon. We don't just want to hard-code these types into our
|
||||
program... so we'll use a type provider to find them at compile time!
|
||||
|
||||
A simple example
|
||||
----------------
|
||||
|
||||
First, let's go over a basic usage of type providers, because foreign
|
||||
functions can be confusing but it's important to remember that
|
||||
providers themselves are simple.
|
||||
|
||||
A type provider is simply an IO action that returns a value of this
|
||||
type:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
data Provider a = Provide a | Error String
|
||||
|
||||
Looks familiar? ``Provider`` is just ``Either a String``, given a
|
||||
slightly more descriptive name.
|
||||
|
||||
Remember though, type providers we use in our program must be IO
|
||||
actions. Let's write a simple one now:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
module Provider
|
||||
-- Asks nicely for the user to supply the size of C's size_t type on this
|
||||
-- machine
|
||||
getSizeT : IO (Provider Int)
|
||||
getSizeT = do
|
||||
putStrLn "I'm sorry, I don't know how big size_t is. Can you tell me, in bytes?"
|
||||
resp <- getLine
|
||||
case readInt resp of
|
||||
Just sizeTSize => return (Provide sizeTSize)
|
||||
Nothing => return (Error "I'm sorry, I don't understand.")
|
||||
-- the readInt function is left as an exercise
|
||||
|
||||
We assume that whoever's compiling the library knows the size of
|
||||
``size_t``, so we'll just ask them! (Don't worry, we'll get it
|
||||
ourselves later.) Then, if their response can be converted to an
|
||||
integer, we present ``Provide sizeTSize`` as the result of our IO
|
||||
action; or if it can't, we signal a failure. (This will then become a
|
||||
compile-time error.)
|
||||
|
||||
Now we can use this IO action as a type provider:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
module Main
|
||||
-- to gain access to the IO action we're using as a provider
|
||||
import Provider
|
||||
|
||||
-- TypeProviders is an extension, so we'll enable it
|
||||
%language TypeProviders
|
||||
|
||||
-- And finally, use the provider!
|
||||
-- Note that the parentheses are mandatory.
|
||||
%provide (sizeTSize : Int) with getSizeT
|
||||
|
||||
-- From now on it's just a normal program where `sizeTSize` is available
|
||||
-- as a top-level constant
|
||||
main : IO ()
|
||||
main = do
|
||||
putStr "Look! I figured out how big size_t is! It's "
|
||||
putStr (show sizeTSize)
|
||||
putStr " bytes!"
|
||||
|
||||
Yay! We... asked the user something at compile time? That's not very
|
||||
good, actually. Our library is going to be difficult to compile! This
|
||||
is hardly a step up from having them edit in the size of ``size_t``
|
||||
themselves!
|
||||
|
||||
Don't worry, there's a better way.
|
||||
|
||||
Foreign Functions
|
||||
-----------------
|
||||
|
||||
It's actually pretty easy to write a C function that figures out the
|
||||
size of ``size_t``:
|
||||
|
||||
.. code:: c
|
||||
|
||||
int sizeof_size_t() { return sizeof(size_t); }
|
||||
|
||||
(Why an int and not a ``size_t``? The FFI needs to know how to receive
|
||||
the return value of this function and translate it into an Idris
|
||||
value. If we knew how to do this for values of C type ``size_t``, we
|
||||
wouldn't need to write this function at all! If we really wanted to be
|
||||
safe from overflow, we could use an array of multiple integers, but
|
||||
the SIZE of ``size_t`` is never going to be a 65535 byte integer.)
|
||||
|
||||
So now we can get the size of ``size_t`` as long as we're in C code.
|
||||
We'd like to be able to use this from Idris. Can we do this? It turns
|
||||
out we can.
|
||||
|
||||
mkForeign
|
||||
~~~~~~~~~
|
||||
|
||||
With mkForeign, we can turn a C function into an IO action. It works
|
||||
like this:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
getSizeT : IO Int
|
||||
getSizeT = mkForeign (FFun "sizeof_size_t" [] FInt)
|
||||
|
||||
Pretty simple. ``mkForeign`` takes a specification of what function it
|
||||
needs to call, and we construct this specification with ``FFun``. And
|
||||
``FFun`` just takes a name, a list of argument types (we have none),
|
||||
and a return type.
|
||||
|
||||
One thing you might want to note: the return type we've specified is
|
||||
``FInt``, not ``Int``. That's because ``Int`` is an idris type and C
|
||||
functions don't return idris types. ``FInt`` is not an idris type, but
|
||||
is just the representation of the type of a C int. It tells the
|
||||
compiler "Treat the return value of this C function like it's a C int,
|
||||
and when you pass it back into Idris, convert it to an Idris int."
|
||||
|
||||
Caveats of mkForeign
|
||||
^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
First and foremost: ``mkForeign`` is not actually a function. It is
|
||||
treated specially by the compiler, and there are certain rules you
|
||||
need to follow when using it.
|
||||
|
||||
- Rule 1: the name string must be a literal or constant
|
||||
|
||||
This does not work:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
intIntToInt : String -> Int -> Int -> IO Int
|
||||
intIntToInt name = mkForeign (FFun name [FInt, FInt] FInt)
|
||||
|
||||
You'll just have to bite the bullet and write out the whole
|
||||
``mkForeign`` and ``FFun`` expression each time.
|
||||
|
||||
- Rule 2: the "call" to ``mkForeign`` must be fully applied
|
||||
|
||||
This just means that every argument appearing in the list of argument
|
||||
types must be applied wherever you write ``mkForeign``. The arguments
|
||||
don't have to be literals or even known at compile time; they just
|
||||
have to be there. For example, if we have ``strlen : String -> IO
|
||||
Int``, then this is fine:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
strlen str = mkForeign (FFun "strlen" [FString] FInt) str
|
||||
|
||||
but this is not fine:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
strlen = mkForeign (FFun "strlen" [FString] FInt)
|
||||
|
||||
Note that this only applies to places where you literally typed
|
||||
``mkForeign``. Once you've defined it, ``strlen`` is just a normal
|
||||
function returning an IO action, and it doesn't need to be fully
|
||||
applied. This is okay:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
lengths : IO [Int]
|
||||
lengths = mapM strlen listOfStrings
|
||||
|
||||
Running foreign functions
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
This is all well and good for writing code that will typecheck. To
|
||||
actually run the code, we'll need to do just a bit more work. Exactly
|
||||
what we need to do depends on whether we want to interpret or compile
|
||||
our code.
|
||||
|
||||
In the interpreter
|
||||
^^^^^^^^^^^^^^^^^^
|
||||
|
||||
If we want to call our foreign functions from interpreted code (such
|
||||
as the REPL or a type provider), we need to dynamically link a library
|
||||
containing the symbols we need. This is pretty easy to do with the
|
||||
``%dynamic`` directive:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
%dynamic "./filename.so"
|
||||
|
||||
Note that the leading "./" is important: currently, the string you
|
||||
provide is interpreted as by ``dlopen()``, which on Unix does not search
|
||||
in the current directory by default. If you use the "./", the library
|
||||
will be searched for in the directory from which you run idris (*not*
|
||||
the location of the file you're running!). Of course, if you're using
|
||||
functions from an installed library rather than something you wrote
|
||||
yourself, the "./" is not necessary.
|
||||
|
||||
In an executable
|
||||
^^^^^^^^^^^^^^^^
|
||||
|
||||
If we want to run our code from an executable, we can statically link
|
||||
instead. We'll use the ``%include`` and ``%link`` directives:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
%include C "filename.h"
|
||||
%link C "filename.o"
|
||||
|
||||
Note the extra argument to the directive! We specify that we're
|
||||
linking a C header and library. Also, unlike ``%dynamic``, these
|
||||
directives search in the current directory by default. (That is, the
|
||||
directory from which we run idris.)
|
||||
|
||||
Putting it all together
|
||||
-----------------------
|
||||
|
||||
So, at the beginning of this article I said we'd use type providers to
|
||||
port ``struct stat`` to Idris. The relevant part is just translating
|
||||
all the mysterious typedef'd C types into Idris types, and that's what
|
||||
we'll do here.
|
||||
|
||||
First, let's write a C file containing functions that we'll bind to.
|
||||
|
||||
.. code-block:: c
|
||||
|
||||
/* stattypes.c */
|
||||
int sizeof_dev_t() { return sizeof(dev_t); }
|
||||
int sizeof_ino_t() { return sizeof(ino_t); }
|
||||
/* lots more functions like this */
|
||||
|
||||
Next, an Idris file to define our providers:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
-- Providers.idr
|
||||
module Providers
|
||||
|
||||
%dynamic "./stattypes.so"
|
||||
|
||||
sizeOfDevT : IO Int
|
||||
sizeOfDevT = mkForeign (FFun "sizeof_dev_t" [] FInt)
|
||||
{- lots of similar functions -}
|
||||
|
||||
-- now we have an integer, but we want a Provider FTy
|
||||
-- since our sizeOf* functions are ordinary IO actions, we
|
||||
-- can just map over them.
|
||||
bytesToType : Int -> Provider FTy
|
||||
bytesToType 1 = Provide (FIntT IT8) -- "8 bit foreign integer"
|
||||
bytesToType 2 = Provide (FIntT IT16)
|
||||
bytesToType 4 = Provide (FIntT IT32)
|
||||
bytesToType 8 = Provide (FIntT IT64)
|
||||
bytesToType _ = Error "Unrecognized integral type."
|
||||
|
||||
getDevT : IO (Provider FTy)
|
||||
getDevT = map bytesToType sizeOfDevT
|
||||
{- lots of similar functions -}
|
||||
|
||||
Finally, we'll write one more idris file where we use the type
|
||||
providers:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
-- Main.idr
|
||||
module Main
|
||||
import Providers
|
||||
%language TypeProviders
|
||||
%provide (FDevT : FTy) with getDevT
|
||||
|
||||
-- interpFTy translates a foreign type to the corresponding idris type
|
||||
DevT : Type
|
||||
DevT = interpFTy FDevT -- on most systems, DevT = Bits64
|
||||
|
||||
-- We can now use DevT in our program and FDevT in our FFun expressions!
|
BIN
docs/image/effects-tree.png
Normal file
BIN
docs/image/effects-tree.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 37 KiB |
115
docs/index.rst
Normal file
115
docs/index.rst
Normal file
@ -0,0 +1,115 @@
|
||||
.. Idris Manual documentation master file, created by
|
||||
sphinx-quickstart on Sat Feb 28 20:41:47 2015.
|
||||
You can adapt this file completely to your liking, but it should at least
|
||||
contain the root `toctree` directive.
|
||||
|
||||
Documentation for the Idris Language
|
||||
====================================
|
||||
|
||||
.. note::
|
||||
|
||||
The documentation for Idris has been published under the Creative
|
||||
Commons CC0 License. As such to the extent possible under law, *The
|
||||
Idris Community* has waived all copyright and related or neighboring
|
||||
rights to Documentation for Idris.
|
||||
|
||||
More information concerning the CC0 can be found online at: http://creativecommons.org/publicdomain/zero/1.0/
|
||||
|
||||
* :ref:`tutorial-index`
|
||||
* :ref:`eff-tutorial-index`
|
||||
* :ref:`faq-index`
|
||||
* :ref:`proofs-index`
|
||||
* :ref:`reference-index`
|
||||
* :ref:`guides-index`
|
||||
|
||||
|
||||
.. _tutorial:
|
||||
|
||||
###################
|
||||
The Idris Tutorial
|
||||
###################
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
|
||||
tutorial/introduction
|
||||
tutorial/starting
|
||||
tutorial/typesfuns
|
||||
tutorial/classes
|
||||
tutorial/modules
|
||||
tutorial/packages
|
||||
tutorial/interp
|
||||
tutorial/views
|
||||
tutorial/theorems
|
||||
tutorial/provisional
|
||||
tutorial/interactive
|
||||
tutorial/syntax
|
||||
tutorial/miscellany
|
||||
tutorial/conclusions
|
||||
|
||||
.. _effects:
|
||||
|
||||
##########################
|
||||
Frequently Asked Questions
|
||||
##########################
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
|
||||
faq/faq
|
||||
|
||||
.. _effects:
|
||||
|
||||
################
|
||||
Learning Effects
|
||||
################
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
|
||||
effects/introduction
|
||||
effects/state
|
||||
effects/simpleeff
|
||||
effects/depeff
|
||||
effects/impleff
|
||||
effects/hangman
|
||||
effects/conclusions
|
||||
effects/summary
|
||||
|
||||
.. _proofs:
|
||||
|
||||
###############
|
||||
Theorem Proving
|
||||
###############
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
|
||||
proofs/pluscomm
|
||||
proofs/inductive
|
||||
proofs/patterns
|
||||
|
||||
.. _reference:
|
||||
|
||||
##################
|
||||
Language Reference
|
||||
##################
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
|
||||
reference/documenting
|
||||
reference/uniqueness-types
|
||||
reference/ffi
|
||||
reference/erasure
|
||||
|
||||
.. _guides:
|
||||
|
||||
############
|
||||
Short Guides
|
||||
############
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
|
||||
guides/type-providers-ffi
|
15
docs/listing/idris-prompt-helloworld.txt
Normal file
15
docs/listing/idris-prompt-helloworld.txt
Normal file
@ -0,0 +1,15 @@
|
||||
$ idris hello.idr
|
||||
____ __ _
|
||||
/ _/___/ /____(_)____
|
||||
/ // __ / ___/ / ___/ Version 0.9.17
|
||||
_/ // /_/ / / / (__ ) http://www.idris-lang.org/
|
||||
/___/\__,_/_/ /_/____/ Type :? for help
|
||||
|
||||
Type checking ./hello.idr
|
||||
*hello> :t main
|
||||
Main.main : IO ()
|
||||
*hello> :c hello
|
||||
*hello> :q
|
||||
Bye bye
|
||||
$ ./hello
|
||||
Hello world
|
12
docs/listing/idris-prompt-interp.txt
Normal file
12
docs/listing/idris-prompt-interp.txt
Normal file
@ -0,0 +1,12 @@
|
||||
$ idris interp.idr
|
||||
____ __ _
|
||||
/ _/___/ /____(_)____
|
||||
/ // __ / ___/ / ___/ Version 0.9.17
|
||||
_/ // /_/ / / / (__ ) http://www.idris-lang.org/
|
||||
/___/\__,_/_/ /_/____/ Type :? for help
|
||||
|
||||
Type checking ./interp.idr
|
||||
*interp> :exec
|
||||
Enter a number: 6
|
||||
720
|
||||
*interp>
|
8
docs/listing/idris-prompt-start.txt
Normal file
8
docs/listing/idris-prompt-start.txt
Normal file
@ -0,0 +1,8 @@
|
||||
$ idris
|
||||
____ __ _
|
||||
/ _/___/ /____(_)____
|
||||
/ // __ / ___/ / ___/ Version 0.9.17
|
||||
_/ // /_/ / / / (__ ) http://www.idris-lang.org/
|
||||
/___/\__,_/_/ /_/____/ Type :? for help
|
||||
|
||||
Idris>
|
263
docs/make.bat
Normal file
263
docs/make.bat
Normal file
@ -0,0 +1,263 @@
|
||||
@ECHO OFF
|
||||
|
||||
REM Command file for Sphinx documentation
|
||||
|
||||
if "%SPHINXBUILD%" == "" (
|
||||
set SPHINXBUILD=sphinx-build
|
||||
)
|
||||
set BUILDDIR=_build
|
||||
set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% .
|
||||
set I18NSPHINXOPTS=%SPHINXOPTS% .
|
||||
if NOT "%PAPER%" == "" (
|
||||
set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS%
|
||||
set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS%
|
||||
)
|
||||
|
||||
if "%1" == "" goto help
|
||||
|
||||
if "%1" == "help" (
|
||||
:help
|
||||
echo.Please use `make ^<target^>` where ^<target^> is one of
|
||||
echo. html to make standalone HTML files
|
||||
echo. dirhtml to make HTML files named index.html in directories
|
||||
echo. singlehtml to make a single large HTML file
|
||||
echo. pickle to make pickle files
|
||||
echo. json to make JSON files
|
||||
echo. htmlhelp to make HTML files and a HTML help project
|
||||
echo. qthelp to make HTML files and a qthelp project
|
||||
echo. devhelp to make HTML files and a Devhelp project
|
||||
echo. epub to make an epub
|
||||
echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter
|
||||
echo. text to make text files
|
||||
echo. man to make manual pages
|
||||
echo. texinfo to make Texinfo files
|
||||
echo. gettext to make PO message catalogs
|
||||
echo. changes to make an overview over all changed/added/deprecated items
|
||||
echo. xml to make Docutils-native XML files
|
||||
echo. pseudoxml to make pseudoxml-XML files for display purposes
|
||||
echo. linkcheck to check all external links for integrity
|
||||
echo. doctest to run all doctests embedded in the documentation if enabled
|
||||
echo. coverage to run coverage check of the documentation if enabled
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "clean" (
|
||||
for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i
|
||||
del /q /s %BUILDDIR%\*
|
||||
goto end
|
||||
)
|
||||
|
||||
|
||||
REM Check if sphinx-build is available and fallback to Python version if any
|
||||
%SPHINXBUILD% 2> nul
|
||||
if errorlevel 9009 goto sphinx_python
|
||||
goto sphinx_ok
|
||||
|
||||
:sphinx_python
|
||||
|
||||
set SPHINXBUILD=python -m sphinx.__init__
|
||||
%SPHINXBUILD% 2> nul
|
||||
if errorlevel 9009 (
|
||||
echo.
|
||||
echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
|
||||
echo.installed, then set the SPHINXBUILD environment variable to point
|
||||
echo.to the full path of the 'sphinx-build' executable. Alternatively you
|
||||
echo.may add the Sphinx directory to PATH.
|
||||
echo.
|
||||
echo.If you don't have Sphinx installed, grab it from
|
||||
echo.http://sphinx-doc.org/
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
:sphinx_ok
|
||||
|
||||
|
||||
if "%1" == "html" (
|
||||
%SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished. The HTML pages are in %BUILDDIR%/html.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "dirhtml" (
|
||||
%SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "singlehtml" (
|
||||
%SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "pickle" (
|
||||
%SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished; now you can process the pickle files.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "json" (
|
||||
%SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished; now you can process the JSON files.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "htmlhelp" (
|
||||
%SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished; now you can run HTML Help Workshop with the ^
|
||||
.hhp project file in %BUILDDIR%/htmlhelp.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "qthelp" (
|
||||
%SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished; now you can run "qcollectiongenerator" with the ^
|
||||
.qhcp project file in %BUILDDIR%/qthelp, like this:
|
||||
echo.^> qcollectiongenerator %BUILDDIR%\qthelp\IdrisManual.qhcp
|
||||
echo.To view the help file:
|
||||
echo.^> assistant -collectionFile %BUILDDIR%\qthelp\IdrisManual.ghc
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "devhelp" (
|
||||
%SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "epub" (
|
||||
%SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished. The epub file is in %BUILDDIR%/epub.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "latex" (
|
||||
%SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished; the LaTeX files are in %BUILDDIR%/latex.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "latexpdf" (
|
||||
%SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex
|
||||
cd %BUILDDIR%/latex
|
||||
make all-pdf
|
||||
cd %~dp0
|
||||
echo.
|
||||
echo.Build finished; the PDF files are in %BUILDDIR%/latex.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "latexpdfja" (
|
||||
%SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex
|
||||
cd %BUILDDIR%/latex
|
||||
make all-pdf-ja
|
||||
cd %~dp0
|
||||
echo.
|
||||
echo.Build finished; the PDF files are in %BUILDDIR%/latex.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "text" (
|
||||
%SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished. The text files are in %BUILDDIR%/text.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "man" (
|
||||
%SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished. The manual pages are in %BUILDDIR%/man.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "texinfo" (
|
||||
%SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "gettext" (
|
||||
%SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished. The message catalogs are in %BUILDDIR%/locale.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "changes" (
|
||||
%SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.The overview file is in %BUILDDIR%/changes.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "linkcheck" (
|
||||
%SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Link check complete; look for any errors in the above output ^
|
||||
or in %BUILDDIR%/linkcheck/output.txt.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "doctest" (
|
||||
%SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Testing of doctests in the sources finished, look at the ^
|
||||
results in %BUILDDIR%/doctest/output.txt.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "coverage" (
|
||||
%SPHINXBUILD% -b coverage %ALLSPHINXOPTS% %BUILDDIR%/coverage
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Testing of coverage in the sources finished, look at the ^
|
||||
results in %BUILDDIR%/coverage/python.txt.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "xml" (
|
||||
%SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished. The XML files are in %BUILDDIR%/xml.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "pseudoxml" (
|
||||
%SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml.
|
||||
goto end
|
||||
)
|
||||
|
||||
:end
|
24
docs/proofs/index.rst
Normal file
24
docs/proofs/index.rst
Normal file
@ -0,0 +1,24 @@
|
||||
.. _proofs-index:
|
||||
|
||||
###############
|
||||
Theorem Proving
|
||||
###############
|
||||
|
||||
A tutorial on theorem proving in Idris.
|
||||
|
||||
.. note::
|
||||
|
||||
The documentation for Idris has been published under the Creative
|
||||
Commons CC0 License. As such to the extent possible under law, *The
|
||||
Idris Community* has waived all copyright and related or neighboring
|
||||
rights to Documentation for Idris.
|
||||
|
||||
More information concerning the CC0 can be found online at: http://creativecommons.org/publicdomain/zero/1.0/
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
|
||||
pluscomm
|
||||
inductive
|
||||
patterns
|
||||
|
98
docs/proofs/inductive.rst
Normal file
98
docs/proofs/inductive.rst
Normal file
@ -0,0 +1,98 @@
|
||||
Inductive Proofs
|
||||
================
|
||||
|
||||
Before embarking on proving ``plus_commutes`` in Idris itself, let us
|
||||
consider the overall structure of a proof of some property of natural
|
||||
numbers. Recall that they are defined recursively, as follows:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
data Nat : Type where
|
||||
Z : Nat
|
||||
S : Nat -> Nat
|
||||
|
||||
A *total* function over natural numbers must both terminate, and cover
|
||||
all possible inputs. Idris checks functions for totality by cheking that
|
||||
all inputs are covered, and that all recursive calls are on
|
||||
*structurally smaller* values (so recursion will always reach a base
|
||||
case). Recalling ``plus``:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
plus : Nat -> Nat -> Nat
|
||||
plus Z m = m
|
||||
plus (S k) m = S (plus k m)
|
||||
|
||||
This is total because it covers all possible inputs (the first argument
|
||||
can only be ``Z`` or ``S k`` for some ``k``, and the second argument
|
||||
``m`` covers all possible ``Nat``\ s) and in the recursive call, ``k``
|
||||
is structurally smaller than ``S k`` so the first argument will always
|
||||
reach the base case ``Z`` in any sequence of recursive calls.
|
||||
|
||||
In some sense, this resembles a mathematical proof by induction (and
|
||||
this is no coincidence!). For some property ``P`` of a natural number
|
||||
``x``, we can show that ``P`` holds for all ``x`` if:
|
||||
|
||||
- ``P`` holds for zero (the base case).
|
||||
|
||||
- Assuming that ``P`` holds for ``k``, we can show ``P`` also holds for
|
||||
``S k`` (the inductive step).
|
||||
|
||||
In ``plus``, the property we are trying to show is somewhat trivial (for
|
||||
all natural numbers ``x``, there is a ``Nat`` which need not have any
|
||||
relation to ``x``). However, it still takes the form of a base case and
|
||||
an inductive step. In the base case, we show that there is a ``Nat``
|
||||
arising from ``plus n m`` when ``n = Z``, and in the inductive step we
|
||||
show that there is a ``Nat`` arising when ``n = S k`` and we know we can
|
||||
get a ``Nat`` inductively from ``plus k m``. We could even write a
|
||||
function capturing all such inductive definitions:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
nat_induction : (P : Nat -> Type) -> -- Property to show
|
||||
(P Z) -> -- Base case
|
||||
((k : Nat) -> P k -> P (S k)) -> -- Inductive step
|
||||
(x : Nat) -> -- Show for all x
|
||||
P x
|
||||
nat_induction P p_Z p_S Z = p_Z
|
||||
nat_induction P p_Z p_S (S k) = p_S k (nat_induction P p_Z p_S k)
|
||||
|
||||
Using ``nat_induction``, we can implement an equivalent inductive
|
||||
version of ``plus``:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
plus_ind : Nat -> Nat -> Nat
|
||||
plus_ind n m
|
||||
= nat_induction (\x => Nat)
|
||||
m -- Base case, plus_ind Z m
|
||||
(\k, k_rec => S k_rec) -- Inductive step plus_ind (S k) m
|
||||
-- where k_rec = plus_ind k m
|
||||
n
|
||||
|
||||
To prove that ``plus n m = plus m n`` for all natural numbers ``n`` and
|
||||
``m``, we can also use induction. Either we can fix ``m`` and perform
|
||||
induction on ``n``, or vice versa. We can sketch an outline of a proof;
|
||||
performing induction on ``n``, we have:
|
||||
|
||||
- Property ``P`` is ``\x => plus x m = plus m x``.
|
||||
|
||||
- Show that ``P`` holds in the base case and inductive step:
|
||||
|
||||
- | Base case: ``P Z``, i.e.
|
||||
| ``plus Z m = plus m Z``, which reduces to
|
||||
| ``m = plus m Z`` due to the definition of ``plus``.
|
||||
|
||||
- | Inductive step: Inductively, we know that ``P k`` holds for a specific, fixed ``k``, i.e.
|
||||
| ``plus k m = plus m k`` (the induction hypothesis). Given this, show ``P (S k)``, i.e.
|
||||
| ``plus (S k) m = plus m (S k)``, which reduces to
|
||||
| ``S (plus k m) = plus m (S k)``. From the induction hypothesis, we can rewrite this to
|
||||
| ``S (plus m k) = plus m (S k)``.
|
||||
|
||||
To complete the proof we therefore need to show that ``m = plus m Z``
|
||||
for all natural numbers ``m``, and that ``S (plus m k) = plus m (S k)``
|
||||
for all natural numbers ``m`` and ``k``. Each of these can also be
|
||||
proved by induction, this time on ``m``.
|
||||
|
||||
We are now ready to embark on a proof of commutativity of ``plus``
|
||||
formally in Idris.
|
350
docs/proofs/patterns.rst
Normal file
350
docs/proofs/patterns.rst
Normal file
@ -0,0 +1,350 @@
|
||||
Pattern Matching Proofs
|
||||
=======================
|
||||
|
||||
In this section, we will provide a proof of ``plus_commutes`` directly,
|
||||
by writing a pattern matching definition. We will use interactive
|
||||
editing features extensively, since it is significantly easier to
|
||||
produce a proof when the machine can give the types of intermediate
|
||||
values and construct components of the proof itself. The commands we
|
||||
will use are summarised below. Where we refer to commands
|
||||
directly, we will use the Vim version, but these commands have a direct
|
||||
mapping to Emacs commands.
|
||||
|
||||
+---------------------+-----------------+---------------+--------------------------------------------------------------------------------------------+
|
||||
|Command | Vim binding | Emacs binding | Explanation |
|
||||
+---------------------+-----------------+---------------+--------------------------------------------------------------------------------------------+
|
||||
| Check type | ``\t`` | ``C-c C-t`` | Show type of identifier or metavariable under the cursor. |
|
||||
+---------------------+-----------------+---------------+--------------------------------------------------------------------------------------------+
|
||||
| Proof search | ``\o`` | ``C-c C-a`` | Attempt to solve metavariable under the cursor by applying simple proof search. |
|
||||
+---------------------+-----------------+---------------+--------------------------------------------------------------------------------------------+
|
||||
| Make new definition | ``\d`` | ``C-c C-s`` | Add a template definition for the type defined under the cursor. |
|
||||
+---------------------+-----------------+---------------+--------------------------------------------------------------------------------------------+
|
||||
| Make lemma | ``\l`` | ``C-c C-e`` | Add a top level function with a type which solves the metavariable under the cursor. |
|
||||
+---------------------+-----------------+---------------+--------------------------------------------------------------------------------------------+
|
||||
| Split cases | ``\c`` | ``C-c C-c`` | Create new constructor patterns for each possible case of the variable under the cursor. |
|
||||
+---------------------+-----------------+---------------+--------------------------------------------------------------------------------------------+
|
||||
|
||||
|
||||
Creating a Definition
|
||||
---------------------
|
||||
|
||||
To begin, create a file ``pluscomm.idr`` containing the following type
|
||||
declaration:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
plus_commutes : (n : Nat) -> (m : Nat) -> n + m = m + n
|
||||
|
||||
To create a template definition for the proof, press ``\d`` (or the
|
||||
equivalent in your editor of choice) on the line with the type
|
||||
declaration. You should see:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
plus_commutes : (n : Nat) -> (m : Nat) -> n + m = m + n
|
||||
plus_commutes n m = ?plus_commutes_rhs
|
||||
|
||||
To prove this by induction on ``n``, as we sketched in Section
|
||||
[sect:induction], we begin with a case split on ``n`` (press
|
||||
``\c`` with the cursor over the ``n`` in the definition.) You
|
||||
should see:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
plus_commutes : (n : Nat) -> (m : Nat) -> n + m = m + n
|
||||
plus_commutes Z m = ?plus_commutes_rhs_1
|
||||
plus_commutes (S k) m = ?plus_commutes_rhs_2
|
||||
|
||||
If we inspect the types of the newly created metavariables,
|
||||
``plus_commutes_rhs_1`` and ``plus_commutes_rhs_2``, we see that the
|
||||
type of each reflects that ``n`` has been refined to ``Z`` and ``S k``
|
||||
in each respective case. Pressing ``\t`` over
|
||||
``plus_commutes_rhs_1`` shows:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
m : Nat
|
||||
--------------------------------------
|
||||
plus_commutes_rhs_1 : m = plus m 0
|
||||
|
||||
Note that ``Z`` renders as ``0`` because the pretty printer renders
|
||||
natural numbers as integer literals for readability. Similarly, for
|
||||
``plus_commutes_rhs_2``:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
k : Nat
|
||||
m : Nat
|
||||
--------------------------------------
|
||||
plus_commutes_rhs_2 : S (plus k m) = plus m (S k)
|
||||
|
||||
It is a good idea to give these slightly more meaningful names:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
plus_commutes : (n : Nat) -> (m : Nat) -> n + m = m + n
|
||||
plus_commutes Z m = ?plus_commutes_Z
|
||||
plus_commutes (S k) m = ?plus_commutes_S
|
||||
|
||||
Base Case
|
||||
---------
|
||||
|
||||
We can create a separate lemma for the base case interactively, by
|
||||
pressing ``\l`` with the cursor over ``plus_commutes_Z``. This
|
||||
yields:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
plus_commutes_Z : m = plus m 0
|
||||
|
||||
plus_commutes : (n : Nat) -> (m : Nat) -> n + m = m + n
|
||||
plus_commutes Z m = plus_commutes_Z
|
||||
plus_commutes (S k) m = ?plus_commutes_S
|
||||
|
||||
That is, the metavariable has been filled with a call to a top level
|
||||
function ``plus_commutes_Z``. The argument ``m`` has been made implicit
|
||||
because it can be inferred from context when it is applied.
|
||||
|
||||
Unfortunately, we cannot prove this lemma directly, since ``plus`` is
|
||||
defined by matching on its *first* argument, and here ``plus m 0`` has a
|
||||
specific value for its *second argument* (in fact, the left hand side of
|
||||
the equality has been reduced from ``plus 0 m``.) Again, we can prove
|
||||
this by induction, this time on ``m``.
|
||||
|
||||
First, create a template definition with ``\d``:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
plus_commutes_Z : m = plus m 0
|
||||
plus_commutes_Z = ?plus_commutes_Z_rhs
|
||||
|
||||
Since we are going to write this by induction on ``m``, which is
|
||||
implciit, we will need to bring ``m`` into scope manually:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
plus_commutes_Z : m = plus m 0
|
||||
plus_commutes_Z {m} = ?plus_commutes_Z_rhs
|
||||
|
||||
Now, case split on ``m`` with ``\c``:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
plus_commutes_Z : m = plus m 0
|
||||
plus_commutes_Z {m = Z} = ?plus_commutes_Z_rhs_1
|
||||
plus_commutes_Z {m = (S k)} = ?plus_commutes_Z_rhs_2
|
||||
|
||||
Checking the type of ``plus_commutes_Z_rhs_1`` shows the following,
|
||||
which is easily proved by reflection:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
--------------------------------------
|
||||
plus_commutes_Z_rhs_1 : 0 = 0
|
||||
|
||||
For such trivial proofs, we can let write the proof automatically by
|
||||
pressing ``\o`` with the cursor over ``plus_commutes_Z_rhs_1``.
|
||||
This yields:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
plus_commutes_Z : m = plus m 0
|
||||
plus_commutes_Z {m = Z} = Refl
|
||||
plus_commutes_Z {m = (S k)} = ?plus_commutes_Z_rhs_2
|
||||
|
||||
For ``plus_commutes_Z_rhs_2``, we are not so lucky:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
k : Nat
|
||||
--------------------------------------
|
||||
plus_commutes_Z_rhs_2 : S k = S (plus k 0)
|
||||
|
||||
Inductively, we should know that ``k = plus k 0``, and we can get access
|
||||
to this inductive hypothesis by making a recursive call on k, as
|
||||
follows:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
plus_commutes_Z : m = plus m 0
|
||||
plus_commutes_Z {m = Z} = Refl
|
||||
plus_commutes_Z {m = (S k)} = let rec = plus_commutes_Z {m=k} in
|
||||
?plus_commutes_Z_rhs_2
|
||||
|
||||
For ``plus_commutes_Z_rhs_2``, we now see:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
k : Nat
|
||||
rec : k = plus k (fromInteger 0)
|
||||
--------------------------------------
|
||||
plus_commutes_Z_rhs_2 : S k = S (plus k 0)
|
||||
|
||||
Again, the ``fromInteger 0`` is merely due to ``Nat`` being an instance
|
||||
of the ``Num`` typeclass. So we know that ``k = plus k 0``, but how do
|
||||
we use this to update the goal to ``S k = S k``?
|
||||
|
||||
To achieve this, Idris provides a ``replace`` function as part of the
|
||||
prelude:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
*pluscomm> :t replace
|
||||
replace : (x = y) -> P x -> P y
|
||||
|
||||
Given a proof that ``x = y``, and a property ``P`` which holds for
|
||||
``x``, we can get a proof of the same property for ``y``, because we
|
||||
know ``x`` and ``y`` must be the same. In practice, this function can be
|
||||
a little tricky to use because in general the implicit argument ``P``
|
||||
can be hard to infer by unification, so Idris provides a high level
|
||||
syntax which calculates the property and applies ``replace``:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
rewrite prf in expr
|
||||
|
||||
If we have ``prf : x = y``, and the required type for ``expr`` is some
|
||||
property of ``x``, the ``rewrite ... in`` syntax will search for ``x``
|
||||
in the required type of ``expr`` and replace it with ``y``. Concretely,
|
||||
in our example, we can say:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
plus_commutes_Z {m = (S k)} = let rec = plus_commutes_Z {m=k} in
|
||||
rewrite rec in ?plus_commutes_Z_rhs_2
|
||||
|
||||
Checking the type of ``plus_commutes_Z_rhs_2`` now gives:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
k : Nat
|
||||
rec : k = plus k (fromInteger 0)
|
||||
_rewrite_rule : plus k 0 = k
|
||||
--------------------------------------
|
||||
plus_commutes_Z_rhs_2 : S (plus k 0) = S (plus k 0)
|
||||
|
||||
Using the rewrite rule ``rec`` (which we can see in the context here as
|
||||
``_rewrite_rule``\ [1]_, the goal type has been updated with ``k``
|
||||
replaced by ``plus k 0``.
|
||||
|
||||
Alternatively, we could have applied the rewrite in the other direction
|
||||
using the ``sym`` function:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
*pluscomm> :t sym
|
||||
sym : (l = r) -> r = l
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
plus_commutes_Z {m = (S k)} = let rec = plus_commutes_Z {m=k} in
|
||||
rewrite sym rec in ?plus_commutes_Z_rhs_2
|
||||
|
||||
In this case, inspecting the type of the hole gives:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
k : Nat
|
||||
rec : k = plus k (fromInteger 0)
|
||||
_rewrite_rule : k = plus k 0
|
||||
--------------------------------------
|
||||
plus_commutes_Z_rhs_2 : S k = S k
|
||||
|
||||
Either way, we can use proof search (``\o``) to complete the
|
||||
proof, giving:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
plus_commutes_Z : m = plus m 0
|
||||
plus_commutes_Z {m = Z} = Refl
|
||||
plus_commutes_Z {m = (S k)} = let rec = plus_commutes_Z {m=k} in
|
||||
rewrite rec in Refl
|
||||
|
||||
The base case is now complete.
|
||||
|
||||
Inductive Step
|
||||
--------------
|
||||
|
||||
Our main theorem, ``plus_commutes`` should currently be in the following
|
||||
state:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
plus_commutes : (n : Nat) -> (m : Nat) -> n + m = m + n
|
||||
plus_commutes Z m = plus_commutes_Z
|
||||
plus_commutes (S k) m = ?plus_commutes_S
|
||||
|
||||
Looking again at the type of ``plus_commutes_S``, we have:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
k : Nat
|
||||
m : Nat
|
||||
--------------------------------------
|
||||
plus_commutes_S : S (plus k m) = plus m (S k)
|
||||
|
||||
Conveniently, by induction we can immediately tell that
|
||||
``plus k m = plus m k``, so let us rewrite directly by making a
|
||||
recursive call to ``plus_commutes``. We add this directly, by hand, as
|
||||
follows:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
plus_commutes : (n : Nat) -> (m : Nat) -> n + m = m + n
|
||||
plus_commutes Z m = plus_commutes_Z
|
||||
plus_commutes (S k) m = rewrite plus_commutes k m in ?plus_commutes_S
|
||||
|
||||
Checking the type of ``plus_commutes_S`` now gives:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
k : Nat
|
||||
m : Nat
|
||||
_rewrite_rule : plus m k = plus k m
|
||||
--------------------------------------
|
||||
plus_commutes_S : S (plus m k) = plus m (S k)
|
||||
|
||||
The good news is that ``m`` and ``k`` now appear in the correct order.
|
||||
However, we still have to show that the successor symbol ``S`` can be
|
||||
moved to the front in the right hand side of this equality. This
|
||||
remaining lemma takes a similar form to the ``plus_commutes_Z``; we
|
||||
begin by making a new top level lemma with ``\l``. This gives:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
plus_commutes_S : (k : Nat) -> (m : Nat) -> S (plus m k) = plus m (S k)
|
||||
|
||||
Unlike the previous case, ``k`` and ``m`` are not made implicit because
|
||||
we cannot in general infer arguments to a function from its result.
|
||||
Again, we make a template definition with ``\d``:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
plus_commutes_S : (k : Nat) -> (m : Nat) -> S (plus m k) = plus m (S k)
|
||||
plus_commutes_S k m = ?plus_commutes_S_rhs
|
||||
|
||||
Again, this is defined by induction over ``m``, since ``plus`` is
|
||||
defined by matching on its first argument. The complete definition is:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
total
|
||||
plus_commutes_S : (k : Nat) -> (m : Nat) -> S (plus m k) = plus m (S k)
|
||||
plus_commutes_S k Z = Refl
|
||||
plus_commutes_S k (S j) = rewrite plus_commutes_S k j in Refl
|
||||
|
||||
All metavariables have now been solved,
|
||||
|
||||
The ``total`` annotation means that we require the final function to
|
||||
pass the totality checker; i.e. it will terminate on all possible
|
||||
well-typed inputs. This is important for proofs, since it provides a
|
||||
guarantee that the proof is valid in *all* cases, not just those for
|
||||
which it happens to be well-defined.
|
||||
|
||||
Now that ``plus_commutes`` has a ``total`` annotation, we have completed the
|
||||
proof of commutativity of addition on natural numbers.
|
||||
|
||||
.. [1]
|
||||
Note that the left and right hand sides of the equality have been
|
||||
swapped, because ``replace`` takes a proof of ``x=y`` and the
|
||||
property for ``x``, not ``y``.
|
188
docs/proofs/pluscomm.rst
Normal file
188
docs/proofs/pluscomm.rst
Normal file
@ -0,0 +1,188 @@
|
||||
Running example: Addition of Natural Numbers
|
||||
============================================
|
||||
|
||||
Throughout this tutorial, we will be working with the following
|
||||
function, defined in the Idris prelude, which defines addition on
|
||||
natural numbers:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
plus : Nat -> Nat -> Nat
|
||||
plus Z m = m
|
||||
plus (S k) m = S (plus k m)
|
||||
|
||||
It is defined by the above equations, meaning that we have for free the
|
||||
properties that adding ``m`` to zero always results in ``m``, and that
|
||||
adding ``m`` to any non-zero number ``S k`` always results in
|
||||
``S (plus k m)``. We can see this by evaluation at the Idris REPL (i.e.
|
||||
the prompt, the read-eval-print loop):
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
Idris> \m => plus Z m
|
||||
\m => m : Nat -> Nat
|
||||
|
||||
Idris> \k,m => plus (S k) m
|
||||
\k => \m => S (plus k m) : Nat -> Nat -> Nat
|
||||
|
||||
Note that unlike many other language REPLs, the Idris REPL performs
|
||||
evaluation on *open* terms, meaning that it can reduce terms which
|
||||
appear inside lambda bindings, like those above. Therefore, we can
|
||||
introduce unknowns ``k`` and ``m`` as lambda bindings and see how
|
||||
``plus`` reduces.
|
||||
|
||||
The ``plus`` function has a number of other useful properties, for
|
||||
example:
|
||||
|
||||
- It is *commutative*, that is for all ``Nat`` inputs ``n`` and ``m``,
|
||||
we know that ``plus n m = plus m n``.
|
||||
|
||||
- It is *associative*, that is for all ``Nat`` inputs ``n``, ``m`` and
|
||||
``p``, we know that ``plus n (plus m p) = plus (plus m n) p``.
|
||||
|
||||
We can use these properties in an Idris program, but in order to do so
|
||||
we must *prove* them.
|
||||
|
||||
Equality Proofs
|
||||
---------------
|
||||
|
||||
Idris has a built-in propositional equality type, conceptually defined
|
||||
as follows:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
data (=) : a -> b -> Type where
|
||||
Refl : x = x
|
||||
|
||||
Note that this must be built-in, rather than defined in the library,
|
||||
because ``=`` is a reserved operator — you cannot define this directly
|
||||
in your own code.
|
||||
|
||||
It is *propositional* equality, where the type states that any two
|
||||
values in different types ``a`` and ``b`` may be proposed to be equal.
|
||||
There is only one way to *prove* equality, however, which is by
|
||||
reflexivity (``Refl``).
|
||||
|
||||
We have a *type* for propositional equality here, and correspondingly a
|
||||
*program* inhabiting an instance of this type can be seen as a proof of
|
||||
the corresponding proposition [1]_. So, trivially, we can prove that
|
||||
``4`` equals ``4``:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
four_eq : 4 = 4
|
||||
four_eq = Refl
|
||||
|
||||
However, trying to prove that ``4 = 5`` results in failure:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
four_eq_five : 4 = 5
|
||||
four_eq_five = Refl
|
||||
|
||||
The type ``4 = 5`` is a perfectly valid type, but is uninhabited, so
|
||||
when trying to type check this definition, Idris gives the following
|
||||
error:
|
||||
|
||||
::
|
||||
|
||||
When elaborating right hand side of four_eq_five:
|
||||
Can't unify
|
||||
x = x (Type of Refl)
|
||||
with
|
||||
4 = 5 (Expected type)
|
||||
|
||||
Type checking equality proofs
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
An important step in type checking Idris programs is *unification*,
|
||||
which attempts to resolve implicit arguments such as the implicit
|
||||
argument ``x`` in ``Refl``. As far as our understanding of type checking
|
||||
proofs is concerned, it suffices to know that unifying two terms
|
||||
involves reducing both to normal form then trying to find an assignment
|
||||
to implicit arguments which will make those normal forms equal.
|
||||
|
||||
When type checking ``Refl``, Idris requires that the type is of the form
|
||||
``x = x``, as we see from the type of ``Refl``. In the case of
|
||||
``four_eq_five``, Idris will try to unify the expected type ``4 = 5``
|
||||
with the type of ``Refl``, ``x = x``, notice that a solution requires
|
||||
that ``x`` be both ``4`` and ``5``, and therefore fail.
|
||||
|
||||
Since type checking involves reduction to normal form, we can write the
|
||||
following equalities directly:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
twoplustwo_eq_four : 2 + 2 = 4
|
||||
twoplustwo_eq_four = Refl
|
||||
|
||||
plus_reduces_Z : (m : Nat) -> plus Z m = m
|
||||
plus_reduces_Z m = Refl
|
||||
|
||||
plus_reduces_Sk : (k, m : Nat) -> plus (S k) m = S (plus k m)
|
||||
plus_reduces_Sk k m = Refl
|
||||
|
||||
Heterogeneous Equality
|
||||
----------------------
|
||||
|
||||
Equality in Idris is *heterogeneous*, meaning that we can even propose
|
||||
equalities between values in different types:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
idris_not_php : 2 = "2"
|
||||
|
||||
Obviously, in Idris the type ``2 = "2"`` is uninhabited, and one might
|
||||
wonder why it is useful to be able to propose equalities between values
|
||||
in different types. However, with dependent types, such equalities can
|
||||
arise naturally. For example, if two vectors are equal, their lengths
|
||||
must be equal:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
vect_eq_length : (xs : Vect n a) -> (ys : Vect m a) ->
|
||||
(xs = ys) -> n = m
|
||||
|
||||
In the above declaration, ``xs`` and ``ys`` have different types because
|
||||
their lengths are different, but we would still like to draw a
|
||||
conclusion about the lengths if they happen to be equal. We can define
|
||||
``vect_eq_length`` as follows:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
vect_eq_length xs xs Refl = Refl
|
||||
|
||||
By matching on ``Refl`` for the third argument, we know that the only
|
||||
valid value for ``ys`` is ``xs``, because they must be equal, and
|
||||
therefore their types must be equal, so the lengths must be equal.
|
||||
|
||||
Alternatively, we can put an underscore for the second ``xs``, since
|
||||
there is only one value which will type check:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
vect_eq_length xs _ Refl = Refl
|
||||
|
||||
Properties of ``plus``
|
||||
----------------------
|
||||
|
||||
Using the ``(=)`` type, we can now state the properties of ``plus``
|
||||
given above as Idris type declarations:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
plus_commutes : (n, m : Nat) -> plus n m = plus m n
|
||||
plus_assoc : (n, m, p : Nat) -> plus n (plus m p) = plus (plus n m) p
|
||||
|
||||
Both of these properties (and many others) are proved for natural number
|
||||
addition in the Idris standard library, using ``(+)`` from the ``Num``
|
||||
type class rather than using ``plus`` directly. They have the names
|
||||
``plusCommutative`` and ``plusAssociative`` respectively.
|
||||
|
||||
In the remainder of this tutorial, we will explore several different
|
||||
ways of proving ``plus_commutes`` (or, to put it another way, writing
|
||||
the function.) We will also discuss how to use such equality proofs, and
|
||||
see where the need for them arises in practice.
|
||||
|
||||
.. [1]
|
||||
This is known as the Curry-Howard correspondence
|
135
docs/reference/documenting.rst
Normal file
135
docs/reference/documenting.rst
Normal file
@ -0,0 +1,135 @@
|
||||
.. _sect-documenting:
|
||||
|
||||
======================
|
||||
Documenting Idris Code
|
||||
======================
|
||||
|
||||
Idris documentation comes in two major forms: comments, which exist
|
||||
for a reader’s edification and are ignored by the compiler, and inline
|
||||
API documentation, which the compiler parses and stores for future
|
||||
reference. To consult the documentation for a declaration ``f``, write
|
||||
``:doc f`` at the REPL or use the appropriate command in your editor
|
||||
(``C-c C-d`` in Emacs, in Vim).
|
||||
|
||||
Comments
|
||||
--------
|
||||
|
||||
Use comments to explain why code is written the way that it
|
||||
is. Idris’s comment syntax is the same as that of Haskell: lines
|
||||
beginning with ``--`` are comments, and regions bracketed by ``{-``
|
||||
and ``-}`` are comments even if they extend across multiple
|
||||
lines. These can be used to comment out lines of code or provide
|
||||
simple documentation for the readers of Idris code.
|
||||
|
||||
Inline Documentation
|
||||
--------------------
|
||||
|
||||
Idris also supports a comprehensive and rich inline syntax for Idris
|
||||
code to be generated. This syntax also allows for named parameters and
|
||||
variables within type signatures to be individually annotated using a
|
||||
syntax similar to Javadoc parameter annotations.
|
||||
|
||||
Documentation always comes before the declaration being documented.
|
||||
Inline documentation applies to either top-level declarations or to
|
||||
constructors. Documentation for specific arguments to constructors, type
|
||||
constructors, or functions can be associated with these arguments using
|
||||
their names.
|
||||
|
||||
The inline documentation for a declaration is an unbroken string of
|
||||
lines, each of which begins with ``|||`` (three pipe symbols). The
|
||||
first paragraph of the documentation is taken to be an overview, and
|
||||
in some contexts, only this overview will be shown. After the
|
||||
documentation for the declaration as a whole, it is possible to
|
||||
associate documetation with specific named parameters, which can
|
||||
either be explicitly name or the results of converting free variables
|
||||
to implicit parameters. Annotations are the same as with Javadoc
|
||||
annotations, that is for the named parameter ``(n : T)``, the
|
||||
corresponding annotation is ``||| @ n Some description`` that is
|
||||
placed before the declaration.
|
||||
|
||||
Documentation is written in Markdown, though not all contexts will
|
||||
display all possible formatting (for example, images are not displayed
|
||||
when viewing documentation in the REPL, and only some terminals render
|
||||
italics correctly). A comprehensive set of examples is given below.
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
|
||||
||| Modules can also be documented.
|
||||
module Docs
|
||||
|
||||
||| Add some numbers.
|
||||
|||
|
||||
||| Addition is really great. This paragraph is not part of the overview.
|
||||
||| Still the same paragraph. Lists are also nifty:
|
||||
||| * Really nifty!
|
||||
||| * Yep!
|
||||
||| * The name `add` is a **bold** choice
|
||||
||| @ n is the recursive param
|
||||
||| @ m is not
|
||||
add : (n, m : Nat) -> Nat
|
||||
add Z m = m
|
||||
add (S n) m = S (add n m)
|
||||
|
||||
|
||||
||| Append some vectors
|
||||
||| @ a the contents of the vectors
|
||||
||| @ xs the first vector (recursive param)
|
||||
||| @ ys the second vector (not analyzed)
|
||||
appendV : (xs : Vect n a) -> (ys : Vect m a) -> Vect (add n m) a
|
||||
appendV [] ys = ys
|
||||
appendV (x::xs) ys = x :: appendV xs ys
|
||||
|
||||
||| Here's a simple datatype
|
||||
data Ty =
|
||||
||| Unit
|
||||
UNIT |
|
||||
||| Functions
|
||||
ARR Ty Ty
|
||||
|
||||
||| Points to a place in a typing context
|
||||
data Elem : Vect n Ty -> Ty -> Type where
|
||||
Here : {ts : Vect n Ty} -> Elem (t::ts) t
|
||||
There : {ts : Vect n Ty} -> Elem ts t -> Elem (t'::ts) t
|
||||
|
||||
||| A more interesting datatype
|
||||
||| @ n the number of free variables
|
||||
||| @ ctxt a typing context for the free variables
|
||||
||| @ ty the type of the term
|
||||
data Term : (ctxt : Vect n Ty) -> (ty : Ty) -> Type where
|
||||
|
||||
||| The consructor of the unit type
|
||||
||| More comment
|
||||
||| @ ctxt the typing context
|
||||
UnitCon : {ctxt : Vect n Ty} -> Term ctxt UNIT
|
||||
|
||||
||| Function application
|
||||
||| @ f the function to apply
|
||||
||| @ x the argument
|
||||
App : {ctxt : Vect n Ty} -> (f : Term ctxt (ARR t1 t2)) -> (x : Term ctxt t1) -> Term ctxt t2
|
||||
|
||||
||| Lambda
|
||||
||| @ body the function body
|
||||
Lam : {ctxt : Vect n Ty} -> (body : Term (t1::ctxt) t2) -> Term ctxt (ARR t1 t2)
|
||||
|
||||
||| Variables
|
||||
||| @ i de Bruijn index
|
||||
Var : {ctxt : Vect n Ty} -> (i : Elem ctxt t) -> Term ctxt t
|
||||
|
||||
||| A computation that may someday finish
|
||||
codata Partial : Type -> Type where
|
||||
|
||||
||| A finished computation
|
||||
||| @ value the result
|
||||
Now : (value : a) -> Partial a
|
||||
|
||||
||| A not-yet-finished computation
|
||||
||| @ rest the remaining work
|
||||
Later : (rest : Partial a) -> Partial a
|
||||
|
||||
||| We can document records just like normal data
|
||||
record Yummy : Type where
|
||||
|
||||
||| Make a yummy
|
||||
||| @ food what to eat
|
||||
MkYummy : (food : String) -> Yummy
|
501
docs/reference/erasure.rst
Normal file
501
docs/reference/erasure.rst
Normal file
@ -0,0 +1,501 @@
|
||||
Erasure By Usage Analysis
|
||||
=========================
|
||||
|
||||
This work stems from this `feature proposal
|
||||
<https://github.com/idris-lang/Idris-dev/wiki/Egg-%232%3A-Erasure-annotations>`__
|
||||
(obsoleted by this page). Beware that the information in the proposal
|
||||
is out of date — and sometimes even in direct contradiction with the
|
||||
eventual implementation.
|
||||
|
||||
Motivation
|
||||
----------
|
||||
|
||||
Traditional dependently typed languages (Agda, Coq) are good at
|
||||
erasing *proofs* (either via irrelevance or an extra universe).
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
half : (n : Nat) -> Even n -> Nat
|
||||
half Z EZ = Z
|
||||
half (S (S n)) (ES pf) = S (half n pf)
|
||||
|
||||
For example, in the above snippet, the second argument is a proof,
|
||||
which is used only to convince the compiler that the function is
|
||||
total. This proof is never inspected at runtime and thus can be
|
||||
erased. In this case, the mere existence of the proof is sufficient
|
||||
and we can use irrelevance-related methods to achieve erasure.
|
||||
|
||||
However, sometimes we want to erase *indices* and this is where the
|
||||
traditional approaches stop being useful, mainly for reasons described
|
||||
in the `original proposal
|
||||
<https://github.com/idris-lang/Idris-dev/wiki/Egg-%232%3A-Erasure-annotations#prop-is-cumbersome-coq>`__.
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
uninterleave : {n : Nat} -> Vect (n * 2) a -> (Vect n a, Vect n a)
|
||||
uninterleave [] = ([] , [])
|
||||
uninterleave (x :: y :: rest) with (unzipPairs rest)
|
||||
| (xs, ys) = (x :: xs, y :: ys)
|
||||
|
||||
Notice that in this case, the second argument is the important one and
|
||||
we would like to get rid of the ``n`` instead, although the shape of
|
||||
the program is generally the same as in the previous case.
|
||||
|
||||
There are methods described by Brady, McBride and McKinna in [BMM04]_
|
||||
to remove the indices from data structures, exploiting the fact that
|
||||
functions operating on them either already have a copy of the
|
||||
appropriate index or the index can be quickly reconstructed if needed.
|
||||
However, we often want to erase the indices altogether, from the whole
|
||||
program, even in those cases where reconstruction is not possible.
|
||||
|
||||
The following two sections describe two cases where doing so improves
|
||||
the runtime performance asymptotically.
|
||||
|
||||
Binary numbers
|
||||
~~~~~~~~~~~~~~
|
||||
|
||||
- O(n) instead of O(log n)
|
||||
|
||||
Consider the following ``Nat``-indexed type family representing binary
|
||||
numbers:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
data Bin : Nat -> Type where
|
||||
N : Bin 0
|
||||
O : {n : Nat} -> Bin n -> Bin (0 + 2*n)
|
||||
I : {n : Nat} -> Bin n -> Bin (1 + 2*n)
|
||||
|
||||
These are supposed to be (at least asymptotically) fast and
|
||||
memory-efficient because their size is logarithmic compared to the
|
||||
numbers they represent.
|
||||
|
||||
Unfortunately this is not the case. The problem is that these binary
|
||||
numbers still carry the *unary* indices with them, performing
|
||||
arithmetic on the indices whenever arithmetic is done on the binary
|
||||
numbers themselves. Hence the real representation of the number 15
|
||||
looks like this:
|
||||
|
||||
::
|
||||
|
||||
I -> I -> I -> I -> N
|
||||
S S S Z
|
||||
S S Z
|
||||
S S
|
||||
S Z
|
||||
S
|
||||
S
|
||||
S
|
||||
Z
|
||||
|
||||
The used memory is actually *linear*, not logarithmic and therefore we
|
||||
cannot get below O(n) with time complexities.
|
||||
|
||||
One could argue that Idris in fact compiles ``Nat`` via GMP but
|
||||
that's a moot point for two reasons:
|
||||
|
||||
+ First, whenever we try to index our datastructures with anything
|
||||
else than ``Nat``, the compiler is not going to come to the rescue.
|
||||
|
||||
+ Second, even with ``Nat``, the GMP integers are *still* there and
|
||||
they slow the runtime down.
|
||||
|
||||
This ought not to be the case since the ``Nat`` are never used at
|
||||
runtime and they are only there for typechecking purposes. Hence we
|
||||
should get rid of them and get runtime code similar to what a idris
|
||||
programmer would write.
|
||||
|
||||
U-views of lists
|
||||
~~~~~~~~~~~~~~~~
|
||||
|
||||
- O(n^2) instead of O(n)
|
||||
|
||||
Consider the type of U-views of lists:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
data U : List a -> Type where
|
||||
nil : U []
|
||||
one : (z : a) -> U [z]
|
||||
two : {xs : List a} -> (x : a) -> (u : U xs) -> (y : a) -> U (x :: xs ++ [y])
|
||||
|
||||
For better intuition, the shape of the U-view of
|
||||
``[x0,x1,x2,z,y2,y1,y0]`` looks like this:
|
||||
|
||||
::
|
||||
|
||||
x0 y0 (two)
|
||||
x1 y1 (two)
|
||||
x2 y1 (two)
|
||||
z (one)
|
||||
|
||||
When recursing over this structure, the values of ``xs`` range over
|
||||
``[x0,x1,x2,z,y2,y1,y0]``, ``[x1,x2,z,y2,y1]``, ``[x2,z,y2]``,
|
||||
``[z]``. No matter whether these lists are stored or built on demand,
|
||||
they take up a quadratic amount of memory (because they cannot share
|
||||
nodes), and hence it takes a quadratic amount of time just to build
|
||||
values of this index alone.
|
||||
|
||||
But the reasonable expectation is that operations with U-views take
|
||||
linear time — so we need to erase the index ``xs`` if we want to
|
||||
achieve this goal.
|
||||
|
||||
Changes to Idris
|
||||
----------------
|
||||
|
||||
Usage analysis is run at every compilation and its outputs are used
|
||||
for various purposes. This is actually invisible to the user but it's
|
||||
a relatively big and important change, which enables the new features.
|
||||
|
||||
Everything that is found to be unused is erased. No annotations are
|
||||
needed, just don't use the thing and it will vanish from the generated
|
||||
code. However, if you wish, you can use the dot annotations to get a
|
||||
warning if the thing is accidentally used.
|
||||
|
||||
"Being used" in this context means that the value of the "thing" may
|
||||
influence run-time behaviour of the program. (More precisely, it is
|
||||
not found to be irrelevant to the run-time behaviour by the usage
|
||||
analysis algorithm.)
|
||||
|
||||
"Things" considered for removal by erasure include:
|
||||
|
||||
* function arguments
|
||||
|
||||
* data constructor fields (including record fields and dictionary
|
||||
fields of class instances)
|
||||
|
||||
For example, ``Either`` often compiles to the same runtime
|
||||
representation as ``Bool``. Constructor field removal sometimes
|
||||
combines with the newtype optimisation to have quite a strong effect.
|
||||
|
||||
There is a new compiler option ``--warnreach``, which will enable
|
||||
warnings coming from erasure. Since we have full usage analysis, we
|
||||
can compile even those programs that violate erasure annotations --
|
||||
it's just that the binaries may run slower than expected. The warnings
|
||||
will be enabled by default in future versions of Idris (and possibly
|
||||
turned to errors). However, in this transitional period, we chose to
|
||||
keep them on-demand to avoid confusion until better documentation is
|
||||
written.
|
||||
|
||||
Case-tree elaboration tries to avoid using dotted "things" whenever
|
||||
possible. (NB. This is not yet perfect and it's being worked on:
|
||||
https://gist.github.com/ziman/10458331)
|
||||
|
||||
Postulates are no longer required to be collapsible. They are now
|
||||
required to be *unused* instead.
|
||||
|
||||
Changes to the language
|
||||
-----------------------
|
||||
|
||||
You can use dots to mark fields that are not intended to be used at
|
||||
runtime.
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
data Bin : Nat -> Type where
|
||||
N : Bin 0
|
||||
O : .{n : Nat} -> Bin n -> Bin (0 + 2*n)
|
||||
I : .{n : Nat} -> Bin n -> Bin (1 + 2*n)
|
||||
|
||||
If these fields are found to be used at runtime, the dots will trigger
|
||||
a warning (with ``--warnreach``).
|
||||
|
||||
Note that free (unbound) implicits are dotted by default so, for
|
||||
example, the constructor ``O`` can be defined as:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
O : Bin n -> Bin (0 + 2*n)
|
||||
|
||||
and this is actually the preferred form.
|
||||
|
||||
If you have a free implicit which is meant to be used at runtime, you
|
||||
have to change it into an (undotted) ``{bound : implicit}``.
|
||||
|
||||
You can also put dots in types of functions to get more guarantees.
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
half : (n : Nat) -> .(pf : Even n) -> Nat
|
||||
|
||||
and free implicits are automatically dotted here, too.
|
||||
|
||||
What it means
|
||||
-------------
|
||||
|
||||
Dot annotations serve two purposes:
|
||||
|
||||
* influence case-tree elaboration to avoid dotted variables
|
||||
|
||||
* trigger warnings when a dotted variable is used
|
||||
|
||||
However, there's no direct connection between being dotted and being
|
||||
erased. The compiler erases everything it can, dotted or not. The dots
|
||||
are there mainly to help the programmer (and the compiler) refrain
|
||||
from using the values they want to erase.
|
||||
|
||||
How to use it
|
||||
-------------
|
||||
|
||||
Ideally, few or no extra annotations are needed -- in practice, it
|
||||
turns out that having free implicits automatically dotted is enough to
|
||||
get good erasure.
|
||||
|
||||
Therefore, just compile with ``--warnreach`` to see warnings if
|
||||
erasure cannot remove parts of the program.
|
||||
|
||||
However, those programs that have been written without runtime
|
||||
behaviour in mind, will need some help to get in the form that
|
||||
compiles to a reasonable binary. Generally, it's sufficient to follow
|
||||
erasure warnings (which may be sometimes unhelpful at the moment).
|
||||
|
||||
Benchmarks
|
||||
----------
|
||||
|
||||
- source: https://github.com/ziman/idris-benchmarks
|
||||
- results: http://ziman.functor.sk/erasure-bm/
|
||||
|
||||
It can be clearly seen that asymptotics are improved by erasure.
|
||||
|
||||
Shortcomings
|
||||
------------
|
||||
|
||||
You can't get warnings in libraries because usage analysis starts from
|
||||
``Main.main``. This will be solved by the planned ``%default_usage``
|
||||
pragma.
|
||||
|
||||
Usage warnings are quite bad and unhelpful at the moment. We should
|
||||
include more information and at least translate argument numbers to
|
||||
their names.
|
||||
|
||||
There is no decent documentation yet. This wiki page is the first one.
|
||||
|
||||
There is no generally accepted terminology. We switch between
|
||||
"dotted", "unused", "erased", "irrelevant", "inaccessible", while each
|
||||
has a slightly different meaning. We need more consistent and
|
||||
understandable naming.
|
||||
|
||||
If the same type is used in both erased and non-erased context, it
|
||||
will retain its fields to accomodate the least common denominator --
|
||||
the non-erased context. This is particularly troublesome in the case
|
||||
of the type of (dependent) pairs, where it actually means that no
|
||||
erasure would be performed. We should probably locate disjoint uses of
|
||||
data types and split them into "sub-types". There are three different
|
||||
flavours of dependent types now: ``Sigma`` (nothing erased),
|
||||
``Exists`` (first component erased), ``Subset`` (second component
|
||||
erased).
|
||||
|
||||
Case-tree building does not avoid dotted values coming from
|
||||
pattern-matched constructors (https://gist.github.com/ziman/10458331).
|
||||
This is to be fixed soon. (Fixed.)
|
||||
|
||||
Higher-order function arguments and opaque functional variables are
|
||||
considered to be using all their arguments. To work around this, you
|
||||
can force erasure via the type system, using the ``Erased`` wrapper:
|
||||
https://github.com/idris-lang/Idris-dev/blob/master/libs/base/Data/Erased.idr
|
||||
|
||||
Typeclass methods are considered to be using the union of all their
|
||||
implementations. In other words, an argument of a method is unused
|
||||
only if it is unused in every implementation of the method that occurs
|
||||
in the program.
|
||||
|
||||
Planned features
|
||||
----------------
|
||||
|
||||
- Fixes to the above shortcomings in general.
|
||||
|
||||
- Improvements to the case-tree elaborator so that it properly avoids
|
||||
dotted fields of data constructors. Done.
|
||||
|
||||
- Compiler pragma ``%default_usage used/unused`` and per-function
|
||||
overrides ``used`` and ``unused``, which allow the programmer to
|
||||
mark the return value of a function as used, even if the function
|
||||
is not used in ``main`` (which is the case when writing library
|
||||
code). These annotations will help library writers discover usage
|
||||
violations in their code before it is actually published and used
|
||||
in compiled programs.
|
||||
|
||||
Troubleshooting
|
||||
---------------
|
||||
|
||||
My program is slower
|
||||
~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
The patch introducing erasure by usage analysis also disabled some
|
||||
optimisations that were in place before; these are subsumed by the new
|
||||
erasure. However, in some erasure-unaware programs, where erasure by
|
||||
usage analysis does not exercise its full potential (but the old
|
||||
optimisations would have worked), certain slowdown may be observed (up
|
||||
to ~10% according to preliminary benchmarking), due to retention and
|
||||
computation of information that should not be necessary at runtime.
|
||||
|
||||
A simple check whether this is the case is to compile with
|
||||
``--warnreach``. If you see warnings, there is some unnecessary code
|
||||
getting compiled into the binary.
|
||||
|
||||
The solution is to change the code so that there are no warnings.
|
||||
|
||||
Usage warnings are unhelpful
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
This is a known issue and we are working on it. For now, see the section
|
||||
`How to read and resolve erasure
|
||||
warnings <#how-to-read-and-resolve-erasure-warnings>`__.
|
||||
|
||||
There should be no warnings in this function
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
A possible cause is non-totality of the function (more precisely,
|
||||
non-coverage). If a function is non-covering, the program needs to
|
||||
inspect all arguments in order to detect coverage failures at runtime.
|
||||
Since the function inspects all its arguments, nothing can be erased
|
||||
and this may transitively cause usage violations. The solution is to
|
||||
make the function total or accept the fact that it will use its
|
||||
arguments and remove some dots from the appropriate constructor fields
|
||||
and function arguments. (Please note that this is not a shortcoming of
|
||||
erasure and there is nothing we can do about it.)
|
||||
|
||||
Another possible cause is the currently imperfect case-tree
|
||||
elaboration, which does not avoid dotted constructor fields (see
|
||||
https://gist.github.com/ziman/10458331). You can either rephrase the
|
||||
function or wait until this is fixed, hopefully soon. Fixed.
|
||||
|
||||
The compiler refuses to recognise this thing as erased
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
You can force anything to be erased by wrapping it in the ``Erased``
|
||||
monad. While this program triggers usage warnings,
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
f : (g : Nat -> Nat) -> .(x : Nat) -> Nat
|
||||
f g x = g x -- WARNING: g uses x
|
||||
|
||||
the following program does not:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
f : (g : Erased Nat -> Nat) -> .(x : Nat) -> Nat
|
||||
f g x = g (Erase x) -- OK
|
||||
|
||||
How to read and resolve erasure warnings
|
||||
----------------------------------------
|
||||
|
||||
Example 1
|
||||
~~~~~~~~~
|
||||
|
||||
Consider the following program:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
vlen : Vect n a -> Nat
|
||||
vlen {n = n} xs = n
|
||||
|
||||
sumLengths : List (Vect n a) -> Nat
|
||||
sumLengths [] = 0
|
||||
sumLengths (v :: vs) = vlen v + sumLengths vs
|
||||
|
||||
main : IO ()
|
||||
main = print . sumLengths $ [[0,1],[2,3]]
|
||||
|
||||
When you compile it using ``--warnreach``, there is one warning:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
Main.sumLengths: inaccessible arguments reachable:
|
||||
n (no more information available)
|
||||
|
||||
The warning does not contain much detail at this point so we can try
|
||||
compiling with ``--dumpcases cases.txt`` and look up the compiled
|
||||
definition in ``cases.txt``:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
Main.sumLengths {e0} {e1} {e2} =
|
||||
case {e2} of
|
||||
| Prelude.List.::({e6}) => LPlus (ATInt ITBig)({e0}, Main.sumLengths({e0}, ____, {e6}))
|
||||
| Prelude.List.Nil() => 0
|
||||
|
||||
The reason for the warning is that ``sumLengths`` calls ``vlen``, which
|
||||
gets inlined. The second clause of ``sumLengths`` then accesses the
|
||||
variable ``n``, compiled as ``{e0}``. Since ``n`` is a free implicit, it
|
||||
is automatically considered dotted and this triggers the warning.
|
||||
|
||||
A solution would be either making the argument ``n`` a bound implicit
|
||||
parameter to indicate that we wish to keep it at runtime,
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
sumLengths : {n : Nat} -> List (Vect n a) -> Nat
|
||||
|
||||
or fixing ``vlen`` to not use the index:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
vlen : Vect n a -> Nat
|
||||
vlen [] = Z
|
||||
vlen (x :: xs) = S (vlen xs)
|
||||
|
||||
Which solution is appropriate depends on the usecase.
|
||||
|
||||
Example 2
|
||||
~~~~~~~~~
|
||||
|
||||
Consider the following program manipulating value-indexed binary
|
||||
numbers.
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
data Bin : Nat -> Type where
|
||||
N : Bin Z
|
||||
O : Bin n -> Bin (0 + n + n)
|
||||
I : Bin n -> Bin (1 + n + n)
|
||||
|
||||
toN : (b : Bin n) -> Nat
|
||||
toN N = Z
|
||||
toN (O {n} bs) = 0 + n + n
|
||||
toN (I {n} bs) = 1 + n + n
|
||||
|
||||
main : IO ()
|
||||
main = print . toN $ I (I (O (O (I N))))
|
||||
|
||||
In the function ``toN``, we attempted to "cheat" and instead of
|
||||
traversing the whole structure, we just projected the value index ``n``
|
||||
out of constructors ``I`` and ``O``. However, this index is a free
|
||||
implicit, therefore it is considered dotted.
|
||||
|
||||
Inspecting it then produces the following warnings when compiling with
|
||||
``--warnreach``:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
Main.I: inaccessible arguments reachable:
|
||||
n from Main.toN arg# 1
|
||||
Main.O: inaccessible arguments reachable:
|
||||
n from Main.toN arg# 1
|
||||
|
||||
We can see that the argument ``n`` of both ``I`` and ``O`` is used in
|
||||
the function ``toN``, argument 1.
|
||||
|
||||
At this stage of development, warnings only contain argument numbers,
|
||||
not names; this will hopefully be fixed. When numbering arguments, we
|
||||
go from 0, taking free implicits first, left-to-right; then the bound
|
||||
arguments. The function ``toN`` has therefore in fact two arguments:
|
||||
``n`` (argument 0) and ``b`` (argument 1). And indeed, as the warning
|
||||
says, we project the dotted field from ``b``.
|
||||
|
||||
Again, one solution is to fix the function ``toN`` to calculate its
|
||||
result honestly; the other one is to accept that we carry a ``Nat``
|
||||
with every constructor of ``Bin`` and make it a bound implicit:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
O : {n : Nat} -> Bin n -> Bin (0 + n + n)
|
||||
I : {n : Nat} -> bin n -> Bin (1 + n + n)
|
||||
|
||||
References
|
||||
----------
|
||||
|
||||
.. [BMM04] Edwin Brady, Conor McBride, James McKinna: `Inductive
|
||||
families need not store their indices
|
||||
<http://citeseerx.ist.psu.edu/viewdoc/summary;jsessionid=1F796FCF0F2C4C535FC70F62BE2FB821?doi=10.1.1.62.3849>`__
|
277
docs/reference/ffi.rst
Normal file
277
docs/reference/ffi.rst
Normal file
@ -0,0 +1,277 @@
|
||||
New Foreign Function Interface
|
||||
==============================
|
||||
|
||||
.. sectionauthor:: Edwin Brady
|
||||
|
||||
Ever since Idris has had multiple backends compiling to different
|
||||
target languages on potentially different platforms, we have had the
|
||||
problem that the foreign function interface (FFI) was written under
|
||||
the assumption of compiling to C. As a result, it has been hard to
|
||||
write generic code for multiple targets, or even to be sure that if
|
||||
code compiles that it will run on the expected target.
|
||||
|
||||
As of 0.9.17, Idris will have a new foreign function interface (FFI)
|
||||
which is aware of multiple targets. Users who are working with the
|
||||
default code generator can happily continue writing programs as before
|
||||
with no changes, but if you are writing bindings for an external
|
||||
library, writing a back end, or working with a non-C back end, there
|
||||
are some things you will need to be aware of, which this page
|
||||
describes.
|
||||
|
||||
The ``IO'`` monad, and ``main``
|
||||
-------------------------------
|
||||
|
||||
The ``IO`` monad exists as before, but is now specific to the C
|
||||
backend (or, more precisely, any backend whose foreign function calls
|
||||
are compatible with C.) Additionally, there is now an ``IO'`` monad,
|
||||
which is parameterised over a FFI descriptor:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
data IO' : (lang : FFI) -> Type -> Type
|
||||
|
||||
The Prelude defines two FFI descriptors which are imported
|
||||
automatically, for C and JavaScript/Node, and defines ``IO`` to use
|
||||
the C FFI and ``JS_IO`` to use the JavaScript FFI:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
FFI_C : FFI
|
||||
FFI_JS : FFI
|
||||
|
||||
IO : Type -> Type
|
||||
IO a = IO' FFI_C a
|
||||
|
||||
JS_IO : Type -> Type
|
||||
JS_IO a = IO' FFI_JS a
|
||||
|
||||
As before, the entry point to an Idris program is ``main``, but the
|
||||
type of ``main`` can now be any instance of ``IO'``, e.g. the
|
||||
following are both valid:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
main : IO ()
|
||||
main : JS_IO ()
|
||||
|
||||
The FFI descriptor includes details about which types can be
|
||||
marshalled between the foreign language and Idris, and the "target" of
|
||||
a foreign function call (typically just a String representation of the
|
||||
function's name, but potentially something more complicated such as an
|
||||
external library file or even a URL).
|
||||
|
||||
FFI descriptors
|
||||
---------------
|
||||
|
||||
An FFI descriptor is a record containing a predicate which holds when
|
||||
a type can be marshalled, and the type of the target of a foreign
|
||||
call:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
record FFI : Type where
|
||||
MkFFI : (ffi_types : Type -> Type) ->
|
||||
(ffi_fn : Type) -> FFI
|
||||
|
||||
For C, this is:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
-- Supported C integer types
|
||||
data C_IntTypes : Type -> Type where
|
||||
C_IntChar : C_IntTypes Char
|
||||
C_IntNative : C_IntTypes Int
|
||||
... -- more integer types
|
||||
|
||||
-- Supported C foreign types
|
||||
data C_Types : Type -> Type where
|
||||
C_Str : C_Types String
|
||||
C_Float : C_Types Float
|
||||
C_Ptr : C_Types Ptr
|
||||
C_MPtr : C_Types ManagedPtr
|
||||
C_Unit : C_Types ()
|
||||
C_Any : C_Types (Raw a)
|
||||
C_IntT : C_IntTypes i -> C_Types i
|
||||
|
||||
FFI_C : FFI
|
||||
FFI_C = MkFFI C_Types
|
||||
String -- the name of the C function
|
||||
|
||||
Foreign calls
|
||||
-------------
|
||||
|
||||
To call a foreign function, the ``foreign`` function is used. For
|
||||
example:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
do_fopen : String -> String -> IO Ptr
|
||||
do_fopen f m
|
||||
= foreign FFI_C "fileOpen" (String -> String -> IO Ptr) f m
|
||||
|
||||
The ``foreign`` function takes an FFI description, a function name (the
|
||||
type is given by the ``ffi_fn`` field of ``FFI_C`` here), and a function
|
||||
type, which gives the expected types of the remaining arguments. Here,
|
||||
we're calling an external function ``fileOpen`` which takes, in the C, a
|
||||
``char*`` file name, a ``char*`` mode, and returns a file pointer. It is
|
||||
the job of the C back end to convert Idris ``String`` to C ``char*``
|
||||
and vice versa.
|
||||
|
||||
The argument types and return type given here must be present in the
|
||||
``fn_types`` predicate of the ``FFI_C`` description for the foreign
|
||||
call to be valid.
|
||||
|
||||
**Note** The arguments to ``foreign`` *must* be known at compile time,
|
||||
because the foreign calls are generated statically. The ``%inline``
|
||||
directive on a function can be used to give hints to help this, for
|
||||
example a shorthand for calling external JavaScript functions:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
%inline
|
||||
jscall : (fname : String) -> (ty : Type) ->
|
||||
{auto fty : FTy FFI_JS [] ty} -> ty
|
||||
jscall fname ty = foreign FFI_JS fname ty
|
||||
|
||||
FFI implementation
|
||||
~~~~~~~~~~~~~~~~~~
|
||||
|
||||
In order to write bindings to external libraries, the details of how
|
||||
``foreign`` works are unnecessary --- you simply need to know that
|
||||
``foreign`` takes an FFI descriptor, the function name, and its
|
||||
type. It is instructive to look a little deeper, however:
|
||||
|
||||
The type of ``foreign`` is as follows:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
foreign : (ffi : FFI)
|
||||
-> (fname : ffi_fn f)
|
||||
-> (ty : Type)
|
||||
-> {auto fty : FTy ffi [] ty}
|
||||
-> ty
|
||||
|
||||
The important argument here is the implicit ``fty``, which contains a
|
||||
proof (``FTy``) that the given type is valid according to the FFI
|
||||
description ``ffi``:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
data FTy : FFI -> List Type -> Type -> Type where
|
||||
FRet : ffi_types f t -> FTy f xs (IO' f t)
|
||||
FFun : ffi_types f s -> FTy f (s :: xs) t -> FTy f xs (s -> t)
|
||||
|
||||
Notice that this uses the ``ffi_types`` field of the FFI descriptor
|
||||
--- these arguments to ``FRet`` and ``FFun`` give explicit proofs that
|
||||
the type is valid in this FFI. For example, the above ``do_fopen``
|
||||
builds the following implicit proof as the ``fty`` argument to
|
||||
``foreign``:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
FFun C_Str (FFun C_Str (FRet C_Ptr))
|
||||
|
||||
Compiling foreign calls
|
||||
-----------------------
|
||||
|
||||
(This section assumes some knowledge of the Idris internals.)
|
||||
|
||||
When writing a back end, we now need to know how to compile
|
||||
``foreign``. We'll skip the details here of how a ``foreign`` call
|
||||
reaches the intermediate representation (the IR), though you can look
|
||||
in ``IO.idr`` in the ``prelude`` package to see a bit more detail ---
|
||||
a ``foreign`` call is implemented by the primitive function
|
||||
``mkForeignPrim``. The important part of the IR as defined in
|
||||
``Lang.hs`` is the following constructor:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
data LExp = ...
|
||||
| LForeign FDesc -- Function descriptor
|
||||
FDesc -- Return type descriptor
|
||||
[(FDesc, LExp)]
|
||||
|
||||
So, a ``foreign`` call appears in the IR as the ``LForeign``
|
||||
constructor, which takes a function descriptor (of a type given by the
|
||||
``ffi_fn`` field in the FFI descriptor), a return type descriptor
|
||||
(given by an application of ``FTy``), and a list of arguments with
|
||||
type descriptors (also given by an application of ``FTy``).
|
||||
|
||||
An ``FDesc`` describes an application of a name to some arguments, and
|
||||
is really just a simplified subset of an ``LExp``:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
data FDesc = FCon Name
|
||||
| FStr String
|
||||
| FUnknown
|
||||
| FApp Name [FDesc]
|
||||
|
||||
There are corresponding structures in the lower level IRs, such as the
|
||||
defunctionalised, simplified and bytecode forms.
|
||||
|
||||
Our ``do_fopen`` example above arrives in the ``LExp`` form as:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
LForeign (FStr "fileOpen") (FCon (sUN "C_Ptr"))
|
||||
[(FCon (sUN "C_Str"), f), (FCon (sUN "C_Str"), m)]
|
||||
|
||||
(Assuming that ``f`` and ``m`` stand for the ``LExp`` representations
|
||||
of the arguments.) This information should be enough for any back end
|
||||
to marshal the arguments and return value appropriately.
|
||||
|
||||
.. note::
|
||||
|
||||
When processing ``FDesc``, be aware that there may be implicit
|
||||
arguments, which have not been erased. For example, ``C_IntT`` has
|
||||
an implicit argument ``i``, so will appear in an ``FDesc`` as
|
||||
something of the form ``FApp (sUN "C_IntT") [i, t]`` where ``i`` is
|
||||
the implicit argument (which can be ignored) and ``t`` is the
|
||||
descriptor of the integer type. See ``CodegenC.hs``, specifically
|
||||
the function ``toFType``, to see how this works in practice.
|
||||
|
||||
JavaScript FFI descriptor
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
The JavaScript FFI descriptor is a little more complex, because the
|
||||
JavaScript FFI supports marshalling functions. It is defined as
|
||||
follows:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
mutual
|
||||
data JsFn t = MkJsFn t
|
||||
|
||||
data JS_IntTypes : Type -> Type where
|
||||
JS_IntChar : JS_IntTypes Char
|
||||
JS_IntNative : JS_IntTypes Int
|
||||
|
||||
data JS_FnTypes : Type -> Type where
|
||||
JS_Fn : JS_Types s -> JS_FnTypes t -> JS_FnTypes (s -> t)
|
||||
JS_FnIO : JS_Types t -> JS_FnTypes (IO' l t)
|
||||
JS_FnBase : JS_Types t -> JS_FnTypes t
|
||||
|
||||
data JS_Types : Type -> Type where
|
||||
JS_Str : JS_Types String
|
||||
JS_Float : JS_Types Float
|
||||
JS_Ptr : JS_Types Ptr
|
||||
JS_Unit : JS_Types ()
|
||||
JS_FnT : JS_FnTypes a -> JS_Types (JsFn a)
|
||||
JS_IntT : JS_IntTypes i -> JS_Types i
|
||||
|
||||
The reason for wrapping function types in a ``JsFn`` is to help the
|
||||
proof search when building ``FTy``. We hope to improve proof search
|
||||
eventually, but for the moment it works much more reliably if the
|
||||
indices are disjoint! An example of using this appears in `IdrisScript
|
||||
<https://github.com/idris-hackers/IdrisScript>`__ when setting
|
||||
timeouts:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
setTimeout : (() -> JS_IO ()) -> (millis : Int) -> JS_IO Timeout
|
||||
setTimeout f millis = do
|
||||
timeout <- jscall "setTimeout(%0, %1)"
|
||||
(JsFn (() -> JS_IO ()) -> Int -> JS_IO Ptr)
|
||||
(MkJsFn f) millis
|
||||
return $ MkTimeout timeout
|
26
docs/reference/index.rst
Normal file
26
docs/reference/index.rst
Normal file
@ -0,0 +1,26 @@
|
||||
.. _reference-index:
|
||||
|
||||
###################
|
||||
Language Reference
|
||||
###################
|
||||
|
||||
This is the reference guide for the Idris Language.
|
||||
It documents the language specification and internals.
|
||||
This will tell you how Idris works, for using it you should read the Idris Tutorial.
|
||||
|
||||
.. note::
|
||||
The documentation for Idris has been published under the Creative
|
||||
Commons CC0 License. As such to the extent possible under law, *The
|
||||
Idris Community* has waived all copyright and related or neighboring
|
||||
rights to Documentation for Idris.
|
||||
|
||||
More information concerning the CC0 can be found online at: http://creativecommons.org/publicdomain/zero/1.0/
|
||||
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
|
||||
documenting
|
||||
uniqueness-types
|
||||
ffi
|
||||
erasure
|
251
docs/reference/uniqueness-types.rst
Normal file
251
docs/reference/uniqueness-types.rst
Normal file
@ -0,0 +1,251 @@
|
||||
Uniqueness Types
|
||||
================
|
||||
|
||||
Uniqueness Types are an experimental feature available from Idris
|
||||
0.9.15. A value with a unique type is guaranteed to have *at most one*
|
||||
reference to it at run-time, which means that it can safely be updated
|
||||
in-place, reducing the need for memory allocation and garbage
|
||||
collection. The motivation is that we would like to be able to write
|
||||
reactive systems, programs which run in limited memory environments,
|
||||
device drivers, and any other system with hard real-time requirements,
|
||||
ideally while giving up as little high level conveniences as possible.
|
||||
|
||||
They are inspired by linear types, `Uniqueness Types
|
||||
<https://en.wikipedia.org/wiki/Uniqueness_type>`__ in the `Clean
|
||||
<http://wiki.clean.cs.ru.nl/Clean>`__ programming language, and
|
||||
ownership types and borrowed pointers in the `Rust
|
||||
<http://www.rust-lang.org/>`__ programming language.
|
||||
|
||||
Some things we hope to be able to do eventually with uniqueness types
|
||||
include:
|
||||
|
||||
- Safe, pure, in-place update of arrays, lists, etc
|
||||
- Provide guarantees of correct resource usage, state transitions, etc
|
||||
- Provide guarantees that critical program fragments will *never*
|
||||
allocate
|
||||
|
||||
Using Uniqueness
|
||||
~~~~~~~~~~~~~~~~
|
||||
|
||||
If ``x : T`` and ``T : UniqueType``, then there is at most one reference
|
||||
to ``x`` at any time during run-time execution. For example, we can
|
||||
declare the type of unique lists as follows:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
data UList : Type -> UniqueType
|
||||
Nil : UList a
|
||||
(::) : a -> UList a -> UList a
|
||||
|
||||
If we have a value ``xs : UList a``, then there is at most one
|
||||
reference to ``xs`` at run-time. The type checker preserves this
|
||||
guarantee by ensuring that there is at most one reference to any value
|
||||
of a unique type in a pattern clause. For example, the following
|
||||
function definition would be valid:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
umap : (a -> b) -> UList a -> UList b
|
||||
umap f [] = []
|
||||
umap f (x :: xs) = f x :: umap f xs
|
||||
|
||||
In the second clause, ``xs`` is a value of a unique type, and only
|
||||
appears once on the right hand side, so this clause is valid. Not only
|
||||
that, since we know there can be no other reference to the ``UList a``
|
||||
argument, we can reuse its space for building the result! The compiler
|
||||
is aware of this, and compiles this definition to an in-place update
|
||||
of the list.
|
||||
|
||||
The following function definition would not be valid (even assuming an
|
||||
implementation of ``++``), however, since ``xs`` appears twice:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
dupList : UList a -> UList a
|
||||
dupList xs = xs ++ xs
|
||||
|
||||
This would result in a shared pointer to ``xs``, so the typechecker
|
||||
reports:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
unique.idr:12:5:Unique name xs is used more than once
|
||||
|
||||
If we explicitly copy, however, the typechecker is happy:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
dup : UList a -> UList a
|
||||
dup [] = []
|
||||
dup (x :: xs) = x :: x :: dup xs
|
||||
|
||||
Note that it's fine to use ``x`` twice, because ``a`` is a ``Type``,
|
||||
rather than a ``UniqueType``.
|
||||
|
||||
There are some other restrictions on where a ``UniqueType`` can
|
||||
appear, so that the uniqueness property is preserved. In particular,
|
||||
the type of the function type, ``(x : a) -> b`` depends on the type of
|
||||
``a`` or ``b`` - if either is a ``UniqueType``, then the function type
|
||||
is also a ``UniqueType``. Then, in a data declaration, if the type
|
||||
constructor builds a ``Type``, then no constructor can have a
|
||||
``UniqueType``. For example, the following definition is invalid,
|
||||
since it would embed a unique value in a possible non-unique value:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
data BadList : UniqueType -> Type
|
||||
Nil : {a : UniqueType} -> BadList a
|
||||
(::) : {a : UniqueType} -> a -> BadList a -> BadList a
|
||||
|
||||
Finally, types may be polymorphic in their uniqueness, to a limited
|
||||
extent. Since ``Type`` and ``UniqueType`` are different types, we are
|
||||
limited in how much we can use polymorphic functions on unique types.
|
||||
For example, if we have function composition defined as follows:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
(.) : {a, b, c : Type} -> (b -> c) -> (a -> b) -> a -> c
|
||||
(.) f g x = f (g x)
|
||||
|
||||
And we have some functions over unique types:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
foo : UList a -> UList b
|
||||
bar : UList b -> UList c
|
||||
|
||||
Then we cannot compose ``foo`` and ``bar`` as ``bar . foo``, because
|
||||
``UList`` does not compute a ``Type``! Instead, we can define
|
||||
composition as follows:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
(.) : {a, b, c : Type*} -> (b -> c) -> (a -> b) -> a -> c
|
||||
(.) f g x = f (g x)
|
||||
|
||||
The ``Type*`` type stands for either unique or non-unique types. Since
|
||||
such a function may be passed a ``UniqueType``, any value of type
|
||||
``Type*`` must also satisfy the requirement that it appears at most
|
||||
once on the right hand side.
|
||||
|
||||
Borrowed Types
|
||||
~~~~~~~~~~~~~~
|
||||
|
||||
It quickly becomes obvious when working with uniqueness types that
|
||||
having only one reference at a time can be painful. For example, what
|
||||
if we want to display a list before updating it?
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
showU : Show a => UList a -> String
|
||||
showU xs = "[" ++ showU' xs ++ "]" where
|
||||
showU' : UList a -> String
|
||||
showU' [] = ""
|
||||
showU' [x] = show x
|
||||
showU' (x :: xs) = show x ++ ", " ++ showU' xs
|
||||
|
||||
This is a valid definition of ``showU``, but unfortunately it consumes
|
||||
the list! So the following function would be invalid:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
printAndUpdate : UList Int -> IO ()
|
||||
printAndUpdate xs = do putStrLn (showU xs)
|
||||
let xs' = umap (*2) xs -- xs no longer available!
|
||||
putStrLn (showU xs')
|
||||
|
||||
Still, one would hope to be able to display a unique list without
|
||||
problem, since it merely *inspects* the list; there are no updates. We
|
||||
can achieve this, using the notion of *borrowing*. A Borrowed type is
|
||||
a Unique type which can be inspected at the top level (by pattern
|
||||
matching, or by *lending* to another function) but no further. This
|
||||
ensures that the internals (i.e. the arguments to top level patterns)
|
||||
will not be passed to any function which will update them.
|
||||
|
||||
``Borrowed`` converts a ``UniqueType`` to a ``BorrowedType``. It is
|
||||
defined as follows (along with some additional rules in the
|
||||
typechecker):
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
data Borrowed : UniqueType -> BorrowedType where
|
||||
Read : {a : UniqueType} -> a -> Borrowed a
|
||||
|
||||
implicit
|
||||
lend : {a : UniqueType} -> a -> Borrowed a
|
||||
lend x = Read x
|
||||
|
||||
A value can be "lent" to another function using ``lend``. Arguments to
|
||||
``lend`` are not counted by the type checker as a reference to a unique
|
||||
value, therefore a value can be lent as many times as desired. Using
|
||||
this, we can write ``showU`` as follows:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
showU : Show a => Borrowed (UList a) -> String
|
||||
showU xs = "[" ++ showU' xs ++ "]" where
|
||||
showU' : Borrowed (UList a) -> String
|
||||
showU' [] = ""
|
||||
showU' [x] = show x
|
||||
showU' (Read (x :: xs)) = show x ++ ", " ++ showU' (lend xs)
|
||||
|
||||
Unlike a unique value, a borrowed value may be referred to as many
|
||||
times as desired. However, there is a restriction on how a borrowed
|
||||
value can be used. After all, much like a library book or your
|
||||
neighbour's lawnmower, if a function borrows a value it is expected to
|
||||
return it in exactly the condition in which it was received!
|
||||
|
||||
The restriction is that when a ``Borrowed`` type is matched, any
|
||||
pattern variables under the ``Read`` which have a unique type may not
|
||||
be referred to at all on the right hand side (unless they are
|
||||
themselves ``lent`` to another function).
|
||||
|
||||
Uniqueness information is stored in the type, and in particular in
|
||||
function types. Once we're in a unique context, any new function which
|
||||
is constructed will be required to have unique type, which prevents
|
||||
the following sort of bad program being implemented:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
foo : UList Int -> IO ()
|
||||
foo xs = do let f = \x : Int => showU xs
|
||||
putStrLn $ free xs
|
||||
putStrLn $ f 42
|
||||
return ()
|
||||
|
||||
Since ``lend`` is implicit, in practice for functions to lend and borrow
|
||||
values merely requires the argument to be marked as ``Borrowed``. We can
|
||||
therefore write ``showU`` as follows:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
showU : Show a => Borrowed (UList a) -> String
|
||||
showU xs = "[" ++ showU' xs ++ "]" where
|
||||
showU' : Borrowed (UList a) -> String
|
||||
showU' [] = ""
|
||||
showU' [x] = show x
|
||||
showU' (x :: xs) = show x ++ ", " ++ showU' xs
|
||||
|
||||
Problems/Disadvantages/Still to do...
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
This is a work in progress, there is lots to do. The most obvious
|
||||
problem is the loss of abstraction. On the one hand, we have more
|
||||
precise control over memory usage with ``UniqueType`` and
|
||||
``BorrowedType``, but they are not in general compatible with
|
||||
functions polymorphic over ``Type``. In the short term, we can start
|
||||
to write reactive and low memory systems with this, but longer term it
|
||||
would be nice to support more abstraction.
|
||||
|
||||
We also haven't checked any of the metatheory, so this could all be
|
||||
fatally flawed! The implementation is based to a large extent on
|
||||
`Uniqueness Typing Simplified
|
||||
<http://lambda-the-ultimate.org/node/2708>`__, by de Vries et al, so
|
||||
there is reason to believe things should be fine, but we still have to
|
||||
do the work.
|
||||
|
||||
Much as there are with linear types, there are some annoyances when
|
||||
trying to prove properties of functions with unique types (for
|
||||
example, what counts as a use of a value). Since we require *at most*
|
||||
one use of a value, rather than *exactly* one, this seems to be less
|
||||
of an issue in practice, but still needs thought.
|
517
docs/tutorial/classes.rst
Normal file
517
docs/tutorial/classes.rst
Normal file
@ -0,0 +1,517 @@
|
||||
.. _sect-classes:
|
||||
|
||||
============
|
||||
Type Classes
|
||||
============
|
||||
|
||||
We often want to define functions which work across several different
|
||||
data types. For example, we would like arithmetic operators to work on
|
||||
``Int``, ``Integer`` and ``Float`` at the very least. We would like
|
||||
``==`` to work on the majority of data types. We would like to be able
|
||||
to display different types in a uniform way.
|
||||
|
||||
To achieve this, we use a feature which has proved to be effective in
|
||||
Haskell, namely *type classes*. To define a type class, we provide a
|
||||
collection of overloaded operations which describe the interface for
|
||||
*instances* of that class. A simple example is the ``Show`` type
|
||||
class, which is defined in the prelude and provides an interface for
|
||||
converting values to ``String``:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
class Show a where
|
||||
show : a -> String
|
||||
|
||||
This generates a function of the following type (which we call a
|
||||
*method* of the ``Show`` class):
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
show : Show a => a -> String
|
||||
|
||||
We can read this as: “under the constraint that ``a`` is an instance
|
||||
of ``Show``, take an input ``a`` and return a ``String``.” An instance
|
||||
of a class is defined with an ``instance`` declaration, which provides
|
||||
implementations of the function for a specific type. For example, the
|
||||
``Show`` instance for ``Nat`` could be defined as:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
instance Show Nat where
|
||||
show Z = "Z"
|
||||
show (S k) = "s" ++ show k
|
||||
|
||||
::
|
||||
|
||||
Idris> show (S (S (S Z)))
|
||||
"sssZ" : String
|
||||
|
||||
Only one instance of a class can be given for a type — instances may
|
||||
not overlap. Instance declarations can themselves have constraints.
|
||||
To help with resolution, the arguments of an instance must be
|
||||
constructors (either data or type constructors), variables or
|
||||
constants (i.e. you cannot give an instance for a function). For
|
||||
example, to define a ``Show`` instance for vectors, we need to know
|
||||
that there is a ``Show`` instance for the element type, because we are
|
||||
going to use it to convert each element to a ``String``:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
instance Show a => Show (Vect n a) where
|
||||
show xs = "[" ++ show' xs ++ "]" where
|
||||
show' : Vect n a -> String
|
||||
show' Nil = ""
|
||||
show' (x :: Nil) = show x
|
||||
show' (x :: xs) = show x ++ ", " ++ show' xs
|
||||
|
||||
Default Definitions
|
||||
-------------------
|
||||
|
||||
The library defines an ``Eq`` class which provides an interface for
|
||||
comparing values for equality or inequality, with instances for all of
|
||||
the built-in types:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
class Eq a where
|
||||
(==) : a -> a -> Bool
|
||||
(/=) : a -> a -> Bool
|
||||
|
||||
To declare an instance of a type, we have to give definitions of all
|
||||
of the methods. For example, for an instance of ``Eq`` for ``Nat``:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
instance Eq Nat where
|
||||
Z == Z = True
|
||||
(S x) == (S y) = x == y
|
||||
Z == (S y) = False
|
||||
(S x) == Z = False
|
||||
|
||||
x /= y = not (x == y)
|
||||
|
||||
It is hard to imagine many cases where the ``/=`` method will be
|
||||
anything other than the negation of the result of applying the ``==``
|
||||
method. It is therefore convenient to give a default definition for
|
||||
each method in the class declaration, in terms of the other method:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
class Eq a where
|
||||
(==) : a -> a -> Bool
|
||||
(/=) : a -> a -> Bool
|
||||
|
||||
x /= y = not (x == y)
|
||||
x == y = not (x /= y)
|
||||
|
||||
A minimal complete definition of an ``Eq`` instance requires either
|
||||
``==`` or ``/=`` to be defined, but does not require both. If a method
|
||||
definition is missing, and there is a default definition for it, then
|
||||
the default is used instead.
|
||||
|
||||
Extending Classes
|
||||
-----------------
|
||||
|
||||
Classes can also be extended. A logical next step from an equality
|
||||
relation ``Eq`` is to define an ordering relation ``Ord``. We can
|
||||
define an ``Ord`` class which inherits methods from ``Eq`` as well as
|
||||
defining some of its own:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
data Ordering = LT | EQ | GT
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
class Eq a => Ord a where
|
||||
compare : a -> a -> Ordering
|
||||
|
||||
(<) : a -> a -> Bool
|
||||
(>) : a -> a -> Bool
|
||||
(<=) : a -> a -> Bool
|
||||
(>=) : a -> a -> Bool
|
||||
max : a -> a -> a
|
||||
min : a -> a -> a
|
||||
|
||||
The ``Ord`` class allows us to compare two values and determine their
|
||||
ordering. Only the ``compare`` method is required; every other method
|
||||
has a default definition. Using this we can write functions such as
|
||||
``sort``, a function which sorts a list into increasing order,
|
||||
provided that the element type of the list is in the ``Ord`` class. We
|
||||
give the constraints on the type variables left of the fat arrow
|
||||
``=>``, and the function type to the right of the fat arrow:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
sort : Ord a => List a -> List a
|
||||
|
||||
Functions, classes and instances can have multiple
|
||||
constraints. Multiple constaints are written in brackets in a comma
|
||||
separated list, for example:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
sortAndShow : (Ord a, Show a) => List a -> String
|
||||
sortAndShow xs = show (sort xs)
|
||||
|
||||
Functors and Applicatives
|
||||
-------------------------
|
||||
|
||||
So far, we have seen single parameter type classes, where the parameter
|
||||
is of type ``Type``. In general, there can be any number (greater than
|
||||
0) of parameters, and the parameters can have *any* type. If the type
|
||||
of the parameter is not ``Type``, we need to give an explicit type
|
||||
declaration. For example, the ``Functor`` class is defined in the
|
||||
library:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
class Functor (f : Type -> Type) where
|
||||
map : (m : a -> b) -> f a -> f b
|
||||
|
||||
A functor allows a function to be applied across a structure, for
|
||||
example to apply a function to every element in a ``List``:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
instance Functor List where
|
||||
map f [] = []
|
||||
map f (x::xs) = f x :: map f xs
|
||||
|
||||
::
|
||||
|
||||
Idris> map (*2) [1..10]
|
||||
[2, 4, 6, 8, 10, 12, 14, 16, 18, 20] : List Integer
|
||||
|
||||
Having defined ``Functor``, we can define ``Applicative`` which
|
||||
abstracts the notion of function application:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
infixl 2 <*>
|
||||
|
||||
class Functor f => Applicative (f : Type -> Type) where
|
||||
pure : a -> f a
|
||||
(<*>) : f (a -> b) -> f a -> f b
|
||||
|
||||
Monads and ``do``-notation
|
||||
--------------------------
|
||||
|
||||
The ``Monad`` class allows us to encapsulate binding and computation,
|
||||
and is the basis of ``do``-notation introduced in Section
|
||||
:ref:`sect-do`. It extends ``Applicative`` as defined above, and is
|
||||
defined as follows:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
class Applicative m => Monad (m : Type -> Type) where
|
||||
(>>=) : m a -> (a -> m b) -> m b
|
||||
|
||||
Inside a ``do`` block, the following syntactic transformations are
|
||||
applied:
|
||||
|
||||
- ``x <- v; e`` becomes ``v >>= (\backslashx => e)``
|
||||
|
||||
- ``v; e`` becomes ``v >>= (\backslash_ => e)``
|
||||
|
||||
- ``let x = v; e`` becomes ``let x = v in e``
|
||||
|
||||
``IO`` is an instance of ``Monad``, defined using primitive functions.
|
||||
We can also define an instance for ``Maybe``, as follows:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
instance Monad Maybe where
|
||||
Nothing >>= k = Nothing
|
||||
(Just x) >>= k = k x
|
||||
|
||||
Using this we can, for example, define a function which adds two
|
||||
``Maybe Int``, using the monad to encapsulate the error handling:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
m_add : Maybe Int -> Maybe Int -> Maybe Int
|
||||
m_add x y = do x' <- x -- Extract value from x
|
||||
y' <- y -- Extract value from y
|
||||
return (x' + y') -- Add them
|
||||
|
||||
This function will extract the values from ``x`` and ``y``, if they
|
||||
are available, or return ``Nothing`` if they are not. Managing the
|
||||
``Nothing`` cases is achieved by the ``>>=`` operator, hidden by the
|
||||
``do`` notation.
|
||||
|
||||
::
|
||||
|
||||
*classes> m_add (Just 20) (Just 22)
|
||||
Just 42 : Maybe Int
|
||||
*classes> m_add (Just 20) Nothing
|
||||
Nothing : Maybe Int
|
||||
|
||||
``!``-notation
|
||||
~~~~~~~~~~~~~~
|
||||
|
||||
In many cases, using ``do``-notation can make programs unnecessarily
|
||||
verbose, particularly in cases such as ``m_add`` above where the value
|
||||
bound is used once, immediately. In these cases, we can use a
|
||||
shorthand version, as follows:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
m_add : Maybe Int -> Maybe Int -> Maybe Int
|
||||
m_add x y = return (!x + !y)
|
||||
|
||||
The notation ``!expr`` means that the expression ``expr`` should be
|
||||
evaluated and then implicitly bound. Conceptually, we can think of
|
||||
``!`` as being a prefix function with the following type:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
(!) : m a -> a
|
||||
|
||||
Note, however, that it is not really a function, merely syntax! In
|
||||
practice, a subexpression ``!expr`` will lift ``expr`` as high as
|
||||
possible within its current scope, bind it to a fresh name ``x``, and
|
||||
replace ``!expr`` with ``x``. Expressions are lifted depth first, left
|
||||
to right. In practice, ``!``-notation allows us to program in a more
|
||||
direct style, while still giving a notational clue as to which
|
||||
expressions are monadic.
|
||||
|
||||
For example, the expression:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
let y = 42 in f !(g !(print y) !x)
|
||||
|
||||
is lifted to:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
let y = 42 in do y' <- print y
|
||||
x' <- x
|
||||
g' <- g y' x'
|
||||
f g'
|
||||
|
||||
Monad comprehensions
|
||||
~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
The list comprehension notation we saw in Section
|
||||
:ref:`sect-more-expr` is more general, and applies to anything which
|
||||
is an instance of both ``Monad`` and ``Alternative``:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
class Applicative f => Alternative (f : Type -> Type) where
|
||||
empty : f a
|
||||
(<|>) : f a -> f a -> f a
|
||||
|
||||
In general, a comprehension takes the form ``[ exp | qual1, qual2, …,
|
||||
qualn ]`` where ``quali`` can be one of:
|
||||
|
||||
- A generator ``x <- e``
|
||||
|
||||
- A *guard*, which is an expression of type ``Bool``
|
||||
|
||||
- A let binding ``let x = e``
|
||||
|
||||
To translate a comprehension ``[exp | qual1, qual2, …, qualn]``, first
|
||||
any qualifier ``qual`` which is a *guard* is translated to ``guard
|
||||
qual``, using the following function:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
guard : Alternative f => Bool -> f ()
|
||||
|
||||
Then the comprehension is converted to ``do`` notation:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
do { qual1; qual2; ...; qualn; return exp; }
|
||||
|
||||
Using monad comprehensions, an alternative definition for ``m_add``
|
||||
would be:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
m_add : Maybe Int -> Maybe Int -> Maybe Int
|
||||
m_add x y = [ x' + y' | x' <- x, y' <- y ]
|
||||
|
||||
Idiom brackets
|
||||
--------------
|
||||
|
||||
While ``do`` notation gives an alternative meaning to sequencing,
|
||||
idioms give an alternative meaning to *application*. The notation and
|
||||
larger example in this section is inspired by Conor McBride and Ross
|
||||
Paterson’s paper “Applicative Programming with Effects” [1]_.
|
||||
|
||||
First, let us revisit ``m_add`` above. All it is really doing is
|
||||
applying an operator to two values extracted from ``Maybe Int``. We
|
||||
could abstract out the application:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
m_app : Maybe (a -> b) -> Maybe a -> Maybe b
|
||||
m_app (Just f) (Just a) = Just (f a)
|
||||
m_app _ _ = Nothing
|
||||
|
||||
Using this, we can write an alternative ``m_add`` which uses this
|
||||
alternative notion of function application, with explicit calls to
|
||||
``m_app``:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
m_add' : Maybe Int -> Maybe Int -> Maybe Int
|
||||
m_add' x y = m_app (m_app (Just (+)) x) y
|
||||
|
||||
Rather than having to insert ``m_app`` everywhere there is an
|
||||
application, we can use to do the job for us. To do this, we can make
|
||||
``Maybe`` an instance of ``Applicative`` as follows, where ``<>`` is
|
||||
defined in the same way as ``m_app`` above (this is defined in the
|
||||
Idris library):
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
instance Applicative Maybe where
|
||||
pure = Just
|
||||
|
||||
(Just f) <*> (Just a) = Just (f a)
|
||||
_ <*> _ = Nothing
|
||||
|
||||
Using we can use this instance as follows, where a function
|
||||
application ``[| f a1 …an |]`` is translated into ``pure f <> a1 <>
|
||||
…<> an``:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
m_add' : Maybe Int -> Maybe Int -> Maybe Int
|
||||
m_add' x y = [| x + y |]
|
||||
|
||||
An error-handling interpreter
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Idiom notation is commonly useful when defining evaluators. McBride
|
||||
and Paterson describe such an evaluator [1]_, for a language similar
|
||||
to the following:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
data Expr = Var String -- variables
|
||||
| Val Int -- values
|
||||
| Add Expr Expr -- addition
|
||||
|
||||
Evaluation will take place relative to a context mapping variables
|
||||
(represented as ``String``s) to integer values, and can possibly fail.
|
||||
We define a data type ``Eval`` to wrap an evaluator:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
data Eval : Type -> Type where
|
||||
MkEval : (List (String, Int) -> Maybe a) -> Eval a
|
||||
|
||||
Wrapping the evaluator in a data type means we will be able to make it
|
||||
an instance of a type class later. We begin by defining a function to
|
||||
retrieve values from the context during evaluation:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
fetch : String -> Eval Int
|
||||
fetch x = MkEval (\e => fetchVal e) where
|
||||
fetchVal : List (String, Int) -> Maybe Int
|
||||
fetchVal [] = Nothing
|
||||
fetchVal ((v, val) :: xs) = if (x == v)
|
||||
then (Just val)
|
||||
else (fetchVal xs)
|
||||
|
||||
When defining an evaluator for the language, we will be applying
|
||||
functions in the context of an ``Eval``, so it is natural to make
|
||||
``Eval`` an instance of ``Applicative``. Before ``Eval`` can be an
|
||||
instance of ``Applicative`` it is necessary to make ``Eval`` an
|
||||
instance of ``Functor``:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
instance Functor Eval where
|
||||
map f (MkEval g) = MkEval (\e => map f (g e))
|
||||
|
||||
instance Applicative Eval where
|
||||
pure x = MkEval (\e => Just x)
|
||||
|
||||
(<*>) (MkEval f) (MkEval g) = MkEval (\x => app (f x) (g x)) where
|
||||
app : Maybe (a -> b) -> Maybe a -> Maybe b
|
||||
app (Just fx) (Just gx) = Just (fx gx)
|
||||
app _ _ = Nothing
|
||||
|
||||
Evaluating an expression can now make use of the idiomatic application
|
||||
to handle errors:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
eval : Expr -> Eval Int
|
||||
eval (Var x) = fetch x
|
||||
eval (Val x) = [| x |]
|
||||
eval (Add x y) = [| eval x + eval y |]
|
||||
|
||||
runEval : List (String, Int) -> Expr -> Maybe Int
|
||||
runEval env e = case eval e of
|
||||
MkEval envFn => envFn env
|
||||
|
||||
Named Instances
|
||||
---------------
|
||||
|
||||
It can be desirable to have multiple instances of a type class, for
|
||||
example to provide alternative methods for sorting or printing values.
|
||||
To achieve this, instances can be *named* as follows:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
instance [myord] Ord Nat where
|
||||
compare Z (S n) = GT
|
||||
compare (S n) Z = LT
|
||||
compare Z Z = EQ
|
||||
compare (S x) (S y) = compare @{myord} x y
|
||||
|
||||
This declares an instance as normal, but with an explicit name,
|
||||
``myord``. The syntax ``compare @{myord}`` gives an explicit instance to
|
||||
``compare``, otherwise it would use the default instance for ``Nat``. We
|
||||
can use this, for example, to sort a list of ``Nat`` in reverse.
|
||||
Given the following list:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
testList : List Nat
|
||||
testList = [3,4,1]
|
||||
|
||||
We can sort it using the default ``Ord`` instance, then the named
|
||||
instance ``myord`` as follows, at the Idris prompt:
|
||||
|
||||
::
|
||||
|
||||
*named_instance> show (sort testList)
|
||||
"[sO, sssO, ssssO]" : String
|
||||
*named_instance> show (sort @{myord} testList)
|
||||
"[ssssO, sssO, sO]" : String
|
||||
|
||||
|
||||
Determining Parameters
|
||||
----------------------
|
||||
|
||||
When a class has more than one parameter, it can help resolution if
|
||||
the parameters used to resolve the type class are restricted. For
|
||||
example:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
class Monad m => MonadState s (m : Type -> Type) | m where
|
||||
get : m s
|
||||
put : s -> m ()
|
||||
|
||||
In this class, only ``m`` needs to be known to resolve this class, and
|
||||
``s`` can then be determined from the instance. This is declared with
|
||||
the ``| m`` after the class declaration. We call ``m`` a *determining
|
||||
parameter* of the ``MonadState`` class, because it is the parameter
|
||||
used to resolve an instance.
|
||||
|
||||
|
||||
.. [1] Conor Mcbride and Ross Paterson. 2008. Applicative programming
|
||||
with effects. J. Funct. Program. 18, 1 (January 2008),
|
||||
1-13. DOI=10.1017/S0956796807006326
|
||||
http://dx.doi.org/10.1017/S0956796807006326
|
54
docs/tutorial/conclusions.rst
Normal file
54
docs/tutorial/conclusions.rst
Normal file
@ -0,0 +1,54 @@
|
||||
.. _sect-concs:
|
||||
|
||||
===============
|
||||
Further Reading
|
||||
===============
|
||||
|
||||
Further information about Idris programming, and programming with
|
||||
dependent types in general, can be obtained from various sources:
|
||||
|
||||
- The Idris web site (http://idris-lang.org/) and by asking
|
||||
questions on the mailing list.
|
||||
|
||||
- The IRC channel ``#idris``, on
|
||||
`chat.freenode.net <http://chat.freenode.net>`__.
|
||||
|
||||
- The wiki (https://github.com/idris-lang/Idris-dev/wiki/) has further
|
||||
user provided information, in particular:
|
||||
|
||||
- https://github.com/idris-lang/Idris-dev/wiki/Manual
|
||||
|
||||
- https://github.com/idris-lang/Idris-dev/wiki/Language-Features
|
||||
|
||||
- Examining the prelude and exploring the ``samples`` in the
|
||||
distribution. The Idris source can be found online at:
|
||||
https://github.com/idris-lang/Idris-dev.
|
||||
|
||||
- Existing projects on the ``Idris Hackers`` web space:
|
||||
http://idris-hackers.github.io.
|
||||
|
||||
- Various papers (e.g. [1]_, [2]_, and [3]_). Although these mostly
|
||||
describe older versions of Idris.
|
||||
|
||||
.. [1] Edwin Brady and Kevin Hammond. 2012. Resource-Safe systems
|
||||
programming with embedded domain specific languages. In
|
||||
Proceedings of the 14th international conference on Practical
|
||||
Aspects of Declarative Languages (PADL'12), Claudio Russo and
|
||||
Neng-Fa Zhou (Eds.). Springer-Verlag, Berlin, Heidelberg,
|
||||
242-257. DOI=10.1007/978-3-642-27694-1_18
|
||||
http://dx.doi.org/10.1007/978-3-642-27694-1_18
|
||||
|
||||
.. [2] Edwin C. Brady. 2011. IDRIS ---: systems programming meets full
|
||||
dependent types. In Proceedings of the 5th ACM workshop on
|
||||
Programming languages meets program verification (PLPV
|
||||
'11). ACM, New York, NY, USA,
|
||||
43-54. DOI=10.1145/1929529.1929536
|
||||
http://doi.acm.org/10.1145/1929529.1929536
|
||||
|
||||
.. [3] Edwin C. Brady and Kevin Hammond. 2010. Scrapping your
|
||||
inefficient engine: using partial evaluation to improve
|
||||
domain-specific language implementation. In Proceedings of the
|
||||
15th ACM SIGPLAN international conference on Functional
|
||||
programming (ICFP '10). ACM, New York, NY, USA,
|
||||
297-308. DOI=10.1145/1863543.1863587
|
||||
http://doi.acm.org/10.1145/1863543.1863587
|
34
docs/tutorial/index.rst
Normal file
34
docs/tutorial/index.rst
Normal file
@ -0,0 +1,34 @@
|
||||
.. _tutorial-index:
|
||||
|
||||
####################
|
||||
The Idris Tutorial
|
||||
####################
|
||||
|
||||
The is the Idris Tutorial. It will teach you about programming in the Idris Language.
|
||||
|
||||
.. note::
|
||||
The documentation for Idris has been published under the Creative
|
||||
Commons CC0 License. As such to the extent possible under law, *The
|
||||
Idris Community* has waived all copyright and related or neighboring
|
||||
rights to Documentation for Idris.
|
||||
|
||||
More information concerning the CC0 can be found online at: http://creativecommons.org/publicdomain/zero/1.0/
|
||||
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
|
||||
introduction
|
||||
starting
|
||||
typesfuns
|
||||
classes
|
||||
modules
|
||||
packages
|
||||
interp
|
||||
views
|
||||
theorems
|
||||
provisional
|
||||
interactive
|
||||
syntax
|
||||
miscellany
|
||||
conclusions
|
258
docs/tutorial/interactive.rst
Normal file
258
docs/tutorial/interactive.rst
Normal file
@ -0,0 +1,258 @@
|
||||
.. _sect-interactive:
|
||||
|
||||
===================
|
||||
Interactive Editing
|
||||
===================
|
||||
|
||||
By now, we have seen several examples of how Idris’ dependent type
|
||||
system can give extra confidence in a function’s correctness by giving
|
||||
a more precise description of its intended behaviour in its *type*. We
|
||||
have also seen an example of how the type system can help with EDSL
|
||||
development by allowing a programmer to describe the type system of an
|
||||
object language. However, precise types give us more than verification
|
||||
of programs — we can also exploit types to help write programs which
|
||||
are *correct by construction*.
|
||||
|
||||
The Idris REPL provides several commands for inspecting and
|
||||
modifying parts of programs, based on their types, such as case
|
||||
splitting on a pattern variable, inspecting the type of a
|
||||
metavariable, and even a basic proof search mechanism. In this
|
||||
section, we explain how these features can be exploited by a text
|
||||
editor, and specifically how to do so in `Vim
|
||||
<https://github.com/idris-hackers/idris-vim>`_. An interactive mode
|
||||
for `Emacs <https://github.com/idris-hackers/idris-emacs>`_ is also
|
||||
available.
|
||||
|
||||
|
||||
Editing at the REPL
|
||||
-------------------
|
||||
|
||||
The REPL provides a number of commands, which we will describe
|
||||
shortly, which generate new program fragments based on the currently
|
||||
loaded module. These take the general form
|
||||
|
||||
::
|
||||
|
||||
:command [line number] [name]
|
||||
|
||||
That is, each command acts on a specific source line, at a specific
|
||||
name, and outputs a new program fragment. Each command has an
|
||||
alternative form, which *updates* the source file in-place:
|
||||
|
||||
::
|
||||
|
||||
:command! [line number] [name]
|
||||
|
||||
When the REPL is loaded, it also starts a background process which
|
||||
accepts and responds to REPL commands, using ``idris --client``. For
|
||||
example, if we have a REPL running elsewhere, we can execute commands
|
||||
such as:
|
||||
|
||||
::
|
||||
|
||||
$ idris --client ':t plus'
|
||||
Prelude.Nat.plus : Nat -> Nat -> Nat
|
||||
$ idris --client '2+2'
|
||||
4 : Integer
|
||||
|
||||
A text editor can take advantage of this, along with the editing
|
||||
commands, in order to provide interactive editing support.
|
||||
|
||||
Editing Commands
|
||||
----------------
|
||||
|
||||
:addclause
|
||||
~~~~~~~~~~
|
||||
|
||||
The ``:addclause n f`` command (abbreviated ``:ac n f``) creates a
|
||||
template definition for the function named ``f`` declared on line
|
||||
``n``. For example, if the code beginning on line 94 contains:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
vzipWith : (a -> b -> c) ->
|
||||
Vect n a -> Vect n b -> Vect n c
|
||||
|
||||
then ``:ac 94 vzipWith`` will give:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
vzipWith f xs ys = ?vzipWith_rhs
|
||||
|
||||
The names are chosen according to hints which may be given by a
|
||||
programmer, and then made unique by the machine by adding a digit if
|
||||
necessary. Hints can be given as follows:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
%name Vect xs, ys, zs, ws
|
||||
|
||||
This declares that any names generated for types in the ``Vect`` family
|
||||
should be chosen in the order ``xs``, ``ys``, ``zs``, ``ws``.
|
||||
|
||||
:casesplit
|
||||
~~~~~~~~~~
|
||||
|
||||
The ``:casesplit n x`` command, abbreviated ``:cs n x``, splits the
|
||||
pattern variable ``x`` on line ``n`` into the various pattern forms it
|
||||
may take, removing any cases which are impossible due to unification
|
||||
errors. For example, if the code beginning on line 94 is:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
vzipWith : (a -> b -> c) ->
|
||||
Vect n a -> Vect n b -> Vect n c
|
||||
vzipWith f xs ys = ?vzipWith_rhs
|
||||
|
||||
then ``:cs 96 xs`` will give:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
vzipWith f [] ys = ?vzipWith_rhs_1
|
||||
vzipWith f (x :: xs) ys = ?vzipWith_rhs_2
|
||||
|
||||
That is, the pattern variable ``xs`` has been split into the two
|
||||
possible cases ``[]`` and ``x :: xs``. Again, the names are chosen
|
||||
according to the same heuristic. If we update the file (using
|
||||
``:cs!``) then case split on ``ys`` on the same line, we get:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
vzipWith f [] [] = ?vzipWith_rhs_3
|
||||
|
||||
That is, the pattern variable ``ys`` has been split into one case
|
||||
``[]``, Idris having noticed that the other possible case ``y ::
|
||||
ys`` would lead to a unification error.
|
||||
|
||||
:addmissing
|
||||
~~~~~~~~~~~
|
||||
|
||||
The ``:addmissing n f`` command, abbreviated ``:am n f``, adds the
|
||||
clauses which are required to make the function ``f`` on line ``n``
|
||||
cover all inputs. For example, if the code beginning on line 94 is
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
vzipWith : (a -> b -> c) ->
|
||||
Vect n a -> Vect n b -> Vect n c
|
||||
vzipWith f [] [] = ?vzipWith_rhs_1
|
||||
|
||||
then ``:am 96 vzipWith`` gives:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
vzipWith f (x :: xs) (y :: ys) = ?vzipWith_rhs_2
|
||||
|
||||
That is, it notices that there are no cases for non-empty vectors,
|
||||
generates the required clauses, and eliminates the clauses which would
|
||||
lead to unification errors.
|
||||
|
||||
:proofsearch
|
||||
~~~~~~~~~~~~
|
||||
|
||||
The ``:proofsearch n f`` command, abbreviated ``:ps n f``, attempts to
|
||||
find a value for the metavariable ``f`` on line ``n`` by proof search,
|
||||
trying values of local variables, recursive calls and constructors of
|
||||
the required family. Optionally, it can take a list of *hints*, which
|
||||
are functions it can try applying to solve the metavariable. For
|
||||
example, if the code beginning on line 94 is:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
vzipWith : (a -> b -> c) ->
|
||||
Vect n a -> Vect n b -> Vect n c
|
||||
vzipWith f [] [] = ?vzipWith_rhs_1
|
||||
vzipWith f (x :: xs) (y :: ys) = ?vzipWith_rhs_2
|
||||
|
||||
then ``:ps 96 vzipWith_rhs_1`` will give
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
[]
|
||||
|
||||
This works because it is searching for a ``Vect`` of length 0, of
|
||||
which the empty vector is the only possibiliy. Similarly, and perhaps
|
||||
surprisingly, there is only one possibility if we try to solve ``:ps
|
||||
97 vzipWith_rhs_2``:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
f x y :: (vzipWith f xs ys)
|
||||
|
||||
This works because ``vzipWith`` has a precise enough type: The
|
||||
resulting vector has to be non-empty (a ``::``); the first element
|
||||
must have type ``c`` and the only way to get this is to apply ``f`` to
|
||||
``x`` and ``y``; finally, the tail of the vector can only be built
|
||||
recursively.
|
||||
|
||||
:makewith
|
||||
~~~~~~~~~
|
||||
|
||||
The ``:makewith n f`` command, abbreviated ``:mw n f``, adds a
|
||||
``with`` to a pattern clause. For example, recall ``parity``. If line
|
||||
10 is:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
parity (S k) = ?parity_rhs
|
||||
|
||||
then ``:mw 10 parity`` will give:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
parity (S k) with (_)
|
||||
parity (S k) | with_pat = ?parity_rhs
|
||||
|
||||
If we then fill in the placeholder ``_`` with ``parity k`` and case
|
||||
split on ``with_pat`` using ``:cs 11 with_pat`` we get the following
|
||||
patterns:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
parity (S (plus n n)) | even = ?parity_rhs_1
|
||||
parity (S (S (plus n n))) | odd = ?parity_rhs_2
|
||||
|
||||
Note that case splitting has normalised the patterns here (giving
|
||||
``plus`` rather than ``+``). In any case, we see that using
|
||||
interactive editing significantly simplifies the implementation of
|
||||
dependent pattern matching by showing a programmer exactly what the
|
||||
valid patterns are.
|
||||
|
||||
Interactive Editing in Vim
|
||||
--------------------------
|
||||
|
||||
The editor mode for Vim provides syntax highlighting, indentation and
|
||||
interactive editing support using the commands described above.
|
||||
Interactive editing is achieved using the following editor commands,
|
||||
each of which update the buffer directly:
|
||||
|
||||
- ``\d`` adds a template definition for the name declared on the
|
||||
current line (using ``:addclause``).
|
||||
|
||||
- ``\c`` case splits the variable at the cursor (using
|
||||
``:casesplit``).
|
||||
|
||||
- ``\m`` adds the missing cases for the name at the cursor (using
|
||||
``:addmissing``).
|
||||
|
||||
- ``\w`` adds a ``with`` clause (using ``:makewith``).
|
||||
|
||||
- ``\o`` invokes a proof search to solve the metavariable under the
|
||||
cursor (using ``:proofsearch``).
|
||||
|
||||
- ``\p`` invokes a proof search with additional hints to solve the
|
||||
metavariable under the cursor (using ``:proofsearch``).
|
||||
|
||||
There are also commands to invoke the type checker and evaluator:
|
||||
|
||||
- ``\t`` displays the type of the (globally visible) name under the
|
||||
cursor. In the case of a metavariable, this displays the context
|
||||
and the expected type.
|
||||
|
||||
- ``\e`` prompts for an expression to evaluate.
|
||||
|
||||
- ``\r`` reloads and type checks the buffer.
|
||||
|
||||
Corresponding commands are also available in the Emacs mode. Support
|
||||
for other editors can be added in a relatively straighforward manner
|
||||
by using ``idris –client``.
|
288
docs/tutorial/interp.rst
Normal file
288
docs/tutorial/interp.rst
Normal file
@ -0,0 +1,288 @@
|
||||
.. _sect-interp:
|
||||
|
||||
===================================
|
||||
Example: The Well-Typed Interpreter
|
||||
===================================
|
||||
|
||||
In this section, we’ll use the features we’ve seen so far to write a
|
||||
larger example, 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.
|
||||
|
||||
Representing Languages
|
||||
----------------------
|
||||
|
||||
First, let us define the types in the language. We have integers,
|
||||
booleans, and functions, represented by ``Ty``:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
data Ty = TyInt | TyBool | TyFun Ty Ty
|
||||
|
||||
We can write a function to translate these representations to a concrete
|
||||
Idris type — remember that types are first class, so can be
|
||||
calculated just like any other value:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
interpTy : Ty -> Type
|
||||
interpTy TyInt = Int
|
||||
interpTy TyBool = Bool
|
||||
interpTy (TyFun A T) = interpTy A -> interpTy T
|
||||
|
||||
We’re going to define a representation of our language in such a way
|
||||
that only well-typed programs can be represented. We’ll index the
|
||||
representations of expressions by their type and the types of local
|
||||
variables (the context), which we’ll be using regularly as an implicit
|
||||
argument, so we define everything in a ``using`` block:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
using (G:Vect n Ty)
|
||||
|
||||
Expressions are indexed by the types of the local variables, and the type of
|
||||
the expression itself:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
data Expr : Vect n Ty -> Ty -> Type
|
||||
|
||||
The full representation of expressions is:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
data HasType : (i : Fin n) -> Vect n Ty -> Ty -> Type where
|
||||
Stop : HasType FZ (t :: G) t
|
||||
Pop : HasType k G t -> HasType (FS k) (u :: G) t
|
||||
|
||||
data Expr : Vect n Ty -> Ty -> Type where
|
||||
Var : HasType i G t -> Expr G t
|
||||
Val : (x : Int) -> Expr G TyInt
|
||||
Lam : Expr (a :: G) t -> Expr G (TyFun a t)
|
||||
App : Expr G (TyFun a t) -> Expr G a -> Expr G t
|
||||
Op : (interpTy a -> interpTy b -> interpTy c) ->
|
||||
Expr G a -> Expr G b -> Expr G c
|
||||
If : Expr G TyBool ->
|
||||
Lazy (Expr G a) ->
|
||||
Lazy (Expr G a) -> Expr G a
|
||||
|
||||
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 G T``, which is a proof that variable ``i``
|
||||
in context ``G`` has type ``T``. This is defined as follows:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
data HasType : (i : Fin n) -> Vect n Ty -> Ty -> Type where
|
||||
Stop : HasType FZ (t :: G) t
|
||||
Pop : HasType k G t -> HasType (FS k) (u :: G) t
|
||||
|
||||
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 ``Var`` constructor:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
Var : HasType i G t -> Expr G t
|
||||
|
||||
So, in an expression ``\x,\y. x y``, the variable ``x`` would have a
|
||||
de Bruijn index of 1, represented as ``Pop Stop``, and ``y 0``,
|
||||
represented as ``Stop``. We find these by counting the number of
|
||||
lambdas between the definition and the use.
|
||||
|
||||
A value carries a concrete representation of an integer:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
Val : (x : Int) -> Expr G TyInt
|
||||
|
||||
A lambda creates a function. In the scope of a function of type ``a ->
|
||||
t``, there is a new local variable of type ``a``, which is expressed
|
||||
by the context index:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
Lam : Expr (a :: G) t -> Expr G (TyFun a t)
|
||||
|
||||
Function application produces a value of type ``t`` given a function
|
||||
from ``a`` to ``t`` and a value of type ``a``:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
App : Expr G (TyFun a t) -> Expr G a -> Expr G t
|
||||
|
||||
We allow arbitrary binary operators, where the type of the operator
|
||||
informs what the types of the arguments must be:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
Op : (interpTy a -> interpTy b -> interpTy c) ->
|
||||
Expr G a -> Expr G b -> Expr G c
|
||||
|
||||
Finally, if expressions make a choice given a boolean. Each branch
|
||||
must have the same type, and we will evaluate the branches lazily so
|
||||
that only the branch which is taken need be evaluated:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
If : Expr G TyBool ->
|
||||
Lazy (Expr G a) ->
|
||||
Lazy (Expr G a) ->
|
||||
Expr G a
|
||||
|
||||
Writing the Interpreter
|
||||
-----------------------
|
||||
|
||||
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 use the usual ``::`` and ``Nil`` constructors 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:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
data Env : Vect n Ty -> Type where
|
||||
Nil : Env Nil
|
||||
(::) : interpTy a -> Env G -> Env (a :: G)
|
||||
|
||||
lookup : HasType i G t -> Env G -> interpTy t
|
||||
lookup Stop (x :: xs) = x
|
||||
lookup (Pop k) (x :: xs) = lookup k xs
|
||||
|
||||
Given this, an interpreter is a function which
|
||||
translates an ``Expr`` into a concrete Idris value with respect to a
|
||||
specific environment:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
interp : Env G -> Expr G t -> interpTy t
|
||||
|
||||
The complete interpreter is defined as follows, for reference. For
|
||||
each constructor, we translate it into the corresponding Idris value:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
interp env (Var i) = lookup i env
|
||||
interp env (Val x) = x
|
||||
interp env (Lam sc) = \x => interp (x :: env) sc
|
||||
interp env (App f s) = interp env f (interp env s)
|
||||
interp env (Op op x y) = op (interp env x) (interp env y)
|
||||
interp env (If x t e) = if interp env x then interp env t
|
||||
else interp env e
|
||||
|
||||
Let us look at each case in turn. To translate a variable, we simply look it
|
||||
up in the environment:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
interp env (Var i) = lookup i env
|
||||
|
||||
To translate a value, we just return the concrete representation of the
|
||||
value:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
interp env (Val x) = x
|
||||
|
||||
Lambdas are more interesting. In this case, we construct a function
|
||||
which interprets the scope of the lambda with a new value in the
|
||||
environment. So, a function in the object language is translated to an
|
||||
Idris function:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
interp env (Lam sc) = \x => interp (x :: env) sc
|
||||
|
||||
For an application, we interpret the function and its argument and apply
|
||||
it directly. We know that interpreting ``f`` must produce a function,
|
||||
because of its type:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
interp env (App f s) = interp env f (interp env s)
|
||||
|
||||
Operators and interpreters are, again, direct translations into the
|
||||
equivalent Idris constructs. For operators, we apply the function to
|
||||
its operands directly, and for ``If``, we apply the Idris
|
||||
``if...then...else`` construct directly.
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
interp env (Op op x y) = op (interp env x) (interp env y)
|
||||
interp env (If x t e) = if interp env x then interp env t
|
||||
else interp env e
|
||||
|
||||
Testing
|
||||
-------
|
||||
|
||||
We can make some simple test functions. Firstly, adding two inputs
|
||||
``\x. \y. y + x`` is written as follows:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
add : Expr G (TyFun TyInt (TyFun TyInt TyInt))
|
||||
add = Lam (Lam (Op (+) (Var Stop) (Var (Pop Stop))))
|
||||
|
||||
More interestingly, a factorial function ``fact``
|
||||
(e.g. ``\. if (x == 0) then 1 else (fact (x-1) * x)``),
|
||||
can be written as:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
fact : Expr G (TyFun TyInt TyInt)
|
||||
fact = Lam (If (Op (==) (Var Stop) (Val 0))
|
||||
(Val 1)
|
||||
(Op (*) (App fact (Op (-) (Var Stop) (Val 1)))
|
||||
(Var Stop)))
|
||||
|
||||
Running
|
||||
-------
|
||||
|
||||
To finish, we write a ``main`` program which interprets the factorial
|
||||
function on user input:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
main : IO ()
|
||||
main = do putStr "Enter a number: "
|
||||
x <- getLine
|
||||
print (interp [] fact (cast x))
|
||||
|
||||
Here, ``cast`` is an overloaded function which converts a value from
|
||||
one type to another if possible. Here, it converts a string to an
|
||||
integer, giving 0 if the input is invalid. An example run of this
|
||||
program at the Idris interactive environment is:
|
||||
|
||||
|
||||
.. _factrun:
|
||||
.. literalinclude:: ../listing/idris-prompt-interp.txt
|
||||
|
||||
|
||||
Aside: ``cast``
|
||||
~~~~~~~~~~~~~~~
|
||||
|
||||
The prelude defines a type class ``Cast`` which allows conversion
|
||||
between types:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
class Cast from to where
|
||||
cast : from -> to
|
||||
|
||||
It is a *multi-parameter* type class, defining the source type and
|
||||
object type of the cast. It must be possible for the type checker to
|
||||
infer *both* parameters at the point where the cast is applied. There
|
||||
are casts defined between all of the primitive types, as far as they
|
||||
make sense.
|
69
docs/tutorial/introduction.rst
Normal file
69
docs/tutorial/introduction.rst
Normal file
@ -0,0 +1,69 @@
|
||||
.. _sect-intro:
|
||||
|
||||
============
|
||||
Introduction
|
||||
============
|
||||
|
||||
In conventional programming languages, there is a clear distinction
|
||||
between *types* and *values*. For example, in `Haskell
|
||||
<http://www.haskell.org>`_, the following are types, representing
|
||||
integers, characters, lists of characters, and lists of any value
|
||||
respectively:
|
||||
|
||||
- ``Int``, ``Char``, ``[Char]``, ``[a]``
|
||||
|
||||
Correspondingly, the following values are examples of inhabitants of
|
||||
those types:
|
||||
|
||||
- ``42``, ``’a’``, ``Hello world!``, ``[2,3,4,5,6]``
|
||||
|
||||
In a language with *dependent types*, however, the distinction is less
|
||||
clear. Dependent types allow types to “depend” on values — in other
|
||||
words, types are a *first class* language construct and can be
|
||||
manipulated like any other value. The standard example is the type of
|
||||
lists of a given length [1]_, ``Vect n a``, where ``a`` is the element
|
||||
type and ``n`` is the length of the list and can be an arbitrary term.
|
||||
|
||||
When types can contain values, and where those values describe
|
||||
properties (e.g. the length of a list) the type of a function can
|
||||
begin to describe its own properties. For example, concatenating two
|
||||
lists has the property that the resulting list’s length is the sum of
|
||||
the lengths of the two input lists. We can therefore give the
|
||||
following type to the ``app`` function, which concatenates vectors:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
app : Vect n a -> Vect m a -> Vect (n + m) a
|
||||
|
||||
This tutorial introduces Idris, a general purpose functional
|
||||
programming language with dependent types. The goal of the Idris
|
||||
project is to build a dependently typed language suitable for
|
||||
verifiable *systems* programming. To this end, Idris is a compiled
|
||||
language which aims to generate efficient executable code. It also has
|
||||
a lightweight foreign function interface which allows easy interaction
|
||||
with external ``C`` libraries.
|
||||
|
||||
Intended Audience
|
||||
-----------------
|
||||
|
||||
This tutorial is intended as a brief introduction to the language, and
|
||||
is aimed at readers already familiar with a functional language such
|
||||
as `Haskell <http://www.haskell.org>`_ or `OCaml <http://ocaml.org>`_.
|
||||
In particular, a certain amount of familiarity with Haskell syntax is
|
||||
assumed, although most concepts will at least be explained
|
||||
briefly. The reader is also assumed to have some interest in using
|
||||
dependent types for writing and verifying systems software.
|
||||
|
||||
Example Code
|
||||
------------
|
||||
|
||||
This tutorial includes some example code, which has been tested with
|
||||
Idris version . The files are available in the Idris
|
||||
distribution, and provided along side the tutorial source, so that you
|
||||
can try them out easily, under ``tutorial/examples``. However, it is
|
||||
strongly recommended that you can type them in yourself, rather than
|
||||
simply loading and reading them.
|
||||
|
||||
.. [1]
|
||||
Typically, and perhaps confusingly, referred to in the dependently
|
||||
typed programming literature as “vectors”
|
546
docs/tutorial/miscellany.rst
Normal file
546
docs/tutorial/miscellany.rst
Normal file
@ -0,0 +1,546 @@
|
||||
.. _sect-misc:
|
||||
|
||||
==========
|
||||
Miscellany
|
||||
==========
|
||||
|
||||
In this section we discuss a variety of additional features:
|
||||
|
||||
+ auto, implicit, and default arguments;
|
||||
+ literate programming;
|
||||
+ interfacing with external libraries through the foreign function
|
||||
+ interface;
|
||||
+ type providers;
|
||||
+ code generation; and
|
||||
+ the universe hierarchy.
|
||||
|
||||
Auto implicit arguments
|
||||
-----------------------
|
||||
|
||||
We have already seen implicit arguments, which allows arguments to be
|
||||
omitted when they can be inferred by the type checker, e.g.
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
index : {a:Type} -> {n:Nat} -> Fin n -> Vect n a -> a
|
||||
|
||||
In other situations, it may be possible to infer arguments not by type
|
||||
checking but by searching the context for an appropriate value, or
|
||||
constructing a proof. For example, the following definition of ``head``
|
||||
which requires a proof that the list is non-empty:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
isCons : List a -> Bool
|
||||
isCons [] = False
|
||||
isCons (x :: xs) = True
|
||||
|
||||
head : (xs : List a) -> (isCons xs = True) -> a
|
||||
head (x :: xs) _ = x
|
||||
|
||||
If the list is statically known to be non-empty, either because its
|
||||
value is known or because a proof already exists in the context, the
|
||||
proof can be constructed automatically. Auto implicit arguments allow
|
||||
this to happen silently. We define ``head`` as follows:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
head : (xs : List a) -> {auto p : isCons xs = True} -> a
|
||||
head (x :: xs) = x
|
||||
|
||||
The ``auto`` annotation on the implicit argument means that Idris
|
||||
will attempt to fill in the implicit argument using the ``trivial``
|
||||
tactic, which searches through the context for a proof, and tries to
|
||||
solve with ``refl`` if a proof is not found. Now when ``head`` is
|
||||
applied, the proof can be omitted. In the case that a proof is not
|
||||
found, it can be provided explicitly as normal:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
head xs {p = ?headProof}
|
||||
|
||||
More generally, we can fill in implicit arguments with a default value
|
||||
by annotating them with ``default``. The definition above is equivalent
|
||||
to:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
head : (xs : List a) ->
|
||||
{default proof { trivial; } p : isCons xs = True} -> a
|
||||
head (x :: xs) = x
|
||||
|
||||
Implicit conversions
|
||||
--------------------
|
||||
|
||||
Idris supports the creation of *implicit conversions*, which allow
|
||||
automatic conversion of values from one type to another when required to
|
||||
make a term type correct. This is intended to increase convenience and
|
||||
reduce verbosity. A contrived but simple example is the following:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
implicit intString : Int -> String
|
||||
intString = show
|
||||
|
||||
test : Int -> String
|
||||
test x = "Number " ++ x
|
||||
|
||||
In general, we cannot append an ``Int`` to a ``String``, but the
|
||||
implicit conversion function ``intString`` can convert ``x`` to a
|
||||
``String``, so the definition of ``test`` is type correct. An implicit
|
||||
conversion is implemented just like any other function, but given the
|
||||
``implicit`` modifier, and restricted to one explicit argument.
|
||||
|
||||
Only one implicit conversion will be applied at a time. That is,
|
||||
implicit conversions cannot be chained. Implicit conversions of simple
|
||||
types, as above, are however discouraged! More commonly, an implicit
|
||||
conversion would be used to reduce verbosity in an embedded domain
|
||||
specific language, or to hide details of a proof. Such examples are
|
||||
beyond the scope of this tutorial.
|
||||
|
||||
Literate programming
|
||||
--------------------
|
||||
|
||||
Like Haskell, Idris supports *literate* programming. If a file has
|
||||
an extension of ``.lidr`` then it is assumed to be a literate file. In
|
||||
literate programs, everything is assumed to be a comment unless the line
|
||||
begins with a greater than sign ``>``, for example:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
> module literate
|
||||
|
||||
This is a comment. The main program is below
|
||||
|
||||
> main : IO ()
|
||||
> main = putStrLn "Hello literate world!\n"
|
||||
|
||||
An additional restriction is that there must be a blank line between a
|
||||
program line (beginning with ``>``) and a comment line (beginning with
|
||||
any other character).
|
||||
|
||||
Foreign function calls
|
||||
----------------------
|
||||
|
||||
For practical programming, it is often necessary to be able to use
|
||||
external libraries, particularly for interfacing with the operating
|
||||
system, file system, networking, *et cetera*. Idris provides a
|
||||
lightweight foreign function interface for achieving this, as part of
|
||||
the prelude. For this, we assume a certain amount of knowledge of C and
|
||||
the ``gcc`` compiler. First, we define a datatype which describes the
|
||||
external types we can handle:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
data FTy = FInt | FFloat | FChar | FString | FPtr | FUnit
|
||||
|
||||
Each of these corresponds directly to a C type. Respectively: ``int``,
|
||||
``double``, ``char``, ``char*``, ``void*`` and ``void``. There is also a
|
||||
translation to a concrete Idris type, described by the following
|
||||
function:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
interpFTy : FTy -> Type
|
||||
interpFTy FInt = Int
|
||||
interpFTy FFloat = Float
|
||||
interpFTy FChar = Char
|
||||
interpFTy FString = String
|
||||
interpFTy FPtr = Ptr
|
||||
interpFTy FUnit = ()
|
||||
|
||||
A foreign function is described by a list of input types and a return
|
||||
type, which can then be converted to an Idris type:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
ForeignTy : (xs:List FTy) -> (t:FTy) -> Type
|
||||
|
||||
A foreign function is assumed to be impure, so ``ForeignTy`` builds an
|
||||
``IO`` type, for example:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
Idris> ForeignTy [FInt, FString] FString
|
||||
Int -> String -> IO String : Type
|
||||
|
||||
Idris> ForeignTy [FInt, FString] FUnit
|
||||
Int -> String -> IO () : Type
|
||||
|
||||
We build a call to a foreign function by giving the name of the
|
||||
function, a list of argument types and the return type. The built in
|
||||
construct ``mkForeign`` converts this description to a function callable
|
||||
by Idris:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
data Foreign : Type -> Type where
|
||||
FFun : String -> (xs:List FTy) -> (t:FTy) ->
|
||||
Foreign (ForeignTy xs t)
|
||||
|
||||
mkForeign : Foreign x -> x
|
||||
|
||||
Note that the compiler expects ``mkForeign`` to be fully applied to
|
||||
build a complete foreign function call. For example, the ``putStr``
|
||||
function is implemented as follows, as a call to an external function
|
||||
``putStr`` defined in the run-time system:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
putStr : String -> IO ()
|
||||
putStr x = mkForeign (FFun "putStr" [FString] FUnit) x
|
||||
|
||||
Include and linker directives
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Foreign function calls are translated directly to calls to C functions,
|
||||
with appropriate conversion between the Idris representation of a
|
||||
value and the C representation. Often this will require extra libraries
|
||||
to be linked in, or extra header and object files. This is made possible
|
||||
through the following directives:
|
||||
|
||||
- ``%lib target x`` — include the ``libx`` library. If the target is
|
||||
``C`` this is equivalent to passing the ``-lx`` option to ``gcc``. If
|
||||
the target is Java the library will be interpreted as a
|
||||
``groupId:artifactId:packaging:version`` dependency coordinate for
|
||||
maven.
|
||||
|
||||
- ``%include target x`` — use the header file or import ``x`` for the
|
||||
given back end target.
|
||||
|
||||
- ``%link target x.o`` — link with the object file ``x.o`` when using
|
||||
the given back end target.
|
||||
|
||||
- ``%dynamic x.so`` — dynamically link the interpreter with the shared
|
||||
object ``x.so``.
|
||||
|
||||
Testing foreign function calls
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Normally, the Idris interpreter (used for typechecking and at the REPL)
|
||||
will not perform IO actions. Additionally, as it neither generates C
|
||||
code nor compiles to machine code, the ``%lib``, ``%include`` and
|
||||
``%link`` directives have no effect. IO actions and FFI calls can be
|
||||
tested using the special REPL command ``:x EXPR``, and C libraries can
|
||||
be dynamically loaded in the interpreter by using the ``:dynamic``
|
||||
command or the ``%dynamic`` directive. For example:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
Idris> :dynamic libm.so
|
||||
Idris> :x unsafePerformIO ((mkForeign (FFun "sin" [FFloat] FFloat)) 1.6)
|
||||
0.9995736030415051 : Float
|
||||
|
||||
Type Providers
|
||||
--------------
|
||||
|
||||
Idris type providers, inspired by F#’s type providers, are a means of
|
||||
making our types be “about” something in the world outside of Idris. For
|
||||
example, given a type that represents a database schema and a query that
|
||||
is checked against it, a type provider could read the schema of a real
|
||||
database during type checking.
|
||||
|
||||
Idris type providers use the ordinary execution semantics of Idris to
|
||||
run an IO action and extract the result. This result is then saved as a
|
||||
constant in the compiled code. It can be a type, in which case it is
|
||||
used like any other type, or it can be a value, in which case it can be
|
||||
used as any other value, including as an index in types.
|
||||
|
||||
Type providers are still an experimental extension. To enable the
|
||||
extension, use the ``%language`` directive:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
%language TypeProviders
|
||||
|
||||
A provider ``p`` for some type ``t`` is simply an expression of type
|
||||
``IO (Provider t)``. The ``%provide`` directive causes the type checker
|
||||
to execute the action and bind the result to a name. This is perhaps
|
||||
best illustrated with a simple example. The type provider ``fromFile``
|
||||
reads a text file. If the file consists of the string ``Int``, then the
|
||||
type ``Int`` will be provided. Otherwise, it will provide the type
|
||||
``Nat``.
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
strToType : String -> Type
|
||||
strToType "Int" = Int
|
||||
strToType _ = Nat
|
||||
|
||||
fromFile : String -> IO (Provider Type)
|
||||
fromFile fname = do str <- readFile fname
|
||||
return (Provide (strToType (trim str)))
|
||||
|
||||
We then use the ``%provide`` directive:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
%provide (T1 : Type) with fromFile "theType"
|
||||
|
||||
foo : T1
|
||||
foo = 2
|
||||
|
||||
If the file named ``theType`` consists of the word ``Int``, then ``foo``
|
||||
will be an ``Int``. Otherwise, it will be a ``Nat``. When Idris
|
||||
encounters the directive, it first checks that the provider expression
|
||||
``fromFile theType`` has type ``IO (Provider Type)``. Next, it executes
|
||||
the provider. If the result is ``Provide t``, then ``T1`` is defined as
|
||||
``t``. Otherwise, the result is an error.
|
||||
|
||||
Our datatype ``Provider t`` has the following definition:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
data Provider a = Error String
|
||||
| Provide a
|
||||
|
||||
We have already seen the ``Provide`` constructor. The ``Error``
|
||||
constructor allows type providers to return useful error messages. The
|
||||
example in this section was purposefully simple. More complex type
|
||||
provider implementations, including a statically-checked SQLite binding,
|
||||
are available in an external collection [1]_.
|
||||
|
||||
C Target
|
||||
--------
|
||||
|
||||
The default target of Idris is C. Compiling via :
|
||||
|
||||
::
|
||||
|
||||
$ idris hello.idr -o hello
|
||||
|
||||
is equivalent to :
|
||||
|
||||
::
|
||||
|
||||
$ idris --codegen C hello.idr -o hello
|
||||
|
||||
When the command above is used, a temporary C source is generated, which
|
||||
is then compiled into an executable named ``hello``.
|
||||
|
||||
In order to view the generated C code, compile via :
|
||||
|
||||
::
|
||||
|
||||
$ idris hello.idr -S -o hello.c
|
||||
|
||||
To turn optimisations on, use the ``%flag C`` pragma within the code, as
|
||||
is shown below :
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
module Main
|
||||
%flag C "-O3"
|
||||
|
||||
factorial : Int -> Int
|
||||
factorial 0 = 1
|
||||
factorial n = n * (factorial (n-1))
|
||||
|
||||
main : IO ()
|
||||
main = do
|
||||
putStrLn $ show $ factorial 3
|
||||
|
||||
JavaScript Target
|
||||
-----------------
|
||||
|
||||
Idris is capable of producing *JavaScript* code that can be run in a
|
||||
browser as well as in the *NodeJS* environment or alike. One can use the
|
||||
FFI to communicate with the *JavaScript* ecosystem.
|
||||
|
||||
Code Generation
|
||||
~~~~~~~~~~~~~~~
|
||||
|
||||
Code generation is split into two separate targets. To generate code
|
||||
that is tailored for running in the browser issue the following command:
|
||||
|
||||
::
|
||||
|
||||
$ idris --codegen javascript hello.idr -o hello.js
|
||||
|
||||
The resulting file can be embedded into your HTML just like any other
|
||||
*JavaScript* code.
|
||||
|
||||
Generating code for *NodeJS* is slightly different. Idris outputs a
|
||||
*JavaScript* file that can be directly executed via ``node``.
|
||||
|
||||
::
|
||||
|
||||
$ idris --codegen node hello.idr -o hello
|
||||
$ ./hello
|
||||
Hello world
|
||||
|
||||
Take into consideration that the *JavaScript* code generator is using
|
||||
``console.log`` to write text to ``stdout``, this means that it will
|
||||
automatically add a newline to the end of each string. This behaviour
|
||||
does not show up in the *NodeJS* code generator.
|
||||
|
||||
Using the FFI
|
||||
~~~~~~~~~~~~~
|
||||
|
||||
To write a useful application we need to communicate with the outside
|
||||
world. Maybe we want to manipulate the DOM or send an Ajax request. For
|
||||
this task we can use the FFI. Since most *JavaScript* APIs demand
|
||||
callbacks we need to extend the FFI so we can pass functions as
|
||||
arguments.
|
||||
|
||||
The *JavaScript* FFI works a little bit differently than the regular
|
||||
FFI. It uses positional arguments to directly insert our arguments into
|
||||
a piece of *JavaScript* code.
|
||||
|
||||
One could use the primitive addition of *JavaScript* like so:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
module Main
|
||||
|
||||
primPlus : Int -> Int -> IO Int
|
||||
primPlus a b = mkForeign (FFun "%0 + %1" [FInt, FInt] FInt) a b
|
||||
|
||||
main : IO ()
|
||||
main = do
|
||||
a <- primPlus 1 1
|
||||
b <- primPlus 1 2
|
||||
print (a, b)
|
||||
|
||||
Notice that the ``%n`` notation qualifies the position of the ``n``-th
|
||||
argument given to our foreign function starting from 0. When you need a
|
||||
percent sign rather than a position simply use ``%%`` instead.
|
||||
|
||||
Passing functions to a foreign function is very similar. Let’s assume
|
||||
that we want to call the following function from the *JavaScript* world:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
function twice(f, x) {
|
||||
return f(f(x));
|
||||
}
|
||||
|
||||
We obviously need to pass a function ``f`` here (we can infer it from
|
||||
the way we use ``f`` in ``twice``, it would be more obvious if
|
||||
*JavaScript* had types).
|
||||
|
||||
The *JavaScript* FFI is able to understand functions as arguments when
|
||||
you give it something of type ``FFunction``. The following example code
|
||||
calls ``twice`` in *JavaScript* and returns the result to our Idris
|
||||
program:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
module Main
|
||||
|
||||
twice : (Int -> Int) -> Int -> IO Int
|
||||
twice f x = mkForeign (
|
||||
FFun "twice(%0,%1)" [FFunction FInt FInt, FInt] FInt
|
||||
) f x
|
||||
|
||||
main : IO ()
|
||||
main = do
|
||||
a <- twice (+1) 1
|
||||
print a
|
||||
|
||||
The program outputs ``3``, just like we expected.
|
||||
|
||||
Including external *JavaScript* files
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Whenever one is working with *JavaScript* one might want to include
|
||||
external libraries or just some functions that she or he wants to call
|
||||
via FFI which are stored in external files. The *JavaScript* and
|
||||
*NodeJS* code generators understand the ``%include`` directive. Keep in
|
||||
mind that *JavaScript* and *NodeJS* are handled as different code
|
||||
generators, therefore you will have to state which one you want to
|
||||
target. This means that you can include different files for *JavaScript*
|
||||
and *NodeJS* in the same Idris source file.
|
||||
|
||||
So whenever you want to add an external *JavaScript* file you can do
|
||||
this like so:
|
||||
|
||||
For *NodeJS*:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
%include Node "path/to/external.js"
|
||||
|
||||
And for use in the browser:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
%include JavaScript "path/to/external.js"
|
||||
|
||||
The given files will be added to the top of the generated code.
|
||||
|
||||
Including *NodeJS* modules
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
The *NodeJS* code generator can also include modules with the ``%lib``
|
||||
directive.
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
%lib Node "fs"
|
||||
|
||||
This directive compiles into the following *JavaScript*
|
||||
|
||||
.. code-block:: javascript
|
||||
|
||||
var fs = require("fs");
|
||||
|
||||
Shrinking down generated *JavaScript*
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Idris can produce very big chunks of *JavaScript* code. However, the
|
||||
generated code can be minified using the ``closure-compiler`` from
|
||||
Google. Any other minifier is also suitable but ``closure-compiler``
|
||||
offers advanced compilation that does some aggressive inlining and code
|
||||
elimination. Idris can take full advantage of this compilation mode
|
||||
and it’s highly recommended to use it when shipping a *JavaScript*
|
||||
application written in Idris.
|
||||
|
||||
Cumulativity
|
||||
------------
|
||||
|
||||
Since values can appear in types and *vice versa*, it is natural that
|
||||
types themselves have types. For example:
|
||||
|
||||
::
|
||||
|
||||
*universe> :t Nat
|
||||
Nat : Type
|
||||
*universe> :t Vect
|
||||
Vect : Nat -> Type -> Type
|
||||
|
||||
But what about the type of ``Type``? If we ask Idris it reports
|
||||
|
||||
::
|
||||
|
||||
*universe> :t Type
|
||||
Type : Type 1
|
||||
|
||||
If ``Type`` were its own type, it would lead to an inconsistency due to
|
||||
`Girard’s paradox <http://www.cs.cmu.edu/afs/cs.cmu.edu/user/kw/www/scans/girard72thesis.pdf>`_ , so internally there is a
|
||||
*hierarchy* of types (or *universes*):
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
Type : Type 1 : Type 2 : Type 3 : ...
|
||||
|
||||
Universes are *cumulative*, that is, if ``x : Type n`` we can also have
|
||||
that ``x : Type m``, as long as ``n < m``. The typechecker generates
|
||||
such universe constraints and reports an error if any inconsistencies
|
||||
are found. Ordinarily, a programmer does not need to worry about this,
|
||||
but it does prevent (contrived) programs such as the following:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
myid : (a : Type) -> a -> a
|
||||
myid _ x = x
|
||||
|
||||
idid : (a : Type) -> a -> a
|
||||
idid = myid _ myid
|
||||
|
||||
The application of ``myid`` to itself leads to a cycle in the universe
|
||||
hierarchy — ``myid``\ ’s first argument is a ``Type``, which cannot be
|
||||
at a lower level than required if it is applied to itself.
|
||||
|
||||
.. [1]
|
||||
https://github.com/david-christiansen/idris-type-providers
|
219
docs/tutorial/modules.rst
Normal file
219
docs/tutorial/modules.rst
Normal file
@ -0,0 +1,219 @@
|
||||
.. _sect-namespaces:
|
||||
|
||||
=======================
|
||||
Modules and Namespaces
|
||||
=======================
|
||||
|
||||
An Idris program consists of a collection of modules. Each module
|
||||
includes an optional ``module`` declaration giving the name of the
|
||||
module, a list of ``import`` statements giving the other modules which
|
||||
are to be imported, and a collection of declarations and definitions of
|
||||
types, classes and functions. For example, the listing below gives a
|
||||
module which defines a binary tree type ``BTree`` (in a file
|
||||
``btree.idr``):
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
module btree
|
||||
|
||||
data BTree a = Leaf
|
||||
| Node (BTree a) a (BTree a)
|
||||
|
||||
insert : Ord a => a -> BTree a -> BTree a
|
||||
insert x Leaf = Node Leaf x Leaf
|
||||
insert x (Node l v r) = if (x < v) then (Node (insert x l) v r)
|
||||
else (Node l v (insert x r))
|
||||
|
||||
toList : BTree a -> List a
|
||||
toList Leaf = []
|
||||
toList (Node l v r) = btree.toList l ++ (v :: btree.toList r)
|
||||
|
||||
toTree : Ord a => List a -> BTree a
|
||||
toTree [] = Leaf
|
||||
toTree (x :: xs) = insert x (toTree xs)
|
||||
|
||||
|
||||
Then, this gives a main program (in a file
|
||||
``bmain.idr``) which uses the ``bst`` module to sort a list:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
module Main
|
||||
|
||||
import btree
|
||||
|
||||
main : IO ()
|
||||
main = do let t = toTree [1,8,2,7,9,3]
|
||||
print (btree.toList t)
|
||||
|
||||
|
||||
The same names can be defined in multiple modules. This is possible
|
||||
because in practice names are *qualified* with the name of the module.
|
||||
The names defined in the ``btree`` module are, in full:
|
||||
|
||||
+ ``btree.BTree``
|
||||
+ ``btree.Leaf``
|
||||
+ ``btree.Node``
|
||||
+ ``btree.insert``
|
||||
+ ``btree.toList``
|
||||
+ ``btree.toTree``
|
||||
|
||||
If names are otherwise unambiguous, there is no need to give the fully
|
||||
qualified name. Names can be disambiguated either by giving an explicit
|
||||
qualification, or according to their type.
|
||||
|
||||
There is no formal link between the module name and its filename,
|
||||
although it is generally advisable to use the same name for each. An
|
||||
``import`` statement refers to a filename, using dots to separate
|
||||
directories. For example, ``import foo.bar`` would import the file
|
||||
``foo/bar.idr``, which would conventionally have the module declaration
|
||||
``module foo.bar``. The only requirement for module names is that the
|
||||
main module, with the ``main`` function, must be called
|
||||
``Main``—although its filename need not be ``Main.idr``.
|
||||
|
||||
Export Modifiers
|
||||
----------------
|
||||
|
||||
By default, all names defined in a module are exported for use by other
|
||||
modules. However, it is good practice only to export a minimal interface
|
||||
and keep internal details abstract. Idris allows functions, types,
|
||||
and classes to be marked as: ``public``, ``abstract`` or ``private``:
|
||||
|
||||
- ``public`` means that both the name and definition are exported. For
|
||||
functions, this means that the implementation is exported (which
|
||||
means, for example, it can be used in a dependent type). For data
|
||||
types, this means that the type name and the constructors are
|
||||
exported. For classes, this means that the class name and method
|
||||
names are exported.
|
||||
|
||||
- ``abstract`` means that only the name is exported. For functions,
|
||||
this means that the implementation is not exported. For data types,
|
||||
this means that the type name is exported but not the constructors.
|
||||
For classes, this means that the class name is exported but not the
|
||||
method names.
|
||||
|
||||
- ``private`` means that neither the name nor the definition is
|
||||
exported.
|
||||
|
||||
.. note::
|
||||
If any definition is given an export modifier, then all names with no modifier are assumed to be ``private``.
|
||||
|
||||
For our ``btree`` module, it makes sense for the tree data type and the
|
||||
functions to be exported as ``abstract``, as we see below:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
module btree
|
||||
|
||||
abstract data BTree a = Leaf
|
||||
| Node (BTree a) a (BTree a)
|
||||
|
||||
abstract
|
||||
insert : Ord a => a -> BTree a -> BTree a
|
||||
insert x Leaf = Node Leaf x Leaf
|
||||
insert x (Node l v r) = if (x < v) then (Node (insert x l) v r)
|
||||
else (Node l v (insert x r))
|
||||
|
||||
abstract
|
||||
toList : BTree a -> List a
|
||||
toList Leaf = []
|
||||
toList (Node l v r) = btree.toList l ++ (v :: btree.toList r)
|
||||
|
||||
abstract
|
||||
toTree : Ord a => List a -> BTree a
|
||||
toTree [] = Leaf
|
||||
toTree (x :: xs) = insert x (toTree xs)
|
||||
|
||||
Finally, the default export mode can be changed with the ``%access``
|
||||
directive, for example:
|
||||
|
||||
In this case, any function with no access modifier will be exported as
|
||||
``abstract``, rather than left ``private``.
|
||||
|
||||
Additionally, a module can re-export a module it has imported, by using
|
||||
the ``public`` modifier on an ``import``. For example:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
module A
|
||||
|
||||
import B import public C
|
||||
|
||||
public a : AType a = ...
|
||||
|
||||
The module ``A`` will export the name ``a``, as well as any public or
|
||||
abstract names in module ``C``, but will not re-export anything from
|
||||
module ``B``.
|
||||
|
||||
Explicit Namespaces
|
||||
-------------------
|
||||
|
||||
Defining a module also defines a namespace implicitly. However,
|
||||
namespaces can also be given *explicitly*. This is most useful if you
|
||||
wish to overload names within the same module:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
module foo
|
||||
|
||||
namespace x
|
||||
test : Int -> Int
|
||||
test x = x * 2
|
||||
|
||||
namespace y
|
||||
test : String -> String
|
||||
test x = x ++ x
|
||||
|
||||
This (admittedly contrived) module defines two functions with fully
|
||||
qualified names ``foo.x.test`` and ``foo.y.test``, which can be
|
||||
disambiguated by their types:
|
||||
|
||||
::
|
||||
|
||||
*foo> test 3
|
||||
6 : Int
|
||||
*foo> test "foo"
|
||||
"foofoo" : String
|
||||
|
||||
Parameterised blocks
|
||||
--------------------
|
||||
|
||||
Groups of functions can be parameterised over a number of arguments
|
||||
using a ``parameters`` declaration, for example:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
parameters (x : Nat, y : Nat)
|
||||
addAll : Nat -> Nat
|
||||
addAll z = x + y + z
|
||||
|
||||
The effect of a ``parameters`` block is to add the declared parameters
|
||||
to every function, type and data constructor within the block. Outside
|
||||
the block, the parameters must be given explicitly:
|
||||
|
||||
::
|
||||
|
||||
*params> :t addAll
|
||||
addAll : Nat -> Nat -> Nat -> Nat
|
||||
|
||||
Parameters blocks can be nested, and can also include data declarations,
|
||||
in which case the parameters are added explicitly to all type and data
|
||||
constructors. They may also be dependent types with implicit arguments:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
parameters (y : Nat, xs : Vect x a)
|
||||
data Vects : Type -> Type where
|
||||
MkVects : Vect y a -> Vects a
|
||||
|
||||
append : Vects a -> Vect (x + y) a
|
||||
append (MkVects ys) = xs ++ ys
|
||||
|
||||
To use ``Vects`` or ``append`` outside the block, we must also give the
|
||||
``xs`` and ``y`` arguments. Here, we can use placeholders for the values
|
||||
which can be inferred by the type checker:
|
||||
|
||||
::
|
||||
|
||||
*params> show (append _ _ (MkVects _ [1,2,3] [4,5,6]))
|
||||
"[1, 2, 3, 4, 5, 6]" : String
|
77
docs/tutorial/packages.rst
Normal file
77
docs/tutorial/packages.rst
Normal file
@ -0,0 +1,77 @@
|
||||
.. _sect-packages:
|
||||
|
||||
========
|
||||
Packages
|
||||
========
|
||||
|
||||
Idris includes a simple system for building packages from a
|
||||
package description file. These files can be used with the Idris
|
||||
compiler to manage the development process of your Idris
|
||||
programmes and packages.
|
||||
|
||||
Package Descriptions
|
||||
--------------------
|
||||
|
||||
A package description includes the following:
|
||||
|
||||
+ A header, consisting of the keyword package followed by the package name.
|
||||
+ Fields describing package contents, ``<field> = <value>``
|
||||
|
||||
At least one field must be the modules field, where the value is a
|
||||
comma separated list of modules. For example, a library test which
|
||||
has two modules ``foo.idr`` and ``bar.idr`` as source files would be
|
||||
written as follows::
|
||||
|
||||
package foo
|
||||
|
||||
modules = foo, bar
|
||||
|
||||
Other examples of package files can be found in the ``libs`` directory
|
||||
of the main Idris repository, and in `third-party libraries <https://github.com/idris-lang/Idris-dev/wiki/Libraries>`_.
|
||||
|
||||
Other common fields which may be present in an ``ipkg`` file are:
|
||||
|
||||
+ ``sourcedir = <dir>``, which gives the directory (relative to the
|
||||
current directory) which contains the source. Default is the current
|
||||
directory.
|
||||
|
||||
+ ``executable = <output>``, which gives the name of the executable
|
||||
file to generate.
|
||||
|
||||
+ ``main = <module>``, which gives the name of the main module, and
|
||||
must be present if the executable field is present.
|
||||
|
||||
+ ``opts = "<idris options>"``, which allows options (such as other
|
||||
packages) to be passed to Idris.
|
||||
|
||||
In more advanced cases, particularly to support creating bindings to
|
||||
external ``C`` libraries, the following options are available:
|
||||
|
||||
+ ``makefile = <file>``, which specifies a ``Makefile``, to be built
|
||||
before the Idris modules, for example to support linking with a
|
||||
``C`` library.
|
||||
|
||||
+ ``libs = <libs>``, which gives a comma separated list of libraries
|
||||
which must be present for the package to be usable.
|
||||
|
||||
+ ``objs = <objs>``, which gives a comma separated list of additional
|
||||
object files to be installed, perhaps generated by the ``Makefile``.
|
||||
|
||||
Using Package files
|
||||
-------------------
|
||||
|
||||
Given an Idris package file ``text.ipkg`` it can be used with the Idris compiler as follows:
|
||||
|
||||
+ ``idris --build test.ipkg`` will build all modules in the package
|
||||
|
||||
+ ``idris --install test.ipkg`` will install the package, making it
|
||||
accessible by other Idris libraries and programs.
|
||||
|
||||
+ ``idris --clean test.ipkg`` will delete all intermediate code and
|
||||
executable files generated when building.
|
||||
|
||||
Once the test package has been installed, the command line option
|
||||
``--package test`` makes it accessible (abbreviated to ``-p test``).
|
||||
For example::
|
||||
|
||||
idris -p test Main.idr
|
268
docs/tutorial/provisional.rst
Normal file
268
docs/tutorial/provisional.rst
Normal file
@ -0,0 +1,268 @@
|
||||
.. _sect-provisional:
|
||||
|
||||
=======================
|
||||
Provisional Definitions
|
||||
=======================
|
||||
|
||||
Sometimes when programming with dependent types, the type required by
|
||||
the type checker and the type of the program we have written will be
|
||||
different (in that they do not have the same normal form), but
|
||||
nevertheless provably equal. For example, recall the ``parity``
|
||||
function:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
data Parity : Nat -> Type where
|
||||
Even : Parity (n + n)
|
||||
Odd : Parity (S (n + n))
|
||||
|
||||
We’d like to implement this as follows:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
parity : (n:Nat) -> Parity n
|
||||
parity Z = Even {n=Z}
|
||||
parity (S Z) = Odd {n=Z}
|
||||
parity (S (S k)) with (parity k)
|
||||
parity (S (S (j + j))) | Even = Even {n=S j}
|
||||
parity (S (S (S (j + j)))) | Odd = Odd {n=S j}
|
||||
|
||||
This simply states that zero is even, one is odd, and recursively, the
|
||||
parity of ``k+2`` is the same as the parity of ``k``. Explicitly marking
|
||||
the value of ``n`` is even and odd is necessary to help type inference.
|
||||
Unfortunately, the type checker rejects this:
|
||||
|
||||
::
|
||||
|
||||
viewsbroken.idr:12:10:When elaborating right hand side of ViewsBroken.parity:
|
||||
Can't unify
|
||||
Parity (plus (S j) (S j))
|
||||
with
|
||||
Parity (S (S (plus j j)))
|
||||
|
||||
Specifically:
|
||||
Can't unify
|
||||
plus (S j) (S j)
|
||||
with
|
||||
S (S (plus j j))
|
||||
|
||||
The type checker is telling us that ``(j+1)+(j+1)`` and ``2+j+j`` do not
|
||||
normalise to the same value. This is because ``plus`` is defined by
|
||||
recursion on its first argument, and in the second value, there is a
|
||||
successor symbol on the second argument, so this will not help with
|
||||
reduction. These values are obviously equal — how can we rewrite the
|
||||
program to fix this problem?
|
||||
|
||||
Provisional definitions
|
||||
-----------------------
|
||||
|
||||
*Provisional definitions* help with this problem by allowing us to defer
|
||||
the proof details until a later point. There are two main reasons why
|
||||
they are useful.
|
||||
|
||||
- When *prototyping*, it is useful to be able to test programs before
|
||||
finishing all the details of proofs.
|
||||
|
||||
- When *reading* a program, it is often much clearer to defer the proof
|
||||
details so that they do not distract the reader from the underlying
|
||||
algorithm.
|
||||
|
||||
Provisional definitions are written in the same way as ordinary
|
||||
definitions, except that they introduce the right hand side with a
|
||||
``?=`` rather than ``=``. We define ``parity`` as follows:
|
||||
|
||||
When written in this form, instead of reporting a type error, Idris
|
||||
will insert a metavariable standing for a theorem which will correct the
|
||||
type error. Idris tells us we have two proof obligations, with names
|
||||
generated from the module and function names:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
*views> :m
|
||||
Global metavariables:
|
||||
[views.parity_lemma_2,views.parity_lemma_1]
|
||||
|
||||
The first of these has the following type:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
*views> :p views.parity_lemma_1
|
||||
|
||||
---------------------------------- (views.parity_lemma_1) --------
|
||||
{hole0} : (j : Nat) -> (Parity (plus (S j) (S j))) -> Parity (S (S (plus j j)))
|
||||
|
||||
-views.parity_lemma_1>
|
||||
|
||||
The two arguments are ``j``, the variable in scope from the pattern
|
||||
match, and ``value``, which is the value we gave in the right hand side
|
||||
of the provisional definition. Our goal is to rewrite the type so that
|
||||
we can use this value. We can achieve this using the following theorem
|
||||
from the prelude:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
plusSuccRightSucc : (left : Nat) -> (right : Nat) ->
|
||||
S (left + right) = left + (S right)
|
||||
|
||||
We need to use ``compute`` again to unfold the definition of ``plus``:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
-views.parity_lemma_1> compute
|
||||
|
||||
|
||||
---------------------------------- (views.parity_lemma_1) --------
|
||||
{hole0} : (j : Nat) -> (Parity (S (plus j (S j)))) -> Parity (S (S (plus j j)))
|
||||
|
||||
After applying ``intros`` we have:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
-views.parity_lemma_1> intros
|
||||
|
||||
j : Nat
|
||||
value : Parity (S (plus j (S j)))
|
||||
---------------------------------- (views.parity_lemma_1) --------
|
||||
{hole2} : Parity (S (S (plus j j)))
|
||||
|
||||
Then we apply the ``plusSuccRightSucc`` rewrite rule, symmetrically, to
|
||||
``j`` and ``j``, giving:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
-views.parity_lemma_1> rewrite sym (plusSuccRightSucc j j)
|
||||
|
||||
j : Nat
|
||||
value : Parity (S (plus j (S j)))
|
||||
---------------------------------- (views.parity_lemma_1) --------
|
||||
{hole3} : Parity (S (plus j (S j)))
|
||||
|
||||
``sym`` is a function, defined in the library, which reverses the order
|
||||
of the rewrite:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
sym : l = r -> r = l
|
||||
sym Refl = Refl
|
||||
|
||||
We can complete this proof using the ``trivial`` tactic, which finds
|
||||
``value`` in the premises. The proof of the second lemma proceeds in
|
||||
exactly the same way.
|
||||
|
||||
We can now test the ``natToBin`` function from Section :ref:`sect-nattobin`
|
||||
at the prompt. The number 42 is 101010 in binary. The binary digits are
|
||||
reversed:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
*views> show (natToBin 42)
|
||||
"[False, True, False, True, False, True]" : String
|
||||
|
||||
Suspension of Disbelief
|
||||
-----------------------
|
||||
|
||||
Idris requires that proofs be complete before compiling programs
|
||||
(although evaluation at the prompt is possible without proof details).
|
||||
Sometimes, especially when prototyping, it is easier not to have to do
|
||||
this. It might even be beneficial to test programs before attempting to
|
||||
prove things about them — if testing finds an error, you know you had
|
||||
better not waste your time proving something!
|
||||
|
||||
Therefore, Idris provides a built-in coercion function, which allows
|
||||
you to use a value of the incorrect types:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
believe_me : a -> b
|
||||
|
||||
Obviously, this should be used with extreme caution. It is useful when
|
||||
prototyping, and can also be appropriate when asserting properties of
|
||||
external code (perhaps in an external C library). The “proof” of
|
||||
``views.parity_lemma_1`` using this is:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
views.parity_lemma_2 = proof {
|
||||
intro;
|
||||
intro;
|
||||
exact believe_me value;
|
||||
}
|
||||
|
||||
The ``exact`` tactic allows us to provide an exact value for the proof.
|
||||
In this case, we assert that the value we gave was correct.
|
||||
|
||||
Example: Binary numbers
|
||||
-----------------------
|
||||
|
||||
Previously, we implemented conversion to binary numbers using the
|
||||
``Parity`` view. Here, we show how to use the same view to implement a
|
||||
verified conversion to binary. We begin by indexing binary numbers over
|
||||
their ``Nat`` equivalent. This is a common pattern, linking a
|
||||
representation (in this case ``Binary``) with a meaning (in this case
|
||||
``Nat``):
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
data Binary : Nat -> Type where
|
||||
bEnd : Binary Z
|
||||
bO : Binary n -> Binary (n + n)
|
||||
bI : Binary n -> Binary (S (n + n))
|
||||
|
||||
``bO`` and ``bI`` take a binary number as an argument and effectively
|
||||
shift it one bit left, adding either a zero or one as the new least
|
||||
significant bit. The index, ``n + n`` or ``S (n + n)`` states the result
|
||||
that this left shift then add will have to the meaning of the number.
|
||||
This will result in a representation with the least significant bit at
|
||||
the front.
|
||||
|
||||
Now a function which converts a Nat to binary will state, in the type,
|
||||
that the resulting binary number is a faithful representation of the
|
||||
original Nat:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
natToBin : (n:Nat) -> Binary n
|
||||
|
||||
The ``Parity`` view makes the definition fairly simple — halving the
|
||||
number is effectively a right shift after all — although we need to use
|
||||
a provisional definition in the odd case:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
natToBin : (n:Nat) -> Binary n
|
||||
natToBin Z = bEnd
|
||||
natToBin (S k) with (parity k)
|
||||
natToBin (S (j + j)) | even = bI (natToBin j)
|
||||
natToBin (S (S (j + j))) | odd ?= bO (natToBin (S j))
|
||||
|
||||
The problem with the odd case is the same as in the definition of
|
||||
``parity``, and the proof proceeds in the same way:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
natToBin_lemma_1 = proof {
|
||||
intro;
|
||||
intro;
|
||||
rewrite sym (plusSuccRightSucc j j);
|
||||
trivial;
|
||||
}
|
||||
|
||||
To finish, we’ll implement a main program which reads an integer from
|
||||
the user and outputs it in binary.
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
main : IO ()
|
||||
main = do putStr "Enter a number: "
|
||||
x <- getLine
|
||||
print (natToBin (fromInteger (cast x)))
|
||||
|
||||
For this to work, of course, we need a ``Show`` instance for
|
||||
``Binary n``:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
instance Show (Binary n) where
|
||||
show (bO x) = show x ++ "0"
|
||||
show (bI x) = show x ++ "1"
|
||||
show bEnd = ""
|
90
docs/tutorial/starting.rst
Normal file
90
docs/tutorial/starting.rst
Normal file
@ -0,0 +1,90 @@
|
||||
.. _sect-starting:
|
||||
|
||||
===============
|
||||
Getting Started
|
||||
===============
|
||||
|
||||
Prerequisites
|
||||
-------------
|
||||
|
||||
Before installing Idris, you will need to make sure you have all
|
||||
of the necessary libraries and tools. You will need:
|
||||
|
||||
- A fairly recent Haskell platform. Version 2013.2.0.0 should be
|
||||
sufficiently recent, though it is better to be completely up to
|
||||
date.
|
||||
|
||||
- The *GNU Multiple Precision Arithmetic Library* (GMP) is available
|
||||
from MacPorts/Homebrew and all major Linux distributions.
|
||||
|
||||
Downloading and Installing
|
||||
--------------------------
|
||||
|
||||
The easiest way to install Idris, if you have all of the
|
||||
prerequisites, is to type:
|
||||
|
||||
::
|
||||
|
||||
cabal update; cabal install idris
|
||||
|
||||
This will install the latest version released on Hackage, along with
|
||||
any dependencies. If, however, you would like the most up to date
|
||||
development version you can find it, as well as build intructions, on
|
||||
GitHub at: https://github.com/idris-lang/Idris-dev.
|
||||
|
||||
To check that installation has succeeded, and to write your first
|
||||
Idris program, create a file called “``hello.idr``” containing the
|
||||
following text:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
module Main
|
||||
|
||||
main : IO ()
|
||||
main = putStrLn "Hello world"
|
||||
|
||||
If you are familiar with Haskell, it should be fairly clear what the
|
||||
program is doing and how it works, but if not, we will explain the
|
||||
details later. You can compile the program to an executable by
|
||||
entering ``idris hello.idr -o hello`` at the shell prompt. This will
|
||||
create an executable called ``hello``, which you can run:
|
||||
|
||||
::
|
||||
|
||||
$ idris hello.idr -o hello
|
||||
$ ./hello
|
||||
Hello world
|
||||
|
||||
Note that the ``$`` indicates the shell prompt! Should the Idris
|
||||
executable not be found please ensure that you have added
|
||||
``~/.cabal/bin`` to your ``$PATH`` environment variable. Mac OS X
|
||||
users may find they need to use ``~/Library/Haskell/bin``
|
||||
instead. Some useful options to the Idris command are:
|
||||
|
||||
- ``-o prog`` to compile to an executable called ``prog``.
|
||||
|
||||
- ``--check`` type check the file and its dependencies without
|
||||
starting the interactive environment.
|
||||
|
||||
- ``--help`` display usage summary and command line options
|
||||
|
||||
The Interactive Environment
|
||||
---------------------------
|
||||
|
||||
Entering ``idris`` at the shell prompt starts up the interactive
|
||||
environment. You should see something like the following:
|
||||
|
||||
.. literalinclude:: ../listing/idris-prompt-start.txt
|
||||
|
||||
This gives a ``ghci`` style interface which allows evaluation of, as
|
||||
well as type checking of, expressions; theorem proving, compilation;
|
||||
editing; and various other operations. The command ``:?`` gives a list
|
||||
of supported commands. Below, we see an example run in
|
||||
which ``hello.idr`` is loaded, the type of ``main`` is checked and
|
||||
then the program is compiled to the executable ``hello``. Type
|
||||
checking a file, if successful, creates a bytecode version of the file
|
||||
(in this case ``hello.ibc``) to speed up loading in future. The
|
||||
bytecode is regenerated if the source file changes.
|
||||
|
||||
.. _run1:
|
||||
.. literalinclude:: ../listing/idris-prompt-helloworld.txt
|
208
docs/tutorial/syntax.rst
Normal file
208
docs/tutorial/syntax.rst
Normal file
@ -0,0 +1,208 @@
|
||||
Syntax Extensions
|
||||
=================
|
||||
|
||||
supports the implementation of *Embedded Domain Specific Languages*
|
||||
(EDSLs) in several ways [1]_. One way, as we have already seen, is
|
||||
through extending ``do`` notation. Another important way is to allow
|
||||
extension of the core syntax. In this section we describe two ways of
|
||||
extending the syntax: ``syntax`` rules and ``dsl`` notation.
|
||||
|
||||
``syntax`` rules
|
||||
----------------
|
||||
|
||||
We have seen ``if...then...else`` expressions, but these are not built
|
||||
in. Instead, we can define a function in the prelude as follows (we
|
||||
have already seen this function in Section :ref:`sect-lazy`):
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
boolCase : (x:Bool) -> Lazy a -> Lazy a -> a;
|
||||
boolCase True t e = t;
|
||||
boolCase False t e = e;
|
||||
|
||||
and then extend the core syntax with a ``syntax`` declaration:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
syntax "if" [test] "then" [t] "else" [e] = boolCase test t e;
|
||||
|
||||
The left hand side of a ``syntax`` declaration describes the syntax
|
||||
rule, and the right hand side describes its expansion. The syntax rule
|
||||
itself consists of:
|
||||
|
||||
- **Keywords** — here, ``if``, ``then`` and ``else``, which must be
|
||||
valid identifiers
|
||||
|
||||
- **Non-terminals** — included in square brackets, ``[test]``, ``[t]``
|
||||
and ``[e]`` here, which stand for arbitrary expressions. To avoid
|
||||
parsing ambiguities, these expressions cannot use syntax extensions
|
||||
at the top level (though they can be used in parentheses).
|
||||
|
||||
- **Names** — included in braces, which stand for names which may be
|
||||
bound on the right hand side.
|
||||
|
||||
- **Symbols** — included in quotations marks, e.g. ``:=``. This can
|
||||
also be used to include reserved words in syntax rules, such as
|
||||
``let`` or ``in``.
|
||||
|
||||
The limitations on the form of a syntax rule are that it must include
|
||||
at least one symbol or keyword, and there must be no repeated
|
||||
variables standing for non-terminals. Any expression can be used, but
|
||||
if there are two non-terminals in a row in a rule, only simple
|
||||
expressions may be used (that is, variables, constants, or bracketed
|
||||
expressions). Rules can use previously defined rules, but may not be
|
||||
recursive. The following syntax extensions would therefore be valid:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
syntax [var] ":=" [val] = Assign var val;
|
||||
syntax [test] "?" [t] ":" [e] = if test then t else e;
|
||||
syntax "select" [x] "from" [t] "where" [w] = SelectWhere x t w;
|
||||
syntax "select" [x] "from" [t] = Select x t;
|
||||
|
||||
Syntax macros can be further restricted to apply only in patterns (i.e.,
|
||||
only on the left hand side of a pattern match clause) or only in terms
|
||||
(i.e. everywhere but the left hand side of a pattern match clause) by
|
||||
being marked as ``pattern`` or ``term`` syntax rules. For example, we
|
||||
might define an interval as follows, with a static check that the lower
|
||||
bound is below the upper bound using ``so``:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
data Interval : Type where
|
||||
MkInterval : (lower : Float) -> (upper : Float) ->
|
||||
so (lower < upper) -> Interval
|
||||
|
||||
We can define a syntax which, in patterns, always matches ``oh`` for
|
||||
the proof argument, and in terms requires a proof term to be provided:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
pattern syntax "[" [x] "..." [y] "]" = MkInterval x y oh
|
||||
term syntax "[" [x] "..." [y] "]" = MkInterval x y ?bounds_lemma
|
||||
|
||||
In terms, the syntax ``[x...y]`` will generate a proof obligation
|
||||
``bounds_lemma`` (possibly renamed).
|
||||
|
||||
Finally, syntax rules may be used to introduce alternative binding
|
||||
forms. For example, a ``for`` loop binds a variable on each iteration:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
syntax "for" {x} "in" [xs] ":" [body] = forLoop xs (\x => body)
|
||||
|
||||
main : IO ()
|
||||
main = do for x in [1..10]:
|
||||
putStrLn ("Number " ++ show x)
|
||||
putStrLn "Done!"
|
||||
|
||||
Note that we have used the ``{x}`` form to state that ``x`` represents
|
||||
a bound variable, substituted on the right hand side. We have also put
|
||||
``in`` in quotation marks since it is already a reserved word.
|
||||
|
||||
``dsl`` notation
|
||||
----------------
|
||||
|
||||
The well-typed interpreter in Section :ref:`sect-interp` is a simple
|
||||
example of a common programming pattern with dependent types. Namely:
|
||||
describe an *object language* and its type system with dependent types
|
||||
to guarantee that only well-typed programs can be represented, then
|
||||
program using that representation. Using this approach we can, for
|
||||
example, write programs for serialising binary data [2]_ or running
|
||||
concurrent processes safely [3]_.
|
||||
|
||||
Unfortunately, the form of object language programs makes it rather
|
||||
hard to program this way in practice. Recall the factorial program in
|
||||
``Expr`` for example:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
fact : Expr G (TyFun TyInt TyInt)
|
||||
fact = Lam (If (Op (==) (Var Stop) (Val 0))
|
||||
(Val 1) (Op (*) (app fact (Op (-) (Var Stop) (Val 1)))
|
||||
(Var Stop)))
|
||||
|
||||
Since this is a particularly useful pattern, Idris provides syntax
|
||||
overloading [1]_ to make it easier to program in such object
|
||||
languages:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
mkLam : TTName -> Expr (t::g) t' -> Expr g (TyFun t t')
|
||||
mkLam _ body = Lam body
|
||||
|
||||
dsl expr
|
||||
variable = Var
|
||||
index_first = Stop
|
||||
index_next = Pop
|
||||
lambda = mkLam
|
||||
|
||||
A ``dsl`` block describes how each syntactic construct is represented
|
||||
in an object language. Here, in the ``expr`` language, any variable is
|
||||
translated to the ``Var`` constructor, using ``Pop`` and ``Stop`` to
|
||||
construct the de Bruijn index (i.e., to count how many bindings since
|
||||
the variable itself was bound); and any lambda is translated to a
|
||||
``Lam`` constructor. The ``mkLam`` function simply ignores its first
|
||||
argument, which is the name that the user chose for the variable. It
|
||||
is also possible to overload ``let`` and dependent function syntax
|
||||
(``pi``) in this way. We can now write ``fact`` as follows:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
fact : Expr G (TyFun TyInt TyInt)
|
||||
fact = expr (\x => If (Op (==) x (Val 0))
|
||||
(Val 1) (Op (*) (app fact (Op (-) x (Val 1))) x))
|
||||
|
||||
In this new version, ``expr`` declares that the next expression will
|
||||
be overloaded. We can take this further, using idiom brackets, by
|
||||
declaring:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
(<$>) : (f : Lazy (Expr G (TyFun a t))) -> Expr G a -> Expr G t
|
||||
(<$>) f a = App f a
|
||||
|
||||
pure : Expr G a -> Expr G a
|
||||
pure = id
|
||||
|
||||
Note that there is no need for these to be part of an instance of
|
||||
``Applicative``, since idiom bracket notation translates directly to
|
||||
the names ``<*>`` and ``pure``, and ad-hoc type-directed overloading
|
||||
is allowed. We can now say:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
fact : Expr G (TyFun TyInt TyInt)
|
||||
fact = expr (\x => If (Op (==) x (Val 0))
|
||||
(Val 1) (Op (*) [| fact (Op (-) x (Val 1)) |] x))
|
||||
|
||||
With some more ad-hoc overloading and type class instances, and a new
|
||||
syntax rule, we can even go as far as:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
syntax "IF" [x] "THEN" [t] "ELSE" [e] = If x t e
|
||||
|
||||
fact : Expr G (TyFun TyInt TyInt)
|
||||
fact = expr (\x => IF x == 0 THEN 1 ELSE [| fact (x - 1) |] * x)
|
||||
|
||||
|
||||
.. [1] Edwin Brady and Kevin Hammond. 2012. Resource-Safe systems
|
||||
programming with embedded domain specific languages. In
|
||||
Proceedings of the 14th international conference on Practical
|
||||
Aspects of Declarative Languages (PADL'12), Claudio Russo and
|
||||
Neng-Fa Zhou (Eds.). Springer-Verlag, Berlin, Heidelberg,
|
||||
242-257. DOI=10.1007/978-3-642-27694-1_18
|
||||
http://dx.doi.org/10.1007/978-3-642-27694-1_18
|
||||
|
||||
.. [2] Edwin C. Brady. 2011. IDRIS ---: systems programming meets full
|
||||
dependent types. In Proceedings of the 5th ACM workshop on
|
||||
Programming languages meets program verification (PLPV
|
||||
'11). ACM, New York, NY, USA,
|
||||
43-54. DOI=10.1145/1929529.1929536
|
||||
http://doi.acm.org/10.1145/1929529.1929536
|
||||
|
||||
.. [3] Edwin Brady and Kevin Hammond. 2010. Correct-by-Construction
|
||||
Concurrency: Using Dependent Types to Verify Implementations of
|
||||
Effectful Resource Usage Protocols. Fundam. Inf. 102, 2 (April
|
||||
2010), 145-176. http://dl.acm.org/citation.cfm?id=1883636
|
447
docs/tutorial/theorems.rst
Normal file
447
docs/tutorial/theorems.rst
Normal file
@ -0,0 +1,447 @@
|
||||
.. _sect-theorems:
|
||||
|
||||
===============
|
||||
Theorem Proving
|
||||
===============
|
||||
|
||||
Equality
|
||||
--------
|
||||
|
||||
Idris allows propositional equalities to be declared, allowing theorems about
|
||||
programs to be stated and proved. Equality is built in, but conceptually
|
||||
has the following definition:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
data (=) : a -> b -> Type where
|
||||
Refl : x = x
|
||||
|
||||
Equalities can be proposed between any values of any types, but the only
|
||||
way to construct a proof of equality is if values actually are equal.
|
||||
For example:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
fiveIsFive : 5 = 5
|
||||
fiveIsFive = Refl
|
||||
|
||||
twoPlusTwo : 2 + 2 = 4
|
||||
twoPlusTwo = Refl
|
||||
|
||||
.. _sect-empty:
|
||||
|
||||
The Empty Type
|
||||
--------------
|
||||
|
||||
There is an empty type, :math:`\bot`, which has no constructors. It is
|
||||
therefore impossible to construct an element of the empty type, at least
|
||||
without using a partially defined or general recursive function (see
|
||||
Section :ref:`sect-totality` for more details). We can therefore use the
|
||||
empty type to prove that something is impossible, for example zero is
|
||||
never equal to a successor:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
disjoint : (n : Nat) -> Z = S n -> Void
|
||||
disjoint n p = replace {P = disjointTy} p ()
|
||||
where
|
||||
disjointTy : Nat -> Type
|
||||
disjointTy Z = ()
|
||||
disjointTy (S k) = Void
|
||||
|
||||
There is no need to worry too much about how this function works —
|
||||
essentially, it applies the library function ``replace``, which uses an
|
||||
equality proof to transform a predicate. Here we use it to transform a
|
||||
value of a type which can exist, the empty tuple, to a value of a type
|
||||
which can’t, by using a proof of something which can’t exist.
|
||||
|
||||
Once we have an element of the empty type, we can prove anything.
|
||||
``void`` is defined in the library, to assist with proofs by
|
||||
contradiction.
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
void : Void -> a
|
||||
|
||||
Simple Theorems
|
||||
---------------
|
||||
|
||||
When type checking dependent types, the type itself gets *normalised*.
|
||||
So imagine we want to prove the following theorem about the reduction
|
||||
behaviour of ``plus``:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
plusReduces : (n:Nat) -> plus Z n = n
|
||||
|
||||
We’ve written down the statement of the theorem as a type, in just the
|
||||
same way as we would write the type of a program. In fact there is no
|
||||
real distinction between proofs and programs. A proof, as far as we are
|
||||
concerned here, is merely a program with a precise enough type to
|
||||
guarantee a particular property of interest.
|
||||
|
||||
We won’t go into details here, but the Curry-Howard correspondence [1]_
|
||||
explains this relationship. The proof itself is trivial, because
|
||||
``plus Z n`` normalises to ``n`` by the definition of ``plus``:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
plusReduces n = Refl
|
||||
|
||||
It is slightly harder if we try the arguments the other way, because
|
||||
plus is defined by recursion on its first argument. The proof also works
|
||||
by recursion on the first argument to ``plus``, namely ``n``.
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
plusReducesZ : (n:Nat) -> n = plus n Z
|
||||
plusReducesZ Z = Refl
|
||||
plusReducesZ (S k) = cong (plusReducesZ k)
|
||||
|
||||
``cong`` is a function defined in the library which states that equality
|
||||
respects function application:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
cong : {f : t -> u} -> a = b -> f a = f b
|
||||
|
||||
We can do the same for the reduction behaviour of plus on successors:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
plusReducesS : (n:Nat) -> (m:Nat) -> S (plus n m) = plus n (S m)
|
||||
plusReducesS Z m = Refl
|
||||
plusReducesS (S k) m = cong (plusReducesS k m)
|
||||
|
||||
Even for trival theorems like these, the proofs are a little tricky to
|
||||
construct in one go. When things get even slightly more complicated, it
|
||||
becomes too much to think about to construct proofs in this ‘batch
|
||||
mode’.
|
||||
|
||||
Idris provides interactive editing capabilities, which can help with
|
||||
building proofs. For more details on building proofs interactively in
|
||||
an editor, see :ref:`proofs-index`. In the rest of this section, we discuss
|
||||
Idris's interactive proof mode.
|
||||
|
||||
Interactive theorem proving
|
||||
---------------------------
|
||||
|
||||
Instead of writing the proof in one go, we can use Idris’s interactive proof
|
||||
mode. To do this, we write the general *structure* of the proof, and use
|
||||
the interactive mode to complete the details. We’ll be constructing the
|
||||
proof by *induction*, so we write the cases for ``Z`` and ``S``, with a
|
||||
recursive call in the ``S`` case giving the inductive hypothesis, and
|
||||
insert *metavariables* for the rest of the definition:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
plusReducesZ' : (n:Nat) -> n = plus n Z
|
||||
plusReducesZ' Z = ?plusredZ_Z
|
||||
plusReducesZ' (S k) = let ih = plusReducesZ' k in
|
||||
?plusredZ_S
|
||||
|
||||
On running , two global names are created, ``plusredZ_Z`` and
|
||||
``plusredZ_S``, with no definition. We can use the ``:m`` command at the
|
||||
prompt to find out which metavariables are still to be solved (or, more
|
||||
precisely, which functions exist but have no definitions), then the
|
||||
``:t`` command to see their types:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
*theorems> :m
|
||||
Global metavariables:
|
||||
[plusredZ_S,plusredZ_Z]
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
*theorems> :t plusredZ_Z
|
||||
plusredZ_Z : Z = plus Z Z
|
||||
|
||||
*theorems> :t plusredZ_S
|
||||
plusredZ_S : (k : Nat) -> (k = plus k Z) -> S k = plus (S k) Z
|
||||
|
||||
The ``:p`` command enters interactive proof mode, which can be used to
|
||||
complete the missing definitions.
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
*theorems> :p plusredZ_Z
|
||||
|
||||
---------------------------------- (plusredZ_Z) --------
|
||||
{hole0} : Z = plus Z Z
|
||||
|
||||
This gives us a list of premises (above the line; there are none here)
|
||||
and the current goal (below the line; named ``{hole0}`` here). At the
|
||||
prompt we can enter tactics to direct the construction of the proof. In
|
||||
this case, we can normalise the goal with the ``compute`` tactic:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
-plusredZ_Z> compute
|
||||
|
||||
---------------------------------- (plusredZ_Z) --------
|
||||
{hole0} : Z = Z
|
||||
|
||||
Now we have to prove that ``Z`` equals ``Z``, which is easy to prove by
|
||||
``Refl``. To apply a function, such as ``Refl``, we use ``refine`` which
|
||||
introduces subgoals for each of the function’s explicit arguments
|
||||
(``Refl`` has none):
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
-plusredZ_Z> refine Refl
|
||||
plusredZ_Z: no more goals
|
||||
|
||||
Here, we could also have used the ``trivial`` tactic, which tries to
|
||||
refine by ``Refl``, and if that fails, tries to refine by each name in
|
||||
the local context. When a proof is complete, we use the ``qed`` tactic
|
||||
to add the proof to the global context, and remove the metavariable from
|
||||
the unsolved metavariables list. This also outputs a trace of the proof:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
-plusredZ_Z> qed
|
||||
plusredZ_Z = proof
|
||||
compute
|
||||
refine Refl
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
*theorems> :m
|
||||
Global metavariables:
|
||||
[plusredZ_S]
|
||||
|
||||
The ``:addproof`` command, at the interactive prompt, will add the proof
|
||||
to the source file (effectively in an appendix). Let us now prove the
|
||||
other required lemma, ``plusredZ_S``:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
*theorems> :p plusredZ_S
|
||||
|
||||
---------------------------------- (plusredZ_S) --------
|
||||
{hole0} : (k : Nat) -> (k = plus k Z) -> S k = plus (S k) Z
|
||||
|
||||
In this case, the goal is a function type, using ``k`` (the argument
|
||||
accessible by pattern matching) and ``ih`` — the local variable
|
||||
containing the result of the recursive call. We can introduce these as
|
||||
premisses using the ``intro`` tactic twice (or ``intros``, which
|
||||
introduces all arguments as premisses). This gives:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
k : Nat
|
||||
ih : k = plus k Z
|
||||
---------------------------------- (plusredZ_S) --------
|
||||
{hole2} : S k = plus (S k) Z
|
||||
|
||||
Since plus is defined by recursion on its first argument, the term
|
||||
``plus (S k) Z`` in the goal can be simplified, so we use ``compute``.
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
k : Nat
|
||||
ih : k = plus k Z
|
||||
---------------------------------- (plusredZ_S) --------
|
||||
{hole2} : S k = S (plus k Z)
|
||||
|
||||
We know, from the type of ``ih``, that ``k = plus k Z``, so we would
|
||||
like to use this knowledge to replace ``plus k Z`` in the goal with
|
||||
``k``. We can achieve this with the ``rewrite`` tactic:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
-plusredZ_S> rewrite ih
|
||||
|
||||
k : Nat
|
||||
ih : k = plus k Z
|
||||
---------------------------------- (plusredZ_S) --------
|
||||
{hole3} : S k = S k
|
||||
|
||||
-plusredZ_S>
|
||||
|
||||
The ``rewrite`` tactic takes an equality proof as an argument, and tries
|
||||
to rewrite the goal using that proof. Here, it results in an equality
|
||||
which is trivially provable:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
-plusredZ_S> trivial
|
||||
plusredZ_S: no more goals
|
||||
-plusredZ_S> qed
|
||||
plusredZ_S = proof {
|
||||
intros;
|
||||
rewrite ih;
|
||||
trivial;
|
||||
}
|
||||
|
||||
Again, we can add this proof to the end of our source file using the
|
||||
``:addproof`` command at the interactive prompt.
|
||||
|
||||
.. _sect-totality:
|
||||
|
||||
Totality Checking
|
||||
-----------------
|
||||
|
||||
If we really want to trust our proofs, it is important that they are
|
||||
defined by *total* functions — that is, a function which is defined for
|
||||
all possible inputs and is guaranteed to terminate. Otherwise we could
|
||||
construct an element of the empty type, from which we could prove
|
||||
anything:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
-- making use of 'hd' being partially defined
|
||||
empty1 : Void
|
||||
empty1 = hd [] where
|
||||
hd : List a -> a
|
||||
hd (x :: xs) = x
|
||||
|
||||
-- not terminating
|
||||
empty2 : Void
|
||||
empty2 = empty2
|
||||
|
||||
Internally, Idris checks every definition for totality, and we can check at
|
||||
the prompt with the ``:total`` command. We see that neither of the above
|
||||
definitions is total:
|
||||
|
||||
::
|
||||
|
||||
*theorems> :total empty1
|
||||
possibly not total due to: empty1#hd
|
||||
not total as there are missing cases
|
||||
*theorems> :total empty2
|
||||
possibly not total due to recursive path empty2
|
||||
|
||||
Note the use of the word “possibly” — a totality check can, of course,
|
||||
never be certain due to the undecidability of the halting problem. The
|
||||
check is, therefore, conservative. It is also possible (and indeed
|
||||
advisable, in the case of proofs) to mark functions as total so that it
|
||||
will be a compile time error for the totality check to fail:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
total empty2 : Void
|
||||
empty2 = empty2
|
||||
|
||||
::
|
||||
|
||||
Type checking ./theorems.idr
|
||||
theorems.idr:25:empty2 is possibly not total due to recursive path empty2
|
||||
|
||||
Reassuringly, our proof in Section :ref:`sect-empty` that the zero and
|
||||
successor constructors are disjoint is total:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
*theorems> :total disjoint
|
||||
Total
|
||||
|
||||
The totality check is, necessarily, conservative. To be recorded as
|
||||
total, a function ``f`` must:
|
||||
|
||||
- Cover all possible inputs
|
||||
|
||||
- Be *well-founded* — i.e. by the time a sequence of (possibly
|
||||
mutually) recursive calls reaches ``f`` again, it must be possible to
|
||||
show that one of its arguments has decreased.
|
||||
|
||||
- Not use any data types which are not *strictly positive*
|
||||
|
||||
- Not call any non-total functions
|
||||
|
||||
Directives and Compiler Flags for Totality
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
By default, Idris allows all well-typed definitions, whether total or not.
|
||||
However, it is desirable for functions to be total as far as possible, as this
|
||||
provides a guarantee that they provide a result for all possible inputs, in
|
||||
finite time. It is possible to make total functions a requirement, either:
|
||||
|
||||
- By using the ``--total`` compiler flag.
|
||||
|
||||
- By adding a ``%default total`` directive to a source file. All
|
||||
definitions after this will be required to be total, unless
|
||||
explicitly flagged as ``partial``.
|
||||
|
||||
All functions *after* a ``%default total`` declaration are required to
|
||||
be total. Correspondingly, after a ``%default partial`` declaration, the
|
||||
requirement is relaxed.
|
||||
|
||||
Finally, the compiler flag ``--warnpartial`` causes to print a warning
|
||||
for any undeclared partial function.
|
||||
|
||||
Totality checking issues
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Please note that the totality checker is not perfect! Firstly, it is
|
||||
necessarily conservative due to the undecidability of the halting
|
||||
problem, so many programs which *are* total will not be detected as
|
||||
such. Secondly, the current implementation has had limited effort put
|
||||
into it so far, so there may still be cases where it believes a function
|
||||
is total which is not. Do not rely on it for your proofs yet!
|
||||
|
||||
Hints for totality
|
||||
~~~~~~~~~~~~~~~~~~
|
||||
|
||||
In cases where you believe a program is total, but Idris does not agree, it is
|
||||
possible to give hints to the checker to give more detail for a termination
|
||||
argument. The checker works by ensuring that all chains of recursive calls
|
||||
eventually lead to one of the arguments decreasing towards a base case, but
|
||||
sometimes this is hard to spot. For example, the following definition cannot be
|
||||
checked as ``total`` because the checker cannot decide that ``filter (<= x) xs``
|
||||
will always be smaller than ``(x :: xs)``:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
qsort : Ord a => List a -> List a
|
||||
qsort [] = []
|
||||
qsort (x :: xs)
|
||||
= qsort (filter (< x) xs) ++
|
||||
(x :: qsort (filter (>= x) xs))
|
||||
|
||||
The function ``assert_smaller``, defined in the Prelude, is intended to
|
||||
address this problem:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
assert_smaller : a -> a -> a
|
||||
assert_smaller x y = y
|
||||
|
||||
It simply evaluates to its second argument, but also asserts to the
|
||||
totality checker that ``y`` is structurally smaller than ``x``. This can
|
||||
be used to explain the reasoning for totality if the checker cannot work
|
||||
it out itself. The above example can now be written as:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
total
|
||||
qsort : Ord a => List a -> List a
|
||||
qsort [] = []
|
||||
qsort (x :: xs)
|
||||
= qsort (assert_smaller (x :: xs) (filter (< x) xs)) ++
|
||||
(x :: qsort (assert_smaller (x :: xs) (filter (>= x) xs)))
|
||||
|
||||
The expression ``assert_smaller (x :: xs) (filter (<= x) xs)`` asserts
|
||||
that the result of the filter will always be smaller than the pattern
|
||||
``(x :: xs)``.
|
||||
|
||||
In more extreme cases, the function ``assert_total`` marks a
|
||||
subexpression as always being total:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
assert_total : a -> a
|
||||
assert_total x = x
|
||||
|
||||
In general, this function should be avoided, but it can be very useful
|
||||
when reasoning about primitives or externally defined functions (for
|
||||
example from a C library) where totality can be shown by an external
|
||||
argument.
|
||||
|
||||
|
||||
.. [1] Timothy G. Griffin. 1989. A formulae-as-type notion of
|
||||
control. In Proceedings of the 17th ACM SIGPLAN-SIGACT
|
||||
symposium on Principles of programming languages (POPL
|
||||
'90). ACM, New York, NY, USA, 47-58. DOI=10.1145/96709.96714
|
||||
http://doi.acm.org/10.1145/96709.96714
|
1048
docs/tutorial/typesfuns.rst
Normal file
1048
docs/tutorial/typesfuns.rst
Normal file
File diff suppressed because it is too large
Load Diff
98
docs/tutorial/views.rst
Normal file
98
docs/tutorial/views.rst
Normal file
@ -0,0 +1,98 @@
|
||||
.. _sec-views:
|
||||
|
||||
=============================
|
||||
Views and the “``with``” rule
|
||||
=============================
|
||||
|
||||
Dependent pattern matching
|
||||
--------------------------
|
||||
|
||||
Since types can depend on values, the form of some arguments can be
|
||||
determined by the value of others. For example, if we were to write
|
||||
down the implicit length arguments to ``(++)``, we’d see that the form
|
||||
of the length argument was determined by whether the vector was empty
|
||||
or not:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
(++) : Vect n a -> Vect m a -> Vect (n + m) a
|
||||
(++) {n=Z} [] ys = ys
|
||||
(++) {n=S k} (x :: xs) ys = x :: xs ++ ys
|
||||
|
||||
If ``n`` was a successor in the ``[]`` case, or zero in the ``::``
|
||||
case, the definition would not be well typed.
|
||||
|
||||
.. _sect-nattobin:
|
||||
|
||||
The ``with`` rule — matching intermediate values
|
||||
------------------------------------------------
|
||||
|
||||
Very often, we need to match on the result of an intermediate
|
||||
computation. Idris provides a construct for this, the ``with``
|
||||
rule, inspired by views in ``Epigram`` [1]_, which takes account of
|
||||
the fact that matching on a value in a dependently typed language can
|
||||
affect what we know about the forms of other values. In its simplest
|
||||
form, the ``with`` rule adds another argument to the function being
|
||||
defined, e.g. we have already seen a vector filter function, defined
|
||||
as follows:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
filter : (a -> Bool) -> Vect n a -> (p ** Vect p a)
|
||||
filter p [] = ( _ ** [] )
|
||||
filter p (x :: xs) with (filter p xs)
|
||||
| ( _ ** xs' ) = if (p x) then ( _ ** x :: xs' ) else ( _ ** xs' )
|
||||
|
||||
Here, the ``with`` clause allows us to deconstruct the result of
|
||||
``filter p xs``. Effectively, it adds this value as an extra argument,
|
||||
which we place after the vertical bar.
|
||||
|
||||
If the intermediate computation itself has a dependent type, then the
|
||||
result can affect the forms of other arguments — we can learn the form
|
||||
of one value by testing another. For example, a ``Nat`` is either even
|
||||
or odd. If it’s even it will be the sum of two equal ``Nat``.
|
||||
Otherwise, it is the sum of two equal ``Nat`` plus one:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
data Parity : Nat -> Type where
|
||||
Even : Parity (n + n)
|
||||
Odd : Parity (S (n + n))
|
||||
|
||||
We say ``Parity`` is a *view* of ``Nat``. It has a *covering function*
|
||||
which tests whether it is even or odd and constructs the predicate
|
||||
accordingly.
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
parity : (n:Nat) -> Parity n
|
||||
|
||||
We’ll come back to the definition of ``parity`` shortly. We can use it
|
||||
to write a function which converts a natural number to a list of
|
||||
binary digits (least significant first) as follows, using the ``with``
|
||||
rule:
|
||||
|
||||
.. code-block:: idris
|
||||
|
||||
natToBin : Nat -> List Bool
|
||||
natToBin Z = Nil
|
||||
natToBin k with (parity k)
|
||||
natToBin (j + j) | Even = False :: natToBin j
|
||||
natToBin (S (j + j)) | Odd = True :: natToBin j
|
||||
|
||||
The value of the result of ``parity k`` affects the form of ``k``,
|
||||
because the result of ``parity k`` depends on ``k``. So, as well as
|
||||
the patterns for the result of the intermediate computation (``Even``
|
||||
and ``odd``) right of the ``\mid``, we also write how the results
|
||||
affect the other patterns left of the :math:`\mid`. Note that there is
|
||||
a function in the patterns (``+``) and repeated occurrences of
|
||||
``j``—this is allowed because another argument has determined the form
|
||||
of these patterns.
|
||||
|
||||
We will return to this function in Section :ref:`sect-provisional` to
|
||||
complete the definition of ``parity``.
|
||||
|
||||
.. [1] Conor McBride and James McKinna. 2004. The view from the
|
||||
left. J. Funct. Program. 14, 1 (January 2004),
|
||||
69-111. DOI=10.1017/S0956796803004829
|
||||
http://dx.doi.org/10.1017/S0956796803004829ñ
|
46
idris.cabal
46
idris.cabal
@ -1,5 +1,5 @@
|
||||
Name: idris
|
||||
Version: 0.9.16
|
||||
Version: 0.9.17.1
|
||||
License: BSD3
|
||||
License-file: LICENSE
|
||||
Author: Edwin Brady
|
||||
@ -99,19 +99,25 @@ Extra-source-files:
|
||||
libs/base/base.ipkg
|
||||
libs/base/*.idr
|
||||
libs/base/Control/*.idr
|
||||
libs/base/Control/Isomorphism/*.idr
|
||||
libs/base/Control/Monad/*.idr
|
||||
libs/base/Data/*.idr
|
||||
libs/base/Data/Vect/*.idr
|
||||
libs/base/Debug/*.idr
|
||||
libs/base/Decidable/*.idr
|
||||
libs/base/Language/Reflection/*.idr
|
||||
libs/base/Makefile
|
||||
libs/base/Network/*.idr
|
||||
libs/base/System/*.idr
|
||||
libs/base/System/Concurrency/*.idr
|
||||
libs/base/Syntax/*.idr
|
||||
|
||||
libs/contrib/contrib.ipkg
|
||||
libs/contrib/Makefile
|
||||
libs/contrib/Classes/*.idr
|
||||
libs/contrib/Control/*.idr
|
||||
libs/contrib/Control/Isomorphism/*.idr
|
||||
libs/contrib/Data/*.idr
|
||||
libs/contrib/Decidable/*.idr
|
||||
libs/contrib/Network/*.idr
|
||||
libs/contrib/System/Concurrency/*.idr
|
||||
|
||||
libs/effects/Makefile
|
||||
libs/effects/effects.ipkg
|
||||
@ -292,6 +298,12 @@ Extra-source-files:
|
||||
test/reg059/run
|
||||
test/reg059/*.idr
|
||||
test/reg059/expected
|
||||
test/reg060/run
|
||||
test/reg060/*.idr
|
||||
test/reg060/expected
|
||||
test/reg061/run
|
||||
test/reg061/*.idr
|
||||
test/reg061/expected
|
||||
|
||||
test/basic001/run
|
||||
test/basic001/*.idr
|
||||
@ -330,10 +342,9 @@ Extra-source-files:
|
||||
test/basic012/run
|
||||
test/basic012/*.idr
|
||||
test/basic012/expected
|
||||
|
||||
test/buffer001-disabled/*.idr
|
||||
test/buffer001-disabled/run
|
||||
test/buffer001-disabled/expected
|
||||
test/basic013/run
|
||||
test/basic013/*.idr
|
||||
test/basic013/expected
|
||||
|
||||
test/delab001/*.idr
|
||||
test/delab001/run
|
||||
@ -504,9 +515,22 @@ Extra-source-files:
|
||||
test/proof004/run
|
||||
test/proof004/*.idr
|
||||
test/proof004/expected
|
||||
test/proof005/run
|
||||
test/proof005/*.idr
|
||||
test/proof005/expected
|
||||
test/proof006/run
|
||||
test/proof006/*.idr
|
||||
test/proof006/expected
|
||||
test/proof007/run
|
||||
test/proof007/*.idr
|
||||
test/proof007/expected
|
||||
test/proof008/run
|
||||
test/proof008/*.idr
|
||||
test/proof008/expected
|
||||
test/proof009/run
|
||||
test/proof009/*.idr
|
||||
test/proof009/expected
|
||||
test/proof009/input
|
||||
|
||||
test/quasiquote001/run
|
||||
test/quasiquote001/*.idr
|
||||
@ -622,7 +646,10 @@ Extra-source-files:
|
||||
test/docs002/input
|
||||
test/docs002/*.idr
|
||||
test/docs002/expected
|
||||
|
||||
test/docs003/run
|
||||
test/docs003/input
|
||||
test/docs003/*.idr
|
||||
test/docs003/expected
|
||||
|
||||
|
||||
source-repository head
|
||||
@ -810,6 +837,7 @@ Library
|
||||
, vector-binary-instances < 0.3
|
||||
, xml < 1.4
|
||||
, zlib < 0.6
|
||||
, safe
|
||||
Extensions: MultiParamTypeClasses
|
||||
, DeriveFoldable
|
||||
, DeriveTraversable
|
||||
|
@ -1,28 +1,33 @@
|
||||
build:
|
||||
$(MAKE) -C prelude build
|
||||
$(MAKE) -C base build
|
||||
$(MAKE) -C contrib build
|
||||
$(MAKE) -C effects build
|
||||
|
||||
|
||||
install:
|
||||
$(MAKE) -C prelude install
|
||||
$(MAKE) -C base install
|
||||
$(MAKE) -C contrib install
|
||||
$(MAKE) -C effects install
|
||||
|
||||
clean:
|
||||
$(MAKE) -C prelude clean
|
||||
$(MAKE) -C base clean
|
||||
$(MAKE) -C contrib clean
|
||||
$(MAKE) -C effects clean
|
||||
|
||||
doc:
|
||||
$(MAKE) -C prelude doc
|
||||
$(MAKE) -C base doc
|
||||
$(MAKE) -C effects doc
|
||||
$(MAKE) -C prelude doc
|
||||
$(MAKE) -C base doc
|
||||
$(MAKE) -C contrib doc
|
||||
$(MAKE) -C effects doc
|
||||
|
||||
doc_clean:
|
||||
$(MAKE) -C prelude doc_clean
|
||||
$(MAKE) -C base doc_clean
|
||||
$(MAKE) -C effects doc_clean
|
||||
$(MAKE) -C base doc_clean
|
||||
$(MAKE) -C contrib doc_clean
|
||||
$(MAKE) -C effects doc_clean
|
||||
|
||||
|
||||
.PHONY: build install clean doc doc_clean
|
||||
|
@ -1,261 +0,0 @@
|
||||
module Data.Buffer
|
||||
|
||||
import Data.Fin
|
||||
|
||||
%default total
|
||||
|
||||
-- !!! TODO: Open issues:
|
||||
-- 1. It may be theoretically nice to represent Buffer size as
|
||||
-- Fin (2 ^ WORD_BITS) instead of Nat
|
||||
-- 2. Primitives take Bits64 when really they should take the
|
||||
-- equivalent of C's size_t (ideally unboxed)
|
||||
-- 3. If we had access to host system information, we could reduce
|
||||
-- the needed primitives by implementing the LE/BE variants on
|
||||
-- top of the native variant plus a possible swab function
|
||||
-- 4. Would be nice to be able to peek/append Int, Char, and Float,
|
||||
-- all have fixed (though possibly implementation-dependent) widths.
|
||||
-- Currently not in place due to lack of host system introspection.
|
||||
-- 5. Would be nice to be able to peek/append the vector types, but
|
||||
-- for now I'm only touching the C backend which AFAICT doesn't
|
||||
-- support them.
|
||||
-- 6. Conversion from Fin to Bits64 (which, re 2, should eventually
|
||||
-- be a fixed-width implementation-dependent type) is likely
|
||||
-- inefficient relative to conversion from Nat to Bits64
|
||||
-- 7. We may want to have a separate type that is a product of Buffer
|
||||
-- and offset rather than storing the offset in Buffer itself, which
|
||||
-- would require exposing the offset argument of prim__appendBuffer
|
||||
|
||||
||| A contiguous chunk of n bytes
|
||||
abstract
|
||||
record Buffer (n : Nat) where
|
||||
constructor MkBuffer
|
||||
offset : Nat
|
||||
realBuffer : prim__UnsafeBuffer
|
||||
|
||||
{-
|
||||
record Buffer : Nat -> Type where
|
||||
MkBuffer : ( offset : Nat ) -> ( realBuffer : prim__UnsafeBuffer ) -> Buffer n
|
||||
-}
|
||||
|
||||
bitsFromNat : Nat -> Bits64
|
||||
bitsFromNat Z = 0
|
||||
bitsFromNat (S k) = 1 + bitsFromNat k
|
||||
|
||||
bitsFromFin : Fin n -> Bits64
|
||||
bitsFromFin FZ = 0
|
||||
bitsFromFin (FS k) = 1 + bitsFromFin k
|
||||
|
||||
||| Allocate an empty Buffer. The size hint can be used to avoid
|
||||
||| unnecessary reallocations and copies under the hood if the
|
||||
||| approximate ultimate size of the Buffer is known. Users can assume
|
||||
||| the new Buffer is word-aligned.
|
||||
public
|
||||
allocate : ( hint : Nat ) -> Buffer Z
|
||||
allocate = MkBuffer Z . prim__allocate . bitsFromNat
|
||||
|
||||
||| Append count repetitions of a Buffer to another Buffer
|
||||
%assert_total
|
||||
public
|
||||
appendBuffer : Buffer n ->
|
||||
( count : Nat ) ->
|
||||
Buffer m ->
|
||||
Buffer ( n + count * m )
|
||||
appendBuffer { n } { m } ( MkBuffer o1 r1 ) c ( MkBuffer o2 r2 ) =
|
||||
MkBuffer o1 $ prim__appendBuffer r1 size1 count size2 off r2
|
||||
where
|
||||
size1 : Bits64
|
||||
size1 = bitsFromNat ( n + o1 )
|
||||
size2 : Bits64
|
||||
size2 = bitsFromNat m
|
||||
count : Bits64
|
||||
count = bitsFromNat c
|
||||
off : Bits64
|
||||
off = bitsFromNat o2
|
||||
|
||||
||| Copy a buffer, potentially allowing the (potentially large) space it
|
||||
||| pointed to to be freed
|
||||
public
|
||||
copy : Buffer n -> Buffer n
|
||||
copy { n } = replace ( plusZeroRightNeutral n ) . appendBuffer ( allocate n ) 1
|
||||
|
||||
||| Create a view over a buffer
|
||||
public
|
||||
peekBuffer : { n : Nat } -> { offset : Nat } -> Buffer ( n + offset ) -> ( offset : Nat ) -> Buffer n
|
||||
peekBuffer ( MkBuffer o r ) off = MkBuffer ( o + off ) r
|
||||
|
||||
peekBits : ( prim__UnsafeBuffer -> Bits64 -> a ) ->
|
||||
Buffer ( m + n ) ->
|
||||
( offset : Fin ( S n ) ) ->
|
||||
a
|
||||
peekBits prim ( MkBuffer o r ) = prim r . bitsFromNat . plus o . finToNat
|
||||
|
||||
appendBits : ( prim__UnsafeBuffer ->
|
||||
Bits64 ->
|
||||
Bits64 ->
|
||||
a ->
|
||||
prim__UnsafeBuffer ) ->
|
||||
Buffer n ->
|
||||
( count : Nat) ->
|
||||
a ->
|
||||
Buffer ( n + count * size )
|
||||
appendBits { n } prim ( MkBuffer o r ) count =
|
||||
MkBuffer o . prim r ( bitsFromNat $ n + o ) ( bitsFromNat count )
|
||||
|
||||
|
||||
||| Read a Bits8 from a Buffer starting at offset
|
||||
%assert_total
|
||||
public
|
||||
peekBits8 : Buffer ( 1 + n ) ->
|
||||
( offset : Fin ( S n ) ) ->
|
||||
Bits8
|
||||
peekBits8 = peekBits { m = 1 } prim__peekB8Native
|
||||
|
||||
||| Append count repetitions of a Bits8 to a Buffer
|
||||
%assert_total
|
||||
public
|
||||
appendBits8 : Buffer n ->
|
||||
( count : Nat ) ->
|
||||
Bits8 ->
|
||||
Buffer ( n + count * 1 )
|
||||
appendBits8 = appendBits prim__appendB8Native
|
||||
|
||||
||| Read a Bits16 in native byte order from a Buffer starting at offset
|
||||
%assert_total
|
||||
public
|
||||
peekBits16Native : Buffer ( 2 + n ) ->
|
||||
( offset : Fin ( S n ) ) ->
|
||||
Bits16
|
||||
peekBits16Native = peekBits { m = 2 } prim__peekB16Native
|
||||
|
||||
||| Read a little-endian Bits16 from a Buffer starting at offset
|
||||
%assert_total
|
||||
public
|
||||
peekBits16LE : Buffer ( 2 + n ) -> ( offset : Fin ( S n ) ) -> Bits16
|
||||
peekBits16LE = peekBits { m = 2 } prim__peekB16LE
|
||||
|
||||
||| Read a big-endian Bits16 from a Buffer starting at offset
|
||||
%assert_total
|
||||
public
|
||||
peekBits16BE : Buffer ( 2 + n ) -> ( offset : Fin ( S n ) ) -> Bits16
|
||||
peekBits16BE = peekBits { m = 2 } prim__peekB16BE
|
||||
|
||||
||| Append count repetitions of a Bits16 in native byte order to a Buffer
|
||||
%assert_total
|
||||
public
|
||||
appendBits16Native : Buffer n ->
|
||||
( count : Nat ) ->
|
||||
Bits16 ->
|
||||
Buffer ( n + count * 2 )
|
||||
appendBits16Native = appendBits prim__appendB16Native
|
||||
|
||||
||| Append count repetitions of a little-endian Bits16 to a Buffer
|
||||
%assert_total
|
||||
public
|
||||
appendBits16LE : Buffer n ->
|
||||
( count : Nat ) ->
|
||||
Bits16 ->
|
||||
Buffer ( n + count * 2 )
|
||||
appendBits16LE = appendBits prim__appendB16LE
|
||||
|
||||
||| Append count repetitions of a big-endian Bits16 to a Buffer
|
||||
%assert_total
|
||||
public
|
||||
appendBits16BE : Buffer n ->
|
||||
( count : Nat ) ->
|
||||
Bits16 ->
|
||||
Buffer ( n + count * 2 )
|
||||
appendBits16BE = appendBits prim__appendB16BE
|
||||
|
||||
||| Read a Bits32 in native byte order from a Buffer starting at offset
|
||||
%assert_total
|
||||
public
|
||||
peekBits32Native : Buffer ( 4 + n ) ->
|
||||
( offset : Fin ( S n ) ) ->
|
||||
Bits32
|
||||
peekBits32Native = peekBits { m = 4 } prim__peekB32Native
|
||||
|
||||
||| Read a little-endian Bits32 from a Buffer starting at offset
|
||||
%assert_total
|
||||
public
|
||||
peekBits32LE : Buffer ( 4 + n ) -> ( offset : Fin ( S n ) ) -> Bits32
|
||||
peekBits32LE = peekBits { m = 4 } prim__peekB32LE
|
||||
|
||||
||| Read a big-endian Bits32 from a Buffer starting at offset
|
||||
%assert_total
|
||||
public
|
||||
peekBits32BE : Buffer ( 4 + n ) -> ( offset : Fin ( S n ) ) -> Bits32
|
||||
peekBits32BE = peekBits { m = 4 } prim__peekB32BE
|
||||
|
||||
||| Append count repetitions of a Bits32 in native byte order to a Buffer
|
||||
%assert_total
|
||||
public
|
||||
appendBits32Native : Buffer n ->
|
||||
( count : Nat ) ->
|
||||
Bits32 ->
|
||||
Buffer ( n + count * 4 )
|
||||
appendBits32Native = appendBits prim__appendB32Native
|
||||
|
||||
||| Append count repetitions of a little-endian Bits32 to a Buffer
|
||||
%assert_total
|
||||
public
|
||||
appendBits32LE : Buffer n ->
|
||||
( count : Nat ) ->
|
||||
Bits32 ->
|
||||
Buffer ( n + count * 4 )
|
||||
appendBits32LE = appendBits prim__appendB32LE
|
||||
|
||||
||| Append count repetitions of a big-endian Bits32 to a Buffer
|
||||
%assert_total
|
||||
public
|
||||
appendBits32BE : Buffer n ->
|
||||
( count : Nat ) ->
|
||||
Bits32 ->
|
||||
Buffer ( n + count * 4 )
|
||||
appendBits32BE = appendBits prim__appendB32BE
|
||||
|
||||
||| Read a Bits64 in native byte order from a Buffer starting at offset
|
||||
%assert_total
|
||||
public
|
||||
peekBits64Native : Buffer ( 8 + n ) ->
|
||||
( offset : Fin ( S n ) ) ->
|
||||
Bits64
|
||||
peekBits64Native = peekBits { m = 8 } prim__peekB64Native
|
||||
|
||||
||| Read a little-endian Bits64 from a Buffer starting at offset
|
||||
%assert_total
|
||||
public
|
||||
peekBits64LE : Buffer ( 8 + n ) -> ( offset : Fin ( S n ) ) -> Bits64
|
||||
peekBits64LE = peekBits { m = 8 } prim__peekB64LE
|
||||
|
||||
||| Read a big-endian Bits64 from a Buffer starting at offset
|
||||
%assert_total
|
||||
public
|
||||
peekBits64BE : Buffer ( 8 + n ) -> ( offset : Fin ( S n ) ) -> Bits64
|
||||
peekBits64BE = peekBits { m = 8 } prim__peekB64BE
|
||||
|
||||
||| Append count repetitions of a Bits64 in native byte order to a Buffer
|
||||
%assert_total
|
||||
public
|
||||
appendBits64Native : Buffer n ->
|
||||
( count : Nat ) ->
|
||||
Bits64 ->
|
||||
Buffer ( n + count * 8 )
|
||||
appendBits64Native = appendBits prim__appendB64Native
|
||||
|
||||
||| Append count repetitions of a little-endian Bits64 to a Buffer
|
||||
%assert_total
|
||||
public
|
||||
appendBits64LE : Buffer n ->
|
||||
( count : Nat ) ->
|
||||
Bits64 ->
|
||||
Buffer ( n + count * 8 )
|
||||
appendBits64LE = appendBits prim__appendB64LE
|
||||
|
||||
||| Append count repetitions of a big-endian Bits64 to a Buffer
|
||||
%assert_total
|
||||
public
|
||||
appendBits64BE : Buffer n ->
|
||||
( count : Nat ) ->
|
||||
Bits64 ->
|
||||
Buffer ( n + count * 8 )
|
||||
appendBits64BE = appendBits prim__appendB64BE
|
@ -93,15 +93,8 @@ instance Show Const where
|
||||
show (B16 b) = "(B16 ...)"
|
||||
show (B32 b) = "(B32 ...)"
|
||||
show (B64 b) = "(B64 ...)"
|
||||
show (B8V xs) = "(B8V ...)"
|
||||
show (B16V xs) = "(B16V ...)"
|
||||
show (B32V xs) = "(B32V ...)"
|
||||
show (B64V xs) = "(B64V ...)"
|
||||
show (AType x) = "(AType ...)"
|
||||
show StrType = "StrType"
|
||||
show PtrType = "PtrType"
|
||||
show ManagedPtrType = "ManagedPtrType"
|
||||
show BufferType = "BufferType"
|
||||
show VoidType = "VoidType"
|
||||
show Forgot = "Forgot"
|
||||
|
||||
@ -117,7 +110,6 @@ instance Eq Reflection.IntTy where
|
||||
ITNative == ITNative = True
|
||||
ITBig == ITBig = True
|
||||
ITChar == ITChar = True
|
||||
(ITVec x i) == (ITVec y j) = x == y && i == j
|
||||
_ == _ = False
|
||||
|
||||
instance Eq ArithTy where
|
||||
@ -135,15 +127,8 @@ instance Eq Const where
|
||||
(B16 x) == (B16 y) = x == y
|
||||
(B32 x) == (B32 y) = x == y
|
||||
(B64 x) == (B64 y) = x == y
|
||||
(B8V xs) == (B8V ys) = False -- TODO
|
||||
(B16V xs) == (B16V ys) = False -- TODO
|
||||
(B32V xs) == (B32V ys) = False -- TODO
|
||||
(B64V xs) == (B64V ys) = False -- TODO
|
||||
(AType x) == (AType y) = x == y
|
||||
StrType == StrType = True
|
||||
PtrType == PtrType = True
|
||||
ManagedPtrType == ManagedPtrType = True
|
||||
BufferType == BufferType = True
|
||||
VoidType == VoidType = True
|
||||
Forgot == Forgot = True
|
||||
_ == _ = False
|
||||
|
@ -3,15 +3,9 @@ package base
|
||||
opts = "--nobasepkgs --total -i ../prelude"
|
||||
modules = System,
|
||||
|
||||
Network.Cgi, Network.Socket,
|
||||
Debug.Error, Debug.Trace,
|
||||
|
||||
System.Info,
|
||||
System.Concurrency.Raw, System.Concurrency.Process,
|
||||
|
||||
Decidable.Decidable, Decidable.Order,
|
||||
|
||||
Providers,
|
||||
|
||||
Language.Reflection.Utils,
|
||||
|
||||
@ -19,20 +13,19 @@ modules = System,
|
||||
|
||||
Data.Morphisms,
|
||||
Data.Bits, Data.Mod2,
|
||||
Data.ZZ, Data.Sign,
|
||||
Data.SortedMap, Data.SortedSet, Data.BoundedList,
|
||||
Data.Fin, Data.Vect, Data.VectType,
|
||||
Data.Fin, Data.Vect, Data.VectType,
|
||||
Data.HVect, Data.Vect.Quantifiers,
|
||||
Data.Floats, Data.Complex, Data.Heap, Data.Fun,
|
||||
Data.Rel, Data.Buffer, Data.Erased,
|
||||
Data.List, Data.Hash, Data.Matrix,
|
||||
Data.Floats, Data.Complex,
|
||||
Data.Erased, Data.List,
|
||||
Data.So,
|
||||
|
||||
Control.Isomorphism, Control.Isomorphism.Primitives,
|
||||
Control.Isomorphism,
|
||||
Control.Monad.Identity,
|
||||
Control.Monad.RWS,
|
||||
Control.Monad.Trans,
|
||||
Control.Monad.State, Control.Monad.Writer, Control.Monad.Reader,
|
||||
Control.Category, Control.Arrow,
|
||||
Control.Catchable, Control.IOExcept
|
||||
Control.Catchable, Control.IOExcept,
|
||||
|
||||
System.Concurrency.Raw
|
||||
|
||||
|
114
libs/contrib/Classes/Verified.idr
Normal file
114
libs/contrib/Classes/Verified.idr
Normal file
@ -0,0 +1,114 @@
|
||||
module Classes.Verified
|
||||
|
||||
import Control.Algebra
|
||||
|
||||
-- Due to these being basically unused and difficult to implement,
|
||||
-- they're in contrib for a bit. Once a design is found that lets them
|
||||
-- be implemented for a number of instances, and we get those
|
||||
-- implementations, then some of them can move back to base (or even
|
||||
-- prelude, in the cases of Functor, Applicative, Monad, Semigroup,
|
||||
-- and Monoid).
|
||||
|
||||
class Functor f => VerifiedFunctor (f : Type -> Type) where
|
||||
functorIdentity : {a : Type} -> (x : f a) -> map id x = id x
|
||||
functorComposition : {a : Type} -> {b : Type} -> (x : f a) ->
|
||||
(g1 : a -> b) -> (g2 : b -> c) ->
|
||||
map (g2 . g1) x = (map g2 . map g1) x
|
||||
|
||||
|
||||
|
||||
class (Applicative f, VerifiedFunctor f) => VerifiedApplicative (f : Type -> Type) where
|
||||
applicativeMap : (x : f a) -> (g : a -> b) ->
|
||||
map g x = pure g <*> x
|
||||
applicativeIdentity : (x : f a) -> pure id <*> x = x
|
||||
applicativeComposition : (x : f a) -> (g1 : f (a -> b)) -> (g2 : f (b -> c)) ->
|
||||
((pure (.) <*> g2) <*> g1) <*> x = g2 <*> (g1 <*> x)
|
||||
applicativeHomomorphism : (x : a) -> (g : a -> b) ->
|
||||
(<*>) {f} (pure g) (pure x) = pure {f} (g x)
|
||||
applicativeInterchange : (x : a) -> (g : f (a -> b)) ->
|
||||
g <*> pure x = pure (\g' : a -> b => g' x) <*> g
|
||||
|
||||
|
||||
class (Monad m, VerifiedApplicative m) => VerifiedMonad (m : Type -> Type) where
|
||||
monadApplicative : (mf : m (a -> b)) -> (mx : m a) ->
|
||||
mf <*> mx = mf >>= \f =>
|
||||
mx >>= \x =>
|
||||
pure (f x)
|
||||
monadLeftIdentity : (x : a) -> (f : a -> m b) -> return x >>= f = f x
|
||||
monadRightIdentity : (mx : m a) -> mx >>= return = mx
|
||||
monadAssociativity : (mx : m a) -> (f : a -> m b) -> (g : b -> m c) ->
|
||||
(mx >>= f) >>= g = mx >>= (\x => f x >>= g)
|
||||
|
||||
|
||||
class Semigroup a => VerifiedSemigroup a where
|
||||
total semigroupOpIsAssociative : (l, c, r : a) -> l <+> (c <+> r) = (l <+> c) <+> r
|
||||
|
||||
instance VerifiedSemigroup (List a) where
|
||||
semigroupOpIsAssociative = appendAssociative
|
||||
|
||||
--instance VerifiedSemigroup Nat where
|
||||
-- semigroupOpIsAssociative = plusAssociative
|
||||
|
||||
|
||||
class (VerifiedSemigroup a, Monoid a) => VerifiedMonoid a where
|
||||
total monoidNeutralIsNeutralL : (l : a) -> l <+> neutral = l
|
||||
total monoidNeutralIsNeutralR : (r : a) -> neutral <+> r = r
|
||||
|
||||
-- instance VerifiedMonoid Nat where
|
||||
-- monoidNeutralIsNeutralL = plusZeroRightNeutral
|
||||
-- monoidNeutralIsNeutralR = plusZeroLeftNeutral
|
||||
|
||||
instance VerifiedMonoid (List a) where
|
||||
monoidNeutralIsNeutralL = appendNilRightNeutral
|
||||
monoidNeutralIsNeutralR xs = Refl
|
||||
|
||||
class (VerifiedMonoid a, Group a) => VerifiedGroup a where
|
||||
total groupInverseIsInverseL : (l : a) -> l <+> inverse l = neutral
|
||||
total groupInverseIsInverseR : (r : a) -> inverse r <+> r = neutral
|
||||
|
||||
class (VerifiedGroup a, AbelianGroup a) => VerifiedAbelianGroup a where
|
||||
total abelianGroupOpIsCommutative : (l, r : a) -> l <+> r = r <+> l
|
||||
|
||||
class (VerifiedAbelianGroup a, Ring a) => VerifiedRing a where
|
||||
total ringOpIsAssociative : (l, c, r : a) -> l <.> (c <.> r) = (l <.> c) <.> r
|
||||
total ringOpIsDistributiveL : (l, c, r : a) -> l <.> (c <+> r) = (l <.> c) <+> (l <.> r)
|
||||
total ringOpIsDistributiveR : (l, c, r : a) -> (l <+> c) <.> r = (l <.> r) <+> (c <.> r)
|
||||
|
||||
class (VerifiedRing a, RingWithUnity a) => VerifiedRingWithUnity a where
|
||||
total ringWithUnityIsUnityL : (l : a) -> l <.> unity = l
|
||||
total ringWithUnityIsUnityR : (r : a) -> unity <.> r = r
|
||||
|
||||
class JoinSemilattice a => VerifiedJoinSemilattice a where
|
||||
total joinSemilatticeJoinIsAssociative : (l, c, r : a) -> join l (join c r) = join (join l c) r
|
||||
total joinSemilatticeJoinIsCommutative : (l, r : a) -> join l r = join r l
|
||||
total joinSemilatticeJoinIsIdempotent : (e : a) -> join e e = e
|
||||
|
||||
class MeetSemilattice a => VerifiedMeetSemilattice a where
|
||||
total meetSemilatticeMeetIsAssociative : (l, c, r : a) -> meet l (meet c r) = meet (meet l c) r
|
||||
total meetSemilatticeMeetIsCommutative : (l, r : a) -> meet l r = meet r l
|
||||
total meetSemilatticeMeetIsIdempotent : (e : a) -> meet e e = e
|
||||
|
||||
class (VerifiedJoinSemilattice a, BoundedJoinSemilattice a) => VerifiedBoundedJoinSemilattice a where
|
||||
total boundedJoinSemilatticeBottomIsBottom : (e : a) -> join e bottom = e
|
||||
|
||||
class (VerifiedMeetSemilattice a, BoundedMeetSemilattice a) => VerifiedBoundedMeetSemilattice a where
|
||||
total boundedMeetSemilatticeTopIsTop : (e : a) -> meet e top = e
|
||||
|
||||
class (VerifiedJoinSemilattice a, VerifiedMeetSemilattice a) => VerifiedLattice a where
|
||||
total latticeMeetAbsorbsJoin : (l, r : a) -> meet l (join l r) = l
|
||||
total latticeJoinAbsorbsMeet : (l, r : a) -> join l (meet l r) = l
|
||||
|
||||
class (VerifiedBoundedJoinSemilattice a, VerifiedBoundedMeetSemilattice a, VerifiedLattice a) => VerifiedBoundedLattice a where { }
|
||||
|
||||
class (VerifiedRing a, Field a) => VerifiedField a where
|
||||
total fieldInverseIsInverseL : (l : a) -> l <.> inverseM l = unity
|
||||
total fieldInverseIsInverseR : (r : a) -> inverseM r <.> r = unity
|
||||
|
||||
-- class (VerifiedRingWithUnity a, VerifiedAbelianGroup b, Module a b) => VerifiedModule a b where
|
||||
-- total moduleScalarMultiplyComposition : (x,y : a) -> (v : b) -> x <#> (y <#> v) = (x <.> y) <#> v
|
||||
-- total moduleScalarUnityIsUnity : (v : b) -> unity <#> v = v
|
||||
-- total moduleScalarMultDistributiveWRTVectorAddition : (s : a) -> (v, w : b) -> s <#> (v <+> w) = (s <#> v) <+> (s <#> w)
|
||||
-- total moduleScalarMultDistributiveWRTModuleAddition : (s, t : a) -> (v : b) -> (s <+> t) <#> v = (s <#> v) <+> (t <#> v)
|
||||
|
||||
-- class (VerifiedField a, VerifiedModule a b) => VerifiedVectorSpace a b where {}
|
||||
|
274
libs/contrib/Control/Algebra.idr
Normal file
274
libs/contrib/Control/Algebra.idr
Normal file
@ -0,0 +1,274 @@
|
||||
module Control.Algebra
|
||||
|
||||
import Data.Heap
|
||||
|
||||
-- XXX: change?
|
||||
infixl 6 <->
|
||||
infixl 6 <.>
|
||||
infixl 5 <#>
|
||||
|
||||
||| Sets equipped with a single binary operation that is associative, along with
|
||||
||| a neutral element for that binary operation and inverses for all elements.
|
||||
||| Must satisfy the following laws:
|
||||
||| + Associativity of `<+>`:
|
||||
||| forall a b c, a <+> (b <+> c) == (a <+> b) <+> c
|
||||
||| + Neutral for `<+>`:
|
||||
||| forall a, a <+> neutral == a
|
||||
||| forall a, neutral <+> a == a
|
||||
||| + Inverse for `<+>`:
|
||||
||| forall a, a <+> inverse a == neutral
|
||||
||| forall a, inverse a <+> a == neutral
|
||||
class Monoid a => Group a where
|
||||
inverse : a -> a
|
||||
|
||||
(<->) : Group a => a -> a -> a
|
||||
(<->) left right = left <+> (inverse right)
|
||||
|
||||
||| Sets equipped with a single binary operation that is associative and
|
||||
||| commutative, along with a neutral element for that binary operation and
|
||||
||| inverses for all elements. Must satisfy the following laws:
|
||||
|||
|
||||
||| + Associativity of `<+>`:
|
||||
||| forall a b c, a <+> (b <+> c) == (a <+> b) <+> c
|
||||
||| + Commutativity of `<+>`:
|
||||
||| forall a b, a <+> b == b <+> a
|
||||
||| + Neutral for `<+>`:
|
||||
||| forall a, a <+> neutral == a
|
||||
||| forall a, neutral <+> a == a
|
||||
||| + Inverse for `<+>`:
|
||||
||| forall a, a <+> inverse a == neutral
|
||||
||| forall a, inverse a <+> a == neutral
|
||||
class Group a => AbelianGroup a where { }
|
||||
|
||||
|
||||
||| Sets equipped with two binary operations, one associative and commutative
|
||||
||| supplied with a neutral element, and the other associative, with
|
||||
||| distributivity laws relating the two operations. Must satisfy the following
|
||||
||| laws:
|
||||
|||
|
||||
||| + Associativity of `<+>`:
|
||||
||| forall a b c, a <+> (b <+> c) == (a <+> b) <+> c
|
||||
||| + Commutativity of `<+>`:
|
||||
||| forall a b, a <+> b == b <+> a
|
||||
||| + Neutral for `<+>`:
|
||||
||| forall a, a <+> neutral == a
|
||||
||| forall a, neutral <+> a == a
|
||||
||| + Inverse for `<+>`:
|
||||
||| forall a, a <+> inverse a == neutral
|
||||
||| forall a, inverse a <+> a == neutral
|
||||
||| + Associativity of `<.>`:
|
||||
||| forall a b c, a <.> (b <.> c) == (a <.> b) <.> c
|
||||
||| + Distributivity of `<.>` and `<->`:
|
||||
||| forall a b c, a <.> (b <+> c) == (a <.> b) <+> (a <.> c)
|
||||
||| forall a b c, (a <+> b) <.> c == (a <.> c) <+> (b <.> c)
|
||||
class AbelianGroup a => Ring a where
|
||||
(<.>) : a -> a -> a
|
||||
|
||||
|
||||
||| Sets equipped with two binary operations, one associative and commutative
|
||||
||| supplied with a neutral element, and the other associative supplied with a
|
||||
||| neutral element, with distributivity laws relating the two operations. Must
|
||||
||| satisfy the following laws:
|
||||
|||
|
||||
||| + Associativity of `<+>`:
|
||||
||| forall a b c, a <+> (b <+> c) == (a <+> b) <+> c
|
||||
||| + Commutativity of `<+>`:
|
||||
||| forall a b, a <+> b == b <+> a
|
||||
||| + Neutral for `<+>`:
|
||||
||| forall a, a <+> neutral == a
|
||||
||| forall a, neutral <+> a == a
|
||||
||| + Inverse for `<+>`:
|
||||
||| forall a, a <+> inverse a == neutral
|
||||
||| forall a, inverse a <+> a == neutral
|
||||
||| + Associativity of `<.>`:
|
||||
||| forall a b c, a <.> (b <.> c) == (a <.> b) <.> c
|
||||
||| + Neutral for `<.>`:
|
||||
||| forall a, a <.> unity == a
|
||||
||| forall a, unity <.> a == a
|
||||
||| + Distributivity of `<.>` and `<->`:
|
||||
||| forall a b c, a <.> (b <+> c) == (a <.> b) <+> (a <.> c)
|
||||
||| forall a b c, (a <+> b) <.> c == (a <.> c) <+> (b <.> c)
|
||||
class Ring a => RingWithUnity a where
|
||||
unity : a
|
||||
|
||||
|
||||
||| Sets equipped with a binary operation that is commutative, associative and
|
||||
||| idempotent. Must satisfy the following laws:
|
||||
|||
|
||||
||| + Associativity of join:
|
||||
||| forall a b c, join a (join b c) == join (join a b) c
|
||||
||| + Commutativity of join:
|
||||
||| forall a b, join a b == join b a
|
||||
||| + Idempotency of join:
|
||||
||| forall a, join a a == a
|
||||
|||
|
||||
||| Join semilattices capture the notion of sets with a "least upper bound".
|
||||
class JoinSemilattice a where
|
||||
join : a -> a -> a
|
||||
|
||||
instance JoinSemilattice Nat where
|
||||
join = maximum
|
||||
|
||||
instance Ord a => JoinSemilattice (MaxiphobicHeap a) where
|
||||
join = merge
|
||||
|
||||
||| Sets equipped with a binary operation that is commutative, associative and
|
||||
||| idempotent. Must satisfy the following laws:
|
||||
|||
|
||||
||| + Associativity of meet:
|
||||
||| forall a b c, meet a (meet b c) == meet (meet a b) c
|
||||
||| + Commutativity of meet:
|
||||
||| forall a b, meet a b == meet b a
|
||||
||| + Idempotency of meet:
|
||||
||| forall a, meet a a == a
|
||||
|||
|
||||
||| Meet semilattices capture the notion of sets with a "greatest lower bound".
|
||||
class MeetSemilattice a where
|
||||
meet : a -> a -> a
|
||||
|
||||
instance MeetSemilattice Nat where
|
||||
meet = minimum
|
||||
|
||||
|
||||
||| Sets equipped with a binary operation that is commutative, associative and
|
||||
||| idempotent and supplied with a unitary element. Must satisfy the following
|
||||
||| laws:
|
||||
|||
|
||||
||| + Associativity of join:
|
||||
||| forall a b c, join a (join b c) == join (join a b) c
|
||||
||| + Commutativity of join:
|
||||
||| forall a b, join a b == join b a
|
||||
||| + Idempotency of join:
|
||||
||| forall a, join a a == a
|
||||
||| + Bottom (Unitary Element):
|
||||
||| forall a, join a bottom == a
|
||||
|||
|
||||
||| Join semilattices capture the notion of sets with a "least upper bound"
|
||||
||| equipped with a "bottom" element.
|
||||
class JoinSemilattice a => BoundedJoinSemilattice a where
|
||||
bottom : a
|
||||
|
||||
|
||||
instance BoundedJoinSemilattice Nat where
|
||||
bottom = Z
|
||||
|
||||
|
||||
||| Sets equipped with a binary operation that is commutative, associative and
|
||||
||| idempotent and supplied with a unitary element. Must satisfy the following
|
||||
||| laws:
|
||||
|||
|
||||
||| + Associativity of meet:
|
||||
||| forall a b c, meet a (meet b c) == meet (meet a b) c
|
||||
||| + Commutativity of meet:
|
||||
||| forall a b, meet a b == meet b a
|
||||
||| + Idempotency of meet:
|
||||
||| forall a, meet a a == a
|
||||
||| + Top (Unitary Element):
|
||||
||| forall a, meet a top == a
|
||||
|||
|
||||
||| Meet semilattices capture the notion of sets with a "greatest lower bound"
|
||||
||| equipped with a "top" element.
|
||||
class MeetSemilattice a => BoundedMeetSemilattice a where
|
||||
top : a
|
||||
|
||||
|
||||
|
||||
||| Sets equipped with two binary operations that are both commutative,
|
||||
||| associative and idempotent, along with absorbtion laws for relating the two
|
||||
||| binary operations. Must satisfy the following:
|
||||
|||
|
||||
||| + Associativity of meet and join:
|
||||
||| forall a b c, meet a (meet b c) == meet (meet a b) c
|
||||
||| forall a b c, join a (join b c) == join (join a b) c
|
||||
||| + Commutativity of meet and join:
|
||||
||| forall a b, meet a b == meet b a
|
||||
||| forall a b, join a b == join b a
|
||||
||| + Idempotency of meet and join:
|
||||
||| forall a, meet a a == a
|
||||
||| forall a, join a a == a
|
||||
||| + Absorbtion laws for meet and join:
|
||||
||| forall a b, meet a (join a b) == a
|
||||
||| forall a b, join a (meet a b) == a
|
||||
class (JoinSemilattice a, MeetSemilattice a) => Lattice a where { }
|
||||
|
||||
|
||||
instance Lattice Nat where { }
|
||||
|
||||
||| Sets equipped with two binary operations that are both commutative,
|
||||
||| associative and idempotent and supplied with neutral elements, along with
|
||||
||| absorbtion laws for relating the two binary operations. Must satisfy the
|
||||
||| following:
|
||||
|||
|
||||
||| + Associativity of meet and join:
|
||||
||| forall a b c, meet a (meet b c) == meet (meet a b) c
|
||||
||| forall a b c, join a (join b c) == join (join a b) c
|
||||
||| + Commutativity of meet and join:
|
||||
||| forall a b, meet a b == meet b a
|
||||
||| forall a b, join a b == join b a
|
||||
||| + Idempotency of meet and join:
|
||||
||| forall a, meet a a == a
|
||||
||| forall a, join a a == a
|
||||
||| + Absorbtion laws for meet and join:
|
||||
||| forall a b, meet a (join a b) == a
|
||||
||| forall a b, join a (meet a b) == a
|
||||
||| + Neutral for meet and join:
|
||||
||| forall a, meet a top == top
|
||||
||| forall a, join a bottom == bottom
|
||||
class (BoundedJoinSemilattice a, BoundedMeetSemilattice a) => BoundedLattice a where { }
|
||||
|
||||
|
||||
-- Fields.
|
||||
||| Sets equipped with two binary operations, both associative and commutative
|
||||
||| supplied with a neutral element, with
|
||||
||| distributivity laws relating the two operations. Must satisfy the following
|
||||
||| laws:
|
||||
|||
|
||||
||| + Associativity of `<+>`:
|
||||
||| forall a b c, a <+> (b <+> c) == (a <+> b) <+> c
|
||||
||| + Commutativity of `<+>`:
|
||||
||| forall a b, a <+> b == b <+> a
|
||||
||| + Neutral for `<+>`:
|
||||
||| forall a, a <+> neutral == a
|
||||
||| forall a, neutral <+> a == a
|
||||
||| + Inverse for `<+>`:
|
||||
||| forall a, a <+> inverse a == neutral
|
||||
||| forall a, inverse a <+> a == neutral
|
||||
||| + Associativity of `<.>`:
|
||||
||| forall a b c, a <.> (b <.> c) == (a <.> b) <.> c
|
||||
||| + Unity for `<.>`:
|
||||
||| forall a, a <.> unity == a
|
||||
||| forall a, unity <.> a == a
|
||||
||| + InverseM of `<.>`:
|
||||
||| forall a, a <.> inverseM a == unity
|
||||
||| forall a, inverseM a <.> a == unity
|
||||
||| + Distributivity of `<.>` and `<->`:
|
||||
||| forall a b c, a <.> (b <+> c) == (a <.> b) <+> (a <.> c)
|
||||
||| forall a b c, (a <+> b) <.> c == (a <.> c) <+> (b <.> c)
|
||||
class RingWithUnity a => Field a where
|
||||
inverseM : a -> a
|
||||
|
||||
|
||||
||| A module over a ring is an additive abelian group of 'vectors' endowed with a
|
||||
||| scale operation multiplying vectors by ring elements, and distributivity laws
|
||||
||| relating the scale operation to both ring addition and module addition.
|
||||
||| Must satisfy the following laws:
|
||||
|||
|
||||
||| + Compatibility of scalar multiplication with ring multiplication:
|
||||
||| forall a b v, a <#> (b <#> v) = (a <.> b) <#> v
|
||||
||| + Ring unity is the identity element of scalar multiplication:
|
||||
||| forall v, unity <#> v = v
|
||||
||| + Distributivity of `<#>` and `<+>`:
|
||||
||| forall a v w, a <#> (v <+> w) == (a <#> v) <+> (a <#> w)
|
||||
||| forall a b v, (a <+> b) <#> v == (a <#> v) <+> (b <#> v)
|
||||
class (RingWithUnity a, AbelianGroup b) => Module a b where
|
||||
(<#>) : a -> b -> b
|
||||
|
||||
|
||||
||| A vector space is a module over a ring that is also a field
|
||||
class (Field a, Module a b) => VectorSpace a b where {}
|
||||
|
||||
|
||||
-- XXX todo:
|
||||
-- Structures where "abs" make sense.
|
||||
-- Euclidean domains, etc.
|
||||
-- Where to put fromInteger and fromRational?
|
8
libs/contrib/Data/Graph.idr
Normal file
8
libs/contrib/Data/Graph.idr
Normal file
@ -0,0 +1,8 @@
|
||||
module Data.Graph
|
||||
|
||||
import Data.Vect
|
||||
|
||||
data Edge e = MkEdge e e
|
||||
|
||||
data Graph : Nat −> Nat −> Type −> Type −> Type where
|
||||
MkGraph : Vect m v −> Vect n (Edge k) −> Graph m n v k
|
@ -5,6 +5,7 @@
|
||||
|
||||
module Data.Heap
|
||||
|
||||
|
||||
%default total
|
||||
%access public
|
||||
|
||||
@ -128,9 +129,6 @@ instance Ord a => Semigroup (MaxiphobicHeap a) where
|
||||
instance Ord a => Monoid (MaxiphobicHeap a) where
|
||||
neutral = empty
|
||||
|
||||
instance Ord a => JoinSemilattice (MaxiphobicHeap a) where
|
||||
join = merge
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
-- Properties
|
||||
--------------------------------------------------------------------------------
|
@ -1,5 +1,7 @@
|
||||
module Data.Matrix
|
||||
|
||||
import Control.Algebra
|
||||
|
||||
import Data.Complex
|
||||
import Data.ZZ
|
||||
import Data.Fin
|
||||
@ -212,14 +214,6 @@ instance Semigroup Nat where
|
||||
instance Monoid Nat where
|
||||
neutral = 0
|
||||
|
||||
instance VerifiedSemigroup Nat where
|
||||
semigroupOpIsAssociative = plusAssociative
|
||||
|
||||
instance VerifiedMonoid Nat where
|
||||
monoidNeutralIsNeutralL = plusZeroRightNeutral
|
||||
monoidNeutralIsNeutralR = plusZeroLeftNeutral
|
||||
|
||||
|
||||
instance Semigroup ZZ where
|
||||
(<+>) = (+)
|
||||
|
9
libs/contrib/Data/Polyhedra.idr
Normal file
9
libs/contrib/Data/Polyhedra.idr
Normal file
@ -0,0 +1,9 @@
|
||||
module Data.Polyhedra
|
||||
|
||||
import Data.Vect
|
||||
import Data.Graph
|
||||
|
||||
data Face f = MkFace (Vect n f)
|
||||
|
||||
data Polyhedron : Nat -> Nat -> Nat -> Type -> Type where
|
||||
MkPolyhedron : Vect k v -> Vect m (Edge v) -> Vect n (Face v) -> Polyhedron k m n v
|
28
libs/contrib/Data/SortedSet.idr
Normal file
28
libs/contrib/Data/SortedSet.idr
Normal file
@ -0,0 +1,28 @@
|
||||
module Data.SortedSet
|
||||
|
||||
import Data.SortedMap
|
||||
|
||||
-- TODO: add intersection, union, difference
|
||||
|
||||
data SortedSet k = SetWrapper (Data.SortedMap.SortedMap k ())
|
||||
|
||||
empty : SortedSet k
|
||||
empty = SetWrapper Data.SortedMap.empty
|
||||
|
||||
insert : Ord k => k -> SortedSet k -> SortedSet k
|
||||
insert k (SetWrapper m) = SetWrapper (Data.SortedMap.insert k () m)
|
||||
|
||||
delete : Ord k => k -> SortedSet k -> SortedSet k
|
||||
delete k (SetWrapper m) = SetWrapper (Data.SortedMap.delete k m)
|
||||
|
||||
contains : Ord k => k -> SortedSet k -> Bool
|
||||
contains k (SetWrapper m) = isJust (Data.SortedMap.lookup k m)
|
||||
|
||||
fromList : Ord k => List k -> SortedSet k
|
||||
fromList l = SetWrapper (Data.SortedMap.fromList (map (\i => (i, ())) l))
|
||||
|
||||
toList : SortedSet k -> List k
|
||||
toList (SetWrapper m) = map (\(i, _) => i) (Data.SortedMap.toList m)
|
||||
|
||||
instance Foldable SortedSet where
|
||||
foldr f e xs = foldr f e (Data.SortedSet.toList xs)
|
24
libs/contrib/Makefile
Normal file
24
libs/contrib/Makefile
Normal file
@ -0,0 +1,24 @@
|
||||
IDRIS := idris
|
||||
PKG := contrib
|
||||
|
||||
build:
|
||||
$(IDRIS) --build ${PKG}.ipkg
|
||||
|
||||
install:
|
||||
$(IDRIS) --install ${PKG}.ipkg
|
||||
|
||||
clean:
|
||||
$(IDRIS) --clean ${PKG}.ipkg
|
||||
|
||||
rebuild: clean build
|
||||
|
||||
doc:
|
||||
${IDRIS} --mkdoc ${PKG}.ipkg
|
||||
|
||||
doc_clean:
|
||||
rm -rf ${PKG}_doc
|
||||
|
||||
linecount:
|
||||
find . -name '*.idr' | xargs wc -l
|
||||
|
||||
.PHONY: build install clean rebuild linecount
|
18
libs/contrib/contrib.ipkg
Normal file
18
libs/contrib/contrib.ipkg
Normal file
@ -0,0 +1,18 @@
|
||||
package contrib
|
||||
|
||||
opts = "--nobasepkgs --total -i ../prelude -i ../base"
|
||||
modules = Control.Algebra,
|
||||
Control.Isomorphism.Primitives,
|
||||
Classes.Verified,
|
||||
Data.Fun, Data.Rel,
|
||||
Data.Hash, Data.Matrix,
|
||||
Data.ZZ, Data.Sign,
|
||||
Data.BoundedList,
|
||||
Data.Heap,
|
||||
Data.SortedMap, Data.SortedSet,
|
||||
|
||||
Decidable.Decidable, Decidable.Order,
|
||||
|
||||
Network.Cgi, Network.Socket,
|
||||
|
||||
System.Concurrency.Process
|
@ -117,9 +117,10 @@ FILE_IO t = MkEff t FileIO
|
||||
||| @ m The file mode.
|
||||
open : (fname : String)
|
||||
-> (m : Mode)
|
||||
-> { [FILE_IO ()] ==> [FILE_IO (if result
|
||||
then OpenFile m
|
||||
else ())] } Eff Bool
|
||||
-> { [FILE_IO ()] ==> {result}
|
||||
[FILE_IO (if result
|
||||
then OpenFile m
|
||||
else ())] } Eff Bool
|
||||
open f m = call $ Open f m
|
||||
|
||||
|
||||
|
@ -149,3 +149,28 @@ believe_me x = prim__believe_me _ _ x
|
||||
public %assert_total
|
||||
really_believe_me : a -> b
|
||||
really_believe_me x = prim__believe_me _ _ x
|
||||
|
||||
-- Deprecated - for backward compatibility
|
||||
Float : Type
|
||||
Float = Double
|
||||
|
||||
-- Pointers as external primitive; there's no literals for these, so no
|
||||
-- need for them to be part of the compiler.
|
||||
|
||||
abstract data Ptr : Type
|
||||
abstract data ManagedPtr : Type
|
||||
|
||||
%extern prim__readFile : prim__WorldType -> Ptr -> String
|
||||
%extern prim__writeFile : prim__WorldType -> Ptr -> String -> Int
|
||||
%extern prim__readString : prim__WorldType -> String
|
||||
%extern prim__writeString : prim__WorldType -> String -> Int
|
||||
|
||||
%extern prim__vm : Ptr
|
||||
%extern prim__stdin : Ptr
|
||||
%extern prim__stdout : Ptr
|
||||
%extern prim__stderr : Ptr
|
||||
|
||||
%extern prim__null : Ptr
|
||||
%extern prim__registerPtr : Ptr -> Int -> ManagedPtr
|
||||
|
||||
|
||||
|
@ -181,15 +181,6 @@ instance DecEq Integer where
|
||||
primitiveEq = believe_me (Refl {x})
|
||||
postulate primitiveNotEq : x = y -> Void
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
-- Float
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
instance DecEq Float where
|
||||
decEq x y = if x == y then Yes primitiveEq else No primitiveNotEq
|
||||
where primitiveEq : x = y
|
||||
primitiveEq = believe_me (Refl {x})
|
||||
postulate primitiveNotEq : x = y -> Void
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
-- String
|
||||
|
@ -51,8 +51,10 @@ applyEnv : (env : FEnv ffi xs) ->
|
||||
applyEnv [] f = f
|
||||
applyEnv (x@(_, _) :: xs) f = applyEnv xs (f x)
|
||||
|
||||
mkForeignPrim : ForeignPrimType xs env t
|
||||
-- compiled as primitive
|
||||
mkForeignPrim : {xs : _} -> {ffi : _} -> {env : FEnv ffi xs} -> {t : Type} ->
|
||||
ForeignPrimType xs env t
|
||||
-- compiled as primitive. Compiler assumes argument order, so make it
|
||||
-- explicit here.
|
||||
|
||||
%inline
|
||||
foreign_prim : (f : FFI) ->
|
||||
@ -134,10 +136,6 @@ data C_IntTypes : Type -> Type where
|
||||
C_IntBits16 : C_IntTypes Bits16
|
||||
C_IntBits32 : C_IntTypes Bits32
|
||||
C_IntBits64 : C_IntTypes Bits64
|
||||
C_IntB8x16 : C_IntTypes Bits8x16
|
||||
C_IntB16x8 : C_IntTypes Bits16x8
|
||||
C_IntB32x4 : C_IntTypes Bits32x4
|
||||
C_IntB64x2 : C_IntTypes Bits64x2
|
||||
|
||||
-- Supported C foreign types
|
||||
data C_Types : Type -> Type where
|
||||
|
@ -38,17 +38,14 @@ data TTUExp =
|
||||
data NativeTy = IT8 | IT16 | IT32 | IT64
|
||||
|
||||
data IntTy = ITFixed NativeTy | ITNative | ITBig | ITChar
|
||||
| ITVec NativeTy Int
|
||||
|
||||
data ArithTy = ATInt Language.Reflection.IntTy | ATFloat
|
||||
|
||||
||| Primitive constants
|
||||
data Const = I Int | BI Integer | Fl Float | Ch Char | Str String
|
||||
| B8 Bits8 | B16 Bits16 | B32 Bits32 | B64 Bits64
|
||||
| B8V Bits8x16 | B16V Bits16x8
|
||||
| B32V Bits32x4 | B64V Bits64x2
|
||||
| AType ArithTy | StrType
|
||||
| PtrType | ManagedPtrType | BufferType | VoidType | Forgot
|
||||
| VoidType | Forgot
|
||||
| WorldType | TheWorld
|
||||
%name Const c, c'
|
||||
|
||||
@ -361,38 +358,6 @@ instance Quotable Integer Raw where
|
||||
quotedTy = `(Integer)
|
||||
quote x = RConstant (BI x)
|
||||
|
||||
instance Quotable Bits8x16 TT where
|
||||
quotedTy = `(Bits8x16)
|
||||
quote x = TConst (B8V x)
|
||||
|
||||
instance Quotable Bits8x16 Raw where
|
||||
quotedTy = `(Bits8x16)
|
||||
quote x = RConstant (B8V x)
|
||||
|
||||
instance Quotable Bits16x8 TT where
|
||||
quotedTy = `(Bits16x8)
|
||||
quote x = TConst (B16V x)
|
||||
|
||||
instance Quotable Bits16x8 Raw where
|
||||
quotedTy = `(Bits16x8)
|
||||
quote x = RConstant (B16V x)
|
||||
|
||||
instance Quotable Bits32x4 TT where
|
||||
quotedTy = `(Bits32x4)
|
||||
quote x = TConst (B32V x)
|
||||
|
||||
instance Quotable Bits32x4 Raw where
|
||||
quotedTy = `(Bits32x4)
|
||||
quote x = RConstant (B32V x)
|
||||
|
||||
instance Quotable Bits64x2 TT where
|
||||
quotedTy = `(Bits64x2)
|
||||
quote x = TConst (B64V x)
|
||||
|
||||
instance Quotable Bits64x2 Raw where
|
||||
quotedTy = `(Bits64x2)
|
||||
quote x = RConstant (B64V x)
|
||||
|
||||
instance Quotable String TT where
|
||||
quotedTy = `(String)
|
||||
quote x = TConst (Str x)
|
||||
@ -459,7 +424,6 @@ instance Quotable Reflection.IntTy TT where
|
||||
quote ITNative = `(Reflection.ITNative)
|
||||
quote ITBig = `(ITBig)
|
||||
quote ITChar = `(Reflection.ITChar)
|
||||
quote (ITVec x y) = `(ITVec ~(quote x) ~(quote y))
|
||||
|
||||
instance Quotable Reflection.IntTy Raw where
|
||||
quotedTy = `(Reflection.IntTy)
|
||||
@ -467,7 +431,6 @@ instance Quotable Reflection.IntTy Raw where
|
||||
quote ITNative = `(Reflection.ITNative)
|
||||
quote ITBig = `(ITBig)
|
||||
quote ITChar = `(Reflection.ITChar)
|
||||
quote (ITVec x y) = `(ITVec ~(quote {t=Raw} x) ~(quote {t=Raw} y))
|
||||
|
||||
instance Quotable ArithTy TT where
|
||||
quotedTy = `(ArithTy)
|
||||
@ -490,15 +453,8 @@ instance Quotable Const TT where
|
||||
quote (B16 x) = `(B16 ~(quote x))
|
||||
quote (B32 x) = `(B32 ~(quote x))
|
||||
quote (B64 x) = `(B64 ~(quote x))
|
||||
quote (B8V xs) = `(B8V ~(quote xs))
|
||||
quote (B16V xs) = `(B16V ~(quote xs))
|
||||
quote (B32V xs) = `(B32V ~(quote xs))
|
||||
quote (B64V xs) = `(B64V ~(quote xs))
|
||||
quote (AType x) = `(AType ~(quote x))
|
||||
quote StrType = `(StrType)
|
||||
quote PtrType = `(PtrType)
|
||||
quote ManagedPtrType = `(ManagedPtrType)
|
||||
quote BufferType = `(BufferType)
|
||||
quote VoidType = `(VoidType)
|
||||
quote Forgot = `(Forgot)
|
||||
quote WorldType = `(WorldType)
|
||||
@ -515,15 +471,8 @@ instance Quotable Const Raw where
|
||||
quote (B16 x) = `(B16 ~(quote {t=Raw} x))
|
||||
quote (B32 x) = `(B32 ~(quote {t=Raw} x))
|
||||
quote (B64 x) = `(B64 ~(quote {t=Raw} x))
|
||||
quote (B8V xs) = `(B8V ~(quote {t=Raw} xs))
|
||||
quote (B16V xs) = `(B16V ~(quote {t=Raw} xs))
|
||||
quote (B32V xs) = `(B32V ~(quote {t=Raw} xs))
|
||||
quote (B64V xs) = `(B64V ~(quote {t=Raw} xs))
|
||||
quote (AType x) = `(AType ~(quote {t=Raw} x))
|
||||
quote StrType = `(StrType)
|
||||
quote PtrType = `(PtrType)
|
||||
quote ManagedPtrType = `(ManagedPtrType)
|
||||
quote BufferType = `(BufferType)
|
||||
quote VoidType = `(VoidType)
|
||||
quote Forgot = `(Forgot)
|
||||
quote WorldType = `(WorldType)
|
||||
|
@ -23,7 +23,7 @@ import public Prelude.Bits
|
||||
import public Prelude.Uninhabited
|
||||
import public Prelude.Pairs
|
||||
import public Prelude.Stream
|
||||
|
||||
import public Prelude.Providers
|
||||
import public Decidable.Equality
|
||||
import public Language.Reflection
|
||||
import public Language.Reflection.Errors
|
||||
@ -115,106 +115,6 @@ instance Show Bits32 where
|
||||
instance Show Bits64 where
|
||||
show b = b64ToString b
|
||||
|
||||
%assert_total
|
||||
viewB8x16 : Bits8x16 -> (Bits8, Bits8, Bits8, Bits8, Bits8, Bits8, Bits8, Bits8, Bits8, Bits8, Bits8, Bits8, Bits8, Bits8, Bits8, Bits8)
|
||||
viewB8x16 x = ( prim__indexB8x16 x (prim__truncBigInt_B32 0)
|
||||
, prim__indexB8x16 x (prim__truncBigInt_B32 1)
|
||||
, prim__indexB8x16 x (prim__truncBigInt_B32 2)
|
||||
, prim__indexB8x16 x (prim__truncBigInt_B32 3)
|
||||
, prim__indexB8x16 x (prim__truncBigInt_B32 4)
|
||||
, prim__indexB8x16 x (prim__truncBigInt_B32 5)
|
||||
, prim__indexB8x16 x (prim__truncBigInt_B32 6)
|
||||
, prim__indexB8x16 x (prim__truncBigInt_B32 7)
|
||||
, prim__indexB8x16 x (prim__truncBigInt_B32 8)
|
||||
, prim__indexB8x16 x (prim__truncBigInt_B32 9)
|
||||
, prim__indexB8x16 x (prim__truncBigInt_B32 10)
|
||||
, prim__indexB8x16 x (prim__truncBigInt_B32 11)
|
||||
, prim__indexB8x16 x (prim__truncBigInt_B32 12)
|
||||
, prim__indexB8x16 x (prim__truncBigInt_B32 13)
|
||||
, prim__indexB8x16 x (prim__truncBigInt_B32 14)
|
||||
, prim__indexB8x16 x (prim__truncBigInt_B32 15)
|
||||
)
|
||||
|
||||
instance Show Bits8x16 where
|
||||
show x =
|
||||
case viewB8x16 x of
|
||||
(a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p) =>
|
||||
"<" ++ prim__toStrB8 a
|
||||
++ ", " ++ prim__toStrB8 b
|
||||
++ ", " ++ prim__toStrB8 c
|
||||
++ ", " ++ prim__toStrB8 d
|
||||
++ ", " ++ prim__toStrB8 e
|
||||
++ ", " ++ prim__toStrB8 f
|
||||
++ ", " ++ prim__toStrB8 g
|
||||
++ ", " ++ prim__toStrB8 h
|
||||
++ ", " ++ prim__toStrB8 i
|
||||
++ ", " ++ prim__toStrB8 j
|
||||
++ ", " ++ prim__toStrB8 k
|
||||
++ ", " ++ prim__toStrB8 l
|
||||
++ ", " ++ prim__toStrB8 m
|
||||
++ ", " ++ prim__toStrB8 n
|
||||
++ ", " ++ prim__toStrB8 o
|
||||
++ ", " ++ prim__toStrB8 p
|
||||
++ ">"
|
||||
|
||||
%assert_total
|
||||
viewB16x8 : Bits16x8 -> (Bits16, Bits16, Bits16, Bits16, Bits16, Bits16, Bits16, Bits16)
|
||||
viewB16x8 x = ( prim__indexB16x8 x (prim__truncBigInt_B32 0)
|
||||
, prim__indexB16x8 x (prim__truncBigInt_B32 1)
|
||||
, prim__indexB16x8 x (prim__truncBigInt_B32 2)
|
||||
, prim__indexB16x8 x (prim__truncBigInt_B32 3)
|
||||
, prim__indexB16x8 x (prim__truncBigInt_B32 4)
|
||||
, prim__indexB16x8 x (prim__truncBigInt_B32 5)
|
||||
, prim__indexB16x8 x (prim__truncBigInt_B32 6)
|
||||
, prim__indexB16x8 x (prim__truncBigInt_B32 7)
|
||||
)
|
||||
|
||||
instance Show Bits16x8 where
|
||||
show x =
|
||||
case viewB16x8 x of
|
||||
(a, b, c, d, e, f, g, h) =>
|
||||
"<" ++ prim__toStrB16 a
|
||||
++ ", " ++ prim__toStrB16 b
|
||||
++ ", " ++ prim__toStrB16 c
|
||||
++ ", " ++ prim__toStrB16 d
|
||||
++ ", " ++ prim__toStrB16 e
|
||||
++ ", " ++ prim__toStrB16 f
|
||||
++ ", " ++ prim__toStrB16 g
|
||||
++ ", " ++ prim__toStrB16 h
|
||||
++ ">"
|
||||
|
||||
%assert_total
|
||||
viewB32x4 : Bits32x4 -> (Bits32, Bits32, Bits32, Bits32)
|
||||
viewB32x4 x = ( prim__indexB32x4 x (prim__truncBigInt_B32 0)
|
||||
, prim__indexB32x4 x (prim__truncBigInt_B32 1)
|
||||
, prim__indexB32x4 x (prim__truncBigInt_B32 2)
|
||||
, prim__indexB32x4 x (prim__truncBigInt_B32 3)
|
||||
)
|
||||
|
||||
instance Show Bits32x4 where
|
||||
show x =
|
||||
case viewB32x4 x of
|
||||
(a, b, c, d) =>
|
||||
"<" ++ prim__toStrB32 a
|
||||
++ ", " ++ prim__toStrB32 b
|
||||
++ ", " ++ prim__toStrB32 c
|
||||
++ ", " ++ prim__toStrB32 d
|
||||
++ ">"
|
||||
|
||||
%assert_total
|
||||
viewB64x2 : Bits64x2 -> (Bits64, Bits64)
|
||||
viewB64x2 x = ( prim__indexB64x2 x (prim__truncBigInt_B32 0)
|
||||
, prim__indexB64x2 x (prim__truncBigInt_B32 1)
|
||||
)
|
||||
|
||||
instance Show Bits64x2 where
|
||||
show x =
|
||||
case viewB64x2 x of
|
||||
(a, b) =>
|
||||
"<" ++ prim__toStrB64 a
|
||||
++ ", " ++ prim__toStrB64 b
|
||||
++ ">"
|
||||
|
||||
instance (Show a, Show b) => Show (a, b) where
|
||||
show (x, y) = "(" ++ show x ++ ", " ++ show y ++ ")"
|
||||
|
||||
@ -419,18 +319,6 @@ curry f a b = f (a, b)
|
||||
uncurry : (a -> b -> c) -> (a, b) -> c
|
||||
uncurry f (a, b) = f a b
|
||||
|
||||
uniformB8x16 : Bits8 -> Bits8x16
|
||||
uniformB8x16 x = prim__mkB8x16 x x x x x x x x x x x x x x x x
|
||||
|
||||
uniformB16x8 : Bits16 -> Bits16x8
|
||||
uniformB16x8 x = prim__mkB16x8 x x x x x x x x
|
||||
|
||||
uniformB32x4 : Bits32 -> Bits32x4
|
||||
uniformB32x4 x = prim__mkB32x4 x x x x
|
||||
|
||||
uniformB64x2 : Bits64 -> Bits64x2
|
||||
uniformB64x2 x = prim__mkB64x2 x x
|
||||
|
||||
---- some basic io
|
||||
|
||||
||| Output a string to stdout without a trailing newline
|
||||
|
@ -22,8 +22,6 @@ infixl 5 <#>
|
||||
class Semigroup a where
|
||||
(<+>) : a -> a -> a
|
||||
|
||||
class Semigroup a => VerifiedSemigroup a where
|
||||
total semigroupOpIsAssociative : (l, c, r : a) -> l <+> (c <+> r) = (l <+> c) <+> r
|
||||
|
||||
||| Sets equipped with a single binary operation that is associative, along with
|
||||
||| a neutral element for that binary operation. Must satisfy the following
|
||||
@ -37,295 +35,3 @@ class Semigroup a => VerifiedSemigroup a where
|
||||
class Semigroup a => Monoid a where
|
||||
neutral : a
|
||||
|
||||
class (VerifiedSemigroup a, Monoid a) => VerifiedMonoid a where
|
||||
total monoidNeutralIsNeutralL : (l : a) -> l <+> neutral = l
|
||||
total monoidNeutralIsNeutralR : (r : a) -> neutral <+> r = r
|
||||
|
||||
||| Sets equipped with a single binary operation that is associative, along with
|
||||
||| a neutral element for that binary operation and inverses for all elements.
|
||||
||| Must satisfy the following laws:
|
||||
||| + Associativity of `<+>`:
|
||||
||| forall a b c, a <+> (b <+> c) == (a <+> b) <+> c
|
||||
||| + Neutral for `<+>`:
|
||||
||| forall a, a <+> neutral == a
|
||||
||| forall a, neutral <+> a == a
|
||||
||| + Inverse for `<+>`:
|
||||
||| forall a, a <+> inverse a == neutral
|
||||
||| forall a, inverse a <+> a == neutral
|
||||
class Monoid a => Group a where
|
||||
inverse : a -> a
|
||||
|
||||
class (VerifiedMonoid a, Group a) => VerifiedGroup a where
|
||||
total groupInverseIsInverseL : (l : a) -> l <+> inverse l = neutral
|
||||
total groupInverseIsInverseR : (r : a) -> inverse r <+> r = neutral
|
||||
|
||||
(<->) : Group a => a -> a -> a
|
||||
(<->) left right = left <+> (inverse right)
|
||||
|
||||
||| Sets equipped with a single binary operation that is associative and
|
||||
||| commutative, along with a neutral element for that binary operation and
|
||||
||| inverses for all elements. Must satisfy the following laws:
|
||||
|||
|
||||
||| + Associativity of `<+>`:
|
||||
||| forall a b c, a <+> (b <+> c) == (a <+> b) <+> c
|
||||
||| + Commutativity of `<+>`:
|
||||
||| forall a b, a <+> b == b <+> a
|
||||
||| + Neutral for `<+>`:
|
||||
||| forall a, a <+> neutral == a
|
||||
||| forall a, neutral <+> a == a
|
||||
||| + Inverse for `<+>`:
|
||||
||| forall a, a <+> inverse a == neutral
|
||||
||| forall a, inverse a <+> a == neutral
|
||||
class Group a => AbelianGroup a where { }
|
||||
|
||||
class (VerifiedGroup a, AbelianGroup a) => VerifiedAbelianGroup a where
|
||||
total abelianGroupOpIsCommutative : (l, r : a) -> l <+> r = r <+> l
|
||||
|
||||
||| Sets equipped with two binary operations, one associative and commutative
|
||||
||| supplied with a neutral element, and the other associative, with
|
||||
||| distributivity laws relating the two operations. Must satisfy the following
|
||||
||| laws:
|
||||
|||
|
||||
||| + Associativity of `<+>`:
|
||||
||| forall a b c, a <+> (b <+> c) == (a <+> b) <+> c
|
||||
||| + Commutativity of `<+>`:
|
||||
||| forall a b, a <+> b == b <+> a
|
||||
||| + Neutral for `<+>`:
|
||||
||| forall a, a <+> neutral == a
|
||||
||| forall a, neutral <+> a == a
|
||||
||| + Inverse for `<+>`:
|
||||
||| forall a, a <+> inverse a == neutral
|
||||
||| forall a, inverse a <+> a == neutral
|
||||
||| + Associativity of `<.>`:
|
||||
||| forall a b c, a <.> (b <.> c) == (a <.> b) <.> c
|
||||
||| + Distributivity of `<.>` and `<->`:
|
||||
||| forall a b c, a <.> (b <+> c) == (a <.> b) <+> (a <.> c)
|
||||
||| forall a b c, (a <+> b) <.> c == (a <.> c) <+> (b <.> c)
|
||||
class AbelianGroup a => Ring a where
|
||||
(<.>) : a -> a -> a
|
||||
|
||||
class (VerifiedAbelianGroup a, Ring a) => VerifiedRing a where
|
||||
total ringOpIsAssociative : (l, c, r : a) -> l <.> (c <.> r) = (l <.> c) <.> r
|
||||
total ringOpIsDistributiveL : (l, c, r : a) -> l <.> (c <+> r) = (l <.> c) <+> (l <.> r)
|
||||
total ringOpIsDistributiveR : (l, c, r : a) -> (l <+> c) <.> r = (l <.> r) <+> (c <.> r)
|
||||
|
||||
||| Sets equipped with two binary operations, one associative and commutative
|
||||
||| supplied with a neutral element, and the other associative supplied with a
|
||||
||| neutral element, with distributivity laws relating the two operations. Must
|
||||
||| satisfy the following laws:
|
||||
|||
|
||||
||| + Associativity of `<+>`:
|
||||
||| forall a b c, a <+> (b <+> c) == (a <+> b) <+> c
|
||||
||| + Commutativity of `<+>`:
|
||||
||| forall a b, a <+> b == b <+> a
|
||||
||| + Neutral for `<+>`:
|
||||
||| forall a, a <+> neutral == a
|
||||
||| forall a, neutral <+> a == a
|
||||
||| + Inverse for `<+>`:
|
||||
||| forall a, a <+> inverse a == neutral
|
||||
||| forall a, inverse a <+> a == neutral
|
||||
||| + Associativity of `<.>`:
|
||||
||| forall a b c, a <.> (b <.> c) == (a <.> b) <.> c
|
||||
||| + Neutral for `<.>`:
|
||||
||| forall a, a <.> unity == a
|
||||
||| forall a, unity <.> a == a
|
||||
||| + Distributivity of `<.>` and `<->`:
|
||||
||| forall a b c, a <.> (b <+> c) == (a <.> b) <+> (a <.> c)
|
||||
||| forall a b c, (a <+> b) <.> c == (a <.> c) <+> (b <.> c)
|
||||
class Ring a => RingWithUnity a where
|
||||
unity : a
|
||||
|
||||
class (VerifiedRing a, RingWithUnity a) => VerifiedRingWithUnity a where
|
||||
total ringWithUnityIsUnityL : (l : a) -> l <.> unity = l
|
||||
total ringWithUnityIsUnityR : (r : a) -> unity <.> r = r
|
||||
|
||||
||| Sets equipped with a binary operation that is commutative, associative and
|
||||
||| idempotent. Must satisfy the following laws:
|
||||
|||
|
||||
||| + Associativity of join:
|
||||
||| forall a b c, join a (join b c) == join (join a b) c
|
||||
||| + Commutativity of join:
|
||||
||| forall a b, join a b == join b a
|
||||
||| + Idempotency of join:
|
||||
||| forall a, join a a == a
|
||||
|||
|
||||
||| Join semilattices capture the notion of sets with a "least upper bound".
|
||||
class JoinSemilattice a where
|
||||
join : a -> a -> a
|
||||
|
||||
class JoinSemilattice a => VerifiedJoinSemilattice a where
|
||||
total joinSemilatticeJoinIsAssociative : (l, c, r : a) -> join l (join c r) = join (join l c) r
|
||||
total joinSemilatticeJoinIsCommutative : (l, r : a) -> join l r = join r l
|
||||
total joinSemilatticeJoinIsIdempotent : (e : a) -> join e e = e
|
||||
|
||||
||| Sets equipped with a binary operation that is commutative, associative and
|
||||
||| idempotent. Must satisfy the following laws:
|
||||
|||
|
||||
||| + Associativity of meet:
|
||||
||| forall a b c, meet a (meet b c) == meet (meet a b) c
|
||||
||| + Commutativity of meet:
|
||||
||| forall a b, meet a b == meet b a
|
||||
||| + Idempotency of meet:
|
||||
||| forall a, meet a a == a
|
||||
|||
|
||||
||| Meet semilattices capture the notion of sets with a "greatest lower bound".
|
||||
class MeetSemilattice a where
|
||||
meet : a -> a -> a
|
||||
|
||||
class MeetSemilattice a => VerifiedMeetSemilattice a where
|
||||
total meetSemilatticeMeetIsAssociative : (l, c, r : a) -> meet l (meet c r) = meet (meet l c) r
|
||||
total meetSemilatticeMeetIsCommutative : (l, r : a) -> meet l r = meet r l
|
||||
total meetSemilatticeMeetIsIdempotent : (e : a) -> meet e e = e
|
||||
|
||||
||| Sets equipped with a binary operation that is commutative, associative and
|
||||
||| idempotent and supplied with a unitary element. Must satisfy the following
|
||||
||| laws:
|
||||
|||
|
||||
||| + Associativity of join:
|
||||
||| forall a b c, join a (join b c) == join (join a b) c
|
||||
||| + Commutativity of join:
|
||||
||| forall a b, join a b == join b a
|
||||
||| + Idempotency of join:
|
||||
||| forall a, join a a == a
|
||||
||| + Bottom (Unitary Element):
|
||||
||| forall a, join a bottom == a
|
||||
|||
|
||||
||| Join semilattices capture the notion of sets with a "least upper bound"
|
||||
||| equipped with a "bottom" element.
|
||||
class JoinSemilattice a => BoundedJoinSemilattice a where
|
||||
bottom : a
|
||||
|
||||
class (VerifiedJoinSemilattice a, BoundedJoinSemilattice a) => VerifiedBoundedJoinSemilattice a where
|
||||
total boundedJoinSemilatticeBottomIsBottom : (e : a) -> join e bottom = e
|
||||
|
||||
||| Sets equipped with a binary operation that is commutative, associative and
|
||||
||| idempotent and supplied with a unitary element. Must satisfy the following
|
||||
||| laws:
|
||||
|||
|
||||
||| + Associativity of meet:
|
||||
||| forall a b c, meet a (meet b c) == meet (meet a b) c
|
||||
||| + Commutativity of meet:
|
||||
||| forall a b, meet a b == meet b a
|
||||
||| + Idempotency of meet:
|
||||
||| forall a, meet a a == a
|
||||
||| + Top (Unitary Element):
|
||||
||| forall a, meet a top == a
|
||||
|||
|
||||
||| Meet semilattices capture the notion of sets with a "greatest lower bound"
|
||||
||| equipped with a "top" element.
|
||||
class MeetSemilattice a => BoundedMeetSemilattice a where
|
||||
top : a
|
||||
|
||||
class (VerifiedMeetSemilattice a, BoundedMeetSemilattice a) => VerifiedBoundedMeetSemilattice a where
|
||||
total boundedMeetSemilatticeTopIsTop : (e : a) -> meet e top = e
|
||||
|
||||
||| Sets equipped with two binary operations that are both commutative,
|
||||
||| associative and idempotent, along with absorbtion laws for relating the two
|
||||
||| binary operations. Must satisfy the following:
|
||||
|||
|
||||
||| + Associativity of meet and join:
|
||||
||| forall a b c, meet a (meet b c) == meet (meet a b) c
|
||||
||| forall a b c, join a (join b c) == join (join a b) c
|
||||
||| + Commutativity of meet and join:
|
||||
||| forall a b, meet a b == meet b a
|
||||
||| forall a b, join a b == join b a
|
||||
||| + Idempotency of meet and join:
|
||||
||| forall a, meet a a == a
|
||||
||| forall a, join a a == a
|
||||
||| + Absorbtion laws for meet and join:
|
||||
||| forall a b, meet a (join a b) == a
|
||||
||| forall a b, join a (meet a b) == a
|
||||
class (JoinSemilattice a, MeetSemilattice a) => Lattice a where { }
|
||||
|
||||
class (VerifiedJoinSemilattice a, VerifiedMeetSemilattice a) => VerifiedLattice a where
|
||||
total latticeMeetAbsorbsJoin : (l, r : a) -> meet l (join l r) = l
|
||||
total latticeJoinAbsorbsMeet : (l, r : a) -> join l (meet l r) = l
|
||||
|
||||
||| Sets equipped with two binary operations that are both commutative,
|
||||
||| associative and idempotent and supplied with neutral elements, along with
|
||||
||| absorbtion laws for relating the two binary operations. Must satisfy the
|
||||
||| following:
|
||||
|||
|
||||
||| + Associativity of meet and join:
|
||||
||| forall a b c, meet a (meet b c) == meet (meet a b) c
|
||||
||| forall a b c, join a (join b c) == join (join a b) c
|
||||
||| + Commutativity of meet and join:
|
||||
||| forall a b, meet a b == meet b a
|
||||
||| forall a b, join a b == join b a
|
||||
||| + Idempotency of meet and join:
|
||||
||| forall a, meet a a == a
|
||||
||| forall a, join a a == a
|
||||
||| + Absorbtion laws for meet and join:
|
||||
||| forall a b, meet a (join a b) == a
|
||||
||| forall a b, join a (meet a b) == a
|
||||
||| + Neutral for meet and join:
|
||||
||| forall a, meet a top == top
|
||||
||| forall a, join a bottom == bottom
|
||||
class (BoundedJoinSemilattice a, BoundedMeetSemilattice a) => BoundedLattice a where { }
|
||||
|
||||
class (VerifiedBoundedJoinSemilattice a, VerifiedBoundedMeetSemilattice a, VerifiedLattice a) => VerifiedBoundedLattice a where { }
|
||||
|
||||
-- Fields.
|
||||
||| Sets equipped with two binary operations, both associative and commutative
|
||||
||| supplied with a neutral element, with
|
||||
||| distributivity laws relating the two operations. Must satisfy the following
|
||||
||| laws:
|
||||
|||
|
||||
||| + Associativity of `<+>`:
|
||||
||| forall a b c, a <+> (b <+> c) == (a <+> b) <+> c
|
||||
||| + Commutativity of `<+>`:
|
||||
||| forall a b, a <+> b == b <+> a
|
||||
||| + Neutral for `<+>`:
|
||||
||| forall a, a <+> neutral == a
|
||||
||| forall a, neutral <+> a == a
|
||||
||| + Inverse for `<+>`:
|
||||
||| forall a, a <+> inverse a == neutral
|
||||
||| forall a, inverse a <+> a == neutral
|
||||
||| + Associativity of `<.>`:
|
||||
||| forall a b c, a <.> (b <.> c) == (a <.> b) <.> c
|
||||
||| + Unity for `<.>`:
|
||||
||| forall a, a <.> unity == a
|
||||
||| forall a, unity <.> a == a
|
||||
||| + InverseM of `<.>`:
|
||||
||| forall a, a <.> inverseM a == unity
|
||||
||| forall a, inverseM a <.> a == unity
|
||||
||| + Distributivity of `<.>` and `<->`:
|
||||
||| forall a b c, a <.> (b <+> c) == (a <.> b) <+> (a <.> c)
|
||||
||| forall a b c, (a <+> b) <.> c == (a <.> c) <+> (b <.> c)
|
||||
class RingWithUnity a => Field a where
|
||||
inverseM : a -> a
|
||||
|
||||
class (VerifiedRing a, Field a) => VerifiedField a where
|
||||
total fieldInverseIsInverseL : (l : a) -> l <.> inverseM l = unity
|
||||
total fieldInverseIsInverseR : (r : a) -> inverseM r <.> r = unity
|
||||
|
||||
||| A module over a ring is an additive abelian group of 'vectors' endowed with a
|
||||
||| scale operation multiplying vectors by ring elements, and distributivity laws
|
||||
||| relating the scale operation to both ring addition and module addition.
|
||||
||| Must satisfy the following laws:
|
||||
|||
|
||||
||| + Compatibility of scalar multiplication with ring multiplication:
|
||||
||| forall a b v, a <#> (b <#> v) = (a <.> b) <#> v
|
||||
||| + Ring unity is the identity element of scalar multiplication:
|
||||
||| forall v, unity <#> v = v
|
||||
||| + Distributivity of `<#>` and `<+>`:
|
||||
||| forall a v w, a <#> (v <+> w) == (a <#> v) <+> (a <#> w)
|
||||
||| forall a b v, (a <+> b) <#> v == (a <#> v) <+> (b <#> v)
|
||||
class (RingWithUnity a, AbelianGroup b) => Module a b where
|
||||
(<#>) : a -> b -> b
|
||||
|
||||
class (VerifiedRingWithUnity a, VerifiedAbelianGroup b, Module a b) => VerifiedModule a b where
|
||||
total moduleScalarMultiplyComposition : (x,y : a) -> (v : b) -> x <#> (y <#> v) = (x <.> y) <#> v
|
||||
total moduleScalarUnityIsUnity : (v : b) -> unity {a} <#> v = v
|
||||
total moduleScalarMultDistributiveWRTVectorAddition : (s : a) -> (v, w : b) -> s <#> (v <+> w) = (s <#> v) <+> (s <#> w)
|
||||
total moduleScalarMultDistributiveWRTModuleAddition : (s, t : a) -> (v : b) -> (s <+> t) <#> v = (s <#> v) <+> (t <#> v)
|
||||
|
||||
||| A vector space is a module over a ring that is also a field
|
||||
class (Field a, Module a b) => VectorSpace a b where {}
|
||||
|
||||
class (VerifiedField a, VerifiedModule a b) => VerifiedVectorSpace a b where {}
|
||||
|
||||
-- XXX todo:
|
||||
-- Structures where "abs" make sense.
|
||||
-- Euclidean domains, etc.
|
||||
-- Where to put fromInteger and fromRational?
|
||||
|
@ -15,17 +15,6 @@ class Functor f => Applicative (f : Type -> Type) where
|
||||
pure : a -> f a
|
||||
(<*>) : f (a -> b) -> f a -> f b
|
||||
|
||||
class (Applicative f, VerifiedFunctor f) => VerifiedApplicative (f : Type -> Type) where
|
||||
applicativeMap : (x : f a) -> (g : a -> b) ->
|
||||
map g x = pure g <*> x
|
||||
applicativeIdentity : (x : f a) -> pure id <*> x = x
|
||||
applicativeComposition : (x : f a) -> (g1 : f (a -> b)) -> (g2 : f (b -> c)) ->
|
||||
((pure (.) <*> g2) <*> g1) <*> x = g2 <*> (g1 <*> x)
|
||||
applicativeHomomorphism : (x : a) -> (g : a -> b) ->
|
||||
(<*>) {f} (pure g) (pure x) = pure {f} (g x)
|
||||
applicativeInterchange : (x : a) -> (g : f (a -> b)) ->
|
||||
g <*> pure x = pure (\g' : a -> b => g' x) <*> g
|
||||
|
||||
infixl 2 <*
|
||||
(<*) : Applicative f => f a -> f b -> f a
|
||||
a <* b = map const a <*> b
|
||||
|
@ -1,5 +1,7 @@
|
||||
module Prelude.Cast
|
||||
|
||||
import public Builtins
|
||||
|
||||
||| Type class for transforming a instance of a data type to another type.
|
||||
class Cast from to where
|
||||
||| Perform a cast operation.
|
||||
|
@ -18,9 +18,3 @@ infixl 4 <$>
|
||||
(<$>) : Functor f => (m : a -> b) -> f a -> f b
|
||||
m <$> x = map m x
|
||||
|
||||
class Functor f => VerifiedFunctor (f : Type -> Type) where
|
||||
functorIdentity : {a : Type} -> (x : f a) -> map id x = id x
|
||||
functorComposition : {a : Type} -> {b : Type} -> (x : f a) ->
|
||||
(g1 : a -> b) -> (g2 : b -> c) ->
|
||||
map (g2 . g1) x = (map g2 . map g1) x
|
||||
|
||||
|
@ -809,12 +809,6 @@ hasAnyByNilFalse p (x::xs) =
|
||||
hasAnyNilFalse : Eq a => (l : List a) -> hasAny [] l = False
|
||||
hasAnyNilFalse l = ?hasAnyNilFalseBody
|
||||
|
||||
instance VerifiedSemigroup (List a) where
|
||||
semigroupOpIsAssociative = appendAssociative
|
||||
|
||||
instance VerifiedMonoid (List a) where
|
||||
monoidNeutralIsNeutralL = appendNilRightNeutral
|
||||
monoidNeutralIsNeutralR xs = Refl
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
-- Proofs
|
||||
|
@ -14,16 +14,6 @@ infixl 5 >>=
|
||||
class Applicative m => Monad (m : Type -> Type) where
|
||||
(>>=) : m a -> (a -> m b) -> m b
|
||||
|
||||
class (Monad m, VerifiedApplicative m) => VerifiedMonad (m : Type -> Type) where
|
||||
monadApplicative : (mf : m (a -> b)) -> (mx : m a) ->
|
||||
mf <*> mx = mf >>= \f =>
|
||||
mx >>= \x =>
|
||||
pure (f x)
|
||||
monadLeftIdentity : (x : a) -> (f : a -> m b) -> return x >>= f = f x
|
||||
monadRightIdentity : (mx : m a) -> mx >>= return = mx
|
||||
monadAssociativity : (mx : m a) -> (f : a -> m b) -> (g : b -> m c) ->
|
||||
(mx >>= f) >>= g = mx >>= (\x => f x >>= g)
|
||||
|
||||
||| Also called `join` or mu
|
||||
flatten : Monad m => m (m a) -> m a
|
||||
flatten a = a >>= id
|
||||
|
@ -243,18 +243,6 @@ instance Monoid Multiplicative where
|
||||
instance Monoid Additive where
|
||||
neutral = getAdditive Z
|
||||
|
||||
instance MeetSemilattice Nat where
|
||||
meet = minimum
|
||||
|
||||
instance JoinSemilattice Nat where
|
||||
join = maximum
|
||||
|
||||
instance Lattice Nat where { }
|
||||
|
||||
instance BoundedJoinSemilattice Nat where
|
||||
bottom = Z
|
||||
|
||||
|
||||
||| Casts negative `Ints` to 0.
|
||||
instance Cast Int Nat where
|
||||
cast i = fromInteger (cast i)
|
||||
|
@ -1,4 +1,8 @@
|
||||
module Providers
|
||||
module Prelude.Providers
|
||||
|
||||
import Prelude.Functor
|
||||
import Prelude.Applicative
|
||||
import Prelude.Monad
|
||||
|
||||
||| Type providers must build one of these in an IO computation.
|
||||
public
|
@ -8,7 +8,7 @@ modules = Builtins, Prelude, IO,
|
||||
Prelude.Maybe, Prelude.Monad, Prelude.Applicative, Prelude.Either,
|
||||
Prelude.Strings, Prelude.Chars, Prelude.Functor,
|
||||
Prelude.Foldable, Prelude.Traversable, Prelude.Bits, Prelude.Stream,
|
||||
Prelude.Uninhabited, Prelude.Pairs,
|
||||
Prelude.Uninhabited, Prelude.Pairs, Prelude.Providers,
|
||||
|
||||
Language.Reflection, Language.Reflection.Errors, Language.Reflection.Tactical,
|
||||
|
||||
|
9
mkpkg.sh
9
mkpkg.sh
@ -10,6 +10,15 @@ git tag v$VERSION -a
|
||||
|
||||
cabal sdist
|
||||
|
||||
# Generate Idris library docs and put them in lib_docs.tar.gz in the root
|
||||
make lib_doc
|
||||
DOCDIR=`mktemp -d /tmp/docsXXXXX`
|
||||
cp -r libs/base/base_doc "$DOCDIR"
|
||||
cp -r libs/prelude/prelude_doc "$DOCDIR"
|
||||
cp -r libs/effects/effects_doc "$DOCDIR"
|
||||
cp -r libs/contrib/contrib_doc "$DOCDIR"
|
||||
tar -czvf lib_docs.tar.gz -C "$DOCDIR" prelude_doc base_doc effects_doc contrib_doc
|
||||
|
||||
cabal configure --prefix=/usr/local
|
||||
cabal build
|
||||
cabal copy --destdir=/tmp/idris-pkg/
|
||||
|
@ -1,9 +1,10 @@
|
||||
include ../config.mk
|
||||
|
||||
OBJS = idris_rts.o idris_heap.o idris_gc.o idris_gmp.o idris_bitstring.o \
|
||||
idris_opts.o idris_stats.o mini-gmp.o
|
||||
idris_opts.o idris_stats.o idris_utf8.o mini-gmp.o
|
||||
HDRS = idris_rts.h idris_heap.h idris_gc.h idris_gmp.h idris_bitstring.h \
|
||||
idris_opts.h idris_stats.h mini-gmp.h idris_stdfgn.h idris_net.h
|
||||
idris_opts.h idris_stats.h mini-gmp.h idris_stdfgn.h idris_net.h \
|
||||
idris_utf8.h
|
||||
CFLAGS:=-fPIC $(CFLAGS)
|
||||
CFLAGS += $(GMP_INCLUDE_DIR) $(GMP) -DIDRIS_TARGET_OS="\"$(OS)\""
|
||||
CFLAGS += -DIDRIS_TARGET_TRIPLE="\"$(MACHINE)\""
|
||||
|
@ -822,71 +822,3 @@ VAL idris_b64T32(VM *vm, VAL a) {
|
||||
return cl;
|
||||
}
|
||||
|
||||
// SSE vectors
|
||||
VAL idris_IDXB8x16(VM* vm, VAL vec, VAL idx) {
|
||||
__m128i sse = *vec->info.bits128p;
|
||||
uint8_t data[16];
|
||||
_mm_storeu_si128((__m128i*)&data, sse);
|
||||
return MKB8(vm, data[idx->info.bits32]);
|
||||
}
|
||||
|
||||
VAL idris_IDXB16x8(VM* vm, VAL vec, VAL idx) {
|
||||
__m128i sse = *vec->info.bits128p;
|
||||
uint16_t data[8];
|
||||
_mm_storeu_si128((__m128i*)&data, sse);
|
||||
return MKB16(vm, data[idx->info.bits32]);
|
||||
}
|
||||
|
||||
VAL idris_IDXB32x4(VM* vm, VAL vec, VAL idx) {
|
||||
__m128i sse = *vec->info.bits128p;
|
||||
uint32_t data[4];
|
||||
_mm_storeu_si128((__m128i*)&data, sse);
|
||||
return MKB32(vm, data[idx->info.bits32]);
|
||||
}
|
||||
|
||||
VAL idris_IDXB64x2(VM* vm, VAL vec, VAL idx) {
|
||||
__m128i sse = *vec->info.bits128p;
|
||||
uint64_t data[2];
|
||||
_mm_storeu_si128((__m128i*)&data, sse);
|
||||
return MKB64(vm, data[idx->info.bits32]);
|
||||
}
|
||||
|
||||
VAL idris_b8x16CopyForGC(VM *vm, VAL vec) {
|
||||
__m128i sse = *vec->info.bits128p;
|
||||
VAL cl = allocate(sizeof(Closure) + 16 + sizeof(__m128i), 1);
|
||||
SETTY(cl, BITS8X16);
|
||||
cl->info.bits128p = (__m128i*)ALIGN((uintptr_t)cl + sizeof(Closure), 16);
|
||||
assert ((uintptr_t)cl->info.bits128p % 16 == 0);
|
||||
*cl->info.bits128p = sse;
|
||||
return cl;
|
||||
}
|
||||
|
||||
VAL idris_b16x8CopyForGC(VM *vm, VAL vec) {
|
||||
__m128i sse = *vec->info.bits128p;
|
||||
VAL cl = allocate(sizeof(Closure) + 16 + sizeof(__m128i), 1);
|
||||
SETTY(cl, BITS16X8);
|
||||
cl->info.bits128p = (__m128i*)ALIGN((uintptr_t)cl + sizeof(Closure), 16);
|
||||
assert ((uintptr_t)cl->info.bits128p % 16 == 0);
|
||||
*cl->info.bits128p = sse;
|
||||
return cl;
|
||||
}
|
||||
|
||||
VAL idris_b32x4CopyForGC(VM *vm, VAL vec) {
|
||||
__m128i sse = *vec->info.bits128p;
|
||||
VAL cl = allocate(sizeof(Closure) + 16 + sizeof(__m128i), 1);
|
||||
SETTY(cl, BITS32X4);
|
||||
cl->info.bits128p = (__m128i*)ALIGN((uintptr_t)cl + sizeof(Closure), 16);
|
||||
assert ((uintptr_t)cl->info.bits128p % 16 == 0);
|
||||
*cl->info.bits128p = sse;
|
||||
return cl;
|
||||
}
|
||||
|
||||
VAL idris_b64x2CopyForGC(VM *vm, VAL vec) {
|
||||
__m128i sse = *vec->info.bits128p;
|
||||
VAL cl = allocate(sizeof(Closure) + 16 + sizeof(__m128i), 1);
|
||||
SETTY(cl, BITS64X2);
|
||||
cl->info.bits128p = (__m128i*)ALIGN((uintptr_t)cl + sizeof(Closure), 16);
|
||||
assert ((uintptr_t)cl->info.bits128p % 16 == 0);
|
||||
*cl->info.bits128p = sse;
|
||||
return cl;
|
||||
}
|
||||
|
@ -126,14 +126,4 @@ VAL idris_b64T8(VM *vm, VAL a);
|
||||
VAL idris_b64T16(VM *vm, VAL a);
|
||||
VAL idris_b64T32(VM *vm, VAL a);
|
||||
|
||||
VAL idris_IDXB8x16(VM* vm, VAL vec, VAL idx);
|
||||
VAL idris_IDXB16x8(VM* vm, VAL vec, VAL idx);
|
||||
VAL idris_IDXB32x4(VM* vm, VAL vec, VAL idx);
|
||||
VAL idris_IDXB64x2(VM* vm, VAL vec, VAL idx);
|
||||
|
||||
VAL idris_b8x16CopyForGC(VM *vm, VAL a);
|
||||
VAL idris_b16x8CopyForGC(VM *vm, VAL a);
|
||||
VAL idris_b32x4CopyForGC(VM *vm, VAL a);
|
||||
VAL idris_b64x2CopyForGC(VM *vm, VAL a);
|
||||
|
||||
#endif
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user