├── .github └── workflows │ └── publish.yml ├── .gitignore ├── Makefile ├── README.md ├── book.toml ├── css └── custom.css ├── src ├── README.md ├── SUMMARY.md ├── conclusion │ └── README.md ├── concurrent │ ├── README.md │ ├── dist.md │ ├── example.md │ ├── msgpass.md │ ├── names.md │ └── processes.md ├── feedback.md ├── front │ └── copyright-page.md ├── images │ ├── cnbb-pub-logo-1.6.png │ ├── cover-large.jpg │ └── cover.jpg ├── introduction │ ├── README.md │ ├── deps.md │ ├── erlang.md │ ├── install.md │ └── lfe.md ├── macros │ ├── README.md │ ├── backquote.md │ ├── eval.md │ └── macros.md ├── records │ ├── README.md │ ├── headers.md │ ├── mods.md │ └── records.md ├── redirects │ ├── docs.html │ └── mdbook.html ├── robust │ ├── README.md │ ├── errors.md │ ├── example.md │ └── timeouts.md ├── sequential │ ├── README.md │ ├── atoms.md │ ├── bifs.md │ ├── conds.md │ ├── example.md │ ├── hofs.md │ ├── lists.md │ ├── matching.md │ ├── modfunc.md │ ├── morelists.md │ ├── output.md │ ├── propmaps.md │ ├── repl.md │ ├── stdlib.md │ └── tuples.md └── title.md └── theme ├── LICENSE ├── README.md ├── book.js ├── css ├── chrome.css ├── general.css ├── print.css └── variables.css ├── favicon.png ├── favicon.svg ├── highlight.css ├── highlight.js └── index.hbs /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | # Sample workflow for building and deploying a mdBook site to GitHub Pages 2 | # 3 | # To get started with mdBook see: https://rust-lang.github.io/mdBook/index.html 4 | # 5 | name: Publish book content to GH Pages 6 | 7 | on: 8 | # Runs on pushes targeting the default branch 9 | push: 10 | branches: ["main"] 11 | 12 | # Allows you to run this workflow manually from the Actions tab 13 | workflow_dispatch: 14 | 15 | # Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages 16 | permissions: 17 | contents: read 18 | pages: write 19 | id-token: write 20 | 21 | # Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued. 22 | # However, do NOT cancel in-progress runs as we want to allow these production deployments to complete. 23 | concurrency: 24 | group: "pages" 25 | cancel-in-progress: false 26 | 27 | jobs: 28 | # Build job 29 | build: 30 | runs-on: ubuntu-latest 31 | env: 32 | MDBOOK_VERSION: 0.4.21 33 | steps: 34 | - uses: actions/checkout@v3 35 | - name: Install mdBook 36 | run: | 37 | curl --proto '=https' --tlsv1.2 https://sh.rustup.rs -sSf -y | sh 38 | rustup update 39 | cargo install --version ${MDBOOK_VERSION} mdbook 40 | - name: Setup Pages 41 | id: pages 42 | uses: actions/configure-pages@v3 43 | - name: Build with mdBook 44 | run: mdbook build 45 | - name: Upload artifact 46 | uses: actions/upload-pages-artifact@v2 47 | with: 48 | path: ./book 49 | 50 | # Deployment job 51 | deploy: 52 | environment: 53 | name: github-pages 54 | url: ${{ steps.deployment.outputs.page_url }} 55 | runs-on: ubuntu-latest 56 | needs: build 57 | steps: 58 | - name: Deploy to GitHub Pages 59 | id: deployment 60 | uses: actions/deploy-pages@v2 61 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | _book 3 | book 4 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | BIN = mdbook 2 | GEN := $(shell which $(BIN) 2> /dev/null) 3 | DOWNLOAD = https://github.com/rust-lang/mdBook/releases 4 | PUBLISH_DIR = book 5 | PORT = 5099 6 | 7 | define BINARY_ERROR 8 | 9 | No $(BIN) found in Path. 10 | 11 | Download $(BIN) from $(DOWNLOAD). 12 | 13 | endef 14 | 15 | build: 16 | ifndef GEN 17 | $(error $(BINARY_ERROR)) 18 | endif 19 | @echo " >> Rebuilding book ..." 20 | @$(GEN) build 21 | 22 | serve: 23 | @bash -c "trap \"$(MAKE) serve-cleanup\" EXIT; $(GEN) serve -p $(PORT)" 24 | 25 | serve-cleanup: book-init build 26 | 27 | run: serve 28 | 29 | clean: 30 | @rm -f $(PUBLISH_DIR)/README.md 31 | 32 | spell-check: 33 | @for FILE in `find . -name "*.md"`; do \ 34 | RESULTS=$$(cat $$FILE | aspell -d en_GB --mode=markdown list | sort -u | sed -e ':a' -e 'N;$$!ba' -e 's/\n/, /g'); \ 35 | if [[ "$$RESULTS" != "" ]] ; then \ 36 | echo "Potential spelling errors in $$FILE:"; \ 37 | echo "$$RESULTS" | \ 38 | sed -e 's/^/ /'; \ 39 | echo; \ 40 | fi; \ 41 | done 42 | 43 | add-word: WORD ?= "" 44 | add-word: 45 | @echo "*$(WORD)\n#" | aspell -a > /dev/null 46 | 47 | add-words: WORDS ?= "" 48 | add-words: 49 | @echo "Adding words:" 50 | @for WORD in `echo $(WORDS)| tr "," "\n"| tr "," "\n" | sed -e 's/^[ ]*//' | sed -e 's/[ ]*$$//'`; \ 51 | do echo " $$WORD ..."; \ 52 | echo "*$$WORD\n#" | aspell -a > /dev/null; \ 53 | done 54 | @echo 55 | 56 | spell-suggest: 57 | @for FILE in `find . -name "*.md"`; do \ 58 | RESULTS=$$(cat $$FILE | aspell -d en_GB --mode=markdown list | sort -u ); \ 59 | if [[ "$$RESULTS" != "" ]] ; then \ 60 | echo "Potential spelling errors in $$FILE:"; \ 61 | for WORD in $$RESULTS; do \ 62 | echo $$WORD| aspell -d en_GB pipe | tail -2|head -1 | sed -e 's/^/ /'; \ 63 | done; \ 64 | echo; \ 65 | fi; \ 66 | done 67 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # The LFE Tutorial 2 | 3 | The LFE Tutorial is based on the Erlang tutorial [Getting Started with 4 | Erlang](http://www.erlang.org/doc/getting_started/intro.html) 5 | 6 | 7 | ## Introduction 8 | 9 | The members of the LFE community are 10 | [working toward](https://github.com/lfe/docs/issues/40) 11 | a more comprehensive LFE tutorial, but we wanted to 12 | [get something out](https://github.com/lfe/docs/issues/42) 13 | sooner rather than later, so we're starting with this. On the positive side, 14 | it's material that has already been created and we have only had to make syntax 15 | changes and other edits at our discretion. However, one downside of this as the first full LFE tutorial is that it's not 100% new-comer friendly: 16 | it doesn't start from absolute basics (some knowledge is presumed) and the tutorial is not comprehensive (much is left out). 17 | 18 | That being said, the this tutorial is an intentional "kick start", aimed at to getting you started with LFE. Everything here is true, but as mentioned, it only represents part of the truth. For example, we will typically only cover 19 | simpler syntax and not explore all the esoteric forms. However, for the curious 20 | reader we've annotated where one may get more detailed information on topics 21 | covered lightly (or skipped entirely). 22 | 23 | 24 | ## Prerequisites 25 | 26 | * Basic to intermediate skills in a programming language. 27 | * Exposure to the concepts behind Erlang via other media such as the Erlang web 28 | site, Erlang conference presentations, and books such as the following: 29 | * [Concurrent Programming in ERLANG](http://www.erlang.org/download/erlang-book-part1.pdf) 30 | * [Learn You Some Erlang for great good!](http://learnyousomeerlang.com/) 31 | * [Programming Erlang](https://pragprog.com/book/jaerlang2/programming-erlang) 32 | * [Erlang and OTP in Action](http://www.manning.com/logan/) 33 | * [Erlang Programming](http://shop.oreilly.com/product/9780596518189.do) 34 | * Exposure to a Lisp, whether by reading books or from a course in school (it 35 | will be most helpful if you've had experience with Common Lisp, as LFE has 36 | more in common with Lisp-2s than with Lisp-1s like Schemes and Clojure). 37 | 38 | 39 | ## What Is Covered 40 | 41 | This tutorial covers the following topics: 42 | 43 | * Sequential programming (installing LFE, the REPL, functions, modules, data 44 | types, the standard library, writing to the terminal, pattern matching, 45 | guards, conditionals, built-in functions, and higher-order functions) 46 | * Concurrent programming (processes, message passing, registered processes, 47 | and distributed programming) 48 | * Robustness (timeouts and error handling) 49 | * Records and macros (header files, records, and macros) 50 | 51 | 52 | ## What Has Been Left Out 53 | 54 | The following topics has been omitted from this tutorial: 55 | 56 | * Erlang References 57 | * Local error handling (catch/throw) 58 | * Single direction links (monitor) 59 | * Handling of binary data (binaries / bit syntax) 60 | * List comprehensions 61 | * How to communicate with the outside world and/or software written in other 62 | languages (ports). 63 | * Very few of the Erlang libraries have been touched on (for example file 64 | handling) 65 | * OTP has been totally skipped and in consequence the Mnesia database has been 66 | skipped. 67 | * Hash tables for Erlang terms (ETS) 68 | * Changing code in running systems 69 | 70 | -------------------------------------------------------------------------------- /book.toml: -------------------------------------------------------------------------------- 1 | [book] 2 | title = "The LFE Tutorial" 3 | author = "Robert Virding and Duncan McGreggor" 4 | description = "Getting Started with LFE" 5 | language = "en" 6 | 7 | [build] 8 | build-dir = "book" 9 | 10 | [preprocessor.index] 11 | 12 | [preprocessor.links] 13 | 14 | [output.html] 15 | default-theme = "lfe-pdp" 16 | preferred-dark-theme = "lfe-pdp" 17 | additional-css = ["css/custom.css"] 18 | google-analytics = "UA-38274766-4" 19 | git-repository-url = "https://github.com/lfe/tutorial/" 20 | [output.html.search] 21 | limit-results = 20 22 | use-boolean-and = true 23 | boost-title = 2 24 | boost-hierarchy = 2 25 | boost-paragraph = 1 26 | expand = true 27 | heading-split-level = 2 28 | -------------------------------------------------------------------------------- /css/custom.css: -------------------------------------------------------------------------------- 1 | code, 2 | :not(pre):not(a)>.hljs { 3 | background-color: var(--inline-code-background-color); 4 | color: var(--inline-code-color); 5 | } 6 | 7 | .alert { 8 | position: relative; 9 | padding: 0.75rem 1.25rem; 10 | margin-bottom: 1rem; 11 | border: 1px solid transparent; 12 | border-radius: 6px; 13 | } 14 | 15 | .alert-heading { 16 | color: inherit; 17 | margin: 0; 18 | padding: 0; 19 | } 20 | 21 | .alert-link { 22 | font-weight: 700; 23 | } 24 | 25 | .alert-primary { 26 | color: #1a3047; 27 | background-color: #d6dfe7; 28 | border-color: #c6d2de; 29 | } 30 | 31 | .alert-primary hr { 32 | border-top-color: #b6c5d5; 33 | } 34 | 35 | .alert-primary .alert-link { 36 | color: #0c1722; 37 | } 38 | 39 | .alert-secondary { 40 | color: #4a4945; 41 | background-color: #e8e8e6; 42 | border-color: #b4b465; 43 | } 44 | 45 | .alert-secondary hr { 46 | border-top-color: #d3d3d0; 47 | } 48 | 49 | .alert-secondary .alert-link { 50 | color: #302f2c; 51 | } 52 | 53 | .alert-success { 54 | color: #4c6627; 55 | background-color: #e9f3db; 56 | border-color: #94b861; 57 | } 58 | 59 | .alert-success hr { 60 | border-top-color: #d5e9ba; 61 | } 62 | 63 | .alert-success .alert-link { 64 | color: #314119; 65 | } 66 | 67 | .alert-info { 68 | color: #155974; 69 | background-color: #d4eef9; 70 | border-color: #66a6c2; 71 | } 72 | 73 | .alert-info hr { 74 | border-top-color: #addef3; 75 | } 76 | 77 | .alert-info .alert-link { 78 | color: #0d3849; 79 | } 80 | 81 | .alert-warning { 82 | color: #7f401f; 83 | background-color: #fde5d8; 84 | border-color: #ce865f; 85 | } 86 | 87 | .alert-warning hr { 88 | border-top-color: #fbcab0; 89 | } 90 | 91 | .alert-warning .alert-link { 92 | color: #562b15; 93 | } 94 | 95 | .alert-danger { 96 | color: #712b29; 97 | background-color: #f7dddc; 98 | border-color: #c07471; 99 | } 100 | 101 | .alert-danger hr { 102 | border-top-color: #efbbb9; 103 | } 104 | 105 | .alert-danger .alert-link { 106 | color: #4c1d1b; 107 | } 108 | 109 | .alert-primary, 110 | .alert-primary>th, 111 | .alert-primary>td { 112 | background-color: #325D88; 113 | } 114 | 115 | .alert-secondary, 116 | .alert-secondary>th, 117 | .alert-secondary>td { 118 | background-color: #8E8C84; 119 | } 120 | 121 | .alert-success, 122 | .alert-success>th, 123 | .alert-success>td { 124 | background-color: #93C54B; 125 | } 126 | 127 | .alert-info, 128 | .alert-info>th, 129 | .alert-info>td { 130 | background-color: #29ABE0; 131 | } 132 | 133 | .alert-danger, 134 | .alert-danger>th, 135 | .alert-danger>td { 136 | background-color: #d9534f; 137 | } 138 | 139 | .alert-warning, 140 | .alert-warning>th, 141 | .alert-warning>td { 142 | background-color: #F47C3C; 143 | } 144 | -------------------------------------------------------------------------------- /src/README.md: -------------------------------------------------------------------------------- 1 | [![][cover]][cover-large] 2 | 3 | 4 | 5 | [cover]: images/cover.jpg 6 | [cover-large]: images/cover-large.jpg 7 | -------------------------------------------------------------------------------- /src/SUMMARY.md: -------------------------------------------------------------------------------- 1 | [The LFE Tutorial](README.md) 2 | [Title Page](title.md) 3 | [Copyright Page](front/copyright-page.md) 4 | 5 | -------------------- 6 | 7 | * [Introduction](introduction/README.md) 8 | * [About Erlang](introduction/erlang.md) 9 | * [About LFE](introduction/lfe.md) 10 | * [Dependencies](introduction/deps.md) 11 | * [Installing LFE](introduction/install.md) 12 | * [Sequential Programming](sequential/README.md) 13 | * [The REPL](sequential/repl.md) 14 | * [Modules and Functions](sequential/modfunc.md) 15 | * [Atoms](sequential/atoms.md) 16 | * [Tuples](sequential/tuples.md) 17 | * [Lists](sequential/lists.md) 18 | * [Property Lists and Maps](sequential/propmaps.md) 19 | * [The Erlang Standard Library](sequential/stdlib.md) 20 | * [Writing Output to a Terminal](sequential/output.md) 21 | * [Example: Converting Temperature](sequential/example.md) 22 | * [Matching, Guards and Scope of Variables](sequential/matching.md) 23 | * [More About Lists](sequential/morelists.md) 24 | * [Conditionals](sequential/conds.md) 25 | * [Built-in Functions](sequential/bifs.md) 26 | * [Higher Order Functions](sequential/hofs.md) 27 | * [Concurrent Programming](concurrent/README.md) 28 | * [Processes](concurrent/processes.md) 29 | * [Message Passing](concurrent/msgpass.md) 30 | * [Registered Process Names](concurrent/names.md) 31 | * [Distributed Programming](concurrent/dist.md) 32 | * [Example: Messenger](concurrent/example.md) 33 | * [Robustness](robust/README.md) 34 | * [Timeouts](robust/timeouts.md) 35 | * [Error Handling](robust/errors.md) 36 | * [Example: Robust Messenger](robust/example.md) 37 | * [Records](records/README.md) 38 | * [Modularising](records/mods.md) 39 | * [Header Files](records/headers.md) 40 | * [Records](records/records.md) 41 | * [Macros](macros/README.md) 42 | * [Eval](macros/eval.md) 43 | * [Macros](macros/macros.md) 44 | * [The Backquote Macro](macros/backquote.md) 45 | * [Conclusion](conclusion/README.md) 46 | 47 | -------------------- 48 | 49 | [Feedback](feedback.md) 50 | [LFE Documentation](redirects/docs.html) 51 | [Built with mdBook](redirects/mdbook.html) 52 | -------------------------------------------------------------------------------- /src/conclusion/README.md: -------------------------------------------------------------------------------- 1 | # Conclusion 2 | 3 | [forthcoming] 4 | 5 | Ticket: [https://github.com/lfe/tutorial/issues/10](https://github.com/lfe/tutorial/issues/10) 6 | -------------------------------------------------------------------------------- /src/concurrent/README.md: -------------------------------------------------------------------------------- 1 | # Concurrent Programming 2 | 3 | In this chapter we will look at the basics of concurrent programming in LFE. 4 | -------------------------------------------------------------------------------- /src/concurrent/dist.md: -------------------------------------------------------------------------------- 1 | ## Distributed Programming 2 | 3 | Now let's re-write the ping pong program with "ping" and "pong" on different computers. Before we do this, there are a few things we need to set up to get this to work. The distributed LFE/Erlang implementation provides a basic security mechanism to prevent unauthorised access to an Erlang system on another computer (*manual*). Erlang systems which talk to each other must have the same *magic cookie*. The easiest way to achieve this is by having a file called ``.erlang.cookie`` in your home directory on all machines which on which you are going to run Erlang systems communicating with each other (on Windows systems the home directory is the directory where pointed to by the $HOME environment variable - you may need to set this. On Linux or Unix you can safely ignore this and simply create a file called ``.erlang.cookie`` in the directory you get to after executing the command ``cd`` without any argument). The ``.erlang.cookie`` files should contain one line with the same atom. For example on Linux or Unix in the OS shell: 4 | 5 | ``` 6 | cd 7 | cat > .erlang.cookie 8 | ``` 9 | Which shows this: 10 | ``` 11 | this_is_very_secret 12 | ``` 13 | Let's make it accessible only by the owner of the file: 14 | ``` 15 | chmod 400 .erlang.cookie 16 | ``` 17 | 18 | Note that this permission change is a requirement. 19 | 20 | When you start an LFE/Erlang system which is going to talk to other LFE/Erlang systems, you must give it a name, e.g.: 21 | 22 | ``` 23 | lfe -sname my-name 24 | ``` 25 | 26 | We will see more details of this later (*manual*). If you want to experiment with distributed Erlang, but you only have one computer to work on, you can start two separate Erlang systems on the same computer but give them different names. Each Erlang system running on a computer is called an Erlang node. 27 | 28 | (Note: ``erl -sname`` assumes that all nodes are in the same IP domain and we can use only the first component of the IP address, if we want to use nodes in different domains we use ``-name`` instead, but then all IP address must be given in full (*manual*). 29 | 30 | Here is the ping pong example modified to run on two separate nodes: 31 | 32 | ```lisp 33 | (defmodule tut21 34 | (export (start-ping 1) (start-pong 0) (ping 2) (pong 0))) 35 | 36 | (defun ping 37 | ((0 pong-node) 38 | (! (tuple 'pong pong-node) 'finished) 39 | (lfe_io:format "Ping finished~n" ())) 40 | ((n pong-node) 41 | (! (tuple 'pong pong-node) (tuple 'ping (self))) 42 | (receive 43 | ('pong (lfe_io:format "Ping received pong~n" ()))) 44 | (ping (- n 1) pong-node))) 45 | 46 | (defun pong () 47 | (receive 48 | ('finished 49 | (lfe_io:format "Pong finished~n" ())) 50 | ((tuple 'ping ping-pid) 51 | (lfe_io:format "Pong received ping~n" ()) 52 | (! ping-pid 'pong) 53 | (pong)))) 54 | 55 | (defun start-pong () 56 | (register 'pong (spawn 'tut21 'pong ()))) 57 | 58 | (defun start-ping (pong-node) 59 | (spawn 'tut21 'ping (list 3 pong-node))) 60 | ``` 61 | 62 | Let us assume we have two computers called gollum and kosken. We will start a node on kosken called ping and then a node on gollum called pong. 63 | 64 | On kosken (on a Linux/Unix system): 65 | 66 | ```shell 67 | lfe -sname ping 68 | ``` 69 | ``` 70 | Erlang/OTP 17 [erts-6.0] [source-07b8f44] [64-bit] [smp:8:8] [async-threads:10] [hipe] [kernel-poll:false] [dtrace] 71 | 72 | LFE Shell V6.0 (abort with ^G) 73 | (ping@kosken)lfe> 74 | ``` 75 | 76 | On gollum: 77 | 78 | ```shell 79 | lfe -sname pong 80 | ``` 81 | 82 | ``` 83 | Erlang/OTP 17 [erts-6.0] [source-07b8f44] [64-bit] [smp:8:8] [async-threads:10] [hipe] [kernel-poll:false] [dtrace] 84 | 85 | LFE Shell V6.0 (abort with ^G) 86 | (pong@gollum)lfe> 87 | ``` 88 | 89 | Now start the "pong" process on gollum: 90 | 91 | ```lisp 92 | (pong@gollum)lfe> (tut21:start-pong) 93 | true 94 | ``` 95 | 96 | and start the "ping" process on kosken (from the code above you will see that a parameter of the ``start-ping`` function is the node name of the Erlang system where "pong" is running): 97 | 98 | ```lisp 99 | (ping@kosken)lfe> (tut21:start-ping 'pong@gollum) 100 | <0.41.0> 101 | (ping@kosken)lfe> Ping received pong 102 | Ping received pong 103 | Ping received pong 104 | Ping finished 105 | ``` 106 | 107 | Here we see that the ping pong program has run, on the "pong" side we see: 108 | 109 | ```lisp 110 | (pong@gollum)lfe> 111 | Pong received ping 112 | Pong received ping 113 | Pong received ping 114 | Pong finished 115 | ``` 116 | 117 | Looking at the ``tut21`` code we see that the ``pong`` function itself is unchanged, the lines: 118 | 119 | ```lisp 120 | ((tuple 'ping ping-pid) 121 | (lfe_io:format "Pong received ping~n" ()) 122 | (! ping-pid 'pong) 123 | ``` 124 | 125 | work in the same way irrespective of on which node the "ping" process is executing. Thus Erlang pids contain information about where the process executes so if you know the pid of a process, the "!" operator can be used to send it a message if the process is on the same node or on a different node. 126 | 127 | A difference is how we send messages to a registered process on another node: 128 | 129 | ```lisp 130 | (! (tuple 'pong pong-node) (tuple 'ping (self))) 131 | ``` 132 | 133 | We use a tuple ``#(registered-name node-name)`` instead of just the ``registered-name``. 134 | 135 | In the previous example, we started "ping" and "pong" from the shells of two separate Erlang nodes. ``spawn`` can also be used to start processes in other nodes. The next example is the ping pong program, yet again, but this time we will start "ping" in another node: 136 | 137 | ```lisp 138 | (defmodule tut22 139 | (export (start 1) (ping 2) (pong 0))) 140 | 141 | (defun ping 142 | ((0 pong-node) 143 | (! (tuple 'pong pong-node) 'finished) 144 | (lfe_io:format "Ping finished~n" ())) 145 | ((n pong-node) 146 | (! (tuple 'pong pong-node) (tuple 'ping (self))) 147 | (receive 148 | ('pong (lfe_io:format "Ping received pong~n" ()))) 149 | (ping (- n 1) pong-node))) 150 | 151 | (defun pong () 152 | (receive 153 | ('finished 154 | (lfe_io:format "Pong finished~n" ())) 155 | ((tuple 'ping ping-pid) 156 | (lfe_io:format "Pong received ping~n" ()) 157 | (! ping-pid 'pong) 158 | (pong)))) 159 | 160 | (defun start (ping-node) 161 | (register 'pong (spawn 'tut22 'pong ())) 162 | (spawn ping-node 'tut22 'ping (list 3 (node)))) 163 | ``` 164 | 165 | The function ``node/0`` returns the name of the current node. 166 | 167 | Assuming an LFE system called ``ping`` (but not the "ping" process) has already been started on kosken, then on gollum we do: 168 | 169 | ```lisp 170 | (pong@gollum)lfe> (tut22:start 'ping@kosken) 171 | <8524.50.0> 172 | (pong@gollum)lfe> Pong received ping 173 | Ping received pong 174 | Pong received ping 175 | Ping received pong 176 | Pong received ping 177 | Ping received pong 178 | Ping finished 179 | Pong finished 180 | ``` 181 | 182 | Notice we get all the output on gollum. This is because the io system finds out where the process is spawned from and sends all output there. 183 | 184 | Although we do not show here there are no problems with running nodes distributed nodes where some run Erlang and some run LFE. The handling of io will also work the same across mixed LFE and Erlang nodes. 185 | -------------------------------------------------------------------------------- /src/concurrent/example.md: -------------------------------------------------------------------------------- 1 | ## Example: Messenger 2 | 3 | Now for a larger example. We will make an extremely simple "messenger". The messenger is a program which allows users to log in on different nodes and send simple messages to each other. 4 | 5 | Before we start, let's note the following: 6 | 7 | - This example will just show the message passing logic no attempt at all has been made to provide a nice graphical user interface - this can of course also be done in LFE - but that's another tutorial. 8 | 9 | - This sort of problem can be solved more easily if you use the facilities in OTP, which will also provide methods for updating code on the fly etc. But again, that's another tutorial. 10 | 11 | - The first program we write will contain some inadequacies as regards handling of nodes which disappear, we will correct these in a later version of the program. 12 | 13 | We will set up the messenger by allowing "clients" to connect to a central server and say who and where they are. I.e. a user won't need to know the name of the Erlang node where another user is located to send a message. 14 | 15 | File `messenger.lfe`: 16 | 17 | ```lisp 18 | ;;; Message passing utility. 19 | ;;; User interface: 20 | ;;; (logon name) 21 | ;;; One user at a time can log in from each Erlang node in the 22 | ;;; system messenger: and choose a suitable name. If the name 23 | ;;; is already logged in at another node or if someone else is 24 | ;;; already logged in at the same node, login will be rejected 25 | ;;; with a suitable error message. 26 | ;;; (logoff) 27 | ;;; Logs off anybody at at node 28 | ;;; (message to-name message) 29 | ;;; sends message to to-name. Error messages if the user of this 30 | ;;; function is not logged on or if to-name is not logged on at 31 | ;;; any node. 32 | ;;; 33 | ;;; One node in the network of Erlang nodes runs a server which maintains 34 | ;;; data about the logged on users. The server is registered as "messenger" 35 | ;;; Each node where there is a user logged on runs a client process registered 36 | ;;; as "mess-client" 37 | ;;; 38 | ;;; Protocol between the client processes and the server 39 | ;;; ---------------------------------------------------- 40 | ;;; 41 | ;;; To server: (tuple client-pid 'logon user-name) 42 | ;;; Reply #(messenger stop user-exists-at-other-node) stops the client 43 | ;;; Reply #(messenger logged-on) logon was successful 44 | ;;; 45 | ;;; To server: (tuple client-pid 'logoff) 46 | ;;; Reply: #(messenger logged-off) 47 | ;;; 48 | ;;; To server: (tuple client-pid 'logoff) 49 | ;;; Reply: no reply 50 | ;;; 51 | ;;; To server: (tuple client-pid 'message-to to-name message) send a message 52 | ;;; Reply: #(messenger stop you-are-not-logged-on) stops the client 53 | ;;; Reply: #(messenger receiver-not-found) no user with this name logged on 54 | ;;; Reply: #(messenger sent) message has been sent (but no guarantee) 55 | ;;; 56 | ;;; To client: (tuple 'message-from name message) 57 | ;;; 58 | ;;; Protocol between the "commands" and the client 59 | ;;; ---------------------------------------------- 60 | ;;; 61 | ;;; Started: (messenger:client server-node name) 62 | ;;; To client: logoff 63 | ;;; To client: (tuple 'message-to to-name message) 64 | ;;; 65 | ;;; Configuration: change the server-node() function to return the 66 | ;;; name of the node where the messenger server runs 67 | 68 | (defmodule messenger 69 | (export (start-server 0) (server 1) (logon 1) (logoff 0) 70 | (message 2) (client 2))) 71 | 72 | ;;; Change the function below to return the name of the node where the 73 | ;;; messenger server runs 74 | 75 | (defun server-node () 'messenger@renat) 76 | 77 | ;;; This is the server process for the "messenger" 78 | ;;; the user list has the format [{ClientPid1, Name1},{ClientPid22, Name2},...] 79 | 80 | (defun server (user-list) 81 | (receive 82 | ((tuple from 'logon name) 83 | (let ((new-user-list (server-logon from name user-list))) 84 | (server new-user-list))) 85 | ((tuple from 'logoff) 86 | (let ((new-user-list (server-logoff from user-list))) 87 | (server new-user-list))) 88 | ((tuple from 'message-to to message) 89 | (server-transfer from to message user-list) 90 | ;;(lfe_io:format "list is now: ~p~n" (list user-list)) 91 | (server user-list)))) 92 | 93 | ;;; Start the server 94 | 95 | (defun start-server () 96 | (register 'messenger (spawn 'messenger 'server '(())))) 97 | 98 | ;;; Server adds a new user to the user list 99 | 100 | (defun server-logon (from name user-list) 101 | ;; Check if logged on anywhere else 102 | (if (lists:keymember name 2 user-list) 103 | (progn ;Reject logon 104 | (! from #(messenger stop user-exists-at-other-node)) 105 | user-list) 106 | (progn ;Add user to the list 107 | (! from #(messenger logged-on)) 108 | (cons (tuple from name) user-list)))) 109 | 110 | ;;; Server deletes a user from the user list 111 | 112 | (defun server-logoff (pid user-list) 113 | (lists:keydelete pid 1 user-list)) 114 | 115 | ;;; Server transfers a message between user 116 | 117 | (defun server-transfer (from-pid to-name message user-list) 118 | ;; Check that the user is logged on and who he is 119 | (case (lists:keyfind from-pid 1 user-list) 120 | ((tuple from-pid from-name) 121 | (server-transfer from-pid from-name to-name message user-list)) 122 | ('false 123 | (! from-pid #(messenger stop you-are-not-logged-on))))) 124 | 125 | ;;; If the user exists, send the message 126 | 127 | (defun server-transfer (from-pid from-name to-name message user-list) 128 | ;; Find the receiver and send the message 129 | (case (lists:keyfind to-name 2 user-list) 130 | ((tuple to-pid to-name) 131 | (! to-pid (tuple 'message-from from-name message)) 132 | (! from-pid #(messenger sent))) 133 | ('false 134 | (! from-pid #(messenger receiver-not-found))))) 135 | 136 | ;;; User Commands 137 | 138 | (defun logon (name) 139 | (case (whereis 'mess-client) 140 | ('undefined 141 | (let ((client (spawn 'messenger 'client (list (server-node) name)))) 142 | (register 'mess-client client))) 143 | (_ 'already-logged-on))) 144 | 145 | (defun logoff () 146 | (! 'mess-client 'logoff)) 147 | 148 | (defun message (to-name message) 149 | (case (whereis 'mess-client) ;Test if the client is running 150 | ('undefined 151 | 'not-logged-on) 152 | (_ (! 'mess-client (tuple 'message-to to-name message)) 153 | 'ok))) 154 | 155 | ;;; The client process which runs on each server node 156 | 157 | (defun client (server-node name) 158 | (! (tuple 'messenger server-node) (tuple (self) 'logon name)) 159 | (await-result) 160 | (client server-node)) 161 | 162 | (defun client (server-node) 163 | (receive 164 | ('logoff 165 | (! (tuple 'messenger server-node) (tuple (self) 'logoff)) 166 | (exit 'normal)) 167 | ((tuple 'message-to to-name message) 168 | (! (tuple 'messenger server-node) 169 | (tuple (self) 'message-to to-name message)) 170 | (await-result)) 171 | ((tuple 'message-from from-name message) 172 | (lfe_io:format "Message from ~p: ~p~n" (list from-name message)))) 173 | (client server-node)) 174 | 175 | ;;; Wait for a response from the server 176 | 177 | (defun await-result () 178 | (receive 179 | ((tuple 'messenger 'stop why) ;Stop the client 180 | (lfe_io:format "~p~n" (list why)) 181 | (exit 'normal)) 182 | ((tuple 'messenger what) ;Normal response 183 | (lfe_io:format "~p~n" (list what))))) 184 | ``` 185 | 186 | To use this program you need to: 187 | 188 | - configure the server_node() function 189 | - copy the compiled code (`essenger.beam` to the directory on each computer where you start Erlang. 190 | 191 | In the following example of use of this program, I have started nodes on four different computers, but if you don't have that many machines available on your network, you could start up several nodes on the same machine. 192 | 193 | We start up four Erlang nodes, messenger@super, c1@bilbo, c2@kosken, c3@gollum. 194 | 195 | First we start up a the server at messenger@super: 196 | 197 | ```lisp 198 | (messenger@super)lfe> (messenger:start-server) 199 | true 200 | ``` 201 | 202 | Now Peter logs on at c1@bilbo: 203 | 204 | ```lisp 205 | (c1@bilbo)lfe> (messenger:logon 'peter) 206 | true 207 | logged-on 208 | ``` 209 | 210 | James logs on at c2@kosken: 211 | 212 | ```lisp 213 | (c2@kosken)lfe> (messenger:logon 'james) 214 | true 215 | logged-on 216 | ``` 217 | 218 | and Fred logs on at c3@gollum: 219 | 220 | ```lisp 221 | (c3@gollum)lfe> (messenger:logon 'fred) 222 | true 223 | logged-on 224 | ``` 225 | 226 | Now Peter sends Fred a message: 227 | 228 | ```lisp 229 | (c1@bilbo)lfe> (messenger:message 'fred "hello") 230 | ok 231 | sent 232 | ``` 233 | 234 | And Fred receives the message and sends a message to Peter and logs off: 235 | 236 | ```lisp 237 | Message from peter: "hello" 238 | (c3@gollum)lfe> (messenger:message 'peter "go away, I'm busy") 239 | ok 240 | sent 241 | (c3@gollum)lfe> (messenger:logoff) 242 | logoff 243 | ``` 244 | 245 | James now tries to send a message to Fred: 246 | 247 | ```lisp 248 | (c2@kosken)lfe> (messenger:message 'fred "peter doesn't like you") 249 | ok 250 | receiver-not-found 251 | ``` 252 | 253 | But this fails as Fred has already logged off. 254 | 255 | First let's look at some of the new concepts we have introduced. 256 | 257 | There are two versions of the `server-transfer` function, one with four arguments (`server-transfer/4`) and one with five (`server_transfer/5`). These are regarded by LFE as two separate functions. 258 | 259 | Note how we write the `server` function so that it calls itself, `(server user-list)` and thus creates a loop. The Erlang-LFE compiler is "clever" and optimises the code so that this really is a sort of loop and not a proper function call. But this only works if there is no code after the call, otherwise the compiler will expect the call to return and make a proper function call. This would result in the process getting bigger and bigger for every loop. 260 | 261 | We use functions in the `lists` module. This is a very useful module and a study of the manual page is recommended (erl -man lists). `(lists:keymember key position lists)` looks through a list of tuples and looks at `position` in each tuple to see if it is the same as `key`. The first element is position 1. If it finds a tuple where the element at `position` is the same as `key`, it returns `true`, otherwise `false`. 262 | 263 | ```lisp 264 | > (lists:keymember 'a 2 '(#(x y z) #(b b b) #(b a c) #(q r s))) 265 | true 266 | > (lists:keymember 'p 2 '(#(x y z) #(b b b) #(b a c) #(q r s))) 267 | false 268 | ``` 269 | 270 | `lists:keydelete` works in the same way but deletes the first tuple found (if any) and returns the remaining list: 271 | 272 | ```lisp 273 | > (lists:keymember 'a 2 '(#(x y z) #(b b b) #(b a c) #(q r s))) 274 | (#(x y z) #(b b b) #(q r s)) 275 | ``` 276 | 277 | `lists:keyfind` is like `lists:keymember`, but it returns the tuple found or the atom `false`: 278 | 279 | ```lisp 280 | > (lists:keyfind 'a 2 '(#(x y z) #(b b b) #(b a c) #(q r s))) 281 | #(b a c) 282 | > (lists:keyfind 'p 2 '(#(x y z) #(b b b) #(b a c) #(q r s))) 283 | false 284 | ``` 285 | 286 | There are a lot more very useful functions in the `lists` module. 287 | 288 | An LFE process will (conceptually) run until it does a `receive` and there is no message which it wants to receive in the message queue. I say "conceptually" because the LFE system shares the CPU time between the active processes in the system. 289 | 290 | A process terminates when there is nothing more for it to do, i.e. the last function it calls simply returns and doesn't call another function. Another way for a process to terminate is for it to call `exit/1`. The argument to `exit/1` has a special meaning which we will look at later. In this example we will do `(exit 'normal)` which has the same effect as a process running out of functions to call. 291 | 292 | The BIF `(whereis registered-name)` checks if a registered process of name `registered-name` exists and return the pid of the process if it does exist or the atom `undefined` if it does not. 293 | 294 | You should by now be able to understand most of the code above so I'll just go through one case: a message is sent from one user to another. 295 | 296 | The first user "sends" the message in the example above by: 297 | 298 | ```lisp 299 | (messenger:message 'fred "hello") 300 | ``` 301 | 302 | After testing that the client process exists: 303 | 304 | ```lisp 305 | (whereis 'mess-client) 306 | ``` 307 | 308 | and a message is sent to `mess-client`: 309 | 310 | ```lisp 311 | (! 'mess-client #(message-to fred "hello")) 312 | ``` 313 | 314 | The client sends the message to the server by: 315 | 316 | ```lisp 317 | (! #(messenger messenger@renat) (tuple (self) 'message-to 'fred "hello")) 318 | ``` 319 | 320 | and waits for a reply from the server. 321 | 322 | The server receives this message and calls: 323 | 324 | ```lisp 325 | (server-transfer from 'fred "hello" user-list) 326 | ``` 327 | 328 | which checks that the pid `from` is in the `user-list`: 329 | 330 | ```lisp 331 | (lists:keyfind from 1 user-list) 332 | ``` 333 | 334 | If `keyfind` returns the atom `false`, some sort of error has occurred and the server sends back the message: 335 | 336 | ```lisp 337 | (! from-pid #(messenger stop you-are-not-logged-on)) 338 | ``` 339 | 340 | which is received by the client which in turn does `(exit 'normal)` and terminates. If `keyfind` returns `(tuple from name)` we know that the user is logged on and is his name (`peter`) is in variable `name`. We now call: 341 | 342 | ```lisp 343 | (server-transfer from 'peter 'fred "hello" user-list) 344 | ``` 345 | 346 | Note that as this is `server-transfer/5` it is not the same as the previous function `server_transfer/4`. We do another `keyfind` on `user-list` to find the pid of the client corresponding to `fred`: 347 | 348 | ```lisp 349 | (lists:keyfind 'fred 2 user-list) 350 | ``` 351 | 352 | This time we use argument 2 which is the second element in the tuple. If this returns the atom `false` we know that fred is not logged on and we send the message: 353 | 354 | ```lisp 355 | (! from-pid #(messenger receiver-not-found) 356 | ``` 357 | 358 | which is received by the client, if `keyfind` returns: 359 | 360 | ```lisp 361 | (tuple to-pid 'fred) 362 | ``` 363 | 364 | we send the message: 365 | 366 | ```lisp 367 | (! to-pid #(message-from peter "hello")) 368 | ``` 369 | 370 | to Fred's client and the message: 371 | 372 | ```lisp 373 | (! from-pid #(messenger sent)) 374 | ``` 375 | 376 | to peter's client. 377 | 378 | Fred's client receives the message and prints it: 379 | 380 | ```lisp 381 | ((tuple 'message-from from-name message) 382 | (lfe_io:format "Message from ~p: ~p~n" (list from-name message)))) 383 | ``` 384 | 385 | and Peter's client receives the message in the `await-result` function. 386 | -------------------------------------------------------------------------------- /src/concurrent/msgpass.md: -------------------------------------------------------------------------------- 1 | ## Message Passing 2 | 3 | In the following example we create two processes which send messages to each other a number of times. 4 | 5 | ```lisp 6 | (defmodule tut19 7 | (export (start 0) (ping 2) (pong 0))) 8 | 9 | (defun ping 10 | ((0 pong-pid) 11 | (! pong-pid 'finished) 12 | (lfe_io:format "Ping finished~n" ())) 13 | ((n pong-pid) 14 | (! pong-pid (tuple 'ping (self))) 15 | (receive 16 | ('pong (lfe_io:format "Ping received pong~n" ()))) 17 | (ping (- n 1) pong-pid))) 18 | 19 | (defun pong () 20 | (receive 21 | ('finished 22 | (lfe_io:format "Pong finished~n" ())) 23 | ((tuple 'ping ping-pid) 24 | (lfe_io:format "Pong received ping~n" ()) 25 | (! ping-pid 'pong) 26 | (pong)))) 27 | 28 | (defun start () 29 | (let ((pong-pid (spawn 'tut19 'pong ()))) 30 | (spawn 'tut19 'ping (list 3 pong-pid)))) 31 | ``` 32 | 33 | ```lisp 34 | lfe> (c "tut19.lfe") 35 | #(module tut19) 36 | lfe> (tut19:start) 37 | <0.36.0> 38 | lfe> Pong received ping 39 | Ping received pong 40 | Pong received ping 41 | Ping received pong 42 | Pong received ping 43 | Ping received pong 44 | Ping finished 45 | Pong finished 46 | ``` 47 | 48 | The function `start` first creates a process, let's call it "pong": 49 | 50 | ```lisp 51 | (let ((pong-pid (spawn 'tut19 'pong ()))) 52 | ``` 53 | 54 | This process executes `(tut19:pong)`. `pong-pid` is the process identity of the "pong" process. The function `start` now creates another process "ping". 55 | 56 | ```lisp 57 | (spawn 'tut19 'ping (list 3 pong-pid)))) 58 | ``` 59 | 60 | this process executes: 61 | 62 | ```lisp 63 | (tut19:ping (list 3 pong-pid)) 64 | ``` 65 | 66 | <0.36.0> is the return value from the `start` function. 67 | 68 | The process "pong" now does: 69 | 70 | ```lisp 71 | (receive 72 | ('finished 73 | (lfe_io:format "Pong finished~n" ())) 74 | ((tuple 'ping ping-pid) 75 | (lfe_io:format "Pong received ping~n" ()) 76 | (! ping-pid 'pong) 77 | (pong))) 78 | ``` 79 | 80 | The `receive` construct is used to allow processes to wait for messages from other processes. It has the format: 81 | 82 | ```lisp 83 | (receive 84 | (pattern1 85 | actions1) 86 | (pattern2 87 | actions2) 88 | .... 89 | (patternN 90 | actionsN)) 91 | ``` 92 | 93 | Messages between LFE processes are simply valid LFE terms. I.e. they can be lists, tuples, integers, atoms, pids etc. 94 | 95 | Each process has its own input queue for messages it receives. New messages received are put at the end of the queue. When a process executes a `receive`, the first message in the queue is matched against the first pattern in the `receive`, if this matches, the message is removed from the queue and the actions corresponding to the the pattern are executed. 96 | 97 | However, if the first pattern does not match, the second pattern is tested, if this matches the message is removed from the queue and the actions corresponding to the second pattern are executed. If the second pattern does not match the third is tried and so on until there are no more pattern to test. If there are no more patterns to test, the first message is kept in the queue and we try the second message instead. If this matches any pattern, the appropriate actions are executed and the second message is removed from the queue (keeping the first message and any other messages in the queue). If the second message does not match we try the third message and so on until we reach the end of the queue. If we reach the end of the queue, the process blocks (stops execution) and waits until a new message is received and this procedure is repeated. 98 | 99 | Of course the LFE implementation is "clever" and minimises the number of times each message is tested against the patterns in each `receive`. 100 | 101 | Now back to the ping pong example. 102 | 103 | "Pong" is waiting for messages. If the atom `finished` is received, "pong" writes "Pong finished" to the output and as it has nothing more to do, terminates. If, however, it receives a message with the format: 104 | 105 | ```lisp 106 | #(ping ping-pid) 107 | ``` 108 | 109 | it writes "Pong received ping" to the output and sends the atom `pong` to the process "ping": 110 | 111 | ```lisp 112 | (! ping-pid 'pong) 113 | ``` 114 | 115 | Note how "!" is used to send messages. The syntax of "!" is: 116 | 117 | ```lisp 118 | (! pid message) 119 | ``` 120 | 121 | I.e. `message` (any LFE term) is sent to the process with identity `pid`. 122 | 123 | After sending the message `pong`, to the process "ping", "pong" calls the `pong` function again, which causes it to get back to the `receive` again and wait for another message. Now let's look at the process "ping". Recall that it was started by executing: 124 | 125 | ```lisp 126 | (tut19:ping 3 pong-pid) 127 | ``` 128 | 129 | Looking at the function `ping/2` we see that the second clause of `ping/2` is executed since the value of the first argument is 3 (not 0) (first clause head is `(0 pong-pid)`, second clause head is `(n pong-pid)`, so `n` becomes 3). 130 | 131 | The second clause sends a message to "pong": 132 | 133 | ```lisp 134 | (! pong-pid (tuple 'ping (self))) 135 | ``` 136 | 137 | `(self)` returns the pid of the process which executes `(self)`, in this case the pid of "ping". (Recall the code for "pong", this will land up in the variable `ping-pid` in the `receive` previously explained). 138 | 139 | "Ping" now waits for a reply from "pong": 140 | 141 | ```lisp 142 | (receive 143 | ('pong (lfe_io:format "Ping received pong~n" ()))) 144 | ``` 145 | 146 | and writes "Ping received pong" when this reply arrives, after which "ping" calls the `ping` function again. 147 | 148 | ```lisp 149 | (ping (- n 1) pong-pid) 150 | ``` 151 | 152 | `(- n 1)` causes the first argument to be decremented until it becomes 0. When this occurs, the first clause of `ping/2` will be executed: 153 | 154 | ```lisp 155 | (defun ping 156 | ((0 pong-pid) 157 | (! pong-pid 'finished) 158 | (lfe_io:format "Ping finished~n" ())) 159 | ``` 160 | 161 | The atom `finished` is sent to "pong" (causing it to terminate as described above) and "Ping finished" is written to the output. "Ping" then itself terminates as it has nothing left to do. 162 | -------------------------------------------------------------------------------- /src/concurrent/names.md: -------------------------------------------------------------------------------- 1 | ## Registered Process Names 2 | 3 | In the above example, we first created "pong" so as to be able to give the identity of "pong" when we started "ping". I.e. in some way "ping" must be able to know the identity of "pong" in order to be able to send a message to it. Sometimes processes which need to know each others identities are started completely independently of each other. Erlang thus provides a mechanism for processes to be given names so that these names can be used as identities instead of pids. This is done by using the ``register`` BIF: 4 | 5 | ```lisp 6 | (register some-atom pid) 7 | ``` 8 | 9 | We will now re-write the ping pong example using this and giving the name ``pong`` to the "pong" process: 10 | 11 | ```lisp 12 | (defmodule tut20 13 | (export (start 0) (ping 1) (pong 0))) 14 | 15 | (defun ping 16 | ((0) 17 | (! 'pong 'finished) 18 | (lfe_io:format "Ping finished~n" ())) 19 | ((n) 20 | (! 'pong (tuple 'ping (self))) 21 | (receive 22 | ('pong (lfe_io:format "Ping received pong~n" ()))) 23 | (ping (- n 1)))) 24 | 25 | (defun pong () 26 | (receive 27 | ('finished 28 | (lfe_io:format "Pong finished~n" ())) 29 | ((tuple 'ping ping-pid) 30 | (lfe_io:format "Pong received ping~n" ()) 31 | (! ping-pid 'pong) 32 | (pong)))) 33 | 34 | (defun start () 35 | (let ((pong-pid (spawn 'tut20 'pong ()))) 36 | (register 'pong pong-pid) 37 | (spawn 'tut20 'ping '(3)))) 38 | ``` 39 | 40 | ```lisp 41 | lfe> (c "tut20") 42 | #(module tut20) 43 | lfe> (tut20:start) 44 | <0.36.0> 45 | lfe> Pong received ping 46 | Ping received pong 47 | Pong received ping 48 | Ping received pong 49 | Pong received ping 50 | Ping received pong 51 | Ping finished 52 | Pong finished 53 | ``` 54 | 55 | In the ``start/0`` function, 56 | 57 | ```lisp 58 | (register 'pong pong-pid) 59 | ``` 60 | 61 | registers the "pong" process and gives it the name ``pong``. In the "ping" process we can now send messages to ``pong`` by: 62 | 63 | ```lisp 64 | (! 'pong (tuple 'ping (self))) 65 | ``` 66 | 67 | so that ``ping/2`` now becomes ``ping/1`` as we don't have to use the argument ``pong-pid``. 68 | -------------------------------------------------------------------------------- /src/concurrent/processes.md: -------------------------------------------------------------------------------- 1 | ## Processes 2 | 3 | One of the main reasons for using LFE/Erlang instead of other functional languages is Erlang/LFE's ability to handle concurrency and distributed programming. By concurrency we mean programs which can handle several threads of execution at the same time. For example, modern operating systems would allow you to use a word processor, a spreadsheet, a mail client and a print job all running at the same time. Of course each processor (CPU) in the system is probably only handling one thread (or job) at a time, but it swaps between the jobs at such a rate that it gives the illusion of running them all at the same time. It is easy to create parallel threads of execution in an LFE program and it is easy to allow these threads to communicate with each other. In LFE we call each thread of execution a *process*. 4 | 5 | (Aside: the term "process" is usually used when the threads of execution share no data with each other and the term "thread" when they share data in some way. Threads of execution in LFE share no data, that's why we call them processes). 6 | 7 | The LFE BIF ``spawn`` is used to create a new process: ``(spawn module exported-function list-of-arguments)``. Consider the following module: 8 | 9 | ```lisp 10 | (defmodule tut18 11 | (export (start 0) (say-something 2))) 12 | 13 | (defun say-something 14 | ([what 0] 'done) 15 | ([what times] 16 | (lfe_io:format "~p~n" (list what)) 17 | (say-something what (- times 1)))) 18 | 19 | (defun start () 20 | (spawn 'tut18 'say-something '(hello 3)) 21 | (spawn 'tut18 'say-something '(goodbye 3))) 22 | ``` 23 | 24 | ```lisp 25 | lfe> (c "tut18.lfe") 26 | #(module tut18) 27 | lfe> (tut18:say-something 'hello 3) 28 | hello 29 | hello 30 | hello 31 | done 32 | ``` 33 | 34 | We can see that function ``say-something`` writes its first argument the number of times specified by second argument. Now look at the function ``start``. It starts two LFE processes, one which writes "hello" three times and one which writes "goodbye" three times. Both of these processes use the function ``say-something``. Note that a function used in this way by ``spawn`` to start a process must be exported from the module (i.e. in the (export ... ) at the start of the module). 35 | 36 | ```lisp 37 | lfe> (tut18:start) 38 | <0.37.0> 39 | hello 40 | goodbye 41 | lfe> hello 42 | goodbye 43 | hello 44 | goodbye 45 | ``` 46 | 47 | Notice that it didn't write "hello" three times and then "goodbye" three times, but the first process wrote a "hello", the second a "goodbye", the first another "hello" and so forth. But where did the <0.37.0> come from? The return value of a function is of course the return value of the last "thing" in the function. The last thing in the function ``start`` is 48 | 49 | ```lisp 50 | (spawn 'tut18 'say-something '(goodbye 3)) 51 | ``` 52 | 53 | ``spawn`` returns a *process identifier*, or *pid*, which uniquely identifies the process. So <0.37.0> is the pid of the spawn function call above. We will see how to use pids in the next example. 54 | 55 | Note as well that we have used ~p instead of ~w in ``lfe_io:format``. To quote the manual: "~p Writes the data with standard syntax in the same way as ~w, but breaks terms whose printed representation is longer than one line into many lines and indents each line sensibly. It also tries to detect lists of printable characters and to output these as strings". 56 | -------------------------------------------------------------------------------- /src/feedback.md: -------------------------------------------------------------------------------- 1 | # Feedback and Docs Bugs 2 | 3 | If you would like to provide feedback about this guide, we would welcome the 4 | chance to improve the experience for everyone. Please 5 | [create a ticket](https://github.com/lfe/tutorial/issues/new) in the 6 | Github issue tracker. Be sure to give a full description so that we can best 7 | help you! 8 | -------------------------------------------------------------------------------- /src/front/copyright-page.md: -------------------------------------------------------------------------------- 1 | # Copyright 2 | 3 | © 1996-2020, Ericsson AB 4 | 5 | © 2015, 2020-2023 Robert Virding, Duncan McGreggor 6 | 7 | The contents of this file are subject to the Erlang Public License, 8 | Version 1.1, (the "License"); you may not use this file except in 9 | compliance with the License. You should have received a copy of the 10 | Erlang Public License along with this software. If not, it can be 11 | retrieved online at http://www.erlang.org/. 12 | 13 | Software distributed under the License is distributed on an "AS IS" 14 | basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See 15 | the License for the specific language governing rights and limitations 16 | under the License. 17 | -------------------------------------------------------------------------------- /src/images/cnbb-pub-logo-1.6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cnbbooks/lfe-tutorial/b1c1e1b004fa33ee26fcd4ecbe1ea3222a0f7507/src/images/cnbb-pub-logo-1.6.png -------------------------------------------------------------------------------- /src/images/cover-large.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cnbbooks/lfe-tutorial/b1c1e1b004fa33ee26fcd4ecbe1ea3222a0f7507/src/images/cover-large.jpg -------------------------------------------------------------------------------- /src/images/cover.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cnbbooks/lfe-tutorial/b1c1e1b004fa33ee26fcd4ecbe1ea3222a0f7507/src/images/cover.jpg -------------------------------------------------------------------------------- /src/introduction/README.md: -------------------------------------------------------------------------------- 1 | # Introduction 2 | 3 | In this chapter we will provide brief introductions to Erlang, OTP, and LFE as well as discussion the dependencies for LFE development, and finally obtaining and building LFE from source. 4 | -------------------------------------------------------------------------------- /src/introduction/deps.md: -------------------------------------------------------------------------------- 1 | # Dependencies 2 | 3 | In order to write LFE scripts, libraries, and applications, you will need to install the following: 4 | 5 | * Erlang 6 | * `make` and related developer tools 7 | * `git` 8 | 9 | ## Installing Erlang 10 | 11 | First and foremost, you will need Erlang installed. 12 | 13 | * On Mac OS X, this is as easy as executing `brew install erlang` 14 | * On Ubuntu `apt-get install erlang`. 15 | 16 | You can also install Erlang from the various pre-built packages 17 | provided on the official Erlang 18 | download page or from the 19 | Erlang 20 | Solutions page (which supports many more package types). 21 | 22 | For those who have the need of installing multiple versions of Erlang, there is also the [kerl](https://github.com/spawngrid/kerl) project. 23 | 24 | ## Installing `make` 25 | 26 | In order to use LFE, you will be calling `make` to compile it. To cover your bases, you'll want to make sure you have the basic development tools installed for your platform. 27 | 28 | Ubuntu: 29 | 30 | ```bash 31 | sudo apt-get install build-essential 32 | ``` 33 | 34 | CentOS/Fedora: 35 | 36 | ```bash 37 | sudo yum groupinstall "Development Tools" 38 | ``` 39 | 40 | For Mac OS X, you will need to install the "Developer Tools" from the AppStore. 41 | 42 | ## Installing `git` 43 | 44 | You will also need `git` to continue with this quick-start. If `git` doesn't come installed on your system and it wasn't installed as part of your systems basic development tools package, you can download it here 45 | or install it using your favourite OS package manager. 46 | -------------------------------------------------------------------------------- /src/introduction/erlang.md: -------------------------------------------------------------------------------- 1 | # About Erlang 2 | 3 | Erlang is a programming language used to build massively scalable soft real-time systems with requirements on high availability. Some of its uses are in telecoms, banking, e-commerce, computer telephony and instant messaging. Erlang's runtime system has built-in support for concurrency, distribution and fault tolerance. 4 | 5 | Most real-world Erlang applications are built using Erlang/OTP. OTP is set of Erlang libraries and design principles providing middleware to develop these systems. It includes its own distributed database, applications to interface towards other languages, debugging and release handling tools. 6 | 7 | As an LFE programmer, you will have the ability and freedom to utilise Erlang and OTP libraries directly from a Lisp syntax. 8 | -------------------------------------------------------------------------------- /src/introduction/install.md: -------------------------------------------------------------------------------- 1 | # Installing LFE 2 | 3 | 4 | ## Downloading the Source 5 | 6 | The most recent version of LFE is always available here: 7 | * https://github.com/rvirding/lfe/ 8 | 9 | With `git` in your `PATH`, you can download LFE: 10 | 11 | ```bash 12 | git clone https://github.com/rvirding/lfe.git 13 | cd lfe 14 | ``` 15 | 16 | You have two choices for the type of LFE build you want to do. 17 | 18 | Now everything is ready to go! 19 | 20 |
21 |

22 | 23 | Stable 24 |

25 |

26 | Stable contains a version of LFE that is known to be fully reliable and used in production for years. 27 |

28 |
29 | 30 | To build the latest stable release, make sure you are on the `master` branch: 31 | 32 | ```bash 33 | git checkout master 34 | ``` 35 | 36 |
37 |

38 | 39 | Unstable 40 |

41 |

42 | Unstable contains LFE that is under active development, has not been released, or has not had extensive use and testing in production environments. 43 |

44 |
45 | 46 | To build the latest unstable LFE, make sure you are on the `develop` branch: 47 | 48 | ```bash 49 | git checkout develop 50 | ``` 51 | 52 | LFE is just a set of Erlang libraries, so like an Erlang project, the source code needs to be compiled to `.beam` files. Running `make` in the `lfe` directory will do that: 53 | 54 | ```bash 55 | make 56 | ``` 57 | -------------------------------------------------------------------------------- /src/introduction/lfe.md: -------------------------------------------------------------------------------- 1 | # About LFE 2 | 3 | LFE, or "Lisp Flavoured Erlang", is a Lisp-2 (thus similar to Common Lisp, and 4 | MACLISP before that) that runs on top of the Erlang VM or "BEAM". It is the 5 | oldest sibling of the many BEAM languages that have been released since its 6 | inception (e.g., Elixir, Joxa, Luerl, Erlog, Haskerl, Clojerl, Hamler, etc.). 7 | 8 | As a Lisp-2, LFE has different namespaces for functions and variables. 9 | Furthermore, LFE has Erlang's notion of arity, so functions with different 10 | arity may share the same name. 11 | 12 | Lisps are great, but the thing that makes LFE so incredible is that it runs 13 | on the Erlang VM and is 100% compatible with Core Erlang. 14 | -------------------------------------------------------------------------------- /src/macros/README.md: -------------------------------------------------------------------------------- 1 | # Macros 2 | 3 | As LFE code is just lists so it seems natural that there should be some way to evaluate data structures. In this chapter we will look at some ways of doing this. 4 | -------------------------------------------------------------------------------- /src/macros/backquote.md: -------------------------------------------------------------------------------- 1 | ## The Backquote Macro 2 | 3 | The backquote macro makes it possible to build lists and tuples from templates. Used by itself a backquote is equivalent to a regular quote: 4 | 5 | ```lisp 6 | lfe> `(a b c) 7 | (a b c) 8 | ``` 9 | 10 | Like a regular quote, a backquote alone protects its arguments from evaluation. The advantage of backquote is that it is possible to turn on evaluation inside forms which are backquoted using ``,`` (comma or "unquote") and ``,@`` (comma-at or "unquote splice").[^1] When something is prefixed with a comma it will be evaluated. For example: 11 | 12 | ```lisp 13 | lfe> (set (tuple a b) #(1 2)) 14 | #(1 2) 15 | lfe> `(a is ,a and b is ,b) 16 | (a is 1 and b is 2) 17 | lfe> `#(a ,a b ,b) 18 | #(a 1 b 2) 19 | ``` 20 | 21 | Quoting works with both lists and tuples. The backquote actually expands to an expression which builds the structure the templates describes. For example, the following 22 | 23 | ```lisp 24 | `(a is ,a and b is ,b) 25 | ``` 26 | 27 | expands to 28 | 29 | ```lisp 30 | (list 'a 'is a 'and b 'is b) 31 | ``` 32 | 33 | and 34 | 35 | ```lisp 36 | `(a . ,a) 37 | ``` 38 | 39 | expands to 40 | 41 | ```lisp 42 | (cons 'a a) 43 | ``` 44 | 45 | This: 46 | 47 | ```lisp 48 | `#(a ,a b ,b) 49 | ``` 50 | 51 | expands to 52 | 53 | ```lisp 54 | (tuple 'a a 'b b) 55 | ``` 56 | 57 | They are very useful in macros as we can write a macro definitions which look like the expansions they produce. For example we could define the ``unless`` from the previous section as: 58 | 59 | ```lisp 60 | (defmacro unless 61 | ((cons test body) `(if (not ,test) (progn ,@body)))) 62 | ``` 63 | 64 | Here we have extended it allow multiple forms in the body. Comma-at is like comma but splices its argument which should be a list. So for example: 65 | 66 | ```lisp 67 | lfe> (macroexpand '(unless (test x) (first-do) (second-do)) $ENV) 68 | (if (not (test x)) (progn (first-do) (second-do))) 69 | ``` 70 | 71 | As the backquote macro expands to the expression which would build the template it is also very useful in patterns as we can use a template to describe the pattern. Here is the [Converting Temperature](../sequential/example.md) example rewritten to use backquote in both the patterns and constructors: 72 | 73 | ```lisp 74 | (defun f->c 75 | ((`#(,name #(C ,temp))) 76 | ;; No conversion needed 77 | `#(,name #(C ,temp))) 78 | ((`#(,name #(F ,temp))) 79 | ;; Do the conversion 80 | `#(,name #(C ,(/ (* (- temp 32) 5) 9))))) 81 | 82 | (defun print-temp 83 | ((`#(,name #(C ,temp))) 84 | (lfe_io:format "~-15w ~w C~n" `(,name ,temp)))) 85 | ``` 86 | 87 | Using the backquote macro also makes it much easier to build expressions which we can evaluate with ``eval``. So if we want to import values into the expression to evaluate we can do it like this: 88 | 89 | ```lisp 90 | lfe> (set expr '(* x y)) ;Expression to evaluate 91 | (* x y) 92 | lfe> (eval `(let ((x 17) (y 42)) ,expr)) 93 | 714 94 | ``` 95 | 96 | ---- 97 | 98 | #### Notes 99 | 100 | [^1] In LFE the backquote is a normal macro and is expanded at the same time as other macros. When they are parsed `` `thing`` becomes ``(backquote thing)``, ``,thing`` becomes ``(comma thing)`` and ``,@thing`` becomes ``(comma-at thing)``. 101 | -------------------------------------------------------------------------------- /src/macros/eval.md: -------------------------------------------------------------------------------- 1 | ## Eval 2 | 3 | The form ``eval`` takes an LFE data structure and evaluates it as an expression and then returns the value: 4 | 5 | ```lisp 6 | lfe> (eval 15) 7 | 15 8 | lfe> (eval '(+ 1 2 3 4)) 9 | 10 10 | lfe> (eval '(list 'a 'b 'c)) 11 | (a b c) 12 | ``` 13 | 14 | Using ``eval`` is one way way to merge lists and code. However, it is not a very good way: 15 | 16 | - It is inefficient as the input expression is evaluated by the LFE interpreter, ``lfe_eval``. This is much slower than running compiled code. 17 | 18 | - The expression is evaluated without a lexical context. So calling ``eval`` inside a ``let`` does not allow the evaluated expression to refer to variables bound by the ``let``: 19 | 20 | ```lisp 21 | lfe> (set expr '(* x y)) ; Expression to evaluate 22 | (* x y) 23 | lfe> (let ((x 17) (y 42)) (eval expr)) 24 | exception error: #(unbound_symb x) 25 | 26 | ``` 27 | 28 | Well, this is not quite true. If we "reverse" the code and build a ``let`` expression which imports and binds the variables and then call ``eval`` on it we can access the variables: 29 | 30 | ```lisp 31 | lfe> (eval (list 'let (list (list 'x 17) (list 'y 42)) expr)) 32 | 714 33 | ``` 34 | -------------------------------------------------------------------------------- /src/macros/macros.md: -------------------------------------------------------------------------------- 1 | ## Macros 2 | 3 | The most common way to write programs in LFE that write programs is by defining macros. *Macros* work by transformation. When you define a macro you say how a call to it should be translated, the *macro expansion*, which is then done automatically by the compile. The code generated by the macro then becomes an integral part of the program. 4 | 5 | Macros are usually defined with the `defmacro` form. It resembles a `defun` but instead of defining the value a call returns it defines how the call should be expanded. For example a macro `unless` which returns an expression which evaluates the `body` if the `test` is `false`: 6 | 7 | ```lisp 8 | (defmacro unless (test body) 9 | (list 'if (list 'not test) body)) 10 | ``` 11 | 12 | So if we type into the top-level REPL: 13 | 14 | ```lisp 15 | lfe> (unless (> 3 4) 'yes) 16 | yes 17 | lfe> (unless (is_number 'foo) 'number) 18 | number 19 | ``` 20 | 21 | To test a macro and look at its expansion we can use the function `macroexpand` which takes a macro call and generates its expansion[^1]: 22 | 23 | ```lisp 24 | lfe> (macroexpand '(unless (> 3 4) 'yes) $ENV) 25 | (if (not (> 3 4)) 'yes) 26 | ``` 27 | 28 | If a macro call expands into another macro call then the compiler or the top-level REPL it will keep expanding until the expansion is no longer a macro. It is the expansion of the macro call which is then inserted into the code. So in the example of `unless` it is the resultant `if` form which is then inserted into the code. 29 | 30 | ---- 31 | 32 | #### Notes 33 | 34 | [^1] The extra argument `$ENV` is needed as this is where the REPL keeps its locally defined functions and macros and we need to tell `macroexpand` where to look. 35 | -------------------------------------------------------------------------------- /src/records/README.md: -------------------------------------------------------------------------------- 1 | # Records 2 | 3 | [forthcoming] 4 | 5 | Ticket: [https://github.com/lfe/tutorial/issues/9](https://github.com/lfe/tutorial/issues/9) 6 | -------------------------------------------------------------------------------- /src/records/headers.md: -------------------------------------------------------------------------------- 1 | ## Header Files 2 | 3 | [forthcoming] 4 | 5 | Ticket: [https://github.com/lfe/tutorial/issues/9](https://github.com/lfe/tutorial/issues/9) 6 | -------------------------------------------------------------------------------- /src/records/mods.md: -------------------------------------------------------------------------------- 1 | ## Modularising 2 | 3 | [forthcoming] 4 | 5 | Ticket: [https://github.com/lfe/tutorial/issues/9](https://github.com/lfe/tutorial/issues/9) 6 | -------------------------------------------------------------------------------- /src/records/records.md: -------------------------------------------------------------------------------- 1 | ## Records 2 | 3 | A record is defined as: 4 | 5 | ```lisp 6 | (defrecord ...) 7 | ``` 8 | The ``defrecord`` macro creates a number of new macro for creating, matching and accessing the fields of the record. 9 | 10 | For example: 11 | 12 | ```lisp 13 | (defrecord message-to to-name message) 14 | ``` 15 | 16 | The record data is a tuple which is exactly equivalent to: 17 | 18 | ```lisp 19 | #(message-to ) 20 | ``` 21 | 22 | Creating record is done with a ``make-`` macro, and is best illustrated by an example: 23 | 24 | ```lisp 25 | (make-message-to message "hello" to-name 'fred) 26 | ``` 27 | 28 | This will create the tuple: 29 | 30 | ```lisp 31 | #(message-to fred "hello") 32 | ``` 33 | 34 | Note that you don't have to worry about the order you assign values to the various parts of the records when you create it. The advantage of using records is that by placing their definitions in header files you can conveniently define interfaces which are easy to change. For example, if you want to add a new field to the record, you will only have to change the code where the new field is used and not at every place the record is referred to. If you leave out a field when creating a record, it will get the value of the atom ``undefined``. (*manual*) 35 | 36 | Pattern matching with records is very similar to creating records. For example inside a function clause, case or receive: 37 | 38 | ```lisp 39 | (match-message-to to-name the-name message the-message) 40 | ``` 41 | 42 | will match a ``message-to`` record and extract the ``to-name`` field in to the variable ``the-name`` and the ``message`` field in to the variable ``the-message``. It is equivalent to writing: 43 | 44 | ```lisp 45 | (tuple 'message-to the-name the-message) 46 | ``` 47 | 48 | Accessing the fields of a record is done through macros which are created when the record is defined. For example the variable ``my-message`` contains a ``message-to`` record then 49 | 50 | ```lisp 51 | (message-to-message my-message) 52 | ``` 53 | 54 | will return the value of the ``message`` field of ``my-message`` and 55 | 56 | ```lisp 57 | (set-message-to-message my-message "goodbye") 58 | ``` 59 | 60 | will return a new record where the ``message`` field now has the value ``"goodbye"``. 61 | -------------------------------------------------------------------------------- /src/redirects/docs.html: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/redirects/mdbook.html: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/robust/README.md: -------------------------------------------------------------------------------- 1 | # Robustness 2 | 3 | There are several things which are wrong with the [messenger example](../concurrent/example.md) from the previous chapter. For example if a node where a user is logged on goes down without doing a log off, the user will remain in the server's ``user-list`` but the client will disappear thus making it impossible for the user to log on again as the server thinks the user already logged on. 4 | 5 | Or what happens if the server goes down in the middle of sending a message leaving the sending client hanging for ever in the ``await-result`` function? 6 | -------------------------------------------------------------------------------- /src/robust/errors.md: -------------------------------------------------------------------------------- 1 | ## Error Handling 2 | 3 | Before we go into details of the supervision and error handling in an LFE system, we need see how LFE processes terminate, or in LFE terminology, *exit*. 4 | 5 | A process which executes ``(exit 'normal)`` or simply runs out of things to do has a *normal* exit. 6 | 7 | A process which encounters a runtime error (e.g. divide by zero, bad match, trying to call a function which doesn't exist etc) exits with an error, i.e. has an *abnormal* exit. A process which executes ``(exit reason)`` where ``reason`` is any LFE term except the atom ``normal``, also has an abnormal exit. 8 | 9 | An LFE process can set up links to other LFE processes. If a process calls ``(link other-pid)`` it sets up a bidirectional link between itself and the process called ``other-pid``. When a process terminates, it sends something called a *signal* to all the processes it has links to. 10 | 11 | The signal carries information about the pid it was sent from and the exit reason. 12 | 13 | The default behaviour of a process which receives a normal exit is to ignore the signal. 14 | 15 | The default behaviour in the two other cases (i.e. abnormal exit) above is to bypass all messages to the receiving process and to kill it and to propagate the same error signal to the killed process' links. In this way you can connect all processes in a transaction together using links and if one of the processes exits abnormally, all the processes in the transaction will be killed. As we often want to create a process and link to it at the same time, there is a special BIF, ``spawn_link`` which does the same as spawn, but also creates a link to the spawned process. 16 | 17 | Now an example of the ping pong example using links to terminate "pong": 18 | 19 | ```lisp 20 | (defmodule tut20 21 | (export (start 1) (ping 2) (pong 0))) 22 | 23 | (defun ping (n pong-pid) 24 | (link pong-pid) 25 | (ping1 n pong-pid)) 26 | 27 | (defun ping1 28 | ((0 pong-pid) 29 | (exit 'ping)) 30 | ((n pong-pid) 31 | (! pong-pid (tuple 'ping (self))) 32 | (receive 33 | ('pong (lfe_io:format "Ping received pong~n" ()))) 34 | (ping1 (- n 1) pong-pid))) 35 | 36 | (defun pong () 37 | (receive 38 | ((tuple 'ping ping-pid) 39 | (lfe_io:format "Pong received ping~n" ()) 40 | (! ping-pid 'pong) 41 | (pong)))) 42 | 43 | (defun start (ping-node) 44 | (let ((pong-pid (spawn 'tut20 'pong ()))) 45 | (spawn ping-node 'tut20 'ping (list 3 pong-pid)))) 46 | ``` 47 | 48 | ```lisp 49 | (s1@bill)lfe> (tut20:start 's2@kosken) 50 | Pong received ping 51 | <5627.43.0> 52 | Ping received pong 53 | Pong received ping 54 | Ping received pong 55 | Pong received ping 56 | Ping received pong 57 | ``` 58 | 59 | This is a slight modification of the ping pong program where both processes are spawned from the same ``start/1`` function, where the "ping" process can be spawned on a separate node. Note the use of the ``link`` BIF. "Ping" calls ``(exit `ping)`` when it finishes and this will cause an exit signal to be sent to "pong" which will also terminate. 60 | 61 | It is possible to modify the default behaviour of a process so that it does not get killed when it receives abnormal exit signals, but all signals will be turned into normal messages on the format ``#(EXIT from-pid reason)`` and added to the end of the receiving processes message queue. This behaviour is set by: 62 | 63 | ```lisp 64 | (process_flag 'trap_exit 'true) 65 | ``` 66 | 67 | There are several other process flags, see *erlang manual*. Changing the default behaviour of a process in this way is usually not done in standard user programs, but is left to the supervisory programs in OTP (but that's another tutorial). However we will modify the ping pong program to illustrate exit trapping. 68 | 69 | ```lisp 70 | (defmodule tut21 71 | (export (start 1) (ping 2) (pong 0))) 72 | 73 | (defun ping (n pong-pid) 74 | (link pong-pid) 75 | (ping1 n pong-pid)) 76 | 77 | (defun ping1 78 | ((0 pong-pid) 79 | (exit 'ping)) 80 | ((n pong-pid) 81 | (! pong-pid (tuple 'ping (self))) 82 | (receive 83 | ('pong (lfe_io:format "Ping received pong~n" ()))) 84 | (ping1 (- n 1) pong-pid))) 85 | 86 | (defun pong () 87 | (process_flag 'trap_exit 'true) 88 | (pong1)) 89 | 90 | (defun pong1 () 91 | (receive 92 | ((tuple 'ping ping-pid) 93 | (lfe_io:format "Pong received ping~n" ()) 94 | (! ping-pid 'pong) 95 | (pong1)) 96 | ((tuple 'EXIT from reason) 97 | (lfe_io:format "Pong exiting, got ~p~n" 98 | (list (tuple 'EXIT from reason)))))) 99 | 100 | (defun start (ping-node) 101 | (let ((pong-pid (spawn 'tut21 'pong ()))) 102 | (spawn ping-node 'tut21 'ping (list 3 pong-pid)))) 103 | ``` 104 | 105 | ```lisp 106 | (s1@bill)lfe> (tut21:start 's2@kosken) 107 | <5627.44.0> 108 | Pong received ping 109 | Ping received pong 110 | Pong received ping 111 | Ping received pong 112 | Pong received ping 113 | Ping received pong 114 | Pong exiting, got #(EXIT <5627.44.0> ping) 115 | ``` 116 | -------------------------------------------------------------------------------- /src/robust/example.md: -------------------------------------------------------------------------------- 1 | ## Example: Robust Messenger 2 | 3 | Now we return to the messenger program and add changes which make it more robust: 4 | 5 | ```lisp 6 | ;;; Message passing utility. 7 | ;;; User interface: 8 | ;;; (logon name) 9 | ;;; One user at a time can log in from each Erlang node in the 10 | ;;; system messenger: and choose a suitable name. If the name 11 | ;;; is already logged in at another node or if someone else is 12 | ;;; already logged in at the same node, login will be rejected 13 | ;;; with a suitable error message. 14 | ;;; (logoff) 15 | ;;; Logs off anybody at at node 16 | ;;; (message to-name message) 17 | ;;; sends message to to-name. Error messages if the user of this 18 | ;;; function is not logged on or if to-name is not logged on at 19 | ;;; any node. 20 | ;;; 21 | ;;; One node in the network of Erlang nodes runs a server which maintains 22 | ;;; data about the logged on users. The server is registered as "messenger" 23 | ;;; Each node where there is a user logged on runs a client process registered 24 | ;;; as "mess-client" 25 | ;;; 26 | ;;; Protocol between the client processes and the server 27 | ;;; ---------------------------------------------------- 28 | ;;; 29 | ;;; To server: (tuple client-pid 'logon user-name) 30 | ;;; Reply #(messenger stop user-exists-at-other-node) stops the client 31 | ;;; Reply #(messenger logged-on) logon was successful 32 | ;;; 33 | ;;; When the client terminates for some reason 34 | ;;; To server: (tuple 'EXIT client-pid reason) 35 | ;;; 36 | ;;; To server: (tuple client-pid 'message-to to-name message) send a message 37 | ;;; Reply: #(messenger stop you-are-not-logged-on) stops the client 38 | ;;; Reply: #(messenger receiver-not-found) no user with this name logged on 39 | ;;; Reply: #(messenger sent) message has been sent (but no guarantee) 40 | ;;; 41 | ;;; To client: (tuple 'message-from name message) 42 | ;;; 43 | ;;; Protocol between the "commands" and the client 44 | ;;; ---------------------------------------------- 45 | ;;; 46 | ;;; Started: (messenger:client server-node name) 47 | ;;; To client: logoff 48 | ;;; To client: (tuple 'message-to to-name message) 49 | ;;; 50 | ;;; Configuration: change the server-node() function to return the 51 | ;;; name of the node where the messenger server runs 52 | 53 | (defmodule messenger 54 | (export (start-server 0) (server 0) 55 | (logon 1) (logoff 0) (message 2) (client 2))) 56 | 57 | ;;; Change the function below to return the name of the node where the 58 | ;;; messenger server runs 59 | 60 | (defun server-node () 'messenger@renat) 61 | 62 | ;;; This is the server process for the "messenger" 63 | ;;; the user list has the format [{ClientPid1, Name1},{ClientPid22, Name2},...] 64 | 65 | (defun server () 66 | (process_flag 'trap_exit 'true) 67 | (server ())) 68 | 69 | (defun server (user-list) 70 | (receive 71 | ((tuple from 'logon name) 72 | (let ((new-user-list (server-logon from name user-list))) 73 | (server new-user-list))) 74 | ((tuple 'EXIT from _) 75 | (let ((new-user-list (server-logoff from user-list))) 76 | (server new-user-list))) 77 | ((tuple from 'message-to to message) 78 | (server-transfer from to message user-list) 79 | (lfe_io:format "list is now: ~p~n" (list user-list)) 80 | (server user-list)))) 81 | 82 | ;;; Start the server 83 | 84 | (defun start-server () 85 | (register 'messenger (spawn 'messenger 'server '()))) 86 | 87 | ;;; Server adds a new user to the user list 88 | 89 | (defun server-logon (from name user-list) 90 | ;; Check if logged on anywhere else 91 | (if (lists:keymember name 2 user-list) 92 | (progn ;Reject logon 93 | (! from #(messenger stop user-exists-at-other-node)) 94 | user-list) 95 | (progn ;Add user to the list 96 | (! from #(messenger logged-on)) 97 | (link from) 98 | (cons (tuple from name) user-list)))) 99 | 100 | ;;; Server deletes a user from the user list 101 | 102 | (defun server-logoff (pid user-list) 103 | (lists:keydelete pid 1 user-list)) 104 | 105 | ;;; Server transfers a message between user 106 | 107 | (defun server-transfer (from-pid to-name message user-list) 108 | ;; Check that the user is logged on and who he is 109 | (case (lists:keyfind from-pid 1 user-list) 110 | ((tuple from-pid from-name) 111 | (server-transfer from-pid from-name to-name message user-list)) 112 | ('false 113 | (! from-pid #(messenger stop you-are-not-logged-on))))) 114 | 115 | ;;; If the user exists, send the message 116 | 117 | (defun server-transfer (from-pid from-name to-name message user-list) 118 | ;; Find the receiver and send the message 119 | (case (lists:keyfind to-name 2 user-list) 120 | ((tuple to-pid to-name) 121 | (! to-pid (tuple 'message-from from-name message)) 122 | (! from-pid #(messenger sent))) 123 | ('false 124 | (! from-pid #(messenger receiver-not-found))))) 125 | 126 | ;;; User Commands 127 | 128 | (defun logon (name) 129 | (case (whereis 'mess-client) 130 | ('undefined 131 | (let ((client (spawn 'messenger 'client (list (server-node) name)))) 132 | (register 'mess-client client))) 133 | (_ 'already-logged-on))) 134 | 135 | (defun logoff () 136 | (! 'mess-client 'logoff)) 137 | 138 | (defun message (to-name message) 139 | (case (whereis 'mess-client) ;Test if the client is running 140 | ('undefined 141 | 'not-logged-on) 142 | (_ (! 'mess-client (tuple 'message-to to-name message)) 143 | 'ok))) 144 | 145 | ;;; The client process which runs on each server node 146 | 147 | (defun client (server-node name) 148 | (! (tuple 'messenger server-node) (tuple (self) 'logon name)) 149 | (await-result) 150 | (client server-node)) 151 | 152 | (defun client (server-node) 153 | (receive 154 | ('logoff 155 | (exit 'normal)) 156 | ((tuple 'message-to to-name message) 157 | (! (tuple 'messenger server-node) 158 | (tuple (self) 'message-to to-name message)) 159 | (await-result)) 160 | ((tuple 'message-from from-name message) 161 | (lfe_io:format "Message from ~p: ~p~n" (list from-name message)))) 162 | (client server-node)) 163 | 164 | ;;; Wait for a response from the server 165 | 166 | (defun await-result () 167 | (receive 168 | ((tuple 'messenger 'stop why) ;Stop the client 169 | (lfe_io:format "~p~n" (list why)) 170 | (exit 'normal)) 171 | ((tuple 'messenger what) ;Normal response 172 | (lfe_io:format "~p~n" (list what))) 173 | (after 5000 174 | (lfe_io:format "No response from server~n" ()) 175 | (exit 'timeout)))) 176 | ``` 177 | 178 | We have added the following changes: 179 | 180 | The messenger server traps exits. If it receives an exit signal, ``#(EXIT from reason)`` this means that a client process has terminated or is unreachable because: 181 | 182 | - the user has logged off (we have removed the "logoff" message), 183 | - the network connection to the client is broken, 184 | - the node on which the client process resides has gone down, or 185 | - the client processes has done some illegal operation. 186 | 187 | If we receive an exit signal as above, we delete the tuple, ``#(from name)`` from the servers ``user-list`` using the ``server-logoff`` function. If the node on which the server runs goes down, an exit signal (automatically generated by the system), will be sent to all of the client processes: ``#(EXIT messenger-pid noconnection)`` causing all the client processes to terminate. 188 | 189 | We have also introduced a timeout of five seconds in the ``await-result`` function. I.e. if the server does not reply within five seconds (5000 ms), the client terminates. This is really only needed in the logon sequence before the client and server are linked. 190 | 191 | An interesting case is if the client was to terminate before the server links to it. This is taken care of because linking to a non-existent process causes an exit signal, ``#(EXIT from noproc)``, to be automatically generated as if the process terminated immediately after the link operation. 192 | -------------------------------------------------------------------------------- /src/robust/timeouts.md: -------------------------------------------------------------------------------- 1 | ## Timeouts 2 | 3 | Before improving the messenger program we will look into some general principles, using the ping pong program as an example. Recall that when "ping" finishes, it tells "pong" that it has done so by sending the atom ``finished`` as a message to "pong" so that "pong" could also finish. Another way to let "pong" finish, is to make "pong" exit if it does not receive a message from ping within a certain time, this can be done by adding a *timeout* to pong as shown in the following example: 4 | 5 | ```lisp 6 | (defmodule tut19 7 | (export (start-ping 1) (start-pong 0) (ping 2) (pong 0))) 8 | 9 | (defun ping 10 | ((0 pong-node) 11 | (lfe_io:format "Ping finished~n" ())) 12 | ((n pong-node) 13 | (! (tuple 'pong pong-node) (tuple 'ping (self))) 14 | (receive 15 | ('pong (lfe_io:format "Ping received pong~n" ()))) 16 | (ping (- n 1) pong-node))) 17 | 18 | (defun pong () 19 | (receive 20 | ((tuple 'ping ping-pid) 21 | (lfe_io:format "Pong received ping~n" ()) 22 | (! ping-pid 'pong) 23 | (pong)) 24 | (after 5000 25 | (lfe_io:format "Pong timed out~n" ())))) 26 | 27 | (defun start-pong () 28 | (register 'pong (spawn 'tut19 'pong ()))) 29 | 30 | (defun start-ping (pong-node) 31 | (spawn 'tut19 'ping (list 3 pong-node))) 32 | ``` 33 | 34 | After we have compiled this and copied the ``tut19.beam`` file to the necessary directories: 35 | 36 | On (pong@kosken): 37 | 38 | ```lisp 39 | (pong@kosken)lfe> (tut19:start-pong) 40 | true 41 | Pong received ping 42 | Pong received ping 43 | Pong received ping 44 | Pong timed out 45 | ``` 46 | 47 | On (ping@gollum): 48 | 49 | ```lisp 50 | (ping@renat)lfe> (tut19:start-ping 'pong@kosken) 51 | <0.40.0> 52 | Ping received pong 53 | Ping received pong 54 | Ping received pong 55 | Ping finished 56 | ``` 57 | 58 | The timeout is set in: 59 | 60 | ```lisp 61 | (defun pong () 62 | (receive 63 | ((tuple 'ping ping-pid) 64 | (lfe_io:format "Pong received ping~n" ()) 65 | (! ping-pid 'pong) 66 | (pong)) 67 | (after 5000 68 | (lfe_io:format "Pong timed out~n" ())))) 69 | ``` 70 | 71 | We start the timeout ``(after 5000)`` when we enter ``receive``. The timeout is cancelled if ``#(ping ping-pid)`` is received. If ``#(ping ping-pid)`` is not received, the actions following the timeout will be done after 5000 milliseconds. ``after`` must be last in the ``receive``, i.e. preceded by all other message reception specifications in the ``receive``. Of course we could also call a function which returned an integer for the timeout: 72 | 73 | ```lisp 74 | (after (pong-timeout) 75 | ``` 76 | 77 | In general, there are better ways than using timeouts to supervise parts of a distributed Erlang system. Timeouts are usually appropriate to supervise external events, for example if you have expected a message from some external system within a specified time. For example, we could use a timeout to log a user out of the messenger system if they have not accessed it, for example, in ten minutes. 78 | -------------------------------------------------------------------------------- /src/sequential/README.md: -------------------------------------------------------------------------------- 1 | # Sequential Programming 2 | 3 | In this chapter we will look at the basics of sequential programming in LFE. 4 | -------------------------------------------------------------------------------- /src/sequential/atoms.md: -------------------------------------------------------------------------------- 1 | ## Atoms 2 | 3 | Atoms are another data type in LFE. They are words, for example `charles`, `centimetre`, `inch` and `ok`. Atoms are similar to symbols in Lisp except that are simply names, nothing else. They are not like variables which can have a value. 4 | 5 | Enter the next program (file: `tut4.lfe`) which could be useful for converting from inches to centimetres and vice versa: 6 | 7 | ```lisp 8 | (defmodule tut4 9 | (export (convert 2))) 10 | 11 | (defun convert 12 | ((m 'inch) (/ m 2.54)) 13 | ((n 'centimetre) (* n 2.54))) 14 | ``` 15 | 16 | Compile and test: 17 | 18 | ```lisp 19 | lfe> (c "tut4.lfe") 20 | #(module tut4) 21 | lfe> (tut4:convert 3 'inch) 22 | 1.1811023622047243 23 | lfe> (tut4:convert 7 'centimetre) 24 | 17.78 25 | ``` 26 | 27 | Notice that atoms and variables look the same so we have to tell LFE when we want it to be an atom. We do this by *quoting* the atom with a `'`, for example `'inch` and `'centimetre`. We have to do this both when we use it as argument in a function definition and when we use it when calling a function, otherwise LFE will assume that it is a variable. 28 | 29 | Also notice that we have introduced decimals (floating point numbers) without any explanation, but I guess you can cope with that. 30 | 31 | See what happens if I enter something other than centimetre or inch in the convert function: 32 | 33 | ```lisp 34 | lfe> (tut4:convert 3 'miles) 35 | exception error: function_clause 36 | in (: tut4 convert 3 miles) 37 | ``` 38 | 39 | The two parts of the convert function are called its *clauses*. Here we see that `miles` is not part of either of the clauses. The LFE system can't **match** either of the clauses so we get an error message `function_clause`. The shell formats the error message nicely, but to see the actual error tuple we can do: 40 | 41 | ```lisp 42 | lfe> (catch (tut4:convert 3 'miles)) 43 | #(EXIT 44 | #(function_clause 45 | (#(tut2 convert (3 miles) (#(file "./tut2.lfe") #(line 4))) 46 | #(lfe_eval eval_expr 2 (#(file "src/lfe_eval.erl") #(line 160))) 47 | #(lfe_shell eval_form_1 2 (#(file "src/lfe_shell.erl") #(line 268))) 48 | #(lists foldl 3 (#(file "lists.erl") #(line 1261))) 49 | #(lfe_shell server_loop 1 (#(file "src/lfe_shell.erl") #(line 101)))))) 50 | ``` 51 | -------------------------------------------------------------------------------- /src/sequential/bifs.md: -------------------------------------------------------------------------------- 1 | ## Built-in Functions 2 | 3 | [forthcoming] 4 | 5 | Ticket: [https://github.com/lfe/tutorial/issues/7](https://github.com/lfe/tutorial/issues/7) 6 | -------------------------------------------------------------------------------- /src/sequential/conds.md: -------------------------------------------------------------------------------- 1 | ## Conditionals 2 | 3 | In the module `tut13.lfe`, we saw our first conditional, the `(if ...)` form. We're going to spend the rest of this section discussing `if`, `cond`, `case`, as well as the use of guards and pattern matching to form conditional code branches. 4 | 5 | ### The `if` Form 6 | 7 | In the previous section, we wrote the function `find-max-min/3` to work out the maximum and minimum temperature. This work was delegated to two helper functions: 8 | * `compare-max/2` 9 | * `compare-min/2` 10 | 11 | In both of those functions, we introduced the new `if` form. If works as follows: 12 | 13 | ```lisp 14 | (if 15 | 16 | ) 17 | ``` 18 | 19 | where `` is executed if `` evaluates to `true` and `` is executed if `` evaluates to `false`. If you have used other programming languages, then this will be quite familiar to you. If you have not, if should remind you a bit of the logic we looked at when discussing guards. 20 | 21 | We can see it in action with the following LFE session in the REPL: 22 | 23 | ```lisp 24 | lfe> (if (=:= 1 1) "They are equal!" "They are *not* equal!") 25 | "They are equal!" 26 | lfe> (if (=:= 2 1) "They are equal!" "They are *not* equal!") 27 | "They are *not* equal!" 28 | ``` 29 | 30 | Or -- you will be more familiar with this -- our code from the last section: 31 | 32 | ```lisp 33 | (if (< temp1 temp2) 34 | city1 35 | city2) 36 | ``` 37 | where, if `temp1` is less than `temp2`, the value stored in `city1` is returned. 38 | 39 | So the `if` form works for two conditions. What about 3? 10? 100? Well, for the situations were we want to check multiple conditions, we'll need the `cond` form. 40 | 41 | ### The `cond` Form 42 | 43 | ```lisp 44 | (cond ( ) 45 | ( ) 46 | ( ) 47 | ... 48 | ( )) 49 | ``` 50 | 51 | A given expression is only executed if its accompanying predicate evaluates to `true`. The `cond` returns the value of the expression for the first predicate that evaluates to `true`. Using `cond`, our temperature test would look like this: 52 | 53 | ```lisp 54 | (cond ((< temp1 temp2) city1) 55 | ((>= temp1 temp2) city2)) 56 | ``` 57 | 58 | Here's an example which takes advantage of `cond` supporting more than two logic branches: 59 | 60 | ```lisp 61 | (cond ((> x 0) x) 62 | ((=:= x 0) 0) 63 | ((< x 0) (- x))) 64 | ``` 65 | 66 | Note that each predicate is an expression with it's own parentheses around it; on its left is the opening parenthesis for that particular branch of the `cond`. 67 | 68 | Often times when using `cond` one needs a "default" or "fall-through" option to be used when no other condition is met. Since it's the last one, and we need it to evaluate to `true` we simply set the last condition to `true` when we need a default. Here's a rather silly example: 69 | 70 | ```lisp 71 | (cond ((lists:member x '(1 2 3)) "First three") 72 | ((=:= x 4) "Is four") 73 | ((>= x 5) "More than four") 74 | ('true "You chose poorly")) 75 | ``` 76 | 77 | Any number that is negative will be caught by the last condition. 78 | 79 | In case you're wondering, yes: `cond` works with patterns as well. Let's take a look. 80 | 81 | ### The Extended `cond` Form 82 | 83 | When we talked about `cond` above, we only discussed the form as any Lisper would be familiar. However, LFE has extended `cond` with additional capabilities provided via pattern matching. LFE's `cond` has the following general form when this is taken into consideration: 84 | 85 | ```lisp 86 | (cond () 87 | () 88 | () 89 | ... 90 | ()) 91 | ``` 92 | 93 | where each `` could be either as it is in the regular `cond`, ` ` or it could be `(?= [] )` -- the latter being the extended form (with an optional guard). When using the extended form, instead of evaluating a predicate for its Boolean result, the data passed to the `cond` is matched against the defined patterns: if the pattern match succeeds, then the associated expression is evaluated. Here's an example: 94 | 95 | ```lisp 96 | (cond ((?= (cons head '()) x) 97 | "Only one element") 98 | ((?= (list 1 2) x) 99 | "Two element list") 100 | ((?= (list a _) (when (is_atom a)) x) 101 | "List starts with an atom") 102 | ((?= (cons _ (cons a _)) (when (is_tuple a)) x) 103 | "Second element is a tuple") 104 | ('true "Anything goes")) 105 | ``` 106 | 107 | That form is not that often used, but it can be very practical. 108 | 109 | 110 | ### The `case` Form 111 | 112 | The `case` form is useful for situations where you want to check for multiple possible values of the same expression. Without guards, the general form for `case` is the following: 113 | 114 | ```lisp 115 | (case 116 | ( ) 117 | ( ) 118 | ... 119 | ( )) 120 | ``` 121 | 122 | So we could rewrite the code for the non-extended `cond` above with the following `case`: 123 | 124 | ```lisp 125 | (case x 126 | ((cons head '()) 127 | "Only one element") 128 | ((list 1 2) 129 | "Two element list") 130 | ((list 'a _) 131 | "List starts with 'a'") 132 | (_ "Anything goes")) 133 | ``` 134 | 135 | The following will happen with the `case` defined above: 136 | * Any 1-element list will be matched by the first clause. 137 | * A 2-element list of `1` and `2` (in that order) will match the second clause. 138 | * Any 2-element list whose first element is the atom `a` will match the third clause. 139 | * Anything *not* matching the first three clauses will be matched by the fourth. 140 | 141 | With guards, the case has the following general form: 142 | 143 | ```lisp 144 | (case 145 | ( [] ) 146 | ( [] ) 147 | ... 148 | ( [] )) 149 | ``` 150 | 151 | Let's update the previous example with a couple of guards: 152 | 153 | ```lisp 154 | (case x 155 | ((cons head '()) 156 | "Only one element") 157 | ((list 1 2) 158 | "Two element list") 159 | ((list a _) (when (is_atom a)) 160 | "List starts with an atom") 161 | ((cons _ (cons a _)) (when (is_tuple a)) 162 | "Second element is a tuple") 163 | (_ "Anything goes")) 164 | ``` 165 | 166 | This changes the logic of the previous example in the following ways: 167 | * Any list whose first element is an atom will match the third clause. 168 | * Any list whose second element is a tuple will match the fourth clause. 169 | * Anything *not* matching the first four clauses will be matched by the fifth. 170 | 171 | ### Function Heads as Conditionals 172 | 173 | Another very common way to express conditional logic in LFE is through the use of pattern matching in function heads. This has the capacity to make code *very* concise while also remaining clear to read -- thus its prevalent use. 174 | 175 | As we've seen, a regular LFE function takes the following form (where the arguments are optional): 176 | 177 | ```lisp 178 | (defun ([ ... ]) 179 | ) 180 | ``` 181 | 182 | When pattern matching in the function head, the form is as follows: 183 | 184 | ```lisp 185 | (defun 186 | (() [] 187 | ) 188 | (() [] 189 | ) 190 | ... 191 | (() [] 192 | )) 193 | ``` 194 | 195 | Note that simple patterns with no expressions are just regular function arguments. In other words ``, ``, etc., may be either a full pattern or they may be simple function arguments. The guards are optional. 196 | 197 | Let's try this out by rewriting the silly `case` example above to use a function with pattern-matching in the function heads: 198 | 199 | ```lisp 200 | (defun check-val 201 | (((cons head '())) 202 | "Only one element") 203 | (((list 1 2)) 204 | "Two element list") 205 | (((list a _)) (when (is_atom a)) 206 | "List starts with an atom") 207 | (((cons _ (cons a _))) (when (is_tuple a)) 208 | "Second element is a tuple") 209 | ((_) "Anything goes")) 210 | ``` 211 | 212 | If you run that in the REPL, you can test it out with the following: 213 | 214 | ```lisp 215 | lfe> (check-val '(1)) 216 | "Only one element" 217 | lfe> (check-val '(a 1)) 218 | "List starts with an atom" 219 | lfe> (check-val '(1 #(b 2))) 220 | "Second element is a tuple" 221 | lfe> (check-val 42) 222 | "Anything goes" 223 | ``` 224 | 225 | And there you have LFE function definitions with much of the power of `if`, `cond`, and `case`! 226 | 227 | Let's use some of these forms in actual code now ... 228 | 229 | ### Example: Inches and Centimetres 230 | 231 | [forthcoming] 232 | 233 | [tutorial #14] 234 | 235 | 236 | ### Example: Leap Years 237 | 238 | [forthcoming] 239 | 240 | [tutorial #15] 241 | -------------------------------------------------------------------------------- /src/sequential/example.md: -------------------------------------------------------------------------------- 1 | ## Example: Converting Temperature 2 | 3 | Now for a larger example to consolidate what we have learnt so far. Assume we have a list of temperature readings from a number of cities in the world. Some of them are in Celsius (Centigrade) and some in Fahrenheit (as in the previous list). First let's convert them all to Celsius, then let's print out the data neatly. Save the following code to ``tut8.lfe``: 4 | 5 | ```lisp 6 | (defmodule tut8 7 | (export (format-temps 1))) 8 | 9 | ;; Only this function is exported 10 | (defun format-temps 11 | ((()) 12 | ;; No output for an empty list 13 | 'ok) 14 | (((cons city rest)) 15 | (print-temp (f->c city)) 16 | (format-temps rest))) 17 | 18 | (defun f->c 19 | (((tuple name (tuple 'C temp))) 20 | ;; No conversion needed 21 | (tuple name (tuple 'C temp))) 22 | (((tuple name (tuple 'F temp))) 23 | ;; Do the conversion 24 | (tuple name (tuple 'C (/ (* (- temp 32) 5) 9))))) 25 | 26 | (defun print-temp 27 | (((tuple name (tuple 'C temp))) 28 | (lfe_io:format "~-15w ~w C~n" (list name temp)))) 29 | ``` 30 | 31 | ```lisp 32 | lfe> (c "tut8.lfe") 33 | #(module tut8) 34 | lfe> (tut8:format-temps 35 | '(#(Moscow #(C 10)) 36 | #(Cape-Town #(F 70)) 37 | #(Stockholm #(C -4)) 38 | #(Paris #(F 28)) 39 | #(London #(F 36))))) 40 | Moscow 10 C 41 | Cape-Town 21.11111111111111 C 42 | Stockholm -4 C 43 | Paris -2.2222222222222223 C 44 | London 2.2222222222222223 C 45 | ok 46 | ``` 47 | 48 | Before we look at how this program works, notice that we have added a few comments to the code. A comment starts with a ``;`` character and goes on to the end of the line. [^1] Note as well that the ``(export (format-temps 1))`` line only includes the function ``format-temps/1``, the other functions are local functions, i.e. they are not visible from outside the module ``temp-convert``. 49 | 50 | When we call ``format-temps/1`` the first time, the ``city`` variable gets the value ``#(Moscow #(C-10))`` and the remainder of the list is assigned to the ``rest`` variable. Next, the ``f->c/1`` function is called inside the ``print-temp/1`` function, with ``f->c/1`` getting passed ``#(Moscow #(C-10))``. 51 | 52 | Note that when we see function calls nested like ``(print-temp (f->c ...))`` -- in other words when one function call is passed as the argument to another function -- we execute (evaluate) them from the inside out. We first evaluate ``(f->c city)`` which gives the value ``#(Moscow #(C 10))`` as the temperature is already in Celsius and then we evaluate ``(print-temp #(Moscow #(C 10)))``. Note that the ``f->c/1`` function works in a similar way to the ``convert-length/1`` function we wrote in a previous section. 53 | 54 | Next, ``print-temp/1`` simply calls ``lfe_io:format/2`` in a similar way to what has been described above. Note that ``~-15w`` says to print the "term" with a field length (width) of 15 and left justify it.[^2] 55 | 56 | Now we call ``(format-temps rest)`` with the remainder of the list as an argument. This way of doing things is similar to the loop constructs in other languages. (Yes, this is recursion, but don't let that worry you). So the same ``format-temps/1`` function is called again, this time ``city`` gets the value ``#(Cape-Town #(F 70))`` and we repeat the same procedure as before. We go on doing this until the list becomes empty, i.e. ``()``, which causes the first clause ``(format-temps '())`` to match. This simply "returns" or "results in" the atom ``ok``, so the program ends. 57 | 58 | ---- 59 | 60 | #### Notes 61 | 62 | [^1] In LFE, the convention is that a comment starting with a single ``;`` is reserved for comments at the end of a line of code; lines which start with a comment use two ``;;``, as above. There are also conventions for ``;;;`` and ``;;;;`` -- to learn more about these, see [Program Development Using LFE - Rules and Conventions](http://docs.lfe.io/prog-rules/1.html) and [LFE Style Guide](http://docs.lfe.io/style-guide/1.html). 63 | 64 | [^2] LFE's ``lfe_io:format`` differs from the Erlang standard library ``io:format`` in that ``lfe_io`` displays its results using LFE-formatted data structures in its Lisp syntax; ``io`` uses the standard Erlang syntax. You may use either from LFE. ``lfe_io`` takes the same formatting parameters as ``io``, so there should be no surprises if you're coming from Erlang. For more information, be sure to read the [io:format documentation](http://www.erlang.org/doc/man/io.html#fwrite-1). 65 | -------------------------------------------------------------------------------- /src/sequential/hofs.md: -------------------------------------------------------------------------------- 1 | ## Higher Order Functions 2 | 3 | [forthcoming] 4 | 5 | ### Refactor Temperature Conversion Example 6 | 7 | [tutorial #16] 8 | 9 | [tutorial #17] 10 | 11 | Ticket: [https://github.com/lfe/tutorial/issues/8](https://github.com/lfe/tutorial/issues/8) 12 | -------------------------------------------------------------------------------- /src/sequential/lists.md: -------------------------------------------------------------------------------- 1 | ## Lists 2 | 3 | Whereas tuples group things together, we also want to be able to represent lists of things. Lists in LFE are surrounded by "(" and ")". For example a list of the temperatures of various cities in the world could be: 4 | 5 | ```lisp 6 | (#(Moscow #(C -10)) 7 | #(Cape-Town #(F 70)) #(Stockholm #(C -4)) 8 | #(Paris #(F 28)) #(London #(F 36))) 9 | ``` 10 | 11 | Note that this list was so long that it didn't fit on one line. This doesn't matter, LFE allows line breaks at all "sensible places" but not, for example, in the middle of atoms, integers etc. 12 | 13 | A very useful way of looking at parts of lists is by using the constructor ``cons``. It can also be used as a pattern to match against lists. This is best explained by an example using the shell. 14 | 15 | ```lisp 16 | lfe> (set (cons first rest) '(1 2 3 4 5)) 17 | (1 2 3 4 5) 18 | lfe> first 19 | 1 20 | lfe> rest 21 | (2 3 4 5) 22 | ``` 23 | 24 | We see here that ``set`` also allows you to define variables using patterns. Using ``cons`` we could separate the first element of the list from the rest of list (``first`` got the value 1 and ``rest`` the value (2 3 4 5)). We also see here that when we want to give a literal list we need to *quote* it with ``'``. This stops LFE trying to evaluate the list as a function call in the same way as quoting an atom stops LFE trying to evaluate the atom as a variable. 25 | 26 | Another example: 27 | 28 | ```lisp 29 | lfe> (set (cons e1 (cons e2 r)) '(1 2 3 4 5 6 7)) 30 | (1 2 3 4 5 6 7) 31 | lfe> e1 32 | 1 33 | lfe> e2 34 | 2 35 | lfe> r 36 | (3 4 5 6 7) 37 | ``` 38 | 39 | We see here nesting ``cons`` to get the first two elements from the list. Of course if we try to get more elements from the list than there are elements in the list we will get an error. Note also the special case of the list with no elements (). 40 | 41 | ```lisp 42 | lfe> (set (cons a (cons b c)) '(1 2)) 43 | (1 2) 44 | lfe> a 45 | 1 46 | lfe> b 47 | 2 48 | lfe> c 49 | () 50 | ``` 51 | 52 | In all examples above we have been using new variables names, not reusing old ones. While ``set`` does allow you to rebind variables normally a variable can only be given a value once in its context (scope). 53 | 54 | The following example shows how we find the length of a list: 55 | 56 | ```lisp 57 | (defmodule tut6 58 | (export (list-length 1))) 59 | 60 | (defun list-length 61 | ((()) 0) 62 | (((cons first rest)) 63 | (+ 1 (list-length rest)))) 64 | ``` 65 | 66 | Compile (file ``tut4.lfe``) and test: 67 | 68 | ```lisp 69 | lfe> (c "tut6.lfe") 70 | #(module tut6) 71 | lfe> (tut6:list-length '(1 2 3 4 5 6 7)) 72 | 7 73 | ``` 74 | 75 | Explanation: 76 | 77 | ```lisp 78 | (defun list-length 79 | ((()) 0) 80 | ``` 81 | 82 | The length of an empty list is obviously 0. 83 | 84 | ```lisp 85 | (((cons first rest)) 86 | (+ 1 (list-length rest)))) 87 | ``` 88 | 89 | The length of a list with the first element ``first`` and the remaining elements ``rest`` is 1 + the length of ``rest``. 90 | 91 | (Advanced readers only: This is not tail recursive, there is a better way to write this function). 92 | 93 | In general we can say we use tuples where we would use "records" or "structs" in other languages and we use lists when we want to represent things which have varying sizes, (i.e. where we would use linked lists in other languages). 94 | 95 | LFE does not have a string data type, instead strings can be represented by lists of Unicode characters. So the list ``(97 98 99)`` is equivalent to "abc". Note that we don't have to quote strings as we do lists. The LFE repl is "clever" and guesses the what sort of list we mean and outputs it in what it thinks is the most appropriate form, for example: 96 | 97 | ```lisp 98 | lfe> '(97 98 99) 99 | "abc" 100 | lfe> "abc" 101 | "abc" 102 | lfe> '"abc" 103 | "abc" 104 | ``` 105 | 106 | Lists can also be surrounded by "[" and "]" instead of parentheses. They are equivalent but must match, for example: 107 | 108 | ```lisp 109 | lfe> '(a b c) 110 | (a b c) 111 | lfe> '[a b c] 112 | (a b c) 113 | lfe> '(a b c] 114 | 1: illegal ']' 115 | ``` 116 | 117 | This can be used to make list structures easier to read. For example, it is often used in function definitions for the list of arguments when there are multiple clauses: 118 | 119 | ```lisp 120 | (defun list-length 121 | ([()] 0) 122 | ([(cons first rest)] 123 | (+ 1 (list-length rest)))) 124 | ``` 125 | -------------------------------------------------------------------------------- /src/sequential/matching.md: -------------------------------------------------------------------------------- 1 | ## Matching and Guards and Scope of Variables 2 | 3 | In the previous section we wrote a little example program for converting temperatures. In creating programs like that with special data structures (in our case, a list of cities and their temperatures), it's often useful to create utility functions which make working with our data more convenient. We will explore that below and use these functions to introduce some new concepts. 4 | 5 | ### A Utility Function 6 | 7 | In this case, it could be useful to find the maximum and minimum temperature in our data. We can add support for this by creating the necessary code little bit at a time. Let's start with creating functions for finding the maximum value of the elements of a property list: 8 | 9 | ```lisp 10 | (defmodule tut9 11 | (export (list-max 1))) 12 | 13 | (defun list-max 14 | (((cons head tail)) 15 | (list-max tail head))) 16 | 17 | (defun list-max 18 | (('() results) 19 | results) 20 | (((cons head tail) result-so-far) (when (> head result-so-far)) 21 | (list-max tail head)) 22 | (((cons head tail) result-so-far) 23 | (list-max tail result-so-far))) 24 | ``` 25 | 26 | Then, in the LFE REPL: 27 | 28 | ```lisp 29 | lfe> (c "tut9.lfe") 30 | #(module tut9) 31 | lfe> (tut9:list-max '(1 2 3 4 5 6 7 4 3 2 1)) 32 | 7 33 | ``` 34 | 35 | ### Pattern Matching 36 | 37 | Before we talk about pattern matching, let's clarify why we have two different functions with the same name. Note that the two ``list-max`` functions above each take a different number of arguments (parameters). Or, another way of saying it: they have different *arity*. In LFE, functions having the same name but differing in arity are actually *different functions*. Where we need to distinguish between these functions we write ``/``, where ```` is an integer representing the number of arguments that function takes. For our example above, we would write ``list-max/1`` and ``list-max/2``. 38 | 39 | The next thing we should explain is the arguments for the ``list-max/2`` function, since that probably looks pretty strange the first time you see it. If you look closely, you will see that there are three clauses in ``list-max/2`` and each clause stats with the function arguments for that clause; in order, they are: 40 | 41 | * an empty list and ``results`` 42 | * a ``cons`` and ``results-so-far`` with something called a *guard* (more on that soon) 43 | * a ``cons`` and ``results-so-far`` just by itself 44 | 45 | What each of these are doing is what is called *pattern matching* in LFE: if the arguments passed to ``list-max/2`` match the first pattern, the first clause gets executed and the others don't. If the first one does not match, the second one is tried, and so on. 46 | 47 | So what is being "matched" here? Well, the first clause will match if it's first argument is an empty list. The second and third will match if the first element is a list. The second has something more, though: let's take a closer look. 48 | 49 | ### Guards 50 | 51 | In the second clause of ``list-max/2`` we see a new form: ``(when ...)`` which contains a comparison operation. The special form ``when`` is a something we can use with LFE patterns to limit a match. In this case we use it to say: "only use this function clause if ``head`` is greater than ``result-so-far``. We call tests of this type a *guard*. If the guard isn't true (usually referred to as "if the guard fails"), then we try the next part of the function. 52 | 53 | ### Stepping Through the Function 54 | 55 | Now that we know how there can be two functions with the same name and how the arguments for ``list-max/2`` are working, let's step through the functions above. They represent an example of walking through a list and "carrying" a value as we do so, in this case ``result-so-far`` is the variable that carries a value as we walk. ``list-max/1`` simply assumes that the max value of the list is the head of the list and calls ``list-max/2`` with the rest of the list and the value of the head of the list, in the above this would be ``(list-max '(2 3 4 5 6 7 4 3 2 1) 1)``. If we tried to use ``list-max/1`` with an empty list or tried to use it with something which isn't a list at all, we would cause an error. The LFE philosophy is not to handle errors of this type in the function they occur, but to do so elsewhere. More about this later. 56 | 57 | In ``list-max/2`` we walk down the list and use ``head`` instead of ``result-so-far`` when ``head`` is greater than ``result-so-far``. In this function, if ``head`` *isn't* greater than ``result-so-far`` then it must be smaller or equal to it, and the next clause is executed under this condition. 58 | 59 | To change the above program to one which works out the minimum value of the element in a list, all we would need to do is to write ``<`` instead of ``>`` in the guard ... but it would be wise to change the name of the function to ``list-min`` :-). 60 | 61 | ### Scope and ``let`` 62 | 63 | In a function, the arguments that are passed to it are *in scope* or "accessible" for all of that function. In another function which has been passed its own arguments, only those are in scope; arguments from other functions are not available for use. 64 | 65 | In the case of functions which are pattern matching on the arguments, like our ``list-max/2``, we have three clauses, each with their own arguments and each with their own scope. The parentheses at the beginning of each clause mark this scope. In that case, the ``results`` variable is only available in the first clause; it is not in scope for the second two clauses. 66 | 67 | But passing arguments to functions and pattern matching function arguments are not the only ways in which you can bind a value to a variable. There is a special form in LFE (and other Lisps) called ``let``. Let's[^1] take a look, shall we? Here's another way we could have written ``list-max/2``: 68 | 69 | ```lisp 70 | (defun list-max 71 | (('() results) 72 | results) 73 | (((cons head tail) result-so-far) (when (> head result-so-far)) 74 | (let ((new-result-so-far head)) 75 | (list-max tail new-result-so-far))) 76 | (((cons head tail) result-so-far) 77 | (list-max tail result-so-far))) 78 | ``` 79 | 80 | We only made a change to the second clause: we assigned the value of ``head`` to the variable ``new-result-so-far``. This assignment didn't involve any computation or new values, it was essentially just a "renaming" mostly for the sake of demonstration, and arguably to make it more clear the purpose of the value stored in ``head``. 81 | 82 | 83 | ---- 84 | 85 | #### Notes 86 | 87 | [^1] We are not going to apologise for that pun. 88 | -------------------------------------------------------------------------------- /src/sequential/modfunc.md: -------------------------------------------------------------------------------- 1 | ## Modules and Functions 2 | 3 | ### Creating a Simple Module 4 | 5 | A programming language isn't much use if you can only run code from a REPL. So next we will write a small LFE program in a file on the file system. In the same directory that you started the LFE REPL, create a new file called `tut1.lfe` (the filename is **important**: be sure you type it just as we have) using your favourite text editor. 6 | 7 | Here's the code to enter: 8 | 9 | ```lisp 10 | (defmodule tut1 11 | (export all)) 12 | 13 | (defun double (x) 14 | (* 2 x)) 15 | ``` 16 | 17 | It's not hard to guess that this "program" doubles the value of numbers. We'll get back to the first two lines later. Let's compile the program. This can be done in the LFE REPL as shown below: 18 | 19 | ```lisp 20 | lfe> (c "tut1.lfe") 21 | #(module tut1) 22 | lfe> 23 | ``` 24 | 25 | The `#(module tut1)` tells you that the compilation was successful. If it said "error" instead, you have made some mistake in the text you entered and there will also be error messages to give you some idea as to what has gone wrong so you can change what you have written and try again. 26 | 27 | Now lets run the program. 28 | 29 | ```lisp 30 | lfe> (tut1:double 108) 31 | 216 32 | lfe> 33 | ``` 34 | 35 | As expected, `108` doubled is 216. 36 | 37 | Now let's get back to the first two lines. LFE programs are written in files. Each file contains what we call an *LFE module*. The first line of code in the module tells LFE that we're defining a module and giving it a name: 38 | 39 | ```lisp 40 | (defmodule tut1 41 | ``` 42 | The name of our module is `tut1` and the file which is used to store the module must have the same name as the module but with the `.lfe` extension. In our case the file name is `tut1.lfe`. 43 | 44 | In LFE, whenever we use a function that has been defined in another module, we use the syntax, `(module:function argument1 argument2 ...)`. So 45 | 46 | ```lisp 47 | lfe> (tut1:double 108) 48 | ``` 49 | 50 | means "call the function `double` in the module `tut1` with the argument of `108`. 51 | 52 | The second line tells LFE which functions we will be exporting -- in this case, all of them (which is only *one* ...): 53 | 54 | ```lisp 55 | (export all)) 56 | ``` 57 | 58 | If we wanted to be explicit about which functions were to be exported, we would have written: 59 | 60 | ```lisp 61 | (defmodule tut1 62 | (export (double 1))) 63 | ``` 64 | 65 | That says "in the module `tut1`, please make available the function called `double` which takes one argument" (`x` in our example). By "make available" we mean that this function can be called from outside the module `tut1`. 66 | 67 | ### A More Complicated Example 68 | 69 | Now for a more complicated example, the factorial of a number (e.g. factorial of 4 is 4 * 3 * 2 * 1). Enter the following code in a file called `tut2.lfe`. 70 | 71 | ```lisp 72 | (defmodule tut2 73 | (export (fac 1))) 74 | 75 | (defun fac 76 | ((1) 1) 77 | ((n) (* n (fac (- n 1))))) 78 | ``` 79 | 80 | Compile the file 81 | 82 | ```lisp 83 | lfe> (c "tut2.lfe") 84 | #(module tut2) 85 | ``` 86 | 87 | And now calculate the factorial of 4. 88 | 89 | ```lisp 90 | lfe> (tut2:fac 4) 91 | 24 92 | ``` 93 | 94 | The function `fac` contains two parts. The first part: 95 | 96 | ```lisp 97 | ((1) 1) 98 | ``` 99 | 100 | says that the factorial of 1 is 1. Note that this part is a separate list in the function definition where the first element is a *list* of the arguments to the function and the rest is the body of the function. The second part: 101 | 102 | ```lisp 103 | ((n) (* n (fac (- n 1))))) 104 | ``` 105 | 106 | says that the factorial of n is n multiplied by the factorial of n - 1. After this part which is the last part we end the function definition with the closing `)`. 107 | 108 | A function can have many arguments. Let's expand the module `tut2` with the rather stupid function to multiply two numbers: 109 | 110 | ```lisp 111 | (defmodule tut3 112 | (export (fac 1) (mult 2))) 113 | 114 | (defun fac 115 | ((1) 1) 116 | ((n) (* n (fac (- n 1))))) 117 | 118 | (defun mult (x y) 119 | (* x y)) 120 | 121 | ``` 122 | 123 | Note that we have also had to expand the `(export` line with the information that there is another function `mult` with two arguments. Compile the file: 124 | 125 | ```lisp 126 | lfe> (c "tut3.lfe") 127 | #(module tut3) 128 | ``` 129 | and try it out: 130 | 131 | ```lisp 132 | lfe> (tut3:mult 3 4) 133 | 12 134 | ``` 135 | 136 | In the example above the numbers are integers and the arguments in the functions in the code, `n`, `x`, `y` are called variables. Examples of variables could be `number`, `shoe-size`, `age` etc. 137 | 138 | Note that when a function has only one part and all the arguments are variables then we can use the shorter form we saw in `double` and `mult`. This means that we could also have written `mult` as: 139 | 140 | ```lisp 141 | (defun mult 142 | ((x y) (* x y))) 143 | ``` 144 | -------------------------------------------------------------------------------- /src/sequential/morelists.md: -------------------------------------------------------------------------------- 1 | ## More About Lists 2 | 3 | ### The ``cons`` Form 4 | Remember how we used ``cons`` to "extract" head and tail values of a list when matching them? In the REPL, we would do it like so: 5 | 6 | ```lisp 7 | lfe> (set (cons head tail) (list 'Paris 'London 'Rome)) 8 | (Paris London Rome) 9 | lfe> head 10 | Paris 11 | lfe> tail 12 | (London Rome) 13 | ``` 14 | Well, that's not how ``cons`` started life[^1]; it's original use was in "cons"tructing lists, not taking them apart. Here is some classic usage: 15 | 16 | ```lisp 17 | lfe> (cons 'Madrid tail) 18 | (Madrid London Rome) 19 | ``` 20 | 21 | Let's look at a more involved example where we use ``cons``es to reverse the order of a list: 22 | 23 | ```lisp 24 | (defmodule tut10 25 | (export all)) 26 | 27 | (defun reverse (list) 28 | (reverse list '())) 29 | 30 | (defun reverse 31 | (((cons head tail) reversed-list) 32 | (reverse tail (cons head reversed-list))) 33 | (('() reversed-list) 34 | reversed-list)) 35 | ``` 36 | 37 | Then, in the REPL: 38 | 39 | ```lisp 40 | lfe> (c "tut10.lfe") 41 | #(module tut10) 42 | lfe> (tut10:reverse (list 1 2 3)) 43 | (3 2 1) 44 | ``` 45 | 46 | Consider how ``reversed-list`` is built: it starts as ``'()``, we then successively take off the heads of the list that was provided and add these heads to the the ``reversed-list`` variable, as detailed by the following: 47 | 48 | ```lisp 49 | (reverse (cons 1 '(2 3)) '()) => (reverse '(2 3) (cons 1 '())) 50 | (reverse (cons 2 '(3)) '(1)) => (reverse '(3) (cons 2 '(1))) 51 | (reverse (cons 3 '()) (2 1)) => (reverse '() (cons 3 '(2 1))) 52 | (reverse '() '(3 2 1)) => '(3 2 1) 53 | ``` 54 | 55 | The Erlang module ``lists`` contains a lot of functions for manipulating lists, for example for reversing them -- our work above was done for demonstration and pedagogical purposes. For serious applications, one should prefer functions in the Erlang standard library.[^2] 56 | 57 | ### Processing Lists 58 | 59 | Now lets get back to the cities and temperatures, but take a more structured approach this time. First let's convert the whole list to Celsius as follows: 60 | 61 | ```lisp 62 | (defmodule tut11 63 | (export (format-temps 1))) 64 | 65 | (defun format-temps (cities) 66 | (->c cities)) 67 | 68 | (defun ->c 69 | (((cons (tuple name (tuple 'F temp)) tail)) 70 | (let ((converted (tuple name (tuple 'C (/ (* (- temp 32) 5) 9))))) 71 | (cons converted (->c tail)))) 72 | (((cons city tail)) 73 | (cons city (->c tail))) 74 | (('()) 75 | '())) 76 | ``` 77 | Now let's test this new function: 78 | 79 | ```lisp 80 | lfe> (c "tut11.lfe") 81 | #(module tut11) 82 | lfe> (tut11:format-temps 83 | '(#(Moscow #(C 10)) 84 | #(Cape-Town #(F 70)) 85 | #(Stockholm #(C -4)) 86 | #(Paris #(F 28)) 87 | #(London #(F 36))))) 88 | (#(Moscow #(C 10)) 89 | #(Cape-Town #(C 21.11111111111111)) 90 | #(Stockholm #(C -4)) 91 | #(Paris #(C -2.2222222222222223)) 92 | #(London #(C 2.2222222222222223))) 93 | ``` 94 | 95 | Let's look at this, bit-by-bit. In the first function: 96 | 97 | ```lisp 98 | (defun format-temps (cities) 99 | (->c cities)) 100 | ``` 101 | 102 | we see that ``format-temps/1`` calls ``->c/1``. ``->c/1`` takes off the head of the List ``cities`` and converts it to Celsius if needed. The ``cons`` function is used to add the (maybe) newly converted city to the converted rest of the list: 103 | 104 | ```lisp 105 | (cons converted (->c tail)) 106 | ``` 107 | or 108 | 109 | ```lisp 110 | (cons city (->c tail)) 111 | ``` 112 | 113 | We go on doing this until we get to the end of the list (i.e. the list is empty): 114 | 115 | ```lisp 116 | (('()) 117 | '()) 118 | ``` 119 | 120 | Now that we have converted the list, we should add a function to print it: 121 | 122 | ```lisp 123 | (defmodule tut12 124 | (export (format-temps 1))) 125 | 126 | (defun format-temps (cities) 127 | (print-temps (->c cities))) 128 | 129 | (defun ->c 130 | (((cons (tuple name (tuple 'F temp)) tail)) 131 | (let ((converted (tuple name (tuple 'C (/ (* (- temp 32) 5) 9))))) 132 | (cons converted (->c tail)))) 133 | (((cons city tail)) 134 | (cons city (->c tail))) 135 | (('()) 136 | '())) 137 | 138 | (defun print-temps 139 | (((cons (tuple name (tuple 'C temp)) tail)) 140 | (io:format "~-15w ~w c~n" (list name temp)) 141 | (print-temps tail)) 142 | (('()) 143 | 'ok)) 144 | ``` 145 | 146 | Let's take a look: 147 | 148 | ```lisp 149 | lfe> (c "tut12.lfe") 150 | #(module tut12) 151 | lfe> (tut12:format-temps 152 | '(#(Moscow #(C 10)) 153 | #(Cape-Town #(F 70)) 154 | #(Stockholm #(C -4)) 155 | #(Paris #(F 28)) 156 | #(London #(F 36))))) 157 | 'Moscow' 10 c 158 | 'Cape-Town' 21.11111111111111 c 159 | 'Stockholm' -4 c 160 | 'Paris' -2.2222222222222223 c 161 | 'London' 2.2222222222222223 c 162 | ok 163 | ``` 164 | 165 | ### Utility Functions Revisited 166 | 167 | Remember a few sections back when we created the utility function for finding the maximum value in a list? Let's put that into action now: we want to add a function which finds the cities with the maximum and minimum temperatures: 168 | 169 | ```lisp 170 | (defun find-max-min 171 | (((cons city tail)) 172 | (find-max-min tail city city))) 173 | 174 | (defun find-max-min 175 | (((cons head tail) max-city min-city) 176 | (find-max-min tail 177 | (compare-max head max-city) 178 | (compare-min head min-city))) 179 | (('() max-city min-city) 180 | (tuple max-city min-city))) 181 | 182 | (defun compare-max 183 | (((= (tuple name1 (tuple 'C temp1)) city1) 184 | (= (tuple name2 (tuple 'C temp2)) city2)) 185 | (if (> temp1 temp2) 186 | city1 187 | city2))) 188 | 189 | (defun compare-min 190 | (((= (tuple name1 (tuple 'C temp1)) city1) 191 | (= (tuple name2 (tuple 'C temp2)) city2)) 192 | (if (< temp1 temp2) 193 | city1 194 | city2))) 195 | ``` 196 | 197 | 198 | ### The Complete Example 199 | 200 | ```lisp 201 | (defmodule tut13 202 | (export (format-temps 1))) 203 | 204 | (defun format-temps (cities) 205 | (let* ((converted (->c cities))) 206 | (print-temps converted) 207 | (print-max-min (find-max-min converted)))) 208 | 209 | (defun ->c 210 | (((cons (tuple name (tuple 'F temp)) tail)) 211 | (let ((converted (tuple name (tuple 'C (/ (* (- temp 32) 5) 9))))) 212 | (cons converted (->c tail)))) 213 | (((cons city tail)) 214 | (cons city (->c tail))) 215 | (('()) 216 | '())) 217 | 218 | (defun print-temps 219 | (((cons (tuple name (tuple 'C temp)) tail)) 220 | (io:format "~-15w ~w c~n" (list name temp)) 221 | (print-temps tail)) 222 | (('()) 223 | 'ok)) 224 | 225 | (defun find-max-min 226 | (((cons city tail)) 227 | (find-max-min tail city city))) 228 | 229 | (defun find-max-min 230 | (((cons head tail) max-city min-city) 231 | (find-max-min tail 232 | (compare-max head max-city) 233 | (compare-min head min-city))) 234 | (('() max-city min-city) 235 | (tuple max-city min-city))) 236 | 237 | (defun compare-max 238 | (((= (tuple name1 (tuple 'C temp1)) city1) 239 | (= (tuple name2 (tuple 'C temp2)) city2)) 240 | (if (> temp1 temp2) 241 | city1 242 | city2))) 243 | 244 | (defun compare-min 245 | (((= (tuple name1 (tuple 'C temp1)) city1) 246 | (= (tuple name2 (tuple 'C temp2)) city2)) 247 | (if (< temp1 temp2) 248 | city1 249 | city2))) 250 | 251 | (defun print-max-min 252 | (((tuple (tuple max-name (tuple 'C max-temp)) 253 | (tuple min-name (tuple 'C min-temp)))) 254 | (io:format "Max temperature was ~w c in ~w~n" (list max-temp max-name)) 255 | (io:format "Min temperature was ~w c in ~w~n" (list min-temp min-name)))) 256 | ``` 257 | 258 | Let's try it out: 259 | 260 | ```lisp 261 | lfe> (c "tut13.lfe") 262 | #(module tut13) 263 | lfe> (tut13:format-temps 264 | '(#(Moscow #(C 10)) 265 | #(Cape-Town #(F 70)) 266 | #(Stockholm #(C -4)) 267 | #(Paris #(F 28)) 268 | #(London #(F 36))))) 269 | 'Moscow' 10 c 270 | 'Cape-Town' 21.11111111111111 c 271 | 'Stockholm' -4 c 272 | 'Paris' -2.2222222222222223 c 273 | 'London' 2.2222222222222223 c 274 | Max temperature was 21.11111111111111 c in 'Cape-Town' 275 | Min temperature was -4 c in 'Stockholm' 276 | ok 277 | ``` 278 | 279 | As you may have noticed, that program isn't the most *efficient* way of doing this, since we walk through the list of cities four times. But it is better to first strive for clarity and correctness and to make programs efficient only if really needed. 280 | 281 | ---- 282 | 283 | #### Notes 284 | 285 | [^1] Way back in the prehistoric times when large, building-size computers still roamed the earth and the languages which ran on them were tiny and furry, Lisp came along with only a handful of forms: ``cons`` was one of them, and it was used to construct lists, one cell at a time. 286 | 287 | [^2] More information about this ``lists`` module is available [here](http://www.erlang.org/doc/man/lists.html). 288 | -------------------------------------------------------------------------------- /src/sequential/output.md: -------------------------------------------------------------------------------- 1 | ## Writing Output to a Terminal 2 | 3 | It's nice to be able to do formatted output in these example, so the next example shows a simple way to use to use the ``lfe_io:format`` function. Of course, just like all other exported functions, you can test the ``lfe_io:format`` function in the repl: 4 | 5 | ```lisp 6 | lfe> (lfe_io:format "hello world~n" ()) 7 | hello world 8 | ok 9 | lfe> (lfe_io:format "this outputs one LFE term: ~w~n" '(hello)) 10 | this outputs one LFE term: hello 11 | ok 12 | lfe> (lfe_io:format "this outputs two LFE terms: ~w~w~n" '(hello world)) 13 | this outputs two LFE terms: helloworld 14 | ok 15 | lfe> (lfe_io:format "this outputs two LFE terms: ~w ~w~n" '(hello world)) 16 | this outputs two LFE terms: hello world 17 | ok 18 | ``` 19 | 20 | The function ``format/2`` (i.e. ``format`` with two arguments) takes two lists. The first one is nearly always a list written as a string between " ". This list is printed out as it stands, except that each ~w is replaced by a term taken in order from the second list. Each ~n is replaced by a new line. The ``lfe_io:format/2`` function itself returns the atom ``ok`` if everything goes as planned. Like other functions in LFE, it crashes if an error occurs. This is not a fault in LFE, it is a deliberate policy. LFE has sophisticated mechanisms to handle errors which we will show later. As an exercise, try to make ``lfe_io:format`` crash, it shouldn't be difficult. But notice that although ``lfe_io:format`` crashes, the Erlang shell itself does not crash. 21 | 22 | ```lisp 23 | lfe> (lfe_io:format "this outputs one LFE term: ~w~n" 'hello) 24 | exception error: badarg 25 | in (: lfe_io fwrite1 "this outputs one LFE term: ~w~n" hello) 26 | in (lfe_io format 3) 27 | ``` 28 | -------------------------------------------------------------------------------- /src/sequential/propmaps.md: -------------------------------------------------------------------------------- 1 | ## Property Lists and Maps 2 | 3 | ### Property Lists 4 | 5 | Property lists in Erlang and LFE are a simple way to create key-value pairs (we actually saw them in the last section, but didn't mention it). They have a very simple structure: a list of tuples, where the key is the first element of each tuple and is an atom. Very often you will see "options" for functions provided as property lists (this is similar to how other programming languages use keywords in function arguments). 6 | 7 | Property lists can be created with just the basic data structures of LFE: 8 | 9 | ```lisp 10 | lfe> (set options (list (tuple 'debug 'true) 11 | (tuple 'default 42))) 12 | (#(debug true) #(default 42)) 13 | ``` 14 | 15 | Or, more commonly, using quoted literals: 16 | 17 | ```lisp 18 | lfe> (set options '(#(debug true) #(default 42))) 19 | (#(debug true) #(default 42)) 20 | ``` 21 | 22 | There are convenience functions provided in the `proplists` module. In the last example, we define a default value to be used in the event that the given key is not found in the proplist: 23 | 24 | ```lisp 25 | lfe> (proplists:get_value 'default options) 26 | 42 27 | lfe> (proplists:get_value 'poetry options) 28 | undefined 29 | lfe> (proplists:get_value 'poetry options "Vogon") 30 | "Vogon" 31 | ``` 32 | 33 | Be sure to read the [module documentation](http://www.erlang.org/doc/man/proplists.html) for more information. Here's an example of our options in action: 34 | 35 | ```lisp 36 | (defmodule tut61 37 | (export (div 2) (div 3))) 38 | 39 | (defun div (a b) 40 | (div a b '())) 41 | 42 | (defun div (a b opts) 43 | (let ((debug (proplists:get_value 'debug opts 'false)) 44 | (ratio? (proplists:get_value 'ratio opts 'false))) 45 | (if (and debug ratio?) 46 | (io:format "Returning as ratio ...~n")) 47 | (if ratio? 48 | (++ (integer_to_list 1) "/" (integer_to_list 2)) 49 | (/ a b)))) 50 | ``` 51 | 52 | Let's try our function without and then with various options: 53 | 54 | ```lisp 55 | lfe> (c "tut61.lfe") 56 | #(module tut61) 57 | lfe> (tut61:div 1 2) 58 | 0.5 59 | lfe> (tut61:div 1 2 '(#(ratio true))) 60 | "1/2" 61 | lfe> (tut61:div 1 2 '(#(ratio true) #(debug true))) 62 | Returning as ratio ... 63 | "1/2" 64 | ``` 65 | 66 | ### Maps 67 | 68 | As with property lists, maps are a set of key to value associations. You may create an association from "key" to value 42 in one of two ways: using the LFE core form `map` or entering a map literal: 69 | 70 | ```lisp 71 | lfe> (map "key" 42) 72 | #M("key" 42) 73 | lfe> #M("key" 42) 74 | #M("key" 42) 75 | ``` 76 | 77 | We will jump straight into the deep end with an example using some interesting features. The following example shows how we calculate alpha blending using maps to reference colour and alpha channels. Save this code as the file `tut7.lfe` in the directory from which you have run the LFE REPL: 78 | 79 | ```lisp 80 | (defmodule tut7 81 | (export (new 4) (blend 2))) 82 | 83 | (defmacro channel? (val) 84 | `(andalso (is_float ,val) (>= ,val 0.0) (=< ,val 1.0))) 85 | 86 | (defmacro all-channels? (r g b a) 87 | `(andalso (channel? ,r) 88 | (channel? ,g) 89 | (channel? ,b) 90 | (channel? ,a))) 91 | 92 | (defun new 93 | ((r g b a) (when (all-channels? r g b a)) 94 | (map 'red r 'green g 'blue b 'alpha a))) 95 | 96 | (defun blend (src dst) 97 | (blend src dst (alpha src dst))) 98 | 99 | (defun blend 100 | ((src dst alpha) (when (> alpha 0.0)) 101 | (map-update dst 102 | 'red (/ (red src dst) alpha) 103 | 'green (/ (green src dst) alpha) 104 | 'blue (/ (blue src dst) alpha) 105 | 'alpha alpha)) 106 | ((_ dst _) 107 | (map-update dst 'red 0 'green 0 'blue 0 'alpha 0))) 108 | 109 | (defun alpha 110 | (((map 'alpha src-alpha) (map 'alpha dst-alpha)) 111 | (+ src-alpha (* dst-alpha (- 1.0 src-alpha))))) 112 | 113 | (defun red 114 | (((map 'red src-val 'alpha src-alpha) 115 | (map 'red dst-val 'alpha dst-alpha)) 116 | (+ (* src-val src-alpha) 117 | (* dst-val dst-alpha (- 1.0 src-alpha))))) 118 | 119 | (defun green 120 | (((map 'green src-val 'alpha src-alpha) 121 | (map 'green dst-val 'alpha dst-alpha)) 122 | (+ (* src-val src-alpha) 123 | (* dst-val dst-alpha (- 1.0 src-alpha))))) 124 | 125 | (defun blue 126 | (((map 'blue src-val 'alpha src-alpha) 127 | (map 'blue dst-val 'alpha dst-alpha)) 128 | (+ (* src-val src-alpha) 129 | (* dst-val dst-alpha (- 1.0 src-alpha))))) 130 | ``` 131 | 132 | Now let's try it out, first compiling it: 133 | 134 | ```lisp 135 | lfe> (c "tut7.lfe") 136 | #(module tut7) 137 | lfe> (set colour-1 (tut7:new 0.3 0.4 0.5 1.0)) 138 | #M(alpha 1.0 blue 0.5 green 0.4 red 0.3) 139 | lfe> (set colour-2 (tut7:new 1.0 0.8 0.1 0.3)) 140 | #M(alpha 0.3 blue 0.1 green 0.8 red 1.0) 141 | lfe> (tut7:blend colour-1 colour-2) 142 | #M(alpha 1.0 blue 0.5 green 0.4 red 0.3) 143 | lfe> (tut7:blend colour-2 colour-1) 144 | #M(alpha 1.0 blue 0.38 green 0.52 red 0.51) 145 | ``` 146 | 147 | This example warrants some explanation. 148 | 149 | First we define a couple macros to help with our guard tests. This is only here for convenience and to reduce syntax cluttering. Guards can be only composed of a limited set of functions, so we needed to use macros that would compile down to just the functions allowed in guards. A full treatment of Lisp macros is beyond the scope of this tutorial, but there is a lot of good material available online for learning macros, including [Paul Graham's book "On Lisp."](http://www.paulgraham.com/onlisptext.html) 150 | 151 | ```lisp 152 | (defun new 153 | ((r g b a) (when (all-channels? r g b a)) 154 | (map 'red r 'green g 'blue b 'alpha a))) 155 | ``` 156 | 157 | The function `new/4` [^1] creates a new map term with and lets the keys `red`, `green`, `blue` and `alpha` be associated with an initial value. In this case we only allow for float values between and including 0.0 and 1.0 as ensured by the `all-channels?` and `channel?` macros. 158 | 159 | By calling `blend/2` on any colour term created by `new/4` we can calculate the resulting colour as determined by the two maps terms. 160 | 161 | The first thing `blend/2` does is to calculate the resulting alpha channel. 162 | 163 | ```lisp 164 | (defun alpha 165 | (((map 'alpha src-alpha) (map 'alpha dst-alpha)) 166 | (+ src-alpha (* dst-alpha (- 1.0 src-alpha))))) 167 | ``` 168 | 169 | We fetch the value associated with key `alpha` for both arguments using the `(map 'alpha )` pattern. Any other keys in the map are ignored, only the key `alpha` is required and checked for. 170 | 171 | This is also the case for functions `red/2`, `blue/2` and `green/2`. 172 | 173 | ```lisp 174 | (defun red 175 | (((map 'red src-val 'alpha src-alpha) 176 | (map 'red dst-val 'alpha dst-alpha)) 177 | (+ (* src-val src-alpha) 178 | (* dst-val dst-alpha (- 1.0 src-alpha))))) 179 | ``` 180 | 181 | The difference here is that we check for two keys in each map argument. The other keys are ignored. 182 | 183 | Finally we return the resulting colour in `blend/3`. 184 | 185 | ```lisp 186 | (defun blend 187 | ((src dst alpha) (when (> alpha 0.0)) 188 | (map-update dst 189 | 'red (/ (red src dst) alpha) 190 | 'green (/ (green src dst) alpha) 191 | 'blue (/ (blue src dst) alpha) 192 | 'alpha alpha)) 193 | ``` 194 | 195 | We update the `dst` map with new channel values. The syntax for updating an existing key with a new value is done with `map-update` form. 196 | 197 | ---- 198 | 199 | #### Notes 200 | 201 | [^1] Note the use of the slash and number after the function name. We will be discussing this more in a future section, though before we get there you will see this again. Until we get to the full explanation, just know that the number represents the arity of a given function and this helps us be explicit about which function we mean. 202 | -------------------------------------------------------------------------------- /src/sequential/repl.md: -------------------------------------------------------------------------------- 1 | ## The LFE REPL 2 | 3 | Most operating systems have a command interpreter or shell -- Unix and Linux have many, while Windows has the Command Prompt. Likewise, Erlang has a shell where you can directly write bits of Erlang code and evaluate (run) them to see what happens. LFE has more than a shell: it's a full REPL (*read-eval-print loop*) like other Lisps, and it can do more than the Erlang shell can (including defining proper functions and Lisp macros). 4 | 5 | ### Starting the LFE REPL 6 | 7 | In your system terminal window where you changed directory to the clone of the LFE repository, you can start the LFE REPL by typing the following: 8 | 9 | ```bash 10 | ./bin/lfe 11 | ``` 12 | 13 | At which point you will see output something like this: 14 | ``` 15 | Erlang/OTP 26 [erts-14.0.2] [source] [64-bit] [smp:10:10] [ds:10:10:10] [async-threads:1] [jit] [dtrace] 16 | 17 | ..-~.~_~---.. 18 | ( \\ ) | A Lisp-2+ on the Erlang VM 19 | |`-.._/_\\_.-': | Type (help) for usage info. 20 | | g |_ \ | 21 | | n | | | Docs: http://docs.lfe.io/ 22 | | a / / | Source: http://github.com/lfe/lfe 23 | \ l |_/ | 24 | \ r / | LFE v2.1.2 (abort with ^G) 25 | `-E___.-' 26 | 27 | lfe> 28 | ``` 29 | 30 | ### Interactive LFE Code 31 | 32 | Now let's multiply two numbers in the REPL by typing ``(* 2 21)`` at the ``> `` prompt: 33 | 34 | ```lisp 35 | lfe> (* 2 21) 36 | ``` 37 | Lisp stands for "**LIS**t **P**rocessor" because nearly everything in Lisp is really just a list of things -- including the code itself. The lists in Lisps are created with parentheses, just like the expression above. As you can see, the multiplication operator goes first, followed by its arguments -- this is called *prefix notation*, due to the operator coming first. 38 | 39 | In order to tell the REPL that you want it to evaluate your LFE code, you need to his the ```` or ```` key. 40 | 41 | ```lisp 42 | 42 43 | lfe> 44 | ``` 45 | 46 | It has correctly given you the answer: 42. 47 | 48 | 49 | Now let's try a more complex calculation: 50 | 51 | ```lisp 52 | lfe> (* 2 (+ 1 2 3 4 5 6)) 53 | ``` 54 | This expression has one nested inside the other. The first one to be executed is the inner-most, in this case, the addition operation. Just like we saw before, the operator comes first (the "addition" operator, in this case) and then all of the numbers to be added. 55 | 56 | Hit ```` to get your answer: 57 | 58 | ```lisp 59 | 42 60 | lfe> 61 | ``` 62 | 63 | ### Defining Variables and Functions 64 | 65 | The LFE REPL allows you do set variables and define functions. Let's define a variable called ``multiplier``: 66 | 67 | ```lisp 68 | lfe> (set multiplier 2) 69 | 2 70 | lfe> 71 | ``` 72 | 73 | When we set the value for that variable, the REPL provided feedback on the expression entered, showing us the value. Now we can use it just like the number for which it stands: 74 | 75 | ```lisp 76 | lfe> (* multiplier (+ 1 2 3 4 5 6)) 77 | 42 78 | lfe> 79 | ``` 80 | 81 | The ``set`` form lets you define a variable; the ``defun`` form lets you define a function. Enter this in the REPL: 82 | 83 | ```lisp 84 | lfe> (defun double (x) 85 | (* 2 x)) 86 | ``` 87 | 88 | Now try it: 89 | 90 | ```lisp 91 | lfe> (double 21) 92 | 42 93 | lfe> 94 | ``` 95 | 96 | As we can see, this function multiplies any given number by ``2``. 97 | 98 | 99 | ### Leaving the REPL 100 | 101 | To exit the REPL and shutdown the underlying Erlang system which started when you executed ``./bin/lfe``, simply exit: 102 | 103 | ```lisp 104 | lfe> (exit) 105 | ok 106 | ``` 107 | At which point you will be presented with your regular system terminal prompt. 108 | 109 | There are two other ways in which you may leave the REPL: 110 | * Hitting ``^c`` twice in a row, or 111 | * Hitting ``^g`` then ``q`` 112 | -------------------------------------------------------------------------------- /src/sequential/stdlib.md: -------------------------------------------------------------------------------- 1 | ## The Erlang Standard Library 2 | 3 | ### ``man`` Pages and Online Docs 4 | Erlang has a lot of standard modules to help you do things which are directly callable from LFE. For example, the module ``io`` contains a lot of functions to help you perform various acts of formatted input/output. Depending upon your Erlang installation, you may have man pages available. From your operating system shell, you can found out by typing ``erl -man `` like so: 5 | 6 | ```bash 7 | erl -man io 8 | ``` 9 | 10 | If you have man pages installed, that command would give output along these lines: 11 | 12 | ``` 13 | ERLANG MODULE DEFINITION io(3) 14 | 15 | MODULE 16 | io - Standard I/O Server Interface Functions 17 | 18 | DESCRIPTION 19 | This module provides an interface to standard Erlang IO 20 | servers. The output functions all return ok if they are suc- 21 | ... 22 | ``` 23 | 24 | If your installation of Erlang doesn't have man pages, you can always find what you're looking for on the documentation web site. Here is the online `man` page for the [io module](http://erlang.org/doc/man/io.html). 25 | 26 | ### Module and Function Tab-Completion in the REPL 27 | 28 | From the LFE REPL, you have some other nice options for standard library discovery. Start up LFE to take a look: 29 | 30 | ``` 31 | ./bin/lfe 32 | Erlang/OTP 23 [erts-11.0.2] [source] [64-bit] [smp:12:12] ... 33 | 34 | lfe> 35 | ``` 36 | 37 | Now, at the prompt, hit your ```` key. You should see something like this: 38 | 39 | ``` 40 | application application_controller application_master 41 | beam_lib binary c 42 | code code_server edlin 43 | edlin_expand epp erl_distribution 44 | erl_eval erl_parse erl_prim_loader 45 | erl_scan erlang error_handler 46 | error_logger error_logger_tty_h erts_internal 47 | ets file file_io_server 48 | file_server filename gb_sets 49 | gb_trees gen gen_event 50 | gen_server global global_group 51 | group heart hipe_unified_loader 52 | inet inet_config inet_db 53 | inet_parse inet_udp init 54 | io io_lib io_lib_format 55 | kernel kernel_config lfe_env 56 | lfe_eval lfe_init lfe_io 57 | lfe_shell lists net_kernel 58 | orddict os otp_ring0 59 | prim_eval prim_file prim_inet 60 | prim_zip proc_lib proplists 61 | ram_file rpc standard_error 62 | supervisor supervisor_bridge sys 63 | unicode user_drv user_sup 64 | zlib 65 | ``` 66 | 67 | These are all the modules available to you by default in the LFE REPL. Now type ``(g`` and hit ````: 68 | 69 | ```lisp 70 | lfe> (g 71 | ``` 72 | ``` 73 | gb_sets gb_trees gen gen_event 74 | gen_server global global_group group 75 | ``` 76 | Let's keep going! Continue typing a full module, and then hit ```` again: 77 | 78 | ```lisp 79 | lfe> (gb_trees: 80 | ``` 81 | ``` 82 | add/2 add_element/2 balance/1 del_element/2 83 | delete/2 delete_any/2 difference/2 empty/0 84 | filter/2 fold/3 from_list/1 from_ordset/1 85 | insert/2 intersection/1 intersection/2 is_disjoint/2 86 | is_element/2 is_empty/1 is_member/2 is_set/1 87 | is_subset/2 iterator/1 largest/1 module_info/0 88 | module_info/1 new/0 next/1 singleton/1 89 | size/1 smallest/1 subtract/2 take_largest/1 90 | take_smallest/1 to_list/1 union/1 union/2 91 | ``` 92 | 93 | Now you can see all the *functions* that are available in the module you have selected. This is a great feature, allowing for easy use as well as exploration and discovery. 94 | -------------------------------------------------------------------------------- /src/sequential/tuples.md: -------------------------------------------------------------------------------- 1 | ## Tuples 2 | 3 | Now the `tut4` program is hardly good programming style. Consider: 4 | 5 | ```lisp 6 | (tut4:convert 3 'inch) 7 | ``` 8 | 9 | Does this mean that 3 is in inches? or that 3 is in centimetres and we want to convert it to inches? So LFE has a way to group things together to make things more understandable. We call these tuples. Tuples are constructed and matched using `(tuple ...)`, with literal tuples being written with `#( ... )`. 10 | 11 | So we can write `#(inch 3)` to denote 3 inches and `#(centimeter 5)` to denote 5 centimetres. Now let's write a new program which converts centimetres to inches and vice versa (file `tut5.lfe`). 12 | 13 | ```lisp 14 | (defmodule tut5 15 | (export (convert-length 1))) 16 | 17 | (defun convert-length 18 | (((tuple 'centimeter x)) (tuple 'inch (/ x 2.54))) 19 | (((tuple 'inch y)) (tuple 'centimeter (* y 2.54)))) 20 | ``` 21 | 22 | Compile and test: 23 | 24 | ```lisp 25 | (c "tut5.lfe") 26 | #(module tut5) 27 | lfe> (tut5:convert-length #(inch 5)) 28 | #(centimeter 12.7) 29 | lfe> (tut5:convert-length (tut5:convert-length #(inch 5))) 30 | #(inch 5.0) 31 | ``` 32 | 33 | Note that in the last call we convert 5 inches to centimetres and back again and reassuringly get back to the original value. I.e the argument to a function can be the result of another function. Pause for a moment and consider how that line (above) works. The argument we have given the function `#(inch 5)` is first matched against the first head clause of `convert-length` i.e. in `((tuple 'centimeter x))` where it can be seen that the pattern `(tuple 'centimeter x)` does not match `#(inch 5)` (the *head* is the first bit in the clause with a list of argument patterns). This having failed, we try the head of the next clause i.e. `((tuple 'inch y))`, this pattern matches `#(inch 5)` and `y` gets the value 5. 34 | 35 | We have shown tuples with two parts above, but tuples can have as many parts as we want and contain any valid LFE **term**. For example, to represent the temperature of various cities of the world we could write 36 | 37 | ```lisp 38 | #(Moscow #(C -10)) 39 | #(Cape-Town #(F 70)) 40 | #(Paris #(F 28)) 41 | ``` 42 | 43 | Tuples have a fixed number of things in them. We call each thing in a tuple an *element*. So in the tuple `#(Moscow #(C -10))`, element 1 is `Moscow` and element 2 is `#(C -10)`. We have chosen `C` meaning Celsius (or Centigrade) and `F` meaning Fahrenheit. 44 | -------------------------------------------------------------------------------- /src/title.md: -------------------------------------------------------------------------------- 1 | # The LFE Tutorial 2 | 3 |
4 |
5 |
6 |
7 |
8 | 9 | *Adapted from the Erlang "Getting Started" tutorial* 10 | 11 | 12 | 13 | 14 |
15 | Original Erlang version by the Erlang/OTP Team 16 |
17 | LFE translation by Robert Virding & Duncan McGreggor 18 | 19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 | 30 | [![publisher logo][publisher-img]][publisher-site] 31 | 32 | 33 | 34 | [publisher-img]: images/cnbb-pub-logo-1.6.png 35 | [publisher-site]: http://cnbb.pub/ 36 | -------------------------------------------------------------------------------- /theme/LICENSE: -------------------------------------------------------------------------------- 1 | Attribution-ShareAlike 4.0 International 2 | 3 | ======================================================================= 4 | 5 | Creative Commons Corporation ("Creative Commons") is not a law firm and 6 | does not provide legal services or legal advice. Distribution of 7 | Creative Commons public licenses does not create a lawyer-client or 8 | other relationship. Creative Commons makes its licenses and related 9 | information available on an "as-is" basis. Creative Commons gives no 10 | warranties regarding its licenses, any material licensed under their 11 | terms and conditions, or any related information. Creative Commons 12 | disclaims all liability for damages resulting from their use to the 13 | fullest extent possible. 14 | 15 | Using Creative Commons Public Licenses 16 | 17 | Creative Commons public licenses provide a standard set of terms and 18 | conditions that creators and other rights holders may use to share 19 | original works of authorship and other material subject to copyright 20 | and certain other rights specified in the public license below. The 21 | following considerations are for informational purposes only, are not 22 | exhaustive, and do not form part of our licenses. 23 | 24 | Considerations for licensors: Our public licenses are 25 | intended for use by those authorized to give the public 26 | permission to use material in ways otherwise restricted by 27 | copyright and certain other rights. Our licenses are 28 | irrevocable. Licensors should read and understand the terms 29 | and conditions of the license they choose before applying it. 30 | Licensors should also secure all rights necessary before 31 | applying our licenses so that the public can reuse the 32 | material as expected. Licensors should clearly mark any 33 | material not subject to the license. This includes other CC- 34 | licensed material, or material used under an exception or 35 | limitation to copyright. More considerations for licensors: 36 | wiki.creativecommons.org/Considerations_for_licensors 37 | 38 | Considerations for the public: By using one of our public 39 | licenses, a licensor grants the public permission to use the 40 | licensed material under specified terms and conditions. If 41 | the licensor's permission is not necessary for any reason--for 42 | example, because of any applicable exception or limitation to 43 | copyright--then that use is not regulated by the license. Our 44 | licenses grant only permissions under copyright and certain 45 | other rights that a licensor has authority to grant. Use of 46 | the licensed material may still be restricted for other 47 | reasons, including because others have copyright or other 48 | rights in the material. A licensor may make special requests, 49 | such as asking that all changes be marked or described. 50 | Although not required by our licenses, you are encouraged to 51 | respect those requests where reasonable. More considerations 52 | for the public: 53 | wiki.creativecommons.org/Considerations_for_licensees 54 | 55 | ======================================================================= 56 | 57 | Creative Commons Attribution-ShareAlike 4.0 International Public 58 | License 59 | 60 | By exercising the Licensed Rights (defined below), You accept and agree 61 | to be bound by the terms and conditions of this Creative Commons 62 | Attribution-ShareAlike 4.0 International Public License ("Public 63 | License"). To the extent this Public License may be interpreted as a 64 | contract, You are granted the Licensed Rights in consideration of Your 65 | acceptance of these terms and conditions, and the Licensor grants You 66 | such rights in consideration of benefits the Licensor receives from 67 | making the Licensed Material available under these terms and 68 | conditions. 69 | 70 | 71 | Section 1 -- Definitions. 72 | 73 | a. Adapted Material means material subject to Copyright and Similar 74 | Rights that is derived from or based upon the Licensed Material 75 | and in which the Licensed Material is translated, altered, 76 | arranged, transformed, or otherwise modified in a manner requiring 77 | permission under the Copyright and Similar Rights held by the 78 | Licensor. For purposes of this Public License, where the Licensed 79 | Material is a musical work, performance, or sound recording, 80 | Adapted Material is always produced where the Licensed Material is 81 | synched in timed relation with a moving image. 82 | 83 | b. Adapter's License means the license You apply to Your Copyright 84 | and Similar Rights in Your contributions to Adapted Material in 85 | accordance with the terms and conditions of this Public License. 86 | 87 | c. BY-SA Compatible License means a license listed at 88 | creativecommons.org/compatiblelicenses, approved by Creative 89 | Commons as essentially the equivalent of this Public License. 90 | 91 | d. Copyright and Similar Rights means copyright and/or similar rights 92 | closely related to copyright including, without limitation, 93 | performance, broadcast, sound recording, and Sui Generis Database 94 | Rights, without regard to how the rights are labeled or 95 | categorized. For purposes of this Public License, the rights 96 | specified in Section 2(b)(1)-(2) are not Copyright and Similar 97 | Rights. 98 | 99 | e. Effective Technological Measures means those measures that, in the 100 | absence of proper authority, may not be circumvented under laws 101 | fulfilling obligations under Article 11 of the WIPO Copyright 102 | Treaty adopted on December 20, 1996, and/or similar international 103 | agreements. 104 | 105 | f. Exceptions and Limitations means fair use, fair dealing, and/or 106 | any other exception or limitation to Copyright and Similar Rights 107 | that applies to Your use of the Licensed Material. 108 | 109 | g. License Elements means the license attributes listed in the name 110 | of a Creative Commons Public License. The License Elements of this 111 | Public License are Attribution and ShareAlike. 112 | 113 | h. Licensed Material means the artistic or literary work, database, 114 | or other material to which the Licensor applied this Public 115 | License. 116 | 117 | i. Licensed Rights means the rights granted to You subject to the 118 | terms and conditions of this Public License, which are limited to 119 | all Copyright and Similar Rights that apply to Your use of the 120 | Licensed Material and that the Licensor has authority to license. 121 | 122 | j. Licensor means the individual(s) or entity(ies) granting rights 123 | under this Public License. 124 | 125 | k. Share means to provide material to the public by any means or 126 | process that requires permission under the Licensed Rights, such 127 | as reproduction, public display, public performance, distribution, 128 | dissemination, communication, or importation, and to make material 129 | available to the public including in ways that members of the 130 | public may access the material from a place and at a time 131 | individually chosen by them. 132 | 133 | l. Sui Generis Database Rights means rights other than copyright 134 | resulting from Directive 96/9/EC of the European Parliament and of 135 | the Council of 11 March 1996 on the legal protection of databases, 136 | as amended and/or succeeded, as well as other essentially 137 | equivalent rights anywhere in the world. 138 | 139 | m. You means the individual or entity exercising the Licensed Rights 140 | under this Public License. Your has a corresponding meaning. 141 | 142 | 143 | Section 2 -- Scope. 144 | 145 | a. License grant. 146 | 147 | 1. Subject to the terms and conditions of this Public License, 148 | the Licensor hereby grants You a worldwide, royalty-free, 149 | non-sublicensable, non-exclusive, irrevocable license to 150 | exercise the Licensed Rights in the Licensed Material to: 151 | 152 | a. reproduce and Share the Licensed Material, in whole or 153 | in part; and 154 | 155 | b. produce, reproduce, and Share Adapted Material. 156 | 157 | 2. Exceptions and Limitations. For the avoidance of doubt, where 158 | Exceptions and Limitations apply to Your use, this Public 159 | License does not apply, and You do not need to comply with 160 | its terms and conditions. 161 | 162 | 3. Term. The term of this Public License is specified in Section 163 | 6(a). 164 | 165 | 4. Media and formats; technical modifications allowed. The 166 | Licensor authorizes You to exercise the Licensed Rights in 167 | all media and formats whether now known or hereafter created, 168 | and to make technical modifications necessary to do so. The 169 | Licensor waives and/or agrees not to assert any right or 170 | authority to forbid You from making technical modifications 171 | necessary to exercise the Licensed Rights, including 172 | technical modifications necessary to circumvent Effective 173 | Technological Measures. For purposes of this Public License, 174 | simply making modifications authorized by this Section 2(a) 175 | (4) never produces Adapted Material. 176 | 177 | 5. Downstream recipients. 178 | 179 | a. Offer from the Licensor -- Licensed Material. Every 180 | recipient of the Licensed Material automatically 181 | receives an offer from the Licensor to exercise the 182 | Licensed Rights under the terms and conditions of this 183 | Public License. 184 | 185 | b. Additional offer from the Licensor -- Adapted Material. 186 | Every recipient of Adapted Material from You 187 | automatically receives an offer from the Licensor to 188 | exercise the Licensed Rights in the Adapted Material 189 | under the conditions of the Adapter's License You apply. 190 | 191 | c. No downstream restrictions. You may not offer or impose 192 | any additional or different terms or conditions on, or 193 | apply any Effective Technological Measures to, the 194 | Licensed Material if doing so restricts exercise of the 195 | Licensed Rights by any recipient of the Licensed 196 | Material. 197 | 198 | 6. No endorsement. Nothing in this Public License constitutes or 199 | may be construed as permission to assert or imply that You 200 | are, or that Your use of the Licensed Material is, connected 201 | with, or sponsored, endorsed, or granted official status by, 202 | the Licensor or others designated to receive attribution as 203 | provided in Section 3(a)(1)(A)(i). 204 | 205 | b. Other rights. 206 | 207 | 1. Moral rights, such as the right of integrity, are not 208 | licensed under this Public License, nor are publicity, 209 | privacy, and/or other similar personality rights; however, to 210 | the extent possible, the Licensor waives and/or agrees not to 211 | assert any such rights held by the Licensor to the limited 212 | extent necessary to allow You to exercise the Licensed 213 | Rights, but not otherwise. 214 | 215 | 2. Patent and trademark rights are not licensed under this 216 | Public License. 217 | 218 | 3. To the extent possible, the Licensor waives any right to 219 | collect royalties from You for the exercise of the Licensed 220 | Rights, whether directly or through a collecting society 221 | under any voluntary or waivable statutory or compulsory 222 | licensing scheme. In all other cases the Licensor expressly 223 | reserves any right to collect such royalties. 224 | 225 | 226 | Section 3 -- License Conditions. 227 | 228 | Your exercise of the Licensed Rights is expressly made subject to the 229 | following conditions. 230 | 231 | a. Attribution. 232 | 233 | 1. If You Share the Licensed Material (including in modified 234 | form), You must: 235 | 236 | a. retain the following if it is supplied by the Licensor 237 | with the Licensed Material: 238 | 239 | i. identification of the creator(s) of the Licensed 240 | Material and any others designated to receive 241 | attribution, in any reasonable manner requested by 242 | the Licensor (including by pseudonym if 243 | designated); 244 | 245 | ii. a copyright notice; 246 | 247 | iii. a notice that refers to this Public License; 248 | 249 | iv. a notice that refers to the disclaimer of 250 | warranties; 251 | 252 | v. a URI or hyperlink to the Licensed Material to the 253 | extent reasonably practicable; 254 | 255 | b. indicate if You modified the Licensed Material and 256 | retain an indication of any previous modifications; and 257 | 258 | c. indicate the Licensed Material is licensed under this 259 | Public License, and include the text of, or the URI or 260 | hyperlink to, this Public License. 261 | 262 | 2. You may satisfy the conditions in Section 3(a)(1) in any 263 | reasonable manner based on the medium, means, and context in 264 | which You Share the Licensed Material. For example, it may be 265 | reasonable to satisfy the conditions by providing a URI or 266 | hyperlink to a resource that includes the required 267 | information. 268 | 269 | 3. If requested by the Licensor, You must remove any of the 270 | information required by Section 3(a)(1)(A) to the extent 271 | reasonably practicable. 272 | 273 | b. ShareAlike. 274 | 275 | In addition to the conditions in Section 3(a), if You Share 276 | Adapted Material You produce, the following conditions also apply. 277 | 278 | 1. The Adapter's License You apply must be a Creative Commons 279 | license with the same License Elements, this version or 280 | later, or a BY-SA Compatible License. 281 | 282 | 2. You must include the text of, or the URI or hyperlink to, the 283 | Adapter's License You apply. You may satisfy this condition 284 | in any reasonable manner based on the medium, means, and 285 | context in which You Share Adapted Material. 286 | 287 | 3. You may not offer or impose any additional or different terms 288 | or conditions on, or apply any Effective Technological 289 | Measures to, Adapted Material that restrict exercise of the 290 | rights granted under the Adapter's License You apply. 291 | 292 | 293 | Section 4 -- Sui Generis Database Rights. 294 | 295 | Where the Licensed Rights include Sui Generis Database Rights that 296 | apply to Your use of the Licensed Material: 297 | 298 | a. for the avoidance of doubt, Section 2(a)(1) grants You the right 299 | to extract, reuse, reproduce, and Share all or a substantial 300 | portion of the contents of the database; 301 | 302 | b. if You include all or a substantial portion of the database 303 | contents in a database in which You have Sui Generis Database 304 | Rights, then the database in which You have Sui Generis Database 305 | Rights (but not its individual contents) is Adapted Material, 306 | 307 | including for purposes of Section 3(b); and 308 | c. You must comply with the conditions in Section 3(a) if You Share 309 | all or a substantial portion of the contents of the database. 310 | 311 | For the avoidance of doubt, this Section 4 supplements and does not 312 | replace Your obligations under this Public License where the Licensed 313 | Rights include other Copyright and Similar Rights. 314 | 315 | 316 | Section 5 -- Disclaimer of Warranties and Limitation of Liability. 317 | 318 | a. UNLESS OTHERWISE SEPARATELY UNDERTAKEN BY THE LICENSOR, TO THE 319 | EXTENT POSSIBLE, THE LICENSOR OFFERS THE LICENSED MATERIAL AS-IS 320 | AND AS-AVAILABLE, AND MAKES NO REPRESENTATIONS OR WARRANTIES OF 321 | ANY KIND CONCERNING THE LICENSED MATERIAL, WHETHER EXPRESS, 322 | IMPLIED, STATUTORY, OR OTHER. THIS INCLUDES, WITHOUT LIMITATION, 323 | WARRANTIES OF TITLE, MERCHANTABILITY, FITNESS FOR A PARTICULAR 324 | PURPOSE, NON-INFRINGEMENT, ABSENCE OF LATENT OR OTHER DEFECTS, 325 | ACCURACY, OR THE PRESENCE OR ABSENCE OF ERRORS, WHETHER OR NOT 326 | KNOWN OR DISCOVERABLE. WHERE DISCLAIMERS OF WARRANTIES ARE NOT 327 | ALLOWED IN FULL OR IN PART, THIS DISCLAIMER MAY NOT APPLY TO YOU. 328 | 329 | b. TO THE EXTENT POSSIBLE, IN NO EVENT WILL THE LICENSOR BE LIABLE 330 | TO YOU ON ANY LEGAL THEORY (INCLUDING, WITHOUT LIMITATION, 331 | NEGLIGENCE) OR OTHERWISE FOR ANY DIRECT, SPECIAL, INDIRECT, 332 | INCIDENTAL, CONSEQUENTIAL, PUNITIVE, EXEMPLARY, OR OTHER LOSSES, 333 | COSTS, EXPENSES, OR DAMAGES ARISING OUT OF THIS PUBLIC LICENSE OR 334 | USE OF THE LICENSED MATERIAL, EVEN IF THE LICENSOR HAS BEEN 335 | ADVISED OF THE POSSIBILITY OF SUCH LOSSES, COSTS, EXPENSES, OR 336 | DAMAGES. WHERE A LIMITATION OF LIABILITY IS NOT ALLOWED IN FULL OR 337 | IN PART, THIS LIMITATION MAY NOT APPLY TO YOU. 338 | 339 | c. The disclaimer of warranties and limitation of liability provided 340 | above shall be interpreted in a manner that, to the extent 341 | possible, most closely approximates an absolute disclaimer and 342 | waiver of all liability. 343 | 344 | 345 | Section 6 -- Term and Termination. 346 | 347 | a. This Public License applies for the term of the Copyright and 348 | Similar Rights licensed here. However, if You fail to comply with 349 | this Public License, then Your rights under this Public License 350 | terminate automatically. 351 | 352 | b. Where Your right to use the Licensed Material has terminated under 353 | Section 6(a), it reinstates: 354 | 355 | 1. automatically as of the date the violation is cured, provided 356 | it is cured within 30 days of Your discovery of the 357 | violation; or 358 | 359 | 2. upon express reinstatement by the Licensor. 360 | 361 | For the avoidance of doubt, this Section 6(b) does not affect any 362 | right the Licensor may have to seek remedies for Your violations 363 | of this Public License. 364 | 365 | c. For the avoidance of doubt, the Licensor may also offer the 366 | Licensed Material under separate terms or conditions or stop 367 | distributing the Licensed Material at any time; however, doing so 368 | will not terminate this Public License. 369 | 370 | d. Sections 1, 5, 6, 7, and 8 survive termination of this Public 371 | License. 372 | 373 | 374 | Section 7 -- Other Terms and Conditions. 375 | 376 | a. The Licensor shall not be bound by any additional or different 377 | terms or conditions communicated by You unless expressly agreed. 378 | 379 | b. Any arrangements, understandings, or agreements regarding the 380 | Licensed Material not stated herein are separate from and 381 | independent of the terms and conditions of this Public License. 382 | 383 | 384 | Section 8 -- Interpretation. 385 | 386 | a. For the avoidance of doubt, this Public License does not, and 387 | shall not be interpreted to, reduce, limit, restrict, or impose 388 | conditions on any use of the Licensed Material that could lawfully 389 | be made without permission under this Public License. 390 | 391 | b. To the extent possible, if any provision of this Public License is 392 | deemed unenforceable, it shall be automatically reformed to the 393 | minimum extent necessary to make it enforceable. If the provision 394 | cannot be reformed, it shall be severed from this Public License 395 | without affecting the enforceability of the remaining terms and 396 | conditions. 397 | 398 | c. No term or condition of this Public License will be waived and no 399 | failure to comply consented to unless expressly agreed to by the 400 | Licensor. 401 | 402 | d. Nothing in this Public License constitutes or may be interpreted 403 | as a limitation upon, or waiver of, any privileges and immunities 404 | that apply to the Licensor or You, including from the legal 405 | processes of any jurisdiction or authority. 406 | 407 | 408 | ======================================================================= 409 | 410 | Creative Commons is not a party to its public 411 | licenses. Notwithstanding, Creative Commons may elect to apply one of 412 | its public licenses to material it publishes and in those instances 413 | will be considered the “Licensor.” The text of the Creative Commons 414 | public licenses is dedicated to the public domain under the CC0 Public 415 | Domain Dedication. Except for the limited purpose of indicating that 416 | material is shared under a Creative Commons public license or as 417 | otherwise permitted by the Creative Commons policies published at 418 | creativecommons.org/policies, Creative Commons does not authorize the 419 | use of the trademark "Creative Commons" or any other trademark or logo 420 | of Creative Commons without its prior written consent including, 421 | without limitation, in connection with any unauthorized modifications 422 | to any of its public licenses or any other arrangements, 423 | understandings, or agreements concerning use of licensed material. For 424 | the avoidance of doubt, this paragraph does not form part of the 425 | public licenses. 426 | 427 | Creative Commons may be contacted at creativecommons.org. 428 | 429 | -------------------------------------------------------------------------------- /theme/README.md: -------------------------------------------------------------------------------- 1 | # The LFE `mdbook` Theme 2 | 3 | This repo is intended to be used as a git submodule in LFE book repos, so that all books may be maintained with the same theme from a single point. 4 | 5 | When cloned/added as a git submodule, this repo's local directory in the book repo should be named `theme` and should be at the top-level, in the same parent directory as the book `src` directory. 6 | -------------------------------------------------------------------------------- /theme/css/chrome.css: -------------------------------------------------------------------------------- 1 | /* CSS for UI elements (a.k.a. chrome) */ 2 | 3 | @import 'variables.css'; 4 | 5 | ::-webkit-scrollbar { 6 | background: var(--bg); 7 | } 8 | ::-webkit-scrollbar-thumb { 9 | background: var(--scrollbar); 10 | } 11 | html { 12 | scrollbar-color: var(--scrollbar) var(--bg); 13 | } 14 | #searchresults a, 15 | .content a:link, 16 | a:visited, 17 | a > .hljs { 18 | color: var(--links); 19 | } 20 | 21 | /* Menu Bar */ 22 | 23 | #menu-bar, 24 | #menu-bar-hover-placeholder { 25 | z-index: 101; 26 | margin: auto calc(0px - var(--page-padding)); 27 | } 28 | #menu-bar { 29 | position: relative; 30 | display: flex; 31 | flex-wrap: wrap; 32 | background-color: var(--bg); 33 | border-bottom-color: var(--bg); 34 | border-bottom-width: 1px; 35 | border-bottom-style: solid; 36 | } 37 | #menu-bar.sticky, 38 | .js #menu-bar-hover-placeholder:hover + #menu-bar, 39 | .js #menu-bar:hover, 40 | .js.sidebar-visible #menu-bar { 41 | position: -webkit-sticky; 42 | position: sticky; 43 | top: 0 !important; 44 | } 45 | #menu-bar-hover-placeholder { 46 | position: sticky; 47 | position: -webkit-sticky; 48 | top: 0; 49 | height: var(--menu-bar-height); 50 | } 51 | #menu-bar.bordered { 52 | border-bottom-color: var(--table-border-color); 53 | } 54 | #menu-bar i, #menu-bar .icon-button { 55 | position: relative; 56 | padding: 0 8px; 57 | z-index: 10; 58 | line-height: var(--menu-bar-height); 59 | cursor: pointer; 60 | transition: color 0.5s; 61 | } 62 | @media only screen and (max-width: 420px) { 63 | #menu-bar i, #menu-bar .icon-button { 64 | padding: 0 5px; 65 | } 66 | } 67 | 68 | .icon-button { 69 | border: none; 70 | background: none; 71 | padding: 0; 72 | color: inherit; 73 | } 74 | .icon-button i { 75 | margin: 0; 76 | } 77 | 78 | .right-buttons { 79 | margin: 0 15px; 80 | } 81 | .right-buttons a { 82 | text-decoration: none; 83 | } 84 | 85 | .left-buttons { 86 | display: flex; 87 | margin: 0 5px; 88 | } 89 | .no-js .left-buttons { 90 | display: none; 91 | } 92 | 93 | .menu-title { 94 | display: inline-block; 95 | font-weight: 200; 96 | font-size: 2rem; 97 | line-height: var(--menu-bar-height); 98 | text-align: center; 99 | margin: 0; 100 | flex: 1; 101 | white-space: nowrap; 102 | overflow: hidden; 103 | text-overflow: ellipsis; 104 | } 105 | .js .menu-title { 106 | cursor: pointer; 107 | } 108 | 109 | .menu-bar, 110 | .menu-bar:visited, 111 | .nav-chapters, 112 | .nav-chapters:visited, 113 | .mobile-nav-chapters, 114 | .mobile-nav-chapters:visited, 115 | .menu-bar .icon-button, 116 | .menu-bar a i { 117 | color: var(--icons); 118 | } 119 | 120 | .menu-bar i:hover, 121 | .menu-bar .icon-button:hover, 122 | .nav-chapters:hover, 123 | .mobile-nav-chapters i:hover { 124 | color: var(--icons-hover); 125 | } 126 | 127 | /* Nav Icons */ 128 | 129 | .nav-chapters { 130 | font-size: 2.5em; 131 | text-align: center; 132 | text-decoration: none; 133 | 134 | position: fixed; 135 | top: 0; 136 | bottom: 0; 137 | margin: 0; 138 | max-width: 150px; 139 | min-width: 90px; 140 | 141 | display: flex; 142 | justify-content: center; 143 | align-content: center; 144 | flex-direction: column; 145 | 146 | transition: color 0.5s, background-color 0.5s; 147 | } 148 | 149 | .nav-chapters:hover { 150 | text-decoration: none; 151 | background-color: var(--theme-hover); 152 | transition: background-color 0.15s, color 0.15s; 153 | } 154 | 155 | .nav-wrapper { 156 | margin-top: 50px; 157 | display: none; 158 | } 159 | 160 | .mobile-nav-chapters { 161 | font-size: 2.5em; 162 | text-align: center; 163 | text-decoration: none; 164 | width: 90px; 165 | border-radius: 5px; 166 | background-color: var(--sidebar-bg); 167 | } 168 | 169 | .previous { 170 | float: left; 171 | } 172 | 173 | .next { 174 | float: right; 175 | right: var(--page-padding); 176 | } 177 | 178 | @media only screen and (max-width: 1080px) { 179 | .nav-wide-wrapper { display: none; } 180 | .nav-wrapper { display: block; } 181 | } 182 | 183 | @media only screen and (max-width: 1380px) { 184 | .sidebar-visible .nav-wide-wrapper { display: none; } 185 | .sidebar-visible .nav-wrapper { display: block; } 186 | } 187 | 188 | /* Inline code */ 189 | 190 | :not(pre) > .hljs { 191 | display: inline; 192 | padding: 0.1em 0.3em; 193 | border-radius: 3px; 194 | } 195 | 196 | :not(pre):not(a) > .hljs { 197 | color: var(--inline-code-color); 198 | overflow-x: initial; 199 | } 200 | 201 | a:hover > .hljs { 202 | text-decoration: underline; 203 | } 204 | 205 | pre { 206 | position: relative; 207 | } 208 | pre > .buttons { 209 | position: absolute; 210 | z-index: 100; 211 | right: 5px; 212 | top: 5px; 213 | 214 | color: var(--sidebar-fg); 215 | cursor: pointer; 216 | } 217 | pre > .buttons :hover { 218 | color: var(--sidebar-active); 219 | } 220 | pre > .buttons i { 221 | margin-left: 8px; 222 | } 223 | pre > .buttons button { 224 | color: inherit; 225 | background: transparent; 226 | border: none; 227 | cursor: inherit; 228 | } 229 | pre > .result { 230 | margin-top: 10px; 231 | } 232 | 233 | /* Search */ 234 | 235 | #searchresults a { 236 | text-decoration: none; 237 | } 238 | 239 | mark { 240 | border-radius: 2px; 241 | padding: 0 3px 1px 3px; 242 | margin: 0 -3px -1px -3px; 243 | background-color: var(--search-mark-bg); 244 | transition: background-color 300ms linear; 245 | cursor: pointer; 246 | } 247 | 248 | mark.fade-out { 249 | background-color: rgba(0,0,0,0) !important; 250 | cursor: auto; 251 | } 252 | 253 | .searchbar-outer { 254 | margin-left: auto; 255 | margin-right: auto; 256 | max-width: var(--content-max-width); 257 | } 258 | 259 | #searchbar { 260 | width: 100%; 261 | margin: 5px auto 0px auto; 262 | padding: 10px 16px; 263 | transition: box-shadow 300ms ease-in-out; 264 | border: 1px solid var(--searchbar-border-color); 265 | border-radius: 3px; 266 | background-color: var(--searchbar-bg); 267 | color: var(--searchbar-fg); 268 | } 269 | #searchbar:focus, 270 | #searchbar.active { 271 | box-shadow: 0 0 3px var(--searchbar-shadow-color); 272 | } 273 | 274 | .searchresults-header { 275 | font-weight: bold; 276 | font-size: 1em; 277 | padding: 18px 0 0 5px; 278 | color: var(--searchresults-header-fg); 279 | } 280 | 281 | .searchresults-outer { 282 | margin-left: auto; 283 | margin-right: auto; 284 | max-width: var(--content-max-width); 285 | border-bottom: 1px dashed var(--searchresults-border-color); 286 | } 287 | 288 | ul#searchresults { 289 | list-style: none; 290 | padding-left: 20px; 291 | } 292 | ul#searchresults li { 293 | margin: 10px 0px; 294 | padding: 2px; 295 | border-radius: 2px; 296 | } 297 | ul#searchresults li.focus { 298 | background-color: var(--searchresults-li-bg); 299 | } 300 | ul#searchresults span.teaser { 301 | display: block; 302 | clear: both; 303 | margin: 5px 0 0 20px; 304 | font-size: 0.8em; 305 | } 306 | ul#searchresults span.teaser em { 307 | font-weight: bold; 308 | font-style: normal; 309 | } 310 | 311 | /* Sidebar */ 312 | 313 | .sidebar { 314 | position: fixed; 315 | left: 0; 316 | top: 0; 317 | bottom: 0; 318 | width: var(--sidebar-width); 319 | font-size: 0.875em; 320 | box-sizing: border-box; 321 | -webkit-overflow-scrolling: touch; 322 | overscroll-behavior-y: contain; 323 | background-color: var(--sidebar-bg); 324 | color: var(--sidebar-fg); 325 | } 326 | .sidebar-resizing { 327 | -moz-user-select: none; 328 | -webkit-user-select: none; 329 | -ms-user-select: none; 330 | user-select: none; 331 | } 332 | .js:not(.sidebar-resizing) .sidebar { 333 | transition: transform 0.3s; /* Animation: slide away */ 334 | } 335 | .sidebar code { 336 | line-height: 2em; 337 | } 338 | .sidebar .sidebar-scrollbox { 339 | overflow-y: auto; 340 | position: absolute; 341 | top: 0; 342 | bottom: 0; 343 | left: 0; 344 | right: 0; 345 | padding: 10px 10px; 346 | } 347 | .sidebar .sidebar-resize-handle { 348 | position: absolute; 349 | cursor: col-resize; 350 | width: 0; 351 | right: 0; 352 | top: 0; 353 | bottom: 0; 354 | } 355 | .js .sidebar .sidebar-resize-handle { 356 | cursor: col-resize; 357 | width: 5px; 358 | } 359 | .sidebar-hidden .sidebar { 360 | transform: translateX(calc(0px - var(--sidebar-width))); 361 | } 362 | .sidebar::-webkit-scrollbar { 363 | background: var(--sidebar-bg); 364 | } 365 | .sidebar::-webkit-scrollbar-thumb { 366 | background: var(--scrollbar); 367 | } 368 | 369 | .sidebar-visible .page-wrapper { 370 | transform: translateX(var(--sidebar-width)); 371 | } 372 | @media only screen and (min-width: 620px) { 373 | .sidebar-visible .page-wrapper { 374 | transform: none; 375 | margin-left: var(--sidebar-width); 376 | } 377 | } 378 | 379 | .chapter { 380 | list-style: none outside none; 381 | padding-left: 0; 382 | line-height: 2.2em; 383 | } 384 | 385 | .chapter ol { 386 | width: 100%; 387 | } 388 | 389 | .chapter li { 390 | display: flex; 391 | color: var(--sidebar-non-existant); 392 | } 393 | .chapter li a { 394 | display: block; 395 | padding: 0; 396 | text-decoration: none; 397 | color: var(--sidebar-fg); 398 | } 399 | 400 | .chapter li a:hover { 401 | color: var(--sidebar-active); 402 | } 403 | 404 | .chapter li a.active { 405 | color: var(--sidebar-active); 406 | } 407 | 408 | .chapter li > a.toggle { 409 | cursor: pointer; 410 | display: block; 411 | margin-left: auto; 412 | padding: 0 10px; 413 | user-select: none; 414 | opacity: 0.68; 415 | } 416 | 417 | .chapter li > a.toggle div { 418 | transition: transform 0.5s; 419 | } 420 | 421 | /* collapse the section */ 422 | .chapter li:not(.expanded) + li > ol { 423 | display: none; 424 | } 425 | 426 | .chapter li.chapter-item { 427 | line-height: 1.5em; 428 | margin-top: 0.6em; 429 | } 430 | 431 | .chapter li.expanded > a.toggle div { 432 | transform: rotate(90deg); 433 | } 434 | 435 | .spacer { 436 | width: 100%; 437 | height: 3px; 438 | margin: 5px 0px; 439 | } 440 | .chapter .spacer { 441 | background-color: var(--sidebar-spacer); 442 | } 443 | 444 | @media (-moz-touch-enabled: 1), (pointer: coarse) { 445 | .chapter li a { padding: 5px 0; } 446 | .spacer { margin: 10px 0; } 447 | } 448 | 449 | .section { 450 | list-style: none outside none; 451 | padding-left: 20px; 452 | line-height: 1.9em; 453 | } 454 | 455 | /* Theme Menu Popup */ 456 | 457 | .theme-popup { 458 | position: absolute; 459 | left: 10px; 460 | top: var(--menu-bar-height); 461 | z-index: 1000; 462 | border-radius: 4px; 463 | font-size: 0.7em; 464 | color: var(--fg); 465 | background: var(--theme-popup-bg); 466 | border: 1px solid var(--theme-popup-border); 467 | margin: 0; 468 | padding: 0; 469 | list-style: none; 470 | display: none; 471 | } 472 | .theme-popup .default { 473 | color: var(--icons); 474 | } 475 | .theme-popup .theme { 476 | width: 100%; 477 | border: 0; 478 | margin: 0; 479 | padding: 2px 10px; 480 | line-height: 25px; 481 | white-space: nowrap; 482 | text-align: left; 483 | cursor: pointer; 484 | color: inherit; 485 | background: inherit; 486 | font-size: inherit; 487 | } 488 | .theme-popup .theme:hover { 489 | background-color: var(--theme-hover); 490 | } 491 | .theme-popup .theme:hover:first-child, 492 | .theme-popup .theme:hover:last-child { 493 | border-top-left-radius: inherit; 494 | border-top-right-radius: inherit; 495 | } 496 | -------------------------------------------------------------------------------- /theme/css/general.css: -------------------------------------------------------------------------------- 1 | /* Base styles and content styles */ 2 | 3 | @import 'variables.css'; 4 | 5 | :root { 6 | /* Browser default font-size is 16px, this way 1 rem = 10px */ 7 | font-size: 62.5%; 8 | } 9 | 10 | html { 11 | font-family: "Open Sans", sans-serif; 12 | color: var(--fg); 13 | background-color: var(--bg); 14 | text-size-adjust: none; 15 | } 16 | 17 | body { 18 | margin: 0; 19 | font-size: 1.6rem; 20 | overflow-x: hidden; 21 | } 22 | 23 | code { 24 | font-family: "Source Code Pro", Consolas, "Ubuntu Mono", Menlo, "DejaVu Sans Mono", monospace, monospace !important; 25 | font-size: 0.875em; /* please adjust the ace font size accordingly in editor.js */ 26 | } 27 | 28 | /* Don't change font size in headers. */ 29 | h1 code, h2 code, h3 code, h4 code, h5 code, h6 code { 30 | font-size: unset; 31 | } 32 | 33 | .left { float: left; } 34 | .right { float: right; } 35 | .boring { opacity: 0.6; } 36 | .hide-boring .boring { display: none; } 37 | .hidden { display: none !important; } 38 | 39 | h2, h3 { margin-top: 2.5em; } 40 | h4, h5 { margin-top: 2em; } 41 | 42 | .header + .header h3, 43 | .header + .header h4, 44 | .header + .header h5 { 45 | margin-top: 1em; 46 | } 47 | 48 | h1 a.header:target::before, 49 | h2 a.header:target::before, 50 | h3 a.header:target::before, 51 | h4 a.header:target::before { 52 | display: inline-block; 53 | content: "»"; 54 | margin-left: -30px; 55 | width: 30px; 56 | } 57 | 58 | h1 a.header:target, 59 | h2 a.header:target, 60 | h3 a.header:target, 61 | h4 a.header:target { 62 | scroll-margin-top: calc(var(--menu-bar-height) + 0.5em); 63 | } 64 | 65 | .page { 66 | outline: 0; 67 | padding: 0 var(--page-padding); 68 | margin-top: calc(0px - var(--menu-bar-height)); /* Compensate for the #menu-bar-hover-placeholder */ 69 | } 70 | .page-wrapper { 71 | box-sizing: border-box; 72 | } 73 | .js:not(.sidebar-resizing) .page-wrapper { 74 | transition: margin-left 0.3s ease, transform 0.3s ease; /* Animation: slide away */ 75 | } 76 | 77 | .content { 78 | overflow-y: auto; 79 | padding: 0 15px; 80 | padding-bottom: 50px; 81 | } 82 | .content main { 83 | margin-left: auto; 84 | margin-right: auto; 85 | max-width: var(--content-max-width); 86 | } 87 | .content p { line-height: 1.45em; } 88 | .content ol { line-height: 1.45em; } 89 | .content ul { line-height: 1.45em; } 90 | .content a { text-decoration: none; } 91 | .content a:hover { text-decoration: underline; } 92 | .content img { max-width: 100%; } 93 | .content .header:link, 94 | .content .header:visited { 95 | color: var(--fg); 96 | } 97 | .content .header:link, 98 | .content .header:visited:hover { 99 | text-decoration: none; 100 | } 101 | 102 | table { 103 | margin: 0 auto; 104 | border-collapse: collapse; 105 | } 106 | table td { 107 | padding: 3px 20px; 108 | border: 1px var(--table-border-color) solid; 109 | } 110 | table thead { 111 | background: var(--table-header-bg); 112 | } 113 | table thead td { 114 | font-weight: 700; 115 | border: none; 116 | } 117 | table thead th { 118 | padding: 3px 20px; 119 | } 120 | table thead tr { 121 | border: 1px var(--table-header-bg) solid; 122 | } 123 | /* Alternate background colors for rows */ 124 | table tbody tr:nth-child(2n) { 125 | background: var(--table-alternate-bg); 126 | } 127 | 128 | 129 | blockquote { 130 | margin: 20px 0; 131 | padding: 0 20px; 132 | color: var(--fg); 133 | background-color: var(--quote-bg); 134 | border-top: .1em solid var(--quote-border); 135 | border-bottom: .1em solid var(--quote-border); 136 | } 137 | 138 | 139 | :not(.footnote-definition) + .footnote-definition, 140 | .footnote-definition + :not(.footnote-definition) { 141 | margin-top: 2em; 142 | } 143 | .footnote-definition { 144 | font-size: 0.9em; 145 | margin: 0.5em 0; 146 | } 147 | .footnote-definition p { 148 | display: inline; 149 | } 150 | 151 | .tooltiptext { 152 | position: absolute; 153 | visibility: hidden; 154 | color: #fff; 155 | background-color: #333; 156 | transform: translateX(-50%); /* Center by moving tooltip 50% of its width left */ 157 | left: -8px; /* Half of the width of the icon */ 158 | top: -35px; 159 | font-size: 0.8em; 160 | text-align: center; 161 | border-radius: 6px; 162 | padding: 5px 8px; 163 | margin: 5px; 164 | z-index: 1000; 165 | } 166 | .tooltipped .tooltiptext { 167 | visibility: visible; 168 | } 169 | 170 | .chapter li.part-title { 171 | color: var(--sidebar-fg); 172 | margin: 5px 0px; 173 | font-weight: bold; 174 | } 175 | 176 | 177 | /* 178 | * 179 | * Items introduced by the LFE Theme 180 | * 181 | */ 182 | 183 | code, 184 | :not(pre):not(a)>.hljs { 185 | background-color: var(--inline-code-background-color); 186 | color: var(--inline-code-color); 187 | } 188 | 189 | .alert { 190 | position: relative; 191 | padding: 0.75rem 1.25rem; 192 | margin-bottom: 1rem; 193 | border: 1px solid transparent; 194 | border-radius: 6px; 195 | } 196 | 197 | .alert-heading { 198 | color: inherit; 199 | margin: 0; 200 | padding: 0; 201 | } 202 | 203 | .alert-link { 204 | font-weight: 700; 205 | } 206 | 207 | /* Primary */ 208 | 209 | .alert-primary { 210 | color: var(--alert-primary-fg); 211 | background-color: var(--alert-primary-bg); 212 | border-color: var(--alert-primary-border); 213 | } 214 | 215 | .alert-primary hr { 216 | border-top-color: var(--alert-primary-hr); 217 | } 218 | 219 | .alert-primary .alert-link { 220 | color: var(--alert-primary-link); 221 | } 222 | 223 | .alert-primary, 224 | .alert-primary>th, 225 | .alert-primary>td { 226 | background-color: var(--alert-primary-bg); 227 | } 228 | 229 | /* Secondary */ 230 | 231 | .alert-secondary { 232 | color: var(--alert-secondary-fg); 233 | background-color: var(--alert-secondary-bg); 234 | border-color: var(--alert-secondary-border); 235 | } 236 | 237 | .alert-secondary hr { 238 | border-top-color: var(--alert-secondary-hr); 239 | } 240 | 241 | .alert-secondary .alert-link { 242 | color: var(--alert-secondary-link); 243 | } 244 | 245 | .alert-secondary, 246 | .alert-secondary>th, 247 | .alert-secondary>td { 248 | background-color: var(--alert-secondary-bg); 249 | } 250 | 251 | /* Success */ 252 | 253 | .alert-success { 254 | color: var(--alert-success-fg); 255 | background-color: var(--alert-success-bg); 256 | border-color: var(--alert-success-border); 257 | } 258 | 259 | .alert-success hr { 260 | border-top-color: var(--alert-success-hr); 261 | } 262 | 263 | .alert-success .alert-link { 264 | color: var(--alert-success-link); 265 | } 266 | 267 | .alert-success, 268 | .alert-success>th, 269 | .alert-success>td { 270 | background-color: var(--alert-success-bg); 271 | } 272 | 273 | /* Info */ 274 | 275 | .alert-info { 276 | color: var(--alert-info-fg); 277 | background-color: var(--alert-info-bg); 278 | border-color: var(--alert-info-border); 279 | } 280 | 281 | .alert-info hr { 282 | border-top-color: var(--alert-info-hr); 283 | } 284 | 285 | .alert-info .alert-link { 286 | color: var(--alert-info-link); 287 | } 288 | 289 | .alert-info, 290 | .alert-info>th, 291 | .alert-info>td { 292 | background-color: var(--alert-info-bg); 293 | } 294 | 295 | /* Warning */ 296 | 297 | .alert-warning { 298 | color: var(--alert-warning-fg); 299 | background-color: var(--alert-warning-bg); 300 | border-color: var(--alert-warning-border); 301 | } 302 | 303 | .alert-warning hr { 304 | border-top-color: var(--alert-warning-hr); 305 | } 306 | 307 | .alert-warning .alert-link { 308 | color: var(--alert-warning-link); 309 | } 310 | 311 | .alert-warning, 312 | .alert-warning>th, 313 | .alert-warning>td { 314 | background-color: var(--alert-warning-bg); 315 | } 316 | 317 | /* Danger */ 318 | 319 | .alert-danger { 320 | color: var(--alert-danger-fg); 321 | background-color: var(--alert-danger-bg); 322 | border-color: var(--alert-danger-border); 323 | } 324 | 325 | .alert-danger hr { 326 | border-top-color: var(--alert-danger-hr); 327 | } 328 | 329 | .alert-danger .alert-link { 330 | color: var(--alert-danger-link); 331 | } 332 | 333 | .alert-danger, 334 | .alert-danger>th, 335 | .alert-danger>td { 336 | background-color: var(--alert-danger-bg); 337 | } 338 | 339 | /* Footnotes */ 340 | 341 | .footnotes { 342 | font-size: 10pt; 343 | padding-top: 2em; 344 | margin-top: 2em; 345 | padding-left: 0; 346 | padding-right: 0; 347 | margin-left: 0; 348 | margin-right: 0; 349 | } 350 | 351 | .footnotes ol, 352 | .footnotes ol li { 353 | padding-left: 0; 354 | padding-right: 0; 355 | margin-left: 0.6em; 356 | margin-right: 0; 357 | } 358 | 359 | .footnotes ol { 360 | /* margin-left: -1em; */ 361 | } 362 | 363 | .footnotes hr { 364 | border: 0; 365 | border-bottom: solid 1px #666; 366 | } 367 | -------------------------------------------------------------------------------- /theme/css/print.css: -------------------------------------------------------------------------------- 1 | 2 | #sidebar, 3 | #menu-bar, 4 | .nav-chapters, 5 | .mobile-nav-chapters { 6 | display: none; 7 | } 8 | 9 | #page-wrapper.page-wrapper { 10 | transform: none; 11 | margin-left: 0px; 12 | overflow-y: initial; 13 | } 14 | 15 | #content { 16 | max-width: none; 17 | margin: 0; 18 | padding: 0; 19 | } 20 | 21 | .page { 22 | overflow-y: initial; 23 | } 24 | 25 | code { 26 | background-color: #666666; 27 | border-radius: 5px; 28 | 29 | /* Force background to be printed in Chrome */ 30 | -webkit-print-color-adjust: exact; 31 | } 32 | 33 | pre > .buttons { 34 | z-index: 2; 35 | } 36 | 37 | a, a:visited, a:active, a:hover { 38 | color: #4183c4; 39 | text-decoration: none; 40 | } 41 | 42 | h1, h2, h3, h4, h5, h6 { 43 | page-break-inside: avoid; 44 | page-break-after: avoid; 45 | } 46 | 47 | pre, code { 48 | page-break-inside: avoid; 49 | white-space: pre-wrap; 50 | } 51 | 52 | .fa { 53 | display: none !important; 54 | } 55 | -------------------------------------------------------------------------------- /theme/css/variables.css: -------------------------------------------------------------------------------- 1 | /* Globals */ 2 | 3 | :root { 4 | --sidebar-width: 300px; 5 | --page-padding: 15px; 6 | --content-max-width: 750px; 7 | --menu-bar-height: 50px; 8 | } 9 | 10 | /* Themes */ 11 | 12 | .lfe-pdp { 13 | --bg: #D9D8D4; 14 | --fg: #1E252B; 15 | 16 | --sidebar-bg: #16161A; 17 | --sidebar-fg: #ECE5C9; 18 | --sidebar-non-existant: #505254; 19 | --sidebar-active: #377CA2; 20 | --sidebar-spacer: #0B2D3F; 21 | 22 | --scrollbar: var(--sidebar-fg); 23 | 24 | --icons: #737480; 25 | --icons-hover: #1E252B; 26 | 27 | --links: #377CA2; 28 | 29 | --inline-code-color: #16455E; 30 | --inline-code-background-color: transparent; 31 | 32 | --theme-popup-bg: #DDD09C; 33 | --theme-popup-border: #b38f6b; 34 | --theme-hover: #B4BEC4; 35 | 36 | --quote-bg: hsl(60, 5%, 75%); 37 | --quote-border: hsl(60, 5%, 70%); 38 | 39 | --table-border-color: hsl(60, 9%, 82%); 40 | --table-header-bg: #b3a497; 41 | --table-alternate-bg: hsl(60, 9%, 84%); 42 | 43 | --searchbar-border-color: #aaa; 44 | --searchbar-bg: #fafafa; 45 | --searchbar-fg: #000; 46 | --searchbar-shadow-color: #aaa; 47 | --searchresults-header-fg: #666; 48 | --searchresults-border-color: #888; 49 | --searchresults-li-bg: #dec2a2; 50 | --search-mark-bg: #e69f67; 51 | 52 | /* New, custom styles */ 53 | --alert-primary-fg: #1a3047; 54 | //--alert-primary-bg: #d6dfe7; 55 | --alert-primary-bg: #325D88; 56 | --alert-primary-border: #c6d2de; 57 | --alert-primary-hr: #b6c5d5; 58 | --alert-primary-link: #0c1722; 59 | 60 | 61 | --alert-secondary-fg: #4a4945; 62 | //--alert-secondary-bg: #e8e8e6; 63 | --alert-secondary-bg: #8E8C84; 64 | --alert-secondary-border: #b4b465; 65 | --alert-secondary-hr: #d3d3d0; 66 | --alert-secondary-link: #302f2c; 67 | 68 | 69 | --alert-success-fg: #4c6627; 70 | //--alert-success-bg: #e9f3db; 71 | --alert-success-bg: #93C54B; 72 | --alert-success-border: #94b861; 73 | --alert-success-hr: #d5e9ba; 74 | --alert-success-link: #314119; 75 | 76 | 77 | --alert-info-fg: #155974; 78 | //--alert-info-bg: #d4eef9; 79 | --alert-info-bg: #29ABE0; 80 | --alert-info-border: #66a6c2; 81 | --alert-info-hr: #addef3; 82 | --alert-info-link: #0d3849; 83 | 84 | 85 | --alert-warning-fg: #7f401f; 86 | //--alert-warning-bg: #fde5d8; 87 | --alert-warning-bg: #F47C3C; 88 | --alert-warning-border: #ce865f; 89 | --alert-warning-hr: #fbcab0; 90 | --alert-warning-link: #562b15; 91 | 92 | --alert-danger-fg: #712b29; 93 | //--alert-danger-bg: #f7dddc; 94 | --alert-danger-bg: #d9534f; 95 | --alert-danger-border: #c07471; 96 | --alert-danger-hr: #efbbb9; 97 | --alert-danger-link: #4c1d1b; 98 | } 99 | 100 | .ayu { 101 | --bg: hsl(210, 25%, 8%); 102 | --fg: #c5c5c5; 103 | 104 | --sidebar-bg: #14191f; 105 | --sidebar-fg: #c8c9db; 106 | --sidebar-non-existant: #5c6773; 107 | --sidebar-active: #ffb454; 108 | --sidebar-spacer: #2d334f; 109 | 110 | --scrollbar: var(--sidebar-fg); 111 | 112 | --icons: #737480; 113 | --icons-hover: #b7b9cc; 114 | 115 | --links: #0096cf; 116 | 117 | --inline-code-color: #ffb454; 118 | 119 | --theme-popup-bg: #14191f; 120 | --theme-popup-border: #5c6773; 121 | --theme-hover: #191f26; 122 | 123 | --quote-bg: hsl(226, 15%, 17%); 124 | --quote-border: hsl(226, 15%, 22%); 125 | 126 | --table-border-color: hsl(210, 25%, 13%); 127 | --table-header-bg: hsl(210, 25%, 28%); 128 | --table-alternate-bg: hsl(210, 25%, 11%); 129 | 130 | --searchbar-border-color: #848484; 131 | --searchbar-bg: #424242; 132 | --searchbar-fg: #fff; 133 | --searchbar-shadow-color: #d4c89f; 134 | --searchresults-header-fg: #666; 135 | --searchresults-border-color: #888; 136 | --searchresults-li-bg: #252932; 137 | --search-mark-bg: #e3b171; 138 | } 139 | 140 | .coal { 141 | --bg: hsl(200, 7%, 8%); 142 | --fg: #98a3ad; 143 | 144 | --sidebar-bg: #292c2f; 145 | --sidebar-fg: #a1adb8; 146 | --sidebar-non-existant: #505254; 147 | --sidebar-active: #3473ad; 148 | --sidebar-spacer: #393939; 149 | 150 | --scrollbar: var(--sidebar-fg); 151 | 152 | --icons: #43484d; 153 | --icons-hover: #b3c0cc; 154 | 155 | --links: #2b79a2; 156 | 157 | --inline-code-color: #c5c8c6; 158 | ; 159 | 160 | --theme-popup-bg: #141617; 161 | --theme-popup-border: #43484d; 162 | --theme-hover: #1f2124; 163 | 164 | --quote-bg: hsl(234, 21%, 18%); 165 | --quote-border: hsl(234, 21%, 23%); 166 | 167 | --table-border-color: hsl(200, 7%, 13%); 168 | --table-header-bg: hsl(200, 7%, 28%); 169 | --table-alternate-bg: hsl(200, 7%, 11%); 170 | 171 | --searchbar-border-color: #aaa; 172 | --searchbar-bg: #b7b7b7; 173 | --searchbar-fg: #000; 174 | --searchbar-shadow-color: #aaa; 175 | --searchresults-header-fg: #666; 176 | --searchresults-border-color: #98a3ad; 177 | --searchresults-li-bg: #2b2b2f; 178 | --search-mark-bg: #355c7d; 179 | } 180 | 181 | .light { 182 | --bg: hsl(0, 0%, 100%); 183 | --fg: #333333; 184 | 185 | --sidebar-bg: #fafafa; 186 | --sidebar-fg: #364149; 187 | --sidebar-non-existant: #aaaaaa; 188 | --sidebar-active: #008cff; 189 | --sidebar-spacer: #f4f4f4; 190 | 191 | --scrollbar: #cccccc; 192 | 193 | --icons: #cccccc; 194 | --icons-hover: #333333; 195 | 196 | --links: #4183c4; 197 | 198 | --inline-code-color: #6e6b5e; 199 | 200 | --theme-popup-bg: #fafafa; 201 | --theme-popup-border: #cccccc; 202 | --theme-hover: #e6e6e6; 203 | 204 | --quote-bg: hsl(197, 37%, 96%); 205 | --quote-border: hsl(197, 37%, 91%); 206 | 207 | --table-border-color: hsl(0, 0%, 95%); 208 | --table-header-bg: hsl(0, 0%, 80%); 209 | --table-alternate-bg: hsl(0, 0%, 97%); 210 | 211 | --searchbar-border-color: #aaa; 212 | --searchbar-bg: #fafafa; 213 | --searchbar-fg: #000; 214 | --searchbar-shadow-color: #aaa; 215 | --searchresults-header-fg: #666; 216 | --searchresults-border-color: #888; 217 | --searchresults-li-bg: #e4f2fe; 218 | --search-mark-bg: #a2cff5; 219 | } 220 | 221 | .navy { 222 | --bg: hsl(226, 23%, 11%); 223 | --fg: #bcbdd0; 224 | 225 | --sidebar-bg: #282d3f; 226 | --sidebar-fg: #c8c9db; 227 | --sidebar-non-existant: #505274; 228 | --sidebar-active: #2b79a2; 229 | --sidebar-spacer: #2d334f; 230 | 231 | --scrollbar: var(--sidebar-fg); 232 | 233 | --icons: #737480; 234 | --icons-hover: #b7b9cc; 235 | 236 | --links: #2b79a2; 237 | 238 | --inline-code-color: #c5c8c6; 239 | ; 240 | 241 | --theme-popup-bg: #161923; 242 | --theme-popup-border: #737480; 243 | --theme-hover: #282e40; 244 | 245 | --quote-bg: hsl(226, 15%, 17%); 246 | --quote-border: hsl(226, 15%, 22%); 247 | 248 | --table-border-color: hsl(226, 23%, 16%); 249 | --table-header-bg: hsl(226, 23%, 31%); 250 | --table-alternate-bg: hsl(226, 23%, 14%); 251 | 252 | --searchbar-border-color: #aaa; 253 | --searchbar-bg: #aeaec6; 254 | --searchbar-fg: #000; 255 | --searchbar-shadow-color: #aaa; 256 | --searchresults-header-fg: #5f5f71; 257 | --searchresults-border-color: #5c5c68; 258 | --searchresults-li-bg: #242430; 259 | --search-mark-bg: #a2cff5; 260 | } 261 | 262 | .rust { 263 | --bg: hsl(60, 9%, 87%); 264 | --fg: #262625; 265 | 266 | --sidebar-bg: #3b2e2a; 267 | --sidebar-fg: #c8c9db; 268 | --sidebar-non-existant: #505254; 269 | --sidebar-active: #e69f67; 270 | --sidebar-spacer: #45373a; 271 | 272 | --scrollbar: var(--sidebar-fg); 273 | 274 | --icons: #737480; 275 | --icons-hover: #262625; 276 | 277 | --links: #2b79a2; 278 | 279 | --inline-code-color: #6e6b5e; 280 | 281 | --theme-popup-bg: #e1e1db; 282 | --theme-popup-border: #b38f6b; 283 | --theme-hover: #99908a; 284 | 285 | --quote-bg: hsl(60, 5%, 75%); 286 | --quote-border: hsl(60, 5%, 70%); 287 | 288 | --table-border-color: hsl(60, 9%, 82%); 289 | --table-header-bg: #b3a497; 290 | --table-alternate-bg: hsl(60, 9%, 84%); 291 | 292 | --searchbar-border-color: #aaa; 293 | --searchbar-bg: #fafafa; 294 | --searchbar-fg: #000; 295 | --searchbar-shadow-color: #aaa; 296 | --searchresults-header-fg: #666; 297 | --searchresults-border-color: #888; 298 | --searchresults-li-bg: #dec2a2; 299 | --search-mark-bg: #e69f67; 300 | } 301 | 302 | @media (prefers-color-scheme: dark) { 303 | .light.no-js { 304 | --bg: hsl(200, 7%, 8%); 305 | --fg: #98a3ad; 306 | 307 | --sidebar-bg: #292c2f; 308 | --sidebar-fg: #a1adb8; 309 | --sidebar-non-existant: #505254; 310 | --sidebar-active: #3473ad; 311 | --sidebar-spacer: #393939; 312 | 313 | --scrollbar: var(--sidebar-fg); 314 | 315 | --icons: #43484d; 316 | --icons-hover: #b3c0cc; 317 | 318 | --links: #2b79a2; 319 | 320 | --inline-code-color: #c5c8c6; 321 | ; 322 | 323 | --theme-popup-bg: #141617; 324 | --theme-popup-border: #43484d; 325 | --theme-hover: #1f2124; 326 | 327 | --quote-bg: hsl(234, 21%, 18%); 328 | --quote-border: hsl(234, 21%, 23%); 329 | 330 | --table-border-color: hsl(200, 7%, 13%); 331 | --table-header-bg: hsl(200, 7%, 28%); 332 | --table-alternate-bg: hsl(200, 7%, 11%); 333 | 334 | --searchbar-border-color: #aaa; 335 | --searchbar-bg: #b7b7b7; 336 | --searchbar-fg: #000; 337 | --searchbar-shadow-color: #aaa; 338 | --searchresults-header-fg: #666; 339 | --searchresults-border-color: #98a3ad; 340 | --searchresults-li-bg: #2b2b2f; 341 | --search-mark-bg: #355c7d; 342 | } 343 | } 344 | -------------------------------------------------------------------------------- /theme/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cnbbooks/lfe-tutorial/b1c1e1b004fa33ee26fcd4ecbe1ea3222a0f7507/theme/favicon.png -------------------------------------------------------------------------------- /theme/favicon.svg: -------------------------------------------------------------------------------- 1 | 2 | 7 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /theme/highlight.css: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Night Owl for highlight.js (c) Carl Baxter 4 | 5 | An adaptation of Sarah Drasner's Night Owl VS Code Theme 6 | https://github.com/sdras/night-owl-vscode-theme 7 | 8 | Copyright (c) 2018 Sarah Drasner 9 | 10 | Permission is hereby granted, free of charge, to any person obtaining a copy 11 | of this software and associated documentation files (the "Software"), to deal 12 | in the Software without restriction, including without limitation the rights 13 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 14 | copies of the Software, and to permit persons to whom the Software is 15 | furnished to do so, subject to the following conditions: 16 | 17 | The above copyright notice and this permission notice shall be included in all 18 | copies or substantial portions of the Software. 19 | 20 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 23 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 24 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 25 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 26 | SOFTWARE. 27 | 28 | */ 29 | 30 | .hljs { 31 | display: block; 32 | overflow-x: auto; 33 | padding: 0.5em; 34 | background: #011627; 35 | color: #d6deeb; 36 | } 37 | 38 | /* General Purpose */ 39 | .hljs-keyword { 40 | color: #c792ea; 41 | font-style: italic; 42 | } 43 | .hljs-built_in { 44 | color: #addb67; 45 | font-style: italic; 46 | } 47 | .hljs-type { 48 | color: #82aaff; 49 | } 50 | .hljs-literal { 51 | color: #ff5874; 52 | } 53 | .hljs-number { 54 | color: #F78C6C; 55 | } 56 | .hljs-regexp { 57 | color: #5ca7e4; 58 | } 59 | .hljs-string { 60 | color: #ecc48d; 61 | } 62 | .hljs-subst { 63 | color: #d3423e; 64 | } 65 | .hljs-symbol { 66 | color: #82aaff; 67 | } 68 | .hljs-class { 69 | color: #ffcb8b; 70 | } 71 | .hljs-function { 72 | color: #82AAFF; 73 | } 74 | .hljs-title { 75 | color: #DCDCAA; 76 | font-style: italic; 77 | } 78 | .hljs-params { 79 | color: #7fdbca; 80 | } 81 | 82 | /* Meta */ 83 | .hljs-comment { 84 | color: #637777; 85 | font-style: italic; 86 | } 87 | .hljs-doctag { 88 | color: #7fdbca; 89 | } 90 | .hljs-meta { 91 | color: #82aaff; 92 | } 93 | .hljs-meta-keyword { 94 | color: #82aaff; 95 | } 96 | .hljs-meta-string { 97 | color: #ecc48d; 98 | } 99 | 100 | /* Tags, attributes, config */ 101 | .hljs-section { 102 | color: #82b1ff; 103 | } 104 | .hljs-tag, 105 | .hljs-name, 106 | .hljs-builtin-name { 107 | color: #7fdbca; 108 | } 109 | .hljs-attr { 110 | color: #7fdbca; 111 | } 112 | .hljs-attribute { 113 | color: #80cbc4; 114 | } 115 | .hljs-variable { 116 | color: #addb67; 117 | } 118 | 119 | /* Markup */ 120 | .hljs-bullet { 121 | color: #d9f5dd; 122 | } 123 | .hljs-code { 124 | color: #80CBC4; 125 | } 126 | .hljs-emphasis { 127 | color: #c792ea; 128 | font-style: italic; 129 | } 130 | .hljs-strong { 131 | color: #addb67; 132 | font-weight: bold; 133 | } 134 | .hljs-formula { 135 | color: #c792ea; 136 | } 137 | .hljs-link { 138 | color: #ff869a; 139 | } 140 | .hljs-quote { 141 | color: #697098; 142 | font-style: italic; 143 | } 144 | 145 | /* CSS */ 146 | .hljs-selector-tag { 147 | color: #ff6363; 148 | } 149 | 150 | .hljs-selector-id { 151 | color: #fad430; 152 | } 153 | 154 | .hljs-selector-class { 155 | color: #addb67; 156 | font-style: italic; 157 | } 158 | 159 | .hljs-selector-attr, 160 | .hljs-selector-pseudo { 161 | color: #c792ea; 162 | font-style: italic; 163 | } 164 | 165 | /* Templates */ 166 | .hljs-template-tag { 167 | color: #c792ea; 168 | } 169 | .hljs-template-variable { 170 | color: #addb67; 171 | } 172 | 173 | /* diff */ 174 | .hljs-addition { 175 | color: #addb67ff; 176 | font-style: italic; 177 | } 178 | 179 | .hljs-deletion { 180 | color: #EF535090; 181 | font-style: italic; 182 | } 183 | -------------------------------------------------------------------------------- /theme/index.hbs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | {{ title }} 8 | {{#if is_print }} 9 | 10 | {{/if}} 11 | {{#if base_url}} 12 | 13 | {{/if}} 14 | 15 | 16 | 17 | {{> head}} 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | {{#if copy_fonts}} 34 | 35 | {{/if}} 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | {{#each additional_css}} 44 | 45 | {{/each}} 46 | 47 | {{#if mathjax_support}} 48 | 49 | 51 | {{/if}} 52 | 53 | 54 | 55 | 56 | 60 | 61 | 62 | 76 | 77 | 78 | 88 | 89 | 90 | 100 | 101 | 107 | 108 |
109 | 110 |
111 | {{> header}} 112 | 113 | 159 | 160 | {{#if search_enabled}} 161 | 173 | {{/if}} 174 | 175 | 176 | 183 | 184 |
185 |
186 | {{{ content }}} 187 |
188 | 189 | 207 |
208 |
209 | 210 | 225 | 226 |
227 | 228 | {{#if livereload}} 229 | 230 | 243 | {{/if}} 244 | 245 | {{#if google_analytics}} 246 | 247 | 264 | {{/if}} 265 | 266 | {{#if playground_line_numbers}} 267 | 270 | {{/if}} 271 | 272 | {{#if playground_copyable}} 273 | 276 | {{/if}} 277 | 278 | {{#if playground_js}} 279 | 280 | 281 | 282 | 283 | 284 | {{/if}} 285 | 286 | {{#if search_js}} 287 | 288 | 289 | 290 | {{/if}} 291 | 292 | 293 | 294 | 295 | 296 | 297 | {{#each additional_js}} 298 | 299 | {{/each}} 300 | 301 | {{#if is_print}} 302 | {{#if mathjax_support}} 303 | 310 | {{else}} 311 | 316 | {{/if}} 317 | {{/if}} 318 | 319 | 320 | 321 | 322 | --------------------------------------------------------------------------------