diff --git a/.github/workflows/superlinter.yml b/.github/workflows/superlinter.yml new file mode 100644 index 0000000..96ce0cd --- /dev/null +++ b/.github/workflows/superlinter.yml @@ -0,0 +1,25 @@ +name: Super-Linter + +# Run this workflow every time a new commit pushed to your repository +on: push + +jobs: + # Set the job key. The key is displayed as the job name + # when a job name is not provided + super-lint: + # Name the Job + name: Lint code base + # Set the type of machine to run on + runs-on: ubuntu-latest + + steps: + # Checks out a copy of your repository on the ubuntu-latest machine + - name: Checkout code + uses: actions/checkout@v2 + + # Runs the Super-Linter action + - name: Run Super-Linter + uses: github/super-linter@v3 + env: + DEFAULT_BRANCH: main + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.gitignore b/.gitignore index 7785f3c..891f0f9 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,8 @@ site/*.html site/*.pdf site/clojure +site/extra +site/meta +site/js/compiled +.cpcache +*.log diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index a75d03c..0000000 --- a/.travis.yml +++ /dev/null @@ -1,25 +0,0 @@ -sudo: required - -services: - - docker - -before_install: - - docker pull asciidoctor/docker-asciidoctor - -script: - - docker run -v $TRAVIS_BUILD_DIR:/documents/ --name asciidoc-to-html asciidoctor/docker-asciidoctor /documents/build.sh - -after_error: - - docker logs asciidoc-to-html - -after_failure: - - docker logs asciidoc-to-html - -deploy: - provider: pages - local-dir: site - target-branch: gh-pages - skip-cleanup: true - github-token: $GITHUB_TOKEN - on: - branch: master diff --git a/Gemfile b/Gemfile index 0bc1973..f90a2b3 100644 --- a/Gemfile +++ b/Gemfile @@ -6,3 +6,4 @@ gem 'concurrent-ruby' gem 'asciidoctor' gem 'asciidoctor-revealjs' gem 'asciidoctor-pdf' +gem 'asciidoctor-mathematical' diff --git a/Gemfile.lock b/Gemfile.lock index 8b511a4..bd96879 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,65 +1,80 @@ GEM remote: https://rubygems.org/ specs: - Ascii85 (1.0.3) - addressable (2.5.2) - public_suffix (>= 2.0.2, < 4.0) + Ascii85 (1.1.0) + addressable (2.8.1) + public_suffix (>= 2.0.2, < 6.0) afm (0.2.2) - asciidoctor (1.5.8) - asciidoctor-pdf (1.5.0.alpha.16) - asciidoctor (>= 1.5.0) - prawn (>= 1.3.0, < 2.3.0) - prawn-icon (= 1.3.0) - prawn-svg (>= 0.21.0, < 0.28.0) - prawn-table (= 0.2.2) - prawn-templates (>= 0.0.3, <= 0.1.1) - safe_yaml (~> 1.0.4) - thread_safe (~> 0.3.6) - treetop (= 1.5.3) - asciidoctor-revealjs (1.1.3) - asciidoctor (~> 1.5.6) + asciidoctor (2.0.17) + asciidoctor-mathematical (0.3.5) + asciidoctor (~> 2.0) + asciimath (~> 2.0) + mathematical (~> 1.6.0) + asciidoctor-pdf (2.3.0) + asciidoctor (~> 2.0) + concurrent-ruby (~> 1.1) + matrix (~> 0.4) + prawn (~> 2.4.0) + prawn-icon (~> 3.0.0) + prawn-svg (~> 0.32.0) + prawn-table (~> 0.2.0) + prawn-templates (~> 0.1.0) + treetop (~> 1.6.0) + asciidoctor-revealjs (4.1.0) + asciidoctor (>= 2.0.0, < 3.0.0) + concurrent-ruby (~> 1.0) thread_safe (~> 0.3.5) - concurrent-ruby (1.0.5) - css_parser (1.6.0) + asciimath (2.0.4) + concurrent-ruby (1.1.10) + css_parser (1.11.0) addressable hashery (2.1.2) - pdf-core (0.7.0) - pdf-reader (2.1.0) - Ascii85 (~> 1.0.0) + i18n (1.12.0) + concurrent-ruby (~> 1.0) + mathematical (1.6.14) + ruby-enum (~> 0.4) + matrix (0.4.2) + pdf-core (0.9.0) + pdf-reader (2.10.0) + Ascii85 (~> 1.0) afm (~> 0.2.1) hashery (~> 2.0) ruby-rc4 ttfunk polyglot (0.3.5) - prawn (2.2.2) - pdf-core (~> 0.7.0) - ttfunk (~> 1.5) - prawn-icon (1.3.0) + prawn (2.4.0) + pdf-core (~> 0.9.0) + ttfunk (~> 1.7) + prawn-icon (3.0.0) prawn (>= 1.1.0, < 3.0.0) - prawn-svg (0.27.1) - css_parser (~> 1.3) + prawn-svg (0.32.0) + css_parser (~> 1.6) prawn (>= 0.11.1, < 3) + rexml (~> 3.2) prawn-table (0.2.2) prawn (>= 1.3.0, < 3.0.0) - prawn-templates (0.1.1) + prawn-templates (0.1.2) pdf-reader (~> 2.0) prawn (~> 2.2) - public_suffix (3.0.3) + public_suffix (5.0.0) + rexml (3.2.5) + ruby-enum (0.9.0) + i18n ruby-rc4 (0.1.5) - safe_yaml (1.0.4) thread_safe (0.3.6) - treetop (1.5.3) + treetop (1.6.11) polyglot (~> 0.3) - ttfunk (1.5.1) + ttfunk (1.7.0) PLATFORMS ruby DEPENDENCIES asciidoctor + asciidoctor-mathematical asciidoctor-pdf asciidoctor-revealjs concurrent-ruby BUNDLED WITH - 1.16.1 + 2.2.3 diff --git a/Makefile b/Makefile index bd98624..2f3cde0 100644 --- a/Makefile +++ b/Makefile @@ -1,27 +1,53 @@ SOURCES := $(wildcard docs/*.adoc) -HTML := $(patsubst docs/%.adoc, site/%.html, $(SOURCES)) -PDF := $(patsubst docs/%.adoc, site/%.pdf, $(SOURCES)) -CLOJURE_COURSE_SOURCES := $(wildcard docs/clojure/*.adoc) -CLOJURE_COURSE := $(patsubst docs/clojure/%.adoc, site/clojure/%.html, $(CLOJURE_COURSE_SOURCES)) +COURSE_SOURCES := $(wildcard docs/*/*.adoc) +ALL_SOURCES := $(SOURCES) $(COURSE_SOURCES) +HTML := $(patsubst docs/%.adoc, site/%.html, $(ALL_SOURCES)) +PDF := $(patsubst docs/%.adoc, site/%.pdf, $(ALL_SOURCES)) +ARTICLES := $(patsubst docs/%.adoc, site/%.html, $(COURSE_SOURCES)) +SLIDES := $(patsubst docs/%.adoc, site/%.slides.html, $(COURSE_SOURCES)) +ALL := $(HTML) $(PDF) $(ARTICLES) $(SLIDES) -.PHONY: all clean setup +# Tasks -all: setup $(HTML) $(PDF) $(CLOJURE_COURSE) +.PHONY: all clean setup deploy lint + +all: setup $(ALL) # site/js/compiled/main.js clean: - rm site/*.html site/*.pdf site/clojure/*.html + rm -fr site/*.html site/*.pdf site/*/*.html site/js/compiled site/img/stem* + +setup: .bundle site/reveal.js + +deploy: all + ./deploy.sh + +lint: + docker run -e RUN_LOCAL=true -v `pwd`:/tmp/lint github/super-linter -setup: .bundle +## Asciidoctor dependencies -.bundle: +.bundle: Gemfile bundle --path=.bundle/gems --binstubs=.bundle/.bin -site/clojure/%.html: docs/clojure/%.adoc - bundle exec asciidoctor-revealjs --destination-dir=site/clojure $< +site/reveal.js: + wget https://github.com/hakimel/reveal.js/archive/master.zip && unzip master.zip && mv reveal.js-master site/reveal.js && rm master.zip + +# Rules + +## Slides +site/%.slides.html: docs/%.adoc docs/docinfo.attrs .bundle + bundle exec asciidoctor-revealjs --out-file $@ $< + +## HTML +site/%.html: docs/%.adoc docs/docinfo.attrs .bundle + bundle exec asciidoctor --out-file $@ $< -site/%.html: docs/%.adoc - bundle exec asciidoctor --destination-dir=site $< +## PDF +site/%.pdf: docs/%.adoc docs/docinfo.attrs .bundle + bundle exec asciidoctor-pdf -r asciidoctor-mathematical --out-file $@ $< -site/%.pdf: docs/%.adoc - bundle exec asciidoctor-pdf --destination-dir=site $< +#site/js/compiled/main.js: deps.edn cljs.edn src/enterprise_clojure_training/main.cljs +# clojure -M --main cljs.main --compile-opts cljs.edn --compile +pdfslides: + docker run --rm -t -v `pwd`:/slides -v ~:/home/user astefanutti/decktape --size 1366x784 site/extra/term-rewriting-with-meander-examples.slides.html slides.pdf diff --git a/README.adoc b/README.adoc index 9a47d0e..6f2d276 100644 --- a/README.adoc +++ b/README.adoc @@ -45,23 +45,11 @@ The Web (Epiphany) browser reloads from disk when the HTML file changes. The easiest way to build is to run - ./bb.sh + make This script relies on bundler (See The Ruby way for more info). It will install dependencies and build the documents into html/pdf. - -=== With installed CLI tools. - -You can install asciidoctor, asciidoctor-pdf, and ascidoctor-revealjs locally and run - - ./build.sh - -But installing these tools is a bit of a pain... so two ways to get the dependencies are provided: - - -==== The Ruby way - Requires https://www.ruby-lang.org/en/documentation/installation[Ruby]. You don't need to install Asciidoctor. @@ -72,16 +60,6 @@ Requires Bundler: gem install bundler -Pull the dependencies: - - bundle --path=.bundle/gems --binstubs=.bundle/.bin - -Now you can call the build script: - - bundle exec ./build.sh - -The `./bb.sh` just does these two steps for you. - === Rebuild on file modified @@ -98,18 +76,8 @@ Or apt install entr -==== The Docker way - -Alternatively, if you prefer pulling a Docker image over installing dependencies: - - docker run -v $(pwd):/documents/ asciidoctor/docker-asciidoctor /documents/build.sh - - == Deploying -TravisCI automatically deploys the latest version to https://timothypratley.github.io/enterprise-clojure-training[GitHub Pages]. -image:https://travis-ci.org/timothypratley/enterprise-clojure-training.svg?branch=master[Build Status, link=https://travis-ci.org/timothypratley/enterprise-clojure-training] - Manual deployment: ./deploy.sh diff --git a/bb.sh b/bb.sh deleted file mode 100755 index ee08df4..0000000 --- a/bb.sh +++ /dev/null @@ -1,10 +0,0 @@ -#!/bin/sh -set -e - -# get dependencies -if [ ! -d .bundle ]; then - bundle --path=.bundle/gems --binstubs=.bundle/.bin -fi - -# build with dependencies -bundle exec ./build.sh diff --git a/build.sh b/build.sh deleted file mode 100755 index 14c02dd..0000000 --- a/build.sh +++ /dev/null @@ -1,8 +0,0 @@ -#!/bin/sh -set -e -FLAGS=--destination-dir=site - -asciidoctor ${FLAGS} docs/index.adoc docs/manual.adoc docs/getting-help.adoc docs/setup.adoc -asciidoctor-pdf ${FLAGS} docs/manual.adoc -asciidoctor-revealjs ${FLAGS} docs/advanced-topics.adoc docs/advanced-topics.adoc docs/talk.adoc -asciidoctor-revealjs --destination-dir=site/clojure docs/clojure/*.adoc diff --git a/cljs.edn b/cljs.edn new file mode 100644 index 0000000..a02f486 --- /dev/null +++ b/cljs.edn @@ -0,0 +1,5 @@ +{:main "enterprise-clojure-training.main" + :output-dir "site/js/compiled" + :output-to "site/js/compiled/main.js" + :asset-path "js/compiled" + :optimizations :none} diff --git a/deploy.sh b/deploy.sh index 76b1081..aafdbaf 100755 --- a/deploy.sh +++ b/deploy.sh @@ -1,6 +1,6 @@ #!/bin/bash set -e -cd $(dirname $0) +cd "$(dirname "$0")" ./build.sh cd site git init diff --git a/deps.edn b/deps.edn new file mode 100644 index 0000000..0eed905 --- /dev/null +++ b/deps.edn @@ -0,0 +1,3 @@ +{:deps {org.clojure/clojurescript {:mvn/version "1.10.758"} + viebel/klipse {:mvn/version "7.10.4"} + meander/epsilon {:mvn/version "0.0.512"}}} diff --git a/docs/clojure/0-introduction.adoc b/docs/clojure/0-introduction.adoc index 17624c2..97b00e9 100644 --- a/docs/clojure/0-introduction.adoc +++ b/docs/clojure/0-introduction.adoc @@ -1,13 +1,13 @@ = Enterprise Clojure Training :copyright: Timothy Pratley :license: Eclipse Public License http://www.eclipse.org/legal/epl-v10.html -include::slide.attrs[] +include::../docinfo.attrs[] [state=title] == Enterprise Clojure Training -image:../img/corgi.jpg[] +image:corgi.jpg[] [state=title] @@ -37,12 +37,12 @@ image:../img/corgi.jpg[] [state=title] == Introductions -image:../img/art1.jpg[] +image:art1.jpg[] [state=title] == Clojure -image:../img/clojure-logo.png[] +image:clojure-logo.png[] "What language is it that meets all the criteria? What language would I choose if I had to choose today? Probably Clojure!" -- Robert Martin diff --git a/docs/clojure/1-ecosystem.adoc b/docs/clojure/1-ecosystem.adoc index bbe95fd..cdbb56a 100644 --- a/docs/clojure/1-ecosystem.adoc +++ b/docs/clojure/1-ecosystem.adoc @@ -1,13 +1,13 @@ = Enterprise Clojure Training :copyright: Timothy Pratley :license: Eclipse Public License http://www.eclipse.org/legal/epl-v10.html -include::slide.attrs[] +include::../docinfo.attrs[] [state=title] == 1. The Clojure Ecosystem -image:../img/ecosystem.jpg[] +image:ecosystem.jpg[] "Integrity is an ecosystem." -- Michael Leunig diff --git a/docs/clojure/10-databases.adoc b/docs/clojure/10-databases.adoc index 0a45b0b..94722fd 100644 --- a/docs/clojure/10-databases.adoc +++ b/docs/clojure/10-databases.adoc @@ -1,13 +1,13 @@ = Enterprise Clojure Training :copyright: Timothy Pratley :license: Eclipse Public License http://www.eclipse.org/legal/epl-v10.html -include::slide.attrs[] +include::../docinfo.attrs[] [state=title] == 10. Interacting with a Database -image:../img/database.jpg[] +image:database.jpg[] "You can have data without information, but you cannot have information without data." -- Daniel Keys Moran diff --git a/docs/clojure/11-spec.adoc b/docs/clojure/11-spec.adoc index a85a910..1c4321d 100644 --- a/docs/clojure/11-spec.adoc +++ b/docs/clojure/11-spec.adoc @@ -1,12 +1,12 @@ = Enterprise Clojure Training :copyright: Timothy Pratley :license: Eclipse Public License http://www.eclipse.org/legal/epl-v10.html -include::slide.attrs[] +include::../docinfo.attrs[] == 11. Spec -image:../img/spec.jpg[] +image:spec.jpg[] "Much of the essence of building a program is in fact the debugging of the specification." -- Fred Brooks diff --git a/docs/clojure/12-macros.adoc b/docs/clojure/12-macros.adoc index a9e4d2a..3a28565 100644 --- a/docs/clojure/12-macros.adoc +++ b/docs/clojure/12-macros.adoc @@ -1,13 +1,13 @@ = Enterprise Clojure Training :copyright: Timothy Pratley :license: Eclipse Public License http://www.eclipse.org/legal/epl-v10.html -include::slide.attrs[] +include::../docinfo.attrs[] [state=title] == 12. Macros -image:../img/macros.jpg[] +image:macros.jpg[] "I never think about myself as an artist working in this time. I think about it in macro." -- Frank Ocean diff --git a/docs/clojure/13-further-reading.adoc b/docs/clojure/13-further-reading.adoc index fc6b996..cb35ac3 100644 --- a/docs/clojure/13-further-reading.adoc +++ b/docs/clojure/13-further-reading.adoc @@ -1,13 +1,13 @@ = Enterprise Clojure Training :copyright: Timothy Pratley :license: Eclipse Public License http://www.eclipse.org/legal/epl-v10.html -include::slide.attrs[] +include::../docinfo.attrs[] [state=title] == 13. Further reading -image:../img/further-reading.jpg[] +image:further-reading.jpg[] "You can never get a cup of tea large enough or a book long enough to suit me." -- C.S. Lewis @@ -56,7 +56,7 @@ Clojure for Java Programmers - Rich Hickey * Build bottom up * Start with data * Create functions to operated on that data -* Eastwood can give hints on better expressions +* Linters clj-kondo and eastwood give hints * Read source code (Clojure, libraries, open source projects) @@ -124,8 +124,8 @@ Produces suggestions on how to write idiomatic Clojure == Get help * REPL can answer most questions -* StackOverflow.com #clojure * Create minimal examples +* StackOverflow.com #clojure * Clojure mailing list https://groups.google.com/forum/#!forum/clojure * ClojureVerse https://clojureverse.org * Clojurians Slack http://clojurians.net diff --git a/docs/clojure/2-syntax.adoc b/docs/clojure/2-syntax.adoc index f9632dd..adfe8d7 100644 --- a/docs/clojure/2-syntax.adoc +++ b/docs/clojure/2-syntax.adoc @@ -1,13 +1,13 @@ = Enterprise Clojure Training :copyright: Timothy Pratley :license: Eclipse Public License http://www.eclipse.org/legal/epl-v10.html -include::slide.attrs[] +include::../docinfo.attrs[] [state=title] == 2. Clojure Syntax -image:../img/syntax.png[] +image:syntax.png[] "If the syntax is good enough for the information, it should be good enough for the meta-information." -- Erik Naggum diff --git a/docs/clojure/3-functions.adoc b/docs/clojure/3-functions.adoc index 6f90b67..7f6db3c 100644 --- a/docs/clojure/3-functions.adoc +++ b/docs/clojure/3-functions.adoc @@ -1,13 +1,13 @@ = Enterprise Clojure Training :copyright: Timothy Pratley :license: Eclipse Public License http://www.eclipse.org/legal/epl-v10.html -include::slide.attrs[] +include::../docinfo.attrs[] [state=title] == 3. Functions -image:../img/functions.jpg[] +image:functions.jpg[] "The chief function of the body is to carry the brain around." -- Thomas A. Edison diff --git a/docs/clojure/4-testing.adoc b/docs/clojure/4-testing.adoc index 278b72b..8060576 100644 --- a/docs/clojure/4-testing.adoc +++ b/docs/clojure/4-testing.adoc @@ -1,13 +1,13 @@ = Enterprise Clojure Training :copyright: Timothy Pratley :license: Eclipse Public License http://www.eclipse.org/legal/epl-v10.html -include::slide.attrs[] +include::../docinfo.attrs[] [state=title] == 4. Testing with clojure.test -image:../img/testing.jpg[] +image:testing.jpg[] "The problem is not that testing is the bottleneck. The problem is that you don’t know what’s in the bottle." -- Michael Bolton diff --git a/docs/clojure/5-control-flow.adoc b/docs/clojure/5-control-flow.adoc index cabb86a..01cbf46 100644 --- a/docs/clojure/5-control-flow.adoc +++ b/docs/clojure/5-control-flow.adoc @@ -1,13 +1,13 @@ = Enterprise Clojure Training :copyright: Timothy Pratley :license: Eclipse Public License http://www.eclipse.org/legal/epl-v10.html -include::slide.attrs[] +include::../docinfo.attrs[] [state=title] == 5. Control Flow -image:../img/control.jpg[] +image:control.jpg[] "Control your own destiny or someone else will." -- Jack Welch diff --git a/docs/clojure/6-functional-programming.adoc b/docs/clojure/6-functional-programming.adoc index b543461..b9ef7b8 100644 --- a/docs/clojure/6-functional-programming.adoc +++ b/docs/clojure/6-functional-programming.adoc @@ -1,13 +1,13 @@ = Enterprise Clojure Training :copyright: Timothy Pratley :license: Eclipse Public License http://www.eclipse.org/legal/epl-v10.html -include::slide.attrs[] +include::../docinfo.attrs[] [state=title] == 6. Functional Programming -image:../img/functional.jpg[] +image:functional.jpg[] "If you don't love something, it's not functional, in my opinion." -- Yves Behar diff --git a/docs/clojure/7-java-interop.adoc b/docs/clojure/7-java-interop.adoc index 41f11af..08b994d 100644 --- a/docs/clojure/7-java-interop.adoc +++ b/docs/clojure/7-java-interop.adoc @@ -1,13 +1,13 @@ = Enterprise Clojure Training :copyright: Timothy Pratley :license: Eclipse Public License http://www.eclipse.org/legal/epl-v10.html -include::slide.attrs[] +include::../docinfo.attrs[] [state=title] == 7. Java Interop -image:../img/interop.jpg[] +image:interop.jpg[] "Sitting in my favorite coffeehouse with a new notebook and a hot cup of java is my idea of Heaven." -- Libba Bray diff --git a/docs/clojure/8-concurrency.adoc b/docs/clojure/8-concurrency.adoc index 6d467f9..f33c910 100644 --- a/docs/clojure/8-concurrency.adoc +++ b/docs/clojure/8-concurrency.adoc @@ -1,13 +1,13 @@ = Enterprise Clojure Training :copyright: Timothy Pratley :license: Eclipse Public License http://www.eclipse.org/legal/epl-v10.html -include::slide.attrs[] +include::../docinfo.attrs[] [state=title] == 8. Parallel Programming and Concurrency -image:../img/parallel.jpg[] +image:parallel.jpg[] "Our moral traditions developed concurrently with our reason, not as its product." -- Friedrich August von Hayek diff --git a/docs/clojure/9-polymorphism.adoc b/docs/clojure/9-polymorphism.adoc index 81fd9e3..c295071 100644 --- a/docs/clojure/9-polymorphism.adoc +++ b/docs/clojure/9-polymorphism.adoc @@ -1,13 +1,13 @@ = Enterprise Clojure Training :copyright: Timothy Pratley :license: Eclipse Public License http://www.eclipse.org/legal/epl-v10.html -include::slide.attrs[] +include::../docinfo.attrs[] [state=title] == 9. Polymorphism and Types -image:../img/types.jpg[] +image:types.jpg[] "You need a lot of different types of people to make the world better." -- Joe Louis diff --git a/docs/clojure/docinfo.html b/docs/clojure/docinfo.html index 68f53b7..9846e5e 100644 --- a/docs/clojure/docinfo.html +++ b/docs/clojure/docinfo.html @@ -1 +1 @@ - + diff --git a/docs/docinfo-footer.html b/docs/docinfo-footer.html index f50dabb..7b9badd 100644 --- a/docs/docinfo-footer.html +++ b/docs/docinfo-footer.html @@ -4,4 +4,4 @@ selector: '.eval-clojure'// css selector for the html elements you want to klipsify }; - + diff --git a/docs/clojure/slide.attrs b/docs/docinfo.attrs similarity index 77% rename from docs/clojure/slide.attrs rename to docs/docinfo.attrs index 3a4b07f..1d5c37b 100644 --- a/docs/clojure/slide.attrs +++ b/docs/docinfo.attrs @@ -1,4 +1,7 @@ -:customcss: ../slides.css +:imagesdir: ../img +:customcss: ../enterprise-clojure-training.css +:docinfo: shared +:icons: font :revealjs_theme: simple :revealjs_center: false :revealjs_controls: false @@ -7,6 +10,3 @@ :revealjs_minScale: 1.0 :revealjs_maxScale: 1.0 :revealjsdir: ../reveal.js -:docinfo: shared -:notitle: -:icons: font diff --git a/docs/advanced-topics.adoc b/docs/extra/advanced-topics.adoc similarity index 75% rename from docs/advanced-topics.adoc rename to docs/extra/advanced-topics.adoc index 317b927..18852f6 100644 --- a/docs/advanced-topics.adoc +++ b/docs/extra/advanced-topics.adoc @@ -1,43 +1,44 @@ = Advanced Topics :copyright: Timothy Pratley :license: Eclipse Public License http://www.eclipse.org/legal/epl-v10.html -:customcss: slides.css -:revealjsdir: https://cdnjs.cloudflare.com/ajax/libs/reveal.js/3.6.0 -:revealjs_theme: simple -:revealjs_center: false -:revealjs_controls: false -:revealjs_transition: none -:docinfo: shared - +include::../docinfo.attrs[] == Topics to be covered 1. Use case: How to define mass data structure using spec and to get the data from that using “conform”. - a. Need sample use case and accustomed project with that. - b. Require more details on why we need this spec and what is the business advantage? +a. Need sample use case and accustomed project with that. +b. Require more details on why we need this spec and what is the business advantage? -2. Use case: To mock test data for resources on validating the specs as a whole data structure. (A sample project or reference available on internet is also required) +2. Use case: To mock test data for resources on validating the specs as a whole data structure. +(A sample project or reference available on internet is also required) -3. How to get the project built using Jenkins or any hosting tools. We would have a demo to do that with pipeline or docker plugin. +3. How to get the project built using Jenkins or any hosting tools. +We would have a demo to do that with pipeline or docker plugin. -4. How to call web services from clojure & to hop around the different environments? How are we going to handle/pass the security configuration in between the layers? +4. How to call web services from clojure & to hop around the different environments? +How are we going to handle/pass the security configuration in between the layers? -5. We use “:gen-class” for all the namespace definition. Is this mandatory for running the application as uber jar? What is the purpose of including this :gen-class? +5. We use “:gen-class” for all the namespace definition. +Is this mandatory for running the application as uber jar? +What is the purpose of including this :gen-class? 6. More examples (different usage scenarios) and elaboration required for Macros. 7. Use case: Require to know Clojure with Hadoop interaction for data enrichment with clojure specs. -8. Does clojure provide any utility to attach a file and send an email? Configuring logs on masking the sensitive data. +8. Does clojure provide any utility to attach a file and send an email? +Configuring logs on masking the sensitive data. 9. How to manage real time dependency management between projects 10. Any industry design patterns for clojure? -11. What is Profile.clj capable of doing? Is it like getting the jar from repo, defining plugins, is that all? Or do we have more to explore. (need more clarity and reference links) - -12. What is Project.clj capable of doing to the project level and where does this scope live? Unlike the above one, require reference materials. +11. What is Profile.clj capable of doing? +Is it like getting the jar from repo, defining plugins, is that all? +Or do we have more to explore. (need more clarity and reference links) +12. What is Project.clj capable of doing to the project level and where does this scope live? +Unlike the above one, require reference materials. == 1) Use case: Parsing data with Spec @@ -49,33 +50,27 @@ Insurance company "Megacorp" offers three pet insurance policies: Can spec help us process applications for these policies? - == a. Defining specs for data structures * See examples/parsing-with-spec - == b. Validating data -(valid?) -expound - +(valid?) expound == c. Extracting data using `conform` * When there are multiple options to match against - == Spec rationale * It is a guiding principle of Clojure to represent information as data (not objects) * Important properties of Clojure systems are represented and conveyed by the shape of the data - - runtime types are maps, vectors, sets - - shape is not captured or checked anywhere +- runtime types are maps, vectors, sets +- shape is not captured or checked anywhere * Manual parsing and error reporting is not good enough * Defining specifications is optional - == Spec enables automatic * Validation @@ -85,28 +80,25 @@ expound * Test-data generation * Generative test generation - == Why not spec? * It is new * Specs are code - - however code is data and you can create specs on the fly +- however code is data and you can create specs on the fly * You can do without it - - however manual approaches become difficult as complexity rises - +- however manual approaches become difficult as complexity rises == Why spec? * Specifying the shape of inputs is hard - - good error messages are hard for complex requirements - - coming up with test data can be tedious - - testing often involves reimplementation +- good error messages are hard for complex requirements +- coming up with test data can be tedious +- testing often involves reimplementation * Additional tooling - - Expound (nice error messages) - - Data generators - - test.check - +- Expound (nice error messages) +- Data generators +- test.check == d. What is the business advantage of spec? @@ -114,8 +106,7 @@ expound * Implement parsing quickly * Deliver correct, well tested solutions * Use the same technology in code and data interfaces - - learn once, apply in many contexts - +- learn once, apply in many contexts == 2) Mock test data @@ -125,18 +116,16 @@ see examples/parsing-with-spec/test/parsing-with-spec/generated-test (stest/check `my-function) - == 3) Continuous Integration * Building projects with Jenkins - - See examples/jenkins-build-server +- See examples/jenkins-build-server === Overview * How Clojure projects are built and executed locally * How to set up a build server to automate test/build/deploy - == Building and Executing Clojure projects lein run @@ -151,13 +140,11 @@ see examples/parsing-with-spec/test/parsing-with-spec/generated-test lein deploy - == lein run * Easy to execute * May need to pull dependencies - == lein uberjar * Your application and all dependencies in a single JAR file @@ -166,7 +153,6 @@ see examples/parsing-with-spec/test/parsing-with-spec/generated-test * Versioned * Preservable - == java -jar myapp.jar * Executes an uberjar @@ -183,7 +169,6 @@ src/myapp/core.clj java -jar myapp.jar -m myapp.core/-main - == lein ring uberjar Sets up a main entry point to start the webservice @@ -194,53 +179,52 @@ Equivalent to (defn -main [& args] (run-jetty handler {:port 3000})) - == lein install * Builds an uberjar and puts it in your local Maven repository `~/.m2` * Useful for testing library snapshots and building from source * Does not publish your artifact - == lein deploy * Publishes your artifact to a repository * Repositories can be - - public (Clojars, Maven Central) - - private (Hosted/S3/Self managed) +- public (Clojars, Maven Central) +- private (Hosted/S3/Self managed) * https://github.com/technomancy/leiningen/blob/master/doc/DEPLOY.md * Easy... if you have a repository... * Not the same as deploying your application! - == Where to publish artifacts to? * Amazon S3 is a low-maintenance choice: - - https://github.com/s3-wagon-private/s3-wagon-private +- https://github.com/s3-wagon-private/s3-wagon-private * Artifactory/Nexus/Archiva * Deps: https://www.deps.co/ * Remeber to include a `repositories` section in your project.clj - - To publish to a private repository - - To pull dependencies from a private repository +- To publish to a private repository +- To pull dependencies from a private repository :repositories [["private" {:url "s3p://mybucket/releases/" :no-auth true}]] - == Deploying a Clojure application * Build an uberjar (or Docker container) * Get the artifact to the host server * Run it - == Implementation details are driven by architecture -* Tomcat? Drop a WAR in a folder -* AWS Elastic Beanstalk? Roll out a new docker container -* AWS Lambda? Upload a new JAR -* Kubernetes? Roll out a new docker container -* Heroku? Deploy from git - +* Tomcat? +Drop a WAR in a folder +* AWS Elastic Beanstalk? +Roll out a new docker container +* AWS Lambda? +Upload a new JAR +* Kubernetes? +Roll out a new docker container +* Heroku? +Deploy from git == Creating a build server overview @@ -248,7 +232,6 @@ Equivalent to * Install Leiningen * Add a build - == What is Docker? * Virtualization @@ -256,7 +239,6 @@ Equivalent to * Building starts with an image and runs the setup tasks, creates a container * Run a container and it behaves like a stand alone computer - == Handy Docker commands * `docker build -t .` to create a container from a Dockerfile @@ -265,67 +247,59 @@ Equivalent to * `docker exec -ti bash` to get a shell in a running container * `docker stop ` to stop it - == Creating a Jenkins server inside a Docker container * Install docker * Create a Dockerfile to extend the base Jenkins image - - see examples/jenkins-build-server/Dockerfile - - https://github.com/jenkinsci/docker/blob/master/README.md - - add steps to install Leiningen +- see examples/jenkins-build-server/Dockerfile +- https://github.com/jenkinsci/docker/blob/master/README.md +- add steps to install Leiningen * Create a Makefile or similar to automate tasks - - see examples/jenkins-build-server/Makefile - - `make run` to start the server +- see examples/jenkins-build-server/Makefile +- `make run` to start the server * Open the UI: http://localhost:8080/ * Enter password from console log * Install suggested plugins * Create admin user - == What to build? * An uberjar * Docker image - == Set up Job * Choose Freestyle project * Configure source code management - - parsing-with-spec is a subdirectory of the enterprise-clojure repository - - `sparse checkout path` only gets a subdirectory +- parsing-with-spec is a subdirectory of the enterprise-clojure repository +- `sparse checkout path` only gets a subdirectory * Under "Build", add build steps, shell command - - `cd examples/parsing-with-spec && lein test` - - `cd examples/parsing-with-spec && lein install` - - or docker build - - don't need to change directory if project is in root +- `cd examples/parsing-with-spec && lein test` +- `cd examples/parsing-with-spec && lein install` +- or docker build +- don't need to change directory if project is in root * Save * Build now * Check the console logs - - tests passed - - jar created - +- tests passed +- jar created == Versioning - == Running the built artifacts * `java -jar myapp.jar` * Use environment variables to behave differently * Can extend a prebuilt docker image (or roll your own) - == Building multiple services * Set up jobs for each project - == Set up another triggered Job to deploy to CI * If the build succeeds, deploy - == 4) Clojure services === a. Creating a webservice @@ -338,18 +312,16 @@ Equivalent to * Change code * curl again - == What is Ring? * Ring is a library that abstracts the details of HTTP into a unified API - - modular components +- modular components * Handlers - functions that take requests and return responses * Request - map of data about the request (params, body, etc) * Response - map containing status, headers, body * Middleware - the mechanism for modular components, higher order functions - == Middleware pattern Middleware are functions that return functions: @@ -363,10 +335,9 @@ Middleware are functions that return functions: * Takes a handler as input * The function returned - - can modify the request before passing it to the handler - - can modify the result from the handler before returning it - - is itself a handler - +- can modify the request before passing it to the handler +- can modify the result from the handler before returning it +- is itself a handler == Middleware @@ -381,20 +352,18 @@ Middleware are functions that return functions: (wrap-params))) * Middleware pattern - - https://github.com/ring-clojure/ring/wiki/Concepts - - https://github.com/ring-clojure/ring/wiki/Middleware-Patterns - +- https://github.com/ring-clojure/ring/wiki/Concepts +- https://github.com/ring-clojure/ring/wiki/Middleware-Patterns == What is Compojure? * A routing library - - http://megacorp.com/insurance-policy/corgi-cover - - `/insurance-policy/corgi-cover` is a route - - `/insurance-policy` is fixed root - - `/corgi-cover` is a policy ID - - could be `poodle-protection` or `poodle-protection-platinum` - - `(GET "/insurance-policy/:id" [id] (fetch-policy id))` - +- http://megacorp.com/insurance-policy/corgi-cover +- `/insurance-policy/corgi-cover` is a route +- `/insurance-policy` is fixed root +- `/corgi-cover` is a policy ID +- could be `poodle-protection` or `poodle-protection-platinum` +- `(GET "/insurance-policy/:id" [id] (fetch-policy id))` == Useful extensions @@ -402,7 +371,6 @@ Middleware are functions that return functions: * Public API creation: https://clojure-liberator.github.io/liberator/ * GraphQL: https://github.com/walmartlabs/lacinia - == Making HTTP requests * clj-http https://github.com/dakrone/clj-http @@ -413,12 +381,10 @@ Middleware are functions that return functions: (ns myns (:require [clj-http.client :as client])) (client/get (str "http://customer-data-service:3000/customer/" id)) - == Multiple services See examples/communicating-services - == Managing multiple services with docker-compose See examples/communicating-services/docker-compose.yml @@ -427,16 +393,14 @@ See examples/communicating-services/docker-compose.yml * Deploying * Reloading code while developing multiple services - == Security and configuration * Follow the Twelve-Factor App https://12factor.net/ - - Store configuration in the environment +- Store configuration in the environment * Don't store configuration in source control * Don't expose endpoints publicly * Authentication/authorization for public endpoints: https://github.com/cemerick/friend - == 5) `:gen-class` What is the purpose of `:gen-class`? @@ -448,7 +412,6 @@ What is the purpose of `:gen-class`? * Avoid `gen-class` and AOT * But why is it used so often? - == Executable jars * An uberjar contains all your project dependencies. @@ -470,7 +433,6 @@ project.clj :main myns.core :aot [myns.core] - == AOT is about when compilation happens (ns my.app) @@ -481,7 +443,6 @@ Behaves differently if evaluated during AOT than during Runtime. * In AOT it is captured during the build prior to deployment. * At Runtime it is whatever is in the environment when the namespace is loaded. - == When would it help to AOT? * Shipping a binary without the source code @@ -489,19 +450,17 @@ Behaves differently if evaluated during AOT than during Runtime. * Generating classes loadable directly from Java for interop purposes (Hadoop) * Platforms such as Android do not support custom class loaders for running new bytecode at runtime. - == Is `:gen-class` mandatory? * No * See examples/aot/too-much-aot - - An example where aot is causing undesirable behavior +- An example where aot is causing undesirable behavior * See examples/aot/no-aot - - Clojure provides an entrypoint `java -cp myuber.jar clojure.main -m myns.core` - - Clojure compiles all code you load on-the-fly into JVM bytecode +- Clojure provides an entrypoint `java -cp myuber.jar clojure.main -m myns.core` +- Clojure compiles all code you load on-the-fly into JVM bytecode * See examples/aot/little-aot - - Avoid transitive aot by providing a bootstrap - - Produces an executable jar - +- Avoid transitive aot by providing a bootstrap +- Produces an executable jar == 6) More on Macros @@ -510,16 +469,14 @@ Examples (different usage scenarios) and elaboration required for Macros. * Many Clojure primitives such as `defn` are macros * Macros provide syntax but are not values * https://lispcast.com/when-to-use-a-macro/ - - rarely - - compile time - - unevaluated arguments - - inline code +- rarely +- compile time +- unevaluated arguments +- inline code * core.async - - macros over state machines to implement - Communicating Sequential Processes (CSP) - - provides a new syntax (go blocks) - - available as a library - +- macros over state machines to implement Communicating Sequential Processes (CSP) +- provides a new syntax (go blocks) +- available as a library == 7) Data enrichment @@ -527,7 +484,6 @@ Require to know Clojure with Hadoop interaction for data enrichment with clojure https://github.com/clojure-cookbook/clojure-cookbook/tree/master/09_distributed-computation - == 8) Sending emails https://github.com/drewr/postal @@ -548,13 +504,12 @@ https://github.com/drewr/postal You need a mail server. (AWS Simple Email Service works well https://aws.amazon.com/ses/) - == Configuring logs on masking the sensitive data. * Clojure.tools.logging - https://github.com/clojure/tools.logging +https://github.com/clojure/tools.logging * Timbre - https://github.com/ptaoussanis/timbre +https://github.com/ptaoussanis/timbre * log4j * logback @@ -564,8 +519,8 @@ You need a mail server. * Standard Java style configuration * "Bear Traps: the Logging Edition" - https://www.youtube.com/watch?v=jjuj-D4-hCE - https://github.com/AlexanderMann/unclogging +https://www.youtube.com/watch?v=jjuj-D4-hCE +https://github.com/AlexanderMann/unclogging == 9) Dependencies @@ -576,13 +531,11 @@ How to manage real time dependency management between projects * https://github.com/amperity/lein-monolith * https://github.com/ladderlife/loonie - == 10) Industry design patterns for Clojure * https://github.com/clojure-cookbook/clojure-cookbook * http://mishadoff.com/blog/clojure-design-patterns/ - == 11) Profile.clj What is Profile.clj capable of doing? @@ -600,7 +553,6 @@ Or do we have more to explore. (need more clarity and reference links) * https://github.com/technomancy/leiningen/blob/master/doc/PROFILES.md - == Common plugins for profiles.clj * lein-test-refresh @@ -612,7 +564,6 @@ Or do we have more to explore. (need more clarity and reference links) * Bikeshed * Alembic - == 12) Project.clj What is Project.clj capable of doing to the project? diff --git a/docs/extra/docinfo-footer.html b/docs/extra/docinfo-footer.html new file mode 100644 index 0000000..363c02b --- /dev/null +++ b/docs/extra/docinfo-footer.html @@ -0,0 +1,12 @@ + + + + \ No newline at end of file diff --git a/docs/extra/docinfo-header.html b/docs/extra/docinfo-header.html new file mode 100644 index 0000000..68f53b7 --- /dev/null +++ b/docs/extra/docinfo-header.html @@ -0,0 +1 @@ + diff --git a/docs/extra/docinfo.html b/docs/extra/docinfo.html new file mode 100644 index 0000000..9846e5e --- /dev/null +++ b/docs/extra/docinfo.html @@ -0,0 +1 @@ + diff --git a/docs/extra/stack-traces.adoc b/docs/extra/stack-traces.adoc new file mode 100644 index 0000000..b8ef0ad --- /dev/null +++ b/docs/extra/stack-traces.adoc @@ -0,0 +1,63 @@ += Stack traces +:copyright: Timothy Pratley +:license: Eclipse Public License http://www.eclipse.org/legal/epl-v10.html +include::../docinfo.attrs[] + +== Oh no + +``` +null pointer +... +... +... +... +... + +``` + +== Actually a little more complicated... + +Exceptions can have causes which are exceptions... +There is an exception stack and each of those have call stacks. + +== How are stack traces presented? + +* Many flavours! + +== Java default (and Clojure < 1.10.1) + +``` +Outter most exception + Innermost frame + Outter frame +Inner exception + Innermost frame + Outter frame +Innermost exception + Innermost frame + *My code* + Outter frame +``` + +== Clojure default as of 1.10.1 + +``` +{:cause Innermost frame of innermost exception + :via [Outtermost exception + Inner exception + Innermost exception] + :stacktrace} +``` + +Defaults to a file, but you can use a flag to have it go to console + +== Pretty `[io.aviso/pretty]` + +image::formatted-exception.png[Formatted exception] + +== What's good about stack traces? + +* Where did the code blow up? (in my code...) The innermost stacktrace, usually have to scan up a little to find the right resolution +* How did we get there? +The exception stack +* Helpful hints diff --git a/docs/extra/term-rewriting-with-meander-examples.adoc b/docs/extra/term-rewriting-with-meander-examples.adoc new file mode 100644 index 0000000..6b995bd --- /dev/null +++ b/docs/extra/term-rewriting-with-meander-examples.adoc @@ -0,0 +1,736 @@ += Term rewriting equations and patterns +:copyright: Timothy Pratley +:license: Eclipse Public License http://www.eclipse.org/legal/epl-v10.html +include::../docinfo.attrs[] +:stem: latexmath +:mathematical-format: png + +ifndef::backend-revealjs[] +[.normal] +For a programmer, functions are the fundamental form of expression. +Programmers write functions that transform inputs to outputs. + +[stem] +++++ +f(x) = x + 1 +++++ + +The inputs and outputs are complex data that represent something in the real world. +I think about the inputs and outputs as abstract data shapes. +I might start with a nested map that needs to be transformed into a sequence of sub-selections. +Data comes in one shape and goes out another. +Squares in, circles out. +endif::[] + +ifdef::backend-revealjs[] +== Introductions + +[horizontal] +Topic:: Data transformation using term rewriting +Speaker:: Timothy Pratley + +[NOTE.speaker] +-- +Please speak up with questions as we go. +-- + +endif::[] + +ifdef::backend-revealjs[== Programming] + +.The square-to-circle machine (TM) +image::fx.png[processing machine] + +ifndef::backend-revealjs[] +For a mathematician, _equations_ are the fundamental form of expression. +Equations define valid rules for transforming one expression into another. +Mathematicians solve problems through symbolic manipulation. +endif::[] + +ifdef::backend-revealjs[== Math] + +[latexmath] +++++ +\begin{align*} +(a + b)^2 &= (a + b)(a + b)\\ + &= aa + ab + ba + bb\\ + &= a^2 + 2ab + b^2 +\end{align*} +++++ + +ifndef::backend-revealjs[] +Functions and equations are both fine ways to specify transformations. +So why only use functions for programming? +What does it look like to program data transformation like equations instead of functions? +Today we will look at some examples of an equation base approach to specifying data transformations. +Before diving into the examples, let's first establish some definitions +endif::[] + +== Definitions + +[horizontal] +Term rewriting:: _Replacing terms with other terms_ +Term:: _A variable or function application:_ stem:[a], stem:[(a + b)], stem:[(a + b)^2] +Variable:: stem:[a,b] can be assigned a value +Function:: `add`, `multiply`, `square` +Rule:: _A pair of matching terms and substitution terms:_ stem:[(a + b)^2 \Rightarrow a^2 + 2ab + b^2] + +ifdef::backend-revealjs[] + +== Definitions continued + +[horizontal] +endif::[] +Matching:: stem:[(1 + 3)^2] _can be made equal to_ stem:[(a + b)^2] _when_ stem:[a=1,b=3] +Substitution:: _Creation of new terms from old ones_ stem:[(1 + 3)^2 \Rightarrow 1^2 + 2\cdot1\cdot3 + 2^2] + +[NOTE.speaker] +-- +Math equations are rules that strictly preserve equivalence. +Term rewriting is a more general concept that permits any transformation. +-- + +== Introducing Meander + +Meander is a term rewriting system packaged as a Clojure library for data transformation. + +[source,clojure] +---- +[meander/epsilon "0.0.650"] +---- + +[source,clojure] +---- +(ns meander.examples + (:require [meander.epsilon :as m])) +---- + +I prefer to call it a _structural pattern syntax_ for data transformation. + +=== Rewrite + +The `m/rewrite` macro is the swiss army knife of Meander. + +ifndef::backend-revealjs[] +Let's do our first transformation from a vector to a map: +endif::[] + +[latexmath] +++++ +[a, b, c, d] \Rightarrow \{a, d\} +++++ + +[source,clojure] +---- +; Meander! +(m/rewrite [:a 1 :c 2] ; input + [?a ?b ?c ?d] ; a match pattern + {?a ?d}) ; a substitution pattern + +;=> {:a 2 :c 1} ; output +---- + +* The _match pattern_ is a data literal with the shape of the input +* The _substitution pattern_ is a data literals with the shape of the output +* `?a` is called a _logic variable_ +* `_` is for ignoring unused terms + +[NOTE.speaker] +-- +`?x` is a valid symbol in Clojure. +Macros manipulate symbols rather than the values associated with those symbols. `m/rewrite` is a macro; it defines how to interpret the patterns. +I think of Meander as a syntax because I specify structure with data literals to pattern match and substitute. +Hence why I prefer to call it a "structural pattern syntax". +-- + +=== Logic + +[source,clojure] +---- +(m/rewrite [1 2] ; input + [?a ?a] ; a match pattern + {?a ?a}) ; a substitute pattern + +;=> nil +---- + +No match. + +`?a` cannot match both `1` and `2`. + +ifdef::backend-revealjs[] +=== Logic success +endif::[] + +[source,clojure] +---- +(m/rewrite [1 1] ; input + [?a ?a] ; a match + {?a ?a}) ; a substitution pattern + +;=> {1 1} +---- + +Matched `?a` with `1` + +[NOTE] +-- +Unification means resolving logical matches. +-- + +=== Repeating terms + +[source,clojure] +---- +(m/rewrite [:a 1 :c 2 :d 3] + [!a ...] + [!a ...]) + +;=> [:a 1 :c 2 :d 3] +---- + +`!a` is a memory variable: _an array that can collect many values_ + +`...` indicates 0 or more repeated occurrences + +[NOTE.speaker] +-- +The match pattern is symmetrical with the substitution pattern. +We got out exactly what we put in. +-- + +=== Repeat scope + +[source,clojure] +---- +(m/rewrite [1 :a 2 :b 3 :c] + [!x !y ...] ; match pairs + [!y !x ...]) ; substitute flipped pairs + +;=> [:a 1 :b 2 :c 3] +---- + +All terms preceeding `...` repeat. + +=== Separator + +Limits the scope of repeats. + +[source,clojure] +---- +(m/rewrite [:hello 1 2 3] + [:hello . !v ...] + [:world . !v ...]) + +;=> [:world 1 2 3] +---- + +Only the terms between `.` and `...` repeat. + +=== Structurally explicit + +ifndef::backend-revealjs[] +Most Clojure functions will coerce their inputs to seqs. +Meander does not do this. +endif::[] + +[horizontal] +`[!x ...]`:: will only match *vectors* +`(!x ...)`:: will only match *lists* and *seqs* +`{:k ?v}`:: will only match *maps* +`#{1}`:: will only match *sets* +`(m/seqable !x ...)`:: will match *maps*, *vectors*, *strings*, etc... + +ifndef::backend-revealjs[] +I like that it will only match the same types unless I choose to be more permissive with `m/seqable`. +endif::[] + +=== Nesting + +As you might expect, match and substitute patterns may be nested. + +[source,clojure] +---- +(m/rewrite {:xs [1 2 3 4 5] + :ys [6 7 8 9 10]} + {:xs [!xs ...] + :ys [!ys ...]} + [!xs ... . !ys ...]) + +;=> [1 2 3 4 5 6 7 8 9 10] +---- + +And Meander does the rearranging for you. + +=== Pop Quiz + +[source,clojure] +---- +(m/rewrite [:hello 1 2 3] + [?k . !v ...] + [?k !v ...]) + +;=> [:hello 1 :hello 2 :hello 3] +---- + +Why? + +[NOTE.speaker] +-- +When matching, we expect a single value, then repeated values. +The substituting pattern has no separator, so we are substituting pairs. +-- + +ifdef::backend-revealjs[] +=== Virtual prize +endif::[] + +ifndef::backend-revealjs[] +Congratulations! +Enjoy these macarons as your reward. +endif::[] + +image::macarons.png[Delicious macarons] + +ifdef::backend-revealjs[] +=== More to Meander +endif::[] + +ifndef::backend-revealjs[] +Meander has a rich feature set. +I have only shown you the core syntax. +We are now going to shift gears and look at some examples in the wild. +I'll introduce a few bits of unexplained but fairly obvious syntax as we go. +endif::[] + +[NOTE] +-- +For a comprehensive treatment of Meander features, see https://github.com/noprompt/meander[Meander on GitHub] which links to documentation, a cookbook, blog posts and a Strange Loop talk. +-- + +== Examples + +=== Rearranging logic expressions + +This example is taken from https://github.com/timothypratley/justice[Justice] (an alternative syntax to Datalog queries). + +[source,clojure] +---- +(def rearrange-logic + (bottom-up + ;; nested commutative logic is raised + ((m/pred #{'and 'or} ?op) . !before ... (?op . !clauses ...) . !after ...) + (?op . !before ... !clauses ... !after ...) + + ;; moves `or` to the outside, + ;; and `and` to the inside to match Datalog rule convention + (and . !before ... (or . !clauses ...) . !after ...) + (or . (and . ~@!before . !clauses . ~@!after) ...) + + ;; identity logic expressions are flattened + ((m/pred #{'and 'or} ?op) ?body) + ?body + + ;; double negatives are removed + (not (not ?body)) + ?body + + ;; moves `not` inside to match Datalog rule convention + (not (or . !clauses ...)) + (and . (not !clauses) ...) + + (not (and . !clauses ...)) + (or . (not !clauses) ...))) +---- + +https://github.com/timothypratley/justice/blob/master/src/justice/translation.cljc[source] + + +ifdef::backend-revealjs[] +=== Trees to entities + +[source,clojure] +---- +(m/rewrite skynet-widgets + [{:basic-info {:producer-code !producer-code} + :widgets [{:widget-code !widget-code + :widget-type-code !widget-type-code} ...] + :widget-types [{:widget-type-code !widget-type-code + :description !description} ...]} ...] + [[!producer-code !widget-code !description] ...]) +;=> [["Cyberdyne" "Model-101" "Resistance Infiltrator"] +; ["ACME" "Model-102" "Mimetic polyalloy"]] +---- + +Blog post: "The answer to map fatigue" +endif::[] + +=== Web scraping + +This example comes from the https://github.com/timothypratley/chartit[Chartit] (an ETL/analytics utility for tracking work) + +[source,clojure] +---- +(def extract-employees + (s/search + (m/$ + [:div {:class "directory-tables"} & + (m/scan + ;; heading/table pairs + [:h3 {} ?department & _] + _ + [:table & + (m/scan + [:tbody & + (m/scan + [:tr & + (m/separated + [:td & (m/scan [:a {} ?name & _])] + [:td {} ?title & _] + [:td & (m/scan [:a {:href ?mailto} & _])])])])])]) + ;;=> + {:department (str/trim ?department) + :name ?name + :title ?title + :email (subs ?mailto 7)})) +---- + +https://github.com/timothypratley/chartit/blob/master/src/chartit/justworks.clj[source] + +=== Parsing defn like forms + +Here is another example from https://github.com/timothypratley/justice[Justice]. +where it was desirable to define a macro that behaves like `defn`. +This example can be directly compared with https://blog.klipse.tech/clojure/2016/10/10/defn-args.html[the Clojure.spec approach]. + +[source,clojure] +---- +(defn wrap-defn + "Returns a function that will parse a form according to `defn` semantics. + Takes a function which will convert fn-spec forms." + [rewrite-fn-spec] + (m/rewrite + (m/and ((pred simple-symbol? ?name) . + (pred string? !?docstring) ... + (pred map? !?attr-map) ... + !tail ...) + (m/guard (<= (count !?docstring) 1)) + (m/guard (<= (count !?attr-map) 1)) + (let + (or (([(m/pred simple-symbol? !params) ... :as !param-list] + . !forms ... :as !fn-specs) ..1) + ([(m/pred simple-symbol? !params) ... :as !param-list] + . !forms ... :as !fn-specs)) + (list* !tail)) + (m/guard (apply distinct? (map count !param-list)))) + ;;> + (defn ?name . !?docstring ... !?attr-map ... + ~@(map rewrite-fn-spec !fn-specs)))) +---- + +https://github.com/timothypratley/justice/blob/master/src/justice/defn.cljc[source] + +=== Schema to code + +This example shows the core functionality of https://github.com/timothypratley/happygapi[HappyGAPI] (a library that exposes Google APIs by generating code from schemas). + +[source,clojure] +---- +(defn summarize-schema [schema request depth] + "Given a json-schema of type definitions, + and a request that is a $ref to one of those types, + resolves $ref(s) to a depth of 3, + discards the distracting information, + and returns a pattern for constructing the required input." + (m/rewrite request + {:type "object" + :id ?id + :additionalProperties ?ap + :properties (m/seqable [!property !item] ...)} + ;;> + {& ([!property (m/app #(summarize-schema schema % depth) !item)] ...)} + + {:type "array" + :items ?item} + ;;> + [~(summarize-schema schema ?item depth)] + + {:type (m/pred string? ?type)} + ;;> + (m/app symbol ?type) + + {:$ref (m/pred string? ?ref)} + ;;> + ~(if (> depth 2) + (symbol ?ref) + (summarize-schema schema (get schema (keyword ?ref)) (inc depth))))) +---- + +https://github.com/timothypratley/happygapi/blob/master/dev/happy/beaver.clj[source] + +=== AST manipulation + +This example comes from https://github.com/echeran/kalai[Kalai], a transpiler from Clojure to Java and Rust. + +[source,clojure] +---- +(def propagate-types-from-bindings-to-locals + "We propagate type information which is stored in metadata + from the the place where they are declared on a symbol + to all future usages of that symbol in scope." + (s/rewrite + {:op :local + :form ?symbol + :env {:locals {?symbol {:form ?symbol-with-meta + :init ?init}} + :as ?env} + & ?more + :as ?ast} + ;;> + {:op :local + :form ~(propagate-ast-type ?init ?symbol-with-meta ?ast) + :env ?env + & ?more} + + ;; otherwise leave the ast as is + ?else + ?else)) +---- + +https://github.com/echeran/kalai/blob/main/src/kalai/pass/kalai/b_kalai_constructs.clj[source] + +== Reading patterns vs functions + +Those were some pretty complicated transformations. + +_Could you follow them?_ + +I bet it was easier than most code. + +_Is that normal?_ + +I claim that term rewriting expressions are much easier to read than functional transformations. + +Next let's compare what is involved in writing functions vs patterns. + +== Writing functions vs patterns + +=== Functions are like recipes + +Functions are all about what to do with inputs. + +ifndef::backend-revealjs[] +Clearly the big difference between functions and rewrite rules is what to do with the inputs. +To write a function is to specify the tasks in order to convert the ingredients into the desired meal. +A function is a verbal description of how to transform inputs to outputs in written form. +endif::[] + +.A recipe for making beef stew +image::recipe.png[Recipe] + +=== Recipe for a function + +[cols="2"] +|=== +| 1. Prepare meat and potatoes | _(example inputs)_ +| 2. Mix ingredients in a pot | _(function definition)_ + +ifndef::backend-revealjs[] +- A little destructuring + +- a dash of `let` binding + +- some sequence seasoning `for` flavor + +- a teaspoon of `conj`, `update`, and `assoc` + +- a splash of threading +endif::[] +| 3. Boil | _(iterate, make it work)_ +| 4. Garnish with documentation | _(docstring or types)_ +| 5. Simmer | _(tests)_ +|=== + +[WARNING.speaker] +-- +Easy to forget why it tastes good + +Easy to skip documentation and testing +-- + +=== Patterns are more like before and after pictures + +Rewrite rules are only the inputs and outputs; the _what to do_ part is completely missing. + +.A hearty pot of beef stew +image::stew.png[Pot of stew] + +=== Depicting a pattern + +[cols="1,2"] +|=== +|1. Prepare example inputs |`[{:k "Meat"} {:k "Potatoes"}]` +|2. Parameterize | `[{:k !ingredient} ...]` +|3. Recombine as outputs |`[!ingredient ... "Stew"]` +|4. Create a test | `(is (= [] (stew example)))` +|5. Verify the output | `["Meat" "Potatoes" "Stew"]` +|=== + +[NOTE.speaker] +-- +Examples are the best kind of documentation. +Writing Meander feels like writing examples. +It gives me confidence I solved the right problem, and it's easy to see what it does coming back to it later. +-- + +I like pictures. +I especially like diagrams. +So much so that I built an app for making diagrams: +https://hummi.app[Hummi.app] and +I've written an article about https://hummi.app/news/why-diagrams[why diagrams] are important. +The thing I like most about Meander is that transformation patterns depict the shape of the data that is coming in and going out. + + +=== When should we use term rewriting, when a function? + +Term rewriting is suitable for largish data reshaping where you would otherwise do destructuring, updates, restructuring, and pipelines. +We have seen some compelling examples, but not every problem is such a clear-cut case of re-shaping. + +[.speaker] +-- +Why isn't this the default way of writing code everywhere? + +* Term rewriting systems are hard to implement, and rarely integrated with programming languages. +* Functions are more familiar in the programming community. +* Not a silver bullet. +-- + +Problems that require aggregation, reduction, and variables tend to be well expressed with a loop or recursive function. +Problems that are extract and reconstitute tend to be well expressed as patterns. + + +=== Limitations of term rewriting + +==== Syntax + +The syntax available in data literals is limited by the host language. +Meander syntax is partially extensible through `defsyntax` to define custom operators. +Execution extension is available by function application (see `m/app` and `~`). +IDEs and linting tools have trouble error checking pattern expressions because they are an entirely new set of expressions with a new grammar. + +==== Less open to extension + +ifndef::backend-revealjs[] + +Multi-methods and Protocols allow for the extension of behavior by including a new function and associating it with the output of the dispatch function. +Multi-method and Protocol dispatch is somewhat similar to a case statement in that we expect distinct values to be matched. +Multi-methods address the possibility of multiple matches by the introduction of hierarchies and preferences: “prefer-method creates an ordering between methods when they would otherwise be ambiguous” (see https://clojure.org/reference/multimethods[Clojure - Multi-methods and Hierarchies]). + +Pattern matching by contrast often defines many overlapping matching patterns, and is thus sensitive to ordering. +The order that patterns are defined matters because the first value match will be used. +This makes extension more challenging because extensions need to be coordinated. + +One possible approach for extending patterns would be to specify them relative to existing clauses. +You could add a pattern at the end, add a pattern before an existing pattern, or add a pattern after an existing pattern. +This could get complicated if several libraries participated in an extensible dispatch. +The order of extension might change the outcome. + +endif::[] + +==== Failure to match is opaque + +ifndef::backend-revealjs[] +Similar to regular expressions, it can be difficult to figure out why a match isn't made for a given input. +Simplifying and decomposing can help. +Function application can be used `(m/app #(doto % prn) ?x)` to spy on terms. +endif::[] + +==== Reduction + +ifndef::backend-revealjs[] +Meander has recursion, so you can reduce. +But recursion has no syntactic advantage. +The next branch of Meander (zeta) contains a neater way to express reduction. +endif::[] + +==== Memory variable correspondence + +ifndef::backend-revealjs[] +Nested memory variables in a single expression can get confusing, +and often don't behave as you'd expect. +There is potential for simplification https://github.com/noprompt/meander/issues/129[issue#129]. +The next branch of Meander (zeta) has flexibility in how variables behave, which may solve this. +endif::[] + +==== Performance + +ifndef::backend-revealjs[] +On par with hand rolled functions. +Meander is a high quality library: fast and reliable. +endif::[] + +==== Decomposition + +ifndef::backend-revealjs[] +The current options for creating sub-patterns are `m/with` and `m/app`, +Which are equivalent to functional decomposition. +endif::[] + +=== Other approaches to data transformation + +ifndef::backend-revealjs[] +Here is a short list of other data transformation approaches in Clojure so that we can examine where they differ from Meander: +endif::[] + +[horizontal] +https://clojure.github.io/clojure/#clojure.core[Clojure.core]:: +ifndef::backend-revealjs[] +Clojure is concise and powerful. +Many functions defined on few primary data structures. +We operate on data but do not specify what the data is. +So it is common to be looking at a function that operates on `x`, and have no context about what `x` aught to be. +endif::[] +What is `x`? + +https://clojure.org/guides/destructuring[Destructuring]:: +Does one job well. +No logic expressions or substitution. + +https://github.com/clojure/core.match[Core.match], https://github.com/clojure/core.logic[Core.logic]:: +Independently work great. There is no convenient syntax for substitution. + +https://clojure.org/guides/spec[Clojure.spec]:: +ifndef::backend-revealjs[] +Defines the shape of `x` as s-expression regexes. +endif::[] +Specs do not look like the data they describe. + +https://github.com/redplanetlabs/specter[Specter]:: +Takes a navigator/action approach. +Improves data manipulation. +Very compact, linguistic. + +ifndef::backend-revealjs[] +Each of these approaches have benefits and drawbacks. +The purpose of this article was to show where Meander sits relative to these methods, and hopefully inspire you to explore this approach. +endif::[] + +== Conclusion + +[.speaker] +-- +The mathematical style term rewriting approach to data transformation has several merits: +-- + +* When reading expressions, inputs and outputs are instantly recognizable +* When writing expressions, examples can be converted to patterns +* The declarative style is symmetrically pleasing +* Visual expression is more effective than verbal expression + +ifdef::backend-revealjs[] + +== Contact + +[horizontal] +icon:rss[]:: https://timothypratley.blogspot.com +icon:twitter[]:: @timothypratley +icon:envelope[]:: timothypratley@gmail.com + +[.big.goal.lead] +Try an equation for your next data transformation! + +endif::[] + +ifndef::backend-revealjs[] +Meander provides term rewriting as a convenient library for Clojure. +I hope you will give it a try and find it as useful as I have. +endif::[] diff --git a/docs/extra/term-rewriting-with-meander-examples.html b/docs/extra/term-rewriting-with-meander-examples.html new file mode 100644 index 0000000..48649e3 --- /dev/null +++ b/docs/extra/term-rewriting-with-meander-examples.html @@ -0,0 +1,1447 @@ + + + + + + + + + +Term Rewriting with Meander + + + + + + + + +
+
+
+
+

As a programmer, my job is to write the function to transform inputs to outputs.

+
+
+
+\[f(x) = x + 1\] +
+
+
+

Usually the inputs and outputs are complex data that represent something in the real world. +Abstractly though, I tend to think about the inputs and outputs as data shapes. +Squares go in, circles come out. +Defining functions is my fundamental form of expression.

+
+
+
+processing machine +
+
Figure 1. The square to circle machine ™
+
+
+

As a mathematician, equations are my fundamental form of expression. +Equations define valid rules for transforming one expression into another. +My job is to solve problems using symbolic manipulation.

+
+
+
+\[\begin{align*} +(a + b)^2 &= (a + b)(a + b)\\ + &= aa + ab + ba + bb\\ + &= a^2 + 2ab + b^2 +\end{align*}\] +
+
+
+

Functions and equations are both fine ways to specify transformations. +So why only use functions for programming? +What does it look like to program data transformation like equations instead of functions? +Today we will look at some examples of an equation base approach to specifying data transformations. +Before diving into the examples, let’s first establish some definitions

+
+
+
+
+

Definitions

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+Term rewriting + +

Replacing terms with other terms

+
+Term + +

A variable or function application: \(a\), \((a + b)\), \((a + b)^2\)

+
+Variable + +

\(a,b\) can be assigned a value

+
+Function + +

add, multiply, square

+
+Rule + +

A pair of matching terms and substitution terms: \((a + b)^2 => a^2 + 2ab + b^2\)

+
+Matching + +

\((1 + 3)^2\) can be made equal to \((a + b)^2\) when \(a=1,b=3\)

+
+Substitution + +

Creation of new terms from old ones \((1 + 3)^2 => 1^2 + 2\cdot1\cdot3 + 2^2\)

+
+
+
+ + + + + +
+ + +
+

Term rewriting allows any transformation. +Math equations are rules that strictly preserve equivalence.

+
+
+
+
+
+
+

Introducing Meander

+
+
+

Meander is a term rewriting system packaged as a Clojure library for data transformation.

+
+
+
+
[meander/epsilon "0.0.512"]
+
+
+
+
+
(ns meander.examples
+  (:require [meander.epsilon :as m]))
+
+
+
+

I prefer to call it a structural pattern syntax for data transformation.

+
+
+

Rewrite

+
+

The m/rewrite macro is the swiss army knife of Meander.

+
+
+

Let’s do our first transformation from a vector to a map:

+
+
+
+\[[a, b, c, d] => \{a, d\}\] +
+
+
+
+
; Meander!
+(m/rewrite [:a 1 :c 2] ; input
+  [?a ?b ?c ?d]        ; a match pattern
+  {?a ?d})             ; a substitution pattern
+
+;=> {:a 2 :c 1}             ; output
+
+
+
+
    +
  • +

    The match pattern is a data literal with the shape of the input

    +
  • +
  • +

    The substitution pattern is a data literals with the shape of the output

    +
  • +
  • +

    ?a is called a logic variable

    +
  • +
  • +

    You can also use _ for unused terms

    +
  • +
+
+
+ + + + + +
+ + +
+

?x is a valid symbol in Clojure. +Macros manipulate symbols rather than the values associated with those symbols. m/rewrite is a macro; it defines how to interpret the patterns. +I think of Meander as a syntax because I specify structure with data literals to pattern match and substitute. +Hence why I prefer to call it a "structural pattern syntax".

+
+
+
+
+
+

Logic

+
+
+
(m/rewrite [1 2] ; input
+  [?a ?a]        ; a match pattern
+  {?a ?a})       ; a substitute pattern
+
+;=> nil
+
+
+
+

No match.

+
+
+

?a cannot match both 1 and 2.

+
+
+
+
(m/rewrite [1 1] ; input
+  [?a ?a]        ; a match
+  {?a ?a})       ; a substitution pattern
+
+;=> {1 1}
+
+
+
+

Matched ?a with 1

+
+
+ + + + + +
+ + +
+

Unification means resolving logical matches.

+
+
+
+
+
+

Repeating terms

+
+
+
(m/rewrite [:a 1 :c 2 :d 3]
+  [!a ...]
+  [!a ...])
+
+;=> [:a 1 :c 2 :d 3]
+
+
+
+

!a is a memory variable: an array that can collect many values
+…​ indicates 0 or more repeated occurrences

+
+
+ + + + + +
+ + +
+

The match pattern is symmetrical with the substitution pattern. +We got out exactly what we put in.

+
+
+
+
+
+

Repeat scope

+
+
+
(m/rewrite [1 :a 2 :b 3 :c]
+  [!x !y ...]      ; match pairs
+  [!y !x ...])     ; substitute flipped pairs
+
+;=> [:a 1 :b 2 :c 3]
+
+
+
+

All terms preceeding …​ repeat.

+
+
+
+

Separator

+
+

Limits the scope of repeats.

+
+
+
+
(m/rewrite [:hello 1 2 3]
+  [:hello . !v ...]
+  [:world . !v ...])
+
+;=> [:world 1 2 3]
+
+
+
+

Only the terms between . and …​ repeat.

+
+
+
+

Structurally explicit

+
+

Most Clojure functions will coerce their inputs to seqs. +Meander does not do this.

+
+
+ + + + + + + + + + + + + + + + + + + + + +
+[!x …​] + +

will only match vectors

+
+(!x …​) + +

will only match lists and seqs

+
+{:k ?v} + +

will only match maps

+
+#{1} + +

will only match sets

+
+(m/seqable !x …​) + +

will match maps, vectors, strings, etc…​

+
+
+
+

I like that it will only match the same types unless you choose to be more permissive with m/seqable.

+
+
+
+

Nesting

+
+

As you might expect, match and substitute patterns may be nested.

+
+
+
+
(m/rewrite {:xs [1 2 3 4 5]
+            :ys [6 7 8 9 10]}
+  {:xs [!xs ...]
+   :ys [!ys ...]}
+  [!xs ... . !ys ...])
+
+;=> [1 2 3 4 5 6 7 8 9 10]
+
+
+
+

And Meander does the rearranging for you.

+
+
+
+

Pop Quiz

+
+
+
(m/rewrite [:hello 1 2 3]
+  [?k . !v ...]
+  [?k !v ...])
+
+;=> [:hello 1 :hello 2 :hello 3]
+
+
+
+

Why?

+
+
+ + + + + +
+ + +
+

When matching, we expect a single value, then repeated values. +The substituting pattern has no separator, so we are substituting pairs.

+
+
+
+
+

Congratulations! +Enjoy these macarons as your reward.

+
+
+
+Delicious macarons +
+
+
+

Meander has a rich feature set. +I have only shown you the core syntax. +We are now going to shift gears and look at some examples in the wild. +I’ll introduce a few bits of unexplained but fairly obvious syntax as we go.

+
+
+ + + + + +
+ + +
+

For a comprehensive treatment of Meander features, see Meander on GitHub which links to documentation, a cookbook, blog posts and a Strange Loop talk.

+
+
+
+
+
+
+
+

Examples

+
+
+

Rearranging logic expressions

+
+

This example is taken from Justice (an alternative syntax to Datalog queries). source

+
+
+
+
(def rearrange-logic
+  (bottom-up
+   ;; nested commutative logic is raised
+   ((m/pred #{'and 'or} ?op) . !before ... (?op . !clauses ...) . !after ...)
+   (?op . !before ... !clauses ... !after ...)
+
+   ;; moves `or` to the outside,
+   ;; and `and` to the inside to match Datalog rule convention
+   (and . !before ... (or . !clauses ...) . !after ...)
+   (or . (and . ~@!before . !clauses . ~@!after) ...)
+
+   ;; identity logic expressions are flattened
+   ((m/pred #{'and 'or} ?op) ?body)
+   ?body
+
+   ;; double negatives are removed
+   (not (not ?body))
+   ?body
+
+   ;; moves `not` inside to match Datalog rule convention
+   (not (or . !clauses ...))
+   (and . (not !clauses) ...)
+
+   (not (and . !clauses ...))
+   (or . (not !clauses) ...)))
+
+
+
+
+

Web scraping

+
+

This example comes from the Chartit (an ETL/analytics utility for tracking work)

+
+
+
+
(def extract-employees
+  (s/search
+   (m/$
+    [:div {:class "directory-tables"} &
+     (m/scan
+      ;; heading/table pairs
+      [:h3 {} ?department & _]
+      _
+      [:table &
+       (m/scan
+        [:tbody &
+         (m/scan
+          [:tr &
+           (m/separated
+            [:td & (m/scan [:a {} ?name & _])]
+            [:td {} ?title & _]
+            [:td & (m/scan [:a {:href ?mailto} & _])])])])])])
+   ;;=>
+   {:department (str/trim ?department)
+    :name ?name
+    :title ?title
+    :email (subs ?mailto 7)}))
+
+
+
+

source

+
+
+
+

Parsing defn like forms

+
+

Here is another example from Justice. +where it was desirable to define a macro that behaves like defn. +This example can be directly compared with the Clojure.spec approach.

+
+
+
+
(defn wrap-defn
+  "Returns a function that will parse a form according to `defn` semantics.
+  Takes a function which will convert fn-spec forms."
+  [rewrite-fn-spec]
+  (m/rewrite
+   (m/and ((pred simple-symbol? ?name) .
+           (pred string? !?docstring) ...
+           (pred map? !?attr-map) ...
+           !tail ...)
+        (m/guard (<= (count !?docstring) 1))
+        (m/guard (<= (count !?attr-map) 1))
+        (let
+         (or (([(m/pred simple-symbol? !params) ... :as !param-list] . !forms ... :as !fn-specs) ..1)
+             ([(m/pred simple-symbol? !params) ... :as !param-list] . !forms ... :as !fn-specs))
+          (list* !tail))
+        (m/guard (apply distinct? (map count !param-list))))
+   ;;>
+   (defn ?name . !?docstring ... !?attr-map ...
+     ~@(map rewrite-fn-spec !fn-specs))))
+
+
+
+

source

+
+
+
+

Schema to code

+
+

This example shows the core functionality of HappyGAPI (a library that exposes Google APIs by generating code from schemas).

+
+
+
+
(defn summarize-schema [schema request depth]
+  "Given a json-schema of type definitions,
+  and a request that is a $ref to one of those types,
+  resolves $ref(s) to a depth of 3,
+  discards the distracting information,
+  and returns a pattern for constructing the required input."
+  (m/rewrite request
+    {:type                 "object"
+     :id                   ?id
+     :additionalProperties ?ap
+     :properties           (m/seqable [!property !item] ...)}
+    ;;>
+    {& ([!property (m/app #(summarize-schema schema % depth) !item)] ...)}
+
+    {:type  "array"
+     :items ?item}
+    ;;>
+    [~(summarize-schema schema ?item depth)]
+
+    {:type (m/pred string? ?type)}
+    ;;>
+    (m/app symbol ?type)
+
+    {:$ref (m/pred string? ?ref)}
+    ;;>
+    ~(if (> depth 2)
+       (symbol ?ref)
+       (summarize-schema schema (get schema (keyword ?ref)) (inc depth)))))
+
+
+
+

source

+
+
+
+

AST manipulation

+
+

This example comes from Kalai, a transpiler from Clojure to Java and Rust.

+
+
+
+
(def propagate-types-from-bindings-to-locals
+  "We propagate type information which is stored in metadata
+  from the the place where they are declared on a symbol
+  to all future usages of that symbol in scope."
+  (s/rewrite
+    {:op   :local
+     :form ?symbol
+     :env  {:locals {?symbol {:form ?symbol-with-meta
+                              :init ?init}}
+            :as     ?env}
+     &     ?more
+     :as   ?ast}
+    ;;>
+    {:op   :local
+     :form ~(propagate-ast-type ?init ?symbol-with-meta ?ast)
+     :env  ?env
+     &     ?more}
+
+    ;; otherwise leave the ast as is
+    ?else
+    ?else))
+
+
+
+

source

+
+
+
+
+
+

Reading patterns vs functions

+
+
+

Those were all pretty complicated transformations.
+Could you follow them?
+I bet it was easier than most code.
+Is that normal?
+I claim that term rewriting expressions are much easier to read than functional transformations.

+
+
+

Next let’s compare what is involved in writing functions vs patterns.

+
+
+
+
+

Writing functions vs patterns

+
+
+

Functions are like recipes

+
+

Functions are all about what to do with inputs.

+
+
+

Clearly the big difference between functions and rewrite rules is what to do with the inputs. +To write a function is to specify in verbiage the tasks in order to convert the ingredients into the desired meal. +This is a verbal description of how to transform inputs to outputs in written form.

+
+
+
+Recipe +
+
Figure 2. A recipe for making beef stew
+
+
+
+

Recipe for a function

+ ++++ + + + + + + + + + + + + + + + + + + + + + + +

1. Prepare meat and potatoes

(example inputs)

2. Mix ingredients in a pot

(function definition)
+- A little destructuring
+- a dash of let binding
+- some sequence seasoning for flavor
+- a teaspoon of conj, update, and assoc
+- a splash of threading

3. Boil

(iterate, make it work)

4. Garnish with documentation

(docstring or types)

5. Simmer

(tests)

+
+ + + + + +
+ + +
+

Easy to forget why it tastes good
+Easy to skip documentation and testing

+
+
+
+
+
+

Patterns are more like pictures

+
+

Rewrite rules are the inputs and outputs; the what to do part is completely missing.

+
+
+
+Pot of stew +
+
Figure 3. A hearty pot of beef stew
+
+
+
+

Picturing a pattern

+ ++++ + + + + + + + + + + + + + + + + + + + + + + +

1. Prepare example inputs

[{:k "Meat"} {:k "Potatoes"}]

2. Parameterize

[{:k !ingredient} …​]

3. Recombine as outputs

[!ingredient …​ "Stew"]

4. Create a test

(is (= [] (stew example)))

5. Verify the output

["Meat" "Potatoes" "Stew"]

+
+ + + + + +
+ + +
+

Examples are the best kind of documentation. +Writing Meander feels like writing examples. +It gives me confidence I solved the right problem, and it’s easy to see what it does coming back to it later.

+
+
+
+
+
+

Data transformation

+
+

When should we use term rewriting instead of functions?

+
+
+

Term rewriting is suitable for largish data reshaping where you would otherwise do destructuring, updates, restructuring, and pipelines.

+
+
+
+
+

Why isn’t this the default way of writing code everywhere?

+
+
+
    +
  • +

    Term rewriting systems are hard to implement, and rarely integrated with programming languages.

    +
  • +
  • +

    Functions are far more familiar in the programming community.

    +
  • +
  • +

    Limitations

    +
  • +
+
+
+

What’s the catch?

+
+
+
+
+
+

Limitations

+
+

Extension

+
+

The syntax available in data literals is limited by the host language. +Meander syntax is partially extensible through defsyntax to define custom operators. +Execution extension is available by function application (see m/app).

+
+
+
+

Failure to match is opaque

+
+

Similar to regular expressions, it can be difficult to figure out why a match isn’t made for a given input. +Simplifying and decomposing can help. +Function application can be used (m/app #(doto % prn) ?x) to spy on terms.

+
+
+
+

Reduction

+
+

Meander has recursion, so you can reduce. +But recursion has no syntactic advantage. +The next branch of Meander (zeta) contains a neater way to express reduction.

+
+
+
+

Memory variable correspondence

+
+

Nested memory variables in a single expression can get confusing. +There is potential for a syntactic simplification issue#129. +The next branch of Meander (zeta) has flexibility in how variables behave, which may solve this.

+
+
+
+

Performance

+
+

On par with hand rolled functions. +Meander is a high quality library: fast and reliable.

+
+
+
+

Decomposition

+
+

The current options for creating sub-patterns are m/with and m/app. +This is equivalent to functional decomposition. +I wish for a more syntactically elegant approach.

+
+
+
+
+

Comparing Meander

+
+

How does Meander compare to other data transformation approaches in Clojure?

+
+
+ + + + + + + + + + + + + + + + + + + + + +
+Clojure.core + +

Clojure is concise and powerful. +Many functions defined on few primary data structures. +We operate on data but do not specify what the data is. +So it is common to be looking at a function that operates on x, and have no context about what x aught to be. +What is x?

+
+Destructuring + +

Does one job well. +No logic expressions or substitution.

+
+Core.match, Core.logic + +

Independently work great. There is no convenient syntax for substitution.

+
+Clojure.spec + +

Defines the shape of x as s-expression regexes. +Specs do not look like the data they describe.

+
+Specter + +

Takes a navigator/action approach. +Improves data manipulation. +Does not address shape.

+
+
+
+
+
+
+

Conclusion

+
+
+
+
+

The mathematical style term rewriting approach to data transformation has several merits:

+
+
+
+
+
    +
  • +

    When reading expressions, inputs and outputs are instantly recognizable

    +
  • +
  • +

    When writing expressions, examples can be converted to patterns

    +
  • +
  • +

    The declarative style is symmetrically pleasing

    +
  • +
  • +

    Visual expression is more effective than verbal expression

    +
  • +
+
+
+

Meander provides term rewriting as a convenient library for Clojure. +I hope you will give it a try and find it as useful as I have.

+
+
+

Thank you for reading.

+
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/docs/extra/term-rewriting-with-meander-examples.pdf b/docs/extra/term-rewriting-with-meander-examples.pdf new file mode 100644 index 0000000..db8b9f4 Binary files /dev/null and b/docs/extra/term-rewriting-with-meander-examples.pdf differ diff --git a/site/forum.html b/docs/forum.adoc similarity index 55% rename from site/forum.html rename to docs/forum.adoc index 28ab2b2..9fba684 100644 --- a/site/forum.html +++ b/docs/forum.adoc @@ -1,12 +1,10 @@ - - - - - Title - - -
- - - - \ No newline at end of file + + +++++ diff --git a/docs/index.adoc b/docs/index.adoc index ef47fda..261f8a7 100644 --- a/docs/index.adoc +++ b/docs/index.adoc @@ -1,5 +1,6 @@ = Enterprise Clojure Training :docinfo: shared +:imagesdir: img "Clojure is serious business!" -- Timothy Pratley @@ -14,4 +15,4 @@ This is a Clojure training course for Developers and Senior Developers working o * https://github.com/timothypratley/enterprise-clojure-training[Course Github] * link:getting-help.html[Getting help] -image:img/corgi.jpg[Corgi] +image:corgi.jpg[Corgi] diff --git a/docs/manual.adoc b/docs/manual.adoc index ba0de60..daf3aa6 100644 --- a/docs/manual.adoc +++ b/docs/manual.adoc @@ -13,12 +13,13 @@ :docinfo: shared :icons: font :!sectnums: +:imagesdir: img [preface] == About -image:img/corgi.jpg[width=300] +image:corgi.jpg[width=300] Welcome to Enterprise Clojure Training. This course is for developers learning Clojure for the purpose of building enterprise software. @@ -33,27 +34,14 @@ Clojure is a functional programming language that produces new values instead of Clojure has been in the "adopt" category of the Thoughtworks tech radar since 2014 because it enables teams to build better software faster. This course is divided into 12 bite sized sections: - the Clojure ecosystem; - Clojure syntax; - functions; - unit testing; - control flow; - functional programming; - Java interop; - concurrency; - polymorphism; - interacting with a database; - specifications; - and macros. +the Clojure ecosystem; Clojure syntax; functions; unit testing; control flow; functional programming; Java interop; concurrency; polymorphism; interacting with a database; specifications; and macros. Each section has exercises to put the information into practise and re-enforce learnings. - === Duration 2days, 10 hr/day + 2 hour webinar after completion of workshop. - === Learning Objectives At the end of this course, you will be able to @@ -64,54 +52,47 @@ At the end of this course, you will be able to * Interface with a database * Use Clojure's parallel programming and concurrency facilities - === Prerequisites General programming knowledge. - === Target Audience Developers and Senior Developers. - === Required setup The following software must be installed on your laptop prior to the course: * Java (https://java.com) -* Leiningen (https://leiningen.org) +* Leiningen (https://leiningen.org) * IntelliJ (https://www.jetbrains.com/idea) * Cursive plugin for IntelliJ (https://cursive-ide.com) - === Pre-assessment * What programming languages have you used before? -* Do you have an interest in Clojure? If so what in particular interests you? +* Do you have an interest in Clojure? +If so what in particular interests you? * What do you plan to do with Clojure? * Name a scenario where you would use a HashMap data structure. * When should you use a Vector instead of a List or an Array? - === Required preparation Complete the first 10 exercises on the 4Clojure website (http://www.4clojure.com). - === Optional reading If you would like to get a head-start, please read the official Clojure introduction tutorial (http://clojure-doc.org/articles/tutorials/introduction.html). This material will be covered as part of the course. Having read it before hand will allow you to focus on working through the exercises of the course. - === Introductions - ==== The instructor -image:img/art1.jpg[width=300] +image:art1.jpg[width=300] Timothy Pratley is the author of the book “Professional Clojure”, and a contributor to the Clojure core language. He has 18 years of professional software development experience in banking, robotics, logistics, and advertising. @@ -122,10 +103,9 @@ Please ask questions! I am here to help. Asking questions is the most valuable part of the course. - ==== Clojure -image:img/clojure-logo.png[width=300] +image:clojure-logo.png[width=300] During this course we will be examining the Clojure language up close. Sometimes a new language can feel different just for difference sake. @@ -136,7 +116,9 @@ Smart people want to use it. Clojure enables teams to build things fast. This makes it excellent for delivering value in enterprise development projects. -"What language is it that meets all the criteria? What language would I choose if I had to choose today? Probably Clojure!" +"What language is it that meets all the criteria? +What language would I choose if I had to choose today? +Probably Clojure!" -- Robert Martin Throughout the course there will be time to reflect on what purpose the differences serve and what trade offs are being made. @@ -149,25 +131,27 @@ These are the Clojure language themes to watch out for as we move through the co * Sequences * Transformations - ===== Functions * Act on general purpose data structures * Pure - ===== A tool for thought * Concise * Unadorned * Abstract - ===== Getting stuff done * Access to libraries * Performance +* What can you do with Clojure/ClojureScript? +* Dynamic development: Modifying the program while it is running +===== Big ideas + +Value oriented programming vs Place oriented programming Immutability by default First class functions Dynamic development: Modifying the program while it is running Everything is data Code is data Data literals Time ==== Syntax Summary @@ -178,12 +162,12 @@ These are the Clojure language themes to watch out for as we move through the co l|int i = 5; l|(def i 5) l|if (x == 0) - return y; +return y; else - return z; +return z; l|(if (zero? x) - y - z) +y +z) l|x * y * z; l|(* x y z) l|foo(x, y, z); @@ -196,20 +180,25 @@ Things that would be declarations, control structures, function calls, operators :sectnums: + == The Clojure Ecosystem -image:img/ecosystem.jpg[width=300] +image:ecosystem.jpg[width=300] "Integrity is an ecosystem." -- Michael Leunig -There are many Clojure libraries. Hosted on Maven and Clojars. Just jars, like any other Java artifact. +There are many Clojure libraries. +Hosted on Maven and Clojars. +Just jars, like any other Java artifact. Clojure is itself a Java library. Clojure can make direct use of Java libraries. ClojureScript can make direct use of JavaScript libraries. -The Clojure compiler is a Java library, a clojure.jar file. The only required installation is that Java must be installed. Clojure is very simple to deploy due to the lack of dependencies. +The Clojure compiler is a Java library, a clojure.jar file. +The only required installation is that Java must be installed. +Clojure is very simple to deploy due to the lack of dependencies. You can use Java tooling to manage your project, but Clojure has some tools to make the process easier. @@ -218,7 +207,8 @@ Please follow along on your laptop and ask questions at any time. === Leiningen -A popular project built tool that provides a convenient way to pull libraries for your project. Follow the installation instructions at (https://leiningen.org). +A popular project built tool that provides a convenient way to pull libraries for your project. +Follow the installation instructions at (https://leiningen.org). lein new training cd training @@ -230,7 +220,6 @@ As you can see, Leiningen created a project with one dependency; Clojure itself. lein repl - === The Read Eval Print Loop (REPL) When you type in this code: @@ -243,8 +232,8 @@ Clojure evaluates it immediately and returns a result: Pressing the up arrow moves through your history. -The REPL is convenient for experimenting and doing informal tests. But the default REPL is not ideal for editing code. - +The REPL is convenient for experimenting and doing informal tests. +But the default REPL is not ideal for editing code. === Editor setup @@ -278,7 +267,6 @@ You can also open a REPL in your browser: (https://repl.it/languages/clojure). For other editor options see (https://cb.codes/what-editor-ide-to-use-for-clojure). - === Exercises Evaluate some math expressions in the REPL: @@ -295,7 +283,6 @@ Open `src/training/core.clj` with your editor, write some expressions, and send * Evaluate the form: `(println "hello world")` * Calculate 5 factorial - === Answers (+ 2 3) @@ -320,10 +307,9 @@ Open `src/training/core.clj` with your editor, write some expressions, and send (* 5 4 3 2 1) => 120 - == Clojure Syntax -image:img/syntax.png[width=300] +image:syntax.png[width=300] "If the syntax is good enough for the information, it should be good enough for the meta-information." -- Erik Naggum @@ -369,7 +355,6 @@ Booleans are represented as `true` and `false`. `nil` means nothing and is considered false in logical tests. - === Collections: lists, vectors, maps, and sets Lists are forms enclosed in parentheses. @@ -399,13 +384,11 @@ Clojure has a sequence abstraction. Sequences can be lazy. Their values are only created as they are consumed. - Lists and sequences are printed the same way. (seq '(1 2 3)) => (1 2 3) - Symbols are resolved. inc @@ -477,7 +460,6 @@ Collections can be combined and nested This is a map that has vector coordinates as keys and maps as values. - === Invoking functions To call a function, wrap it in parenthesis: @@ -485,12 +467,13 @@ To call a function, wrap it in parenthesis: (inc 1) => 2 -The first element in a list is a function to be called. The remaining elements are the arguments to the function. - +The first element in a list is a function to be called. +The remaining elements are the arguments to the function. === Defining vars -A var is used to store a mutable reference to a value. Vars are unbound if no value is supplied. +A var is used to store a mutable reference to a value. +Vars are unbound if no value is supplied. (def x) x @@ -502,7 +485,8 @@ It is more common to supply an initial value. x => 1 -Def created a var named `x` which is bound to the value `1`. Vars are automatically dereferenced when evaluated. +Def created a var named `x` which is bound to the value `1`. +Vars are automatically dereferenced when evaluated. To represent values that changes over time, you can use an atom. @@ -511,16 +495,19 @@ To represent values that changes over time, you can use an atom. @a => 2 -We defined `a` to be an atom with initial value `1`, then swapped the atom's value with the `inc` function. We retrieved the value of the atom by dereference it with `@`. The current value of `a` is now `2`, the increment of `1`. `@` is shorthand for `deref`. +We defined `a` to be an atom with initial value `1`, then swapped the atom's value with the `inc` function. +We retrieved the value of the atom by dereference it with `@`. +The current value of `a` is now `2`, the increment of `1`. `@` is shorthand for `deref`. (deref a) => 2 -Atoms provide compare and set, which is suitable for non-transactional changes. Refs provide transactional change, which is suitable for multi-threaded change management. Agents provide update serialization as an alternative strategy for multi-threaded change. +Atoms provide compare and set, which is suitable for non-transactional changes. +Refs provide transactional change, which is suitable for multi-threaded change management. +Agents provide update serialization as an alternative strategy for multi-threaded change. Deref also blocks and gets the result of futures, promises and delays, which are operations that do not block until dereferenced. - === Binding names with let Symbols: @@ -538,8 +525,10 @@ Symbols can be bound to a value in a scope with let. The symbol `x` is bound to the value `1`, and the function `inc` is called on `x`, resulting in `2`. -The binding scope is within the parentheses enclosing the let form, and will shadow any existing bindings. It is preferable to use let instead of def for values that can be contained in a scope. Vars can be changed, but you should almost never modify them directly. Instead Clojure provides local bindings, atoms, refs and agents for managing change. - +The binding scope is within the parentheses enclosing the let form, and will shadow any existing bindings. +It is preferable to use let instead of def for values that can be contained in a scope. +Vars can be changed, but you should almost never modify them directly. +Instead Clojure provides local bindings, atoms, refs and agents for managing change. === Destructuring (also known as binding forms) @@ -547,7 +536,8 @@ The binding scope is within the parentheses enclosing the let form, and will sha (+ x y)) => 3 -Destructuring is providing a literal data structure containing symbols that get bound to the respective parts of a value with a matching structure. Where we might otherwise bind the vector `[1 2]` to a single symbol, here we destructure two symbols `x` and `y` by providing a pattern that matches the vector. +Destructuring is providing a literal data structure containing symbols that get bound to the respective parts of a value with a matching structure. +Where we might otherwise bind the vector `[1 2]` to a single symbol, here we destructure two symbols `x` and `y` by providing a pattern that matches the vector. (defn normalize "Divide all dimensions by the sum of squares" @@ -555,7 +545,8 @@ Destructuring is providing a literal data structure containing symbols that get (let [length (Math/sqrt (+ (* x x) (* y y)))] [(/ x length) (/ y length)])) -Note that function arguments are already a destructured vector. The above case is an example of a vector of arguments which contains a vector of `x` and `y`. +Note that function arguments are already a destructured vector. +The above case is an example of a vector of arguments which contains a vector of `x` and `y`. Without destructuring we would extract substructure manually: @@ -591,7 +582,9 @@ There is no need to restrict normalize to use 2 dimensions, instead we can write (normalize [3 4]) -> (0.6 0.8) (normalize [3 4 5]) -> (0.424 0.566 0.707) -Variadic functions are destructured using `&`. Variadic means variable number of arguments. Arity means number of arguments. +Variadic functions are destructured using `&`. +Variadic means variable number of arguments. +Arity means number of arguments. (defn sub [& vs] vs) @@ -630,7 +623,6 @@ Nested destructuring {{b :b} :a} - === Namespaces Namespace forms occur at the start of files. @@ -641,13 +633,17 @@ Namespace forms occur at the start of files. (string/upper-case "shout") -The namespace must match the path and filename. The namespace training.core -Must be defined in the `src/training/core.clj` file. Filename hyphens are replaced with underscores, and dot separators indicate directories. - -The `ns` form allows us to require other namespaces and import java Classes. There are other valid `ns` forms which are best to be avoided and so are not shown here. If you do see them in other code, just know that you can and should achieve the same thing with the regular ns form described previously. +The namespace must match the path and filename. +The namespace training.core Must be defined in the `src/training/core.clj` file. +Filename hyphens are replaced with underscores, and dot separators indicate directories. -Clojure programs are written in expressions which are evaluated to results. If an expression needs to be compiled, it will be. Programs can be loaded from files or evaluated dynamically. +The `ns` form allows us to require other namespaces and import java Classes. +There are other valid `ns` forms which are best to be avoided and so are not shown here. +If you do see them in other code, just know that you can and should achieve the same thing with the regular ns form described previously. +Clojure programs are written in expressions which are evaluated to results. +If an expression needs to be compiled, it will be. +Programs can be loaded from files or evaluated dynamically. === Regex @@ -656,7 +652,6 @@ Regular expressions are written as `#"pattern"` (re-seq #"\w+" "the quick brown fox") => ("the" "quick" "brown" "fox") - === Exercises Write code into a new file called `src/training/syntax.clj`, and send the lines to the REPL as you enter them. @@ -667,13 +662,12 @@ Write code into a new file called `src/training/syntax.clj`, and send the lines * Create a `let` binding that binds the symbol `message` to `"well hello there"`, and prints out `message` inside the `let` block. * Print out message again, outside of the `let` block. * Create a let binding that destructures the map - `{:greeting "good morning", :tone "happy"}` - and prints the greeting and tone inside the let block. +`{:greeting "good morning", :tone "happy"}` +and prints the greeting and tone inside the let block. * Destructure a single map input containing - `{:greeting "good morning", :tone "happy"}` - and return a string combining greeting and tone. - Use the `str` function. - +`{:greeting "good morning", :tone "happy"}` +and return a string combining greeting and tone. +Use the `str` function. === Answers @@ -687,7 +681,8 @@ Write code into a new file called `src/training/syntax.clj`, and send the lines => "greetings" nil -Note the prn and println behave slightly differently; prn keeps the quotes around strings. This is often useful when experimenting, because you can visually see the type of the values more clearly. +Note the prn and println behave slightly differently; prn keeps the quotes around strings. +This is often useful when experimenting, because you can visually see the type of the values more clearly. (let [message "well hello there"] (prn message)) @@ -711,10 +706,9 @@ Note that the message global var is still the original value. (hi m) => "good morning - happy" - == Functions -image:img/functions.jpg[width=300] +image:functions.jpg[width=300] "The chief function of the body is to carry the brain around." -- Thomas A. Edison @@ -727,7 +721,8 @@ Functions are defined like this: (defn square [x] (* x x)) -All functions return a result, the result of the last expression in the form. Defn binds the symbol square to a var which refers to a function which returns the result of multiplying the input parameter x by itself. +All functions return a result, the result of the last expression in the form. +Defn binds the symbol square to a var which refers to a function which returns the result of multiplying the input parameter x by itself. (square 2) => 4 @@ -764,7 +759,8 @@ Closures are functions that capture values from the environment. (greet) => "Hello world" -Functions are values and can be passed as arguments to other functions. Functions that take a function as an argument are called higher order functions. +Functions are values and can be passed as arguments to other functions. +Functions that take a function as an argument are called higher order functions. (defn higher-order-function [f] (f)) @@ -783,12 +779,14 @@ Unnamed closures are useful as arguments to higher order functions. (map #(+ x %) [1 2 3])) => (6 7 8) -Here we have the symbol x bound to 5. We call the map function. Our first argument is an unnamed function that captures x from the environment; a closure. The closure is called on every element of the vector 1 2 3, resulting in a sequence 6 7 8. Higher order functions, closures, and unnamed functions are terms that describe specific uses of functions that allow concise expressions. - +Here we have the symbol x bound to 5. We call the map function. +Our first argument is an unnamed function that captures x from the environment; a closure. +The closure is called on every element of the vector 1 2 3, resulting in a sequence 6 7 8. Higher order functions, closures, and unnamed functions are terms that describe specific uses of functions that allow concise expressions. === Pre- and post-conditions -You can make assertions about inputs and outputs of a function. Place a map after the arguments vector containing :pre and :post, which are a sequence of conditions which must hold true. +You can make assertions about inputs and outputs of a function. +Place a map after the arguments vector containing :pre and :post, which are a sequence of conditions which must hold true. (defn f [x] {:pre [(pos? x)] @@ -804,7 +802,8 @@ You can make assertions about inputs and outputs of a function. Place a map afte (f 1.5) => AssertionError Assert failed: (int? %) -In practise pre and post are rarely used. It is more common to check for a condition and throw an exception: +In practise pre and post are rarely used. +It is more common to check for a condition and throw an exception: (defn f [x] (when-not (pos? x) @@ -817,19 +816,19 @@ In practise pre and post are rarely used. It is more common to check for a condi Or to use a schema or spec (which will be covered later in the course). While pre and post are more concise, they suffer the following drawbacks: -Syntax is easy to get wrong, resulting in no assertion being made -Assertions can be disabled -Less control over error description and handling - +Syntax is easy to get wrong, resulting in no assertion being made Assertions can be disabled Less control over error description and handling === Anonymous functions -We usually define functions with defn, which creates a global var to hold our function. But sometimes the function need not be globally available. We can specify functions without names like so: +We usually define functions with defn, which creates a global var to hold our function. +But sometimes the function need not be globally available. +We can specify functions without names like so: (fn [x] (inc x)) -But we would only do this if we wanted to make use of them in some way. The simplest way to use a function is to call it immediately: +But we would only do this if we wanted to make use of them in some way. +The simplest way to use a function is to call it immediately: ((fn [x] (inc x) @@ -880,7 +879,6 @@ is shorthand for (fn [x] (inc x))) - === Function literals There is a special syntax for creating anonymous functions concisely: @@ -894,7 +892,8 @@ This allows the construction of very terse but powerful expressions: (map #(* % %) [1 2 3 4]) => (1 4 9 16) -I encourage you to use the (fn) form as much as possible instead of the #() form, it is not much more typing and affords more opportunity to name parameters and functions in meaningful ways which will describe your program better. For example: +I encourage you to use the (fn) form as much as possible instead of the #() form, it is not much more typing and affords more opportunity to name parameters and functions in meaningful ways which will describe your program better. +For example: (map (fn square [x] (* x x)) @@ -903,7 +902,6 @@ I encourage you to use the (fn) form as much as possible instead of the #() form Is longer, but provides a semantic summary of the operation and a hint at the expected input values. - === Keyword and variadic arguments (defn f [& args] @@ -932,18 +930,19 @@ Prefer instead ([x] (inc x)) ([x y] (+ x y))) -Clojure supports keyword arguments, but this style is discouraged because it prevents users from passing a map of options. We cannot apply a map to a keyword argument function, so use a map argument instead of keyword arguments. - +Clojure supports keyword arguments, but this style is discouraged because it prevents users from passing a map of options. +We cannot apply a map to a keyword argument function, so use a map argument instead of keyword arguments. === Exercises -Create a new namespace called `fun-functions`. Define the following functions and call them with some test input: +Create a new namespace called `fun-functions`. +Define the following functions and call them with some test input: -* A function that computes the square of an input number. What is the square of 55? +* A function that computes the square of an input number. +What is the square of 55? * A function that takes a number as input, ensures that the number is less than 100, and returns the square of the square of the input. * A function that takes two numbers as input, and returns a vector where the first element is the second input, and the second element is the sum of the first and second input. - === Answers (defn square [x] @@ -969,8 +968,8 @@ Create a new namespace called `fun-functions`. Define the following functions an (fib-step 2 3) => [3 5] - :!sectnums: + == Challenge 1: Corgi Cover eligibility Insuricorp is about to launch a marketing campaign for a new “corgi cover” insurance policy. @@ -983,9 +982,8 @@ You are tasked with building a system to validate applications for the policy. Write a function that takes as input a state and corgi-count, and returns a boolean indicating the person's eligibility for the “corgi cover” policy. - .Test data -[style="literal", options="header"] +[style="literal",options="header"] |=== |Name |State |Corgi count | Existing policy count |Chloe |IL |1 |0 @@ -996,17 +994,16 @@ Write a function that takes as input a state and corgi-count, and returns a bool See `if` `=`. - === Part 2: silver, gold and platinum A focus group of corgi owners has revealed that “corgi cover” needs to be offered at 3 different tiers: “corgi cover silver”, “corgi cover gold”, and “corgi cover platinum”. Platinum is available when covering 7 or more corgis OR covering at least 3 corgis and also having one other policy with Insuricorp. -Gold is available when covering at least 3 corgis. Silver is the original “corgi cover” policy. +Gold is available when covering at least 3 corgis. +Silver is the original “corgi cover” policy. Create a new function that takes an additional argument policy-count and returns a keyword indicating their eligibility. See `cond`. - === Part 3: accept a map as an argument The “corgi cover” applications Insuricorp collect contain more information than necessary to determine eligibility. @@ -1017,7 +1014,6 @@ The data should look something like: {:name "Chloe", :state "IL", :corgi-count 1, :policy-count 0} - === Part 4: cross-reference policies Insuricorp just merged with Megacorp. @@ -1033,11 +1029,13 @@ It should apply the same logic, but make use of the Megacorp data. :sectnums: + == Testing with clojure.test -image:img/testing.jpg[width=300] +image:testing.jpg[width=300] -"The problem is not that testing is the bottleneck. The problem is that you don’t know what’s in the bottle." +"The problem is not that testing is the bottleneck. +The problem is that you don’t know what’s in the bottle." -- Michael Bolton @@ -1075,13 +1073,13 @@ To run all tests in a project from the command line: 0 failures, 0 errors. {:test 0, :pass 0, :fail 0, :error 0, :type :summary} - === lein-test-refresh Lein-test-refresh is a Leiningen plugin that reloads code and re-runs tests when you save a file. https://github.com/jakemcc/lein-test-refresh. -Add lein-test-refresh to your `~/.lein/profiles.clj`. It should look similar to below. +Add lein-test-refresh to your `~/.lein/profiles.clj`. +It should look similar to below. {:user {:plugins [[com.jakemccrary/lein-test-refresh "0.22.0"]]}} @@ -1104,7 +1102,6 @@ If you change `my-test` now to print a new message, the tests are re-run as soon Seeing as saving the file executes code, you can use lein-test-refresh like a REPL. - === Assert with is Let's begin with a false assertion: @@ -1172,7 +1169,6 @@ Occasionally we need to assert that an exception is thrown: (is (thrown-with-msg? Exception #"oh no" (bad 42)))) - === Test fixtures Test fixtures are for setting up and tearing down resources required by your tests. @@ -1200,7 +1196,6 @@ Otherwise the test report can be cluttered. Another common use case is when doing database tests, we can wrap the test execution inside a transaction and rollback after the test completes. This avoids cleaning up data after the tests run, as no data was created. - === Using with-redefs for mocking behavior Often when we are writing tests we want to isolate particular behaviors. @@ -1221,7 +1216,6 @@ We changed the behavior of the str function whose definition is outside the scop We replaced it with an anonymous function that always returns “Goodbye world” regardless of its inputs. Note that we could have used (constantly "Goodbye world") instead, which produces an anonymous function just like the one we defined. - === Debugging While working on a function, sometimes it is useful to print out an intermediary value. @@ -1249,7 +1243,6 @@ This is also very useful when interacting with Java, because you can construct a (.put "b" 2)) => {"a" 1, "b" 2} - === Workflow To demonstrate these techniques in action let's walk through the first 2 parts of Challenge 1 from the manual. @@ -1323,8 +1316,7 @@ There is an action to assist with this called `goto test`. When working from a REPL it is convenient to create a commented form. The commented form can be sent to the REPL for evaluation. -But if we forget to re-evaluate the function that the comment calls in the REPL, -then any changes will not be active in the environment yet. +But if we forget to re-evaluate the function that the comment calls in the REPL, then any changes will not be active in the environment yet. I find it a little distracting to think about what needs to be evaluated. Delegating this responsibility to test refresh frees up my attention to focus on the code. @@ -1334,18 +1326,18 @@ Delegating this responsibility to test refresh frees up my attention to focus on ** paredit etc - === Exercises * Start lein-test-refresh running in your existing project directory. * Create a new namespace in the “test” directory called `training.core-test` * Write a function called `pythag` that returns the square root of the sum of squares for two inputs. -* Write a test containing an assertion that exercises your function. Expect `5` when passing `4` and `3` as arguments. +* Write a test containing an assertion that exercises your function. +Expect `5` when passing `4` and `3` as arguments. * Write another test case with different inputs. * Introduce a bug into pythag to make sure your tests discover the problem. * Fix `pythag` so that all tests pass. -* Copy the test `test-post` from the "with-redefs" section and modify it so that it counts how many times `str` gets called. Call `post` several times and make an assertion about how many times `str` should get called. - +* Copy the test `test-post` from the "with-redefs" section and modify it so that it counts how many times `str` gets called. +Call `post` several times and make an assertion about how many times `str` should get called. === Answers @@ -1369,10 +1361,9 @@ Delegating this responsibility to test refresh frees up my attention to focus on (post "http://service.com/greet") (is (= 3 @c))))) - == Control Flow -image:img/control.jpg[width=300] +image:control.jpg[width=300] "Control your own destiny or someone else will." -- Jack Welch @@ -1415,10 +1406,10 @@ Cond allows for multiple branches. Note that `:else` is not a special keyword, it just happens to be a truthy value. - === Recursion -Functions that call themselves are called recursive. Here is an example of recursion: +Functions that call themselves are called recursive. +Here is an example of recursion: (defn sum-up [coll result] (if (empty? coll) @@ -1434,7 +1425,6 @@ In Clojure there is a special way to do recursion which avoids consuming the sta Recur can only occur at the last position of a function (where scope can be discarded). - === Loops Loop establishes bindings, and allows you to recur back to the start of the loop with new values. @@ -1446,7 +1436,6 @@ Loop establishes bindings, and allows you to recur back to the start of the loop a)) => fib number below 1000 - === Exception handling You can work with exceptions using try catch finally and throw. @@ -1456,7 +1445,6 @@ You can work with exceptions using try catch finally and throw. (catch Exception e (println "cat cannot be incremented"))) - === Comments Anything following a semicolon is a comment @@ -1483,15 +1471,14 @@ Commas are optional and treated as whitespace. (= {:a 1, :b 2, :c 3} {:a 1 :b 2 :c 3}) - === Exercises * Create a function that given a test score between 0 and 100 returns a grade A B C D or F for fail. -* Write a function that takes a number and uses a loop to calculate the factorial of that number. Factorial 5 is 1*2*3*4*5. +* Write a function that takes a number and uses a loop to calculate the factorial of that number. +Factorial 5 is 1*2*3*4*5. * Write a new version of factorial that does not use a loop but recursively calls itself. * Write a loop for the Fibonacci sequence (1 1 2 3 5 8 13) that finds the maximum Fibonacci number less than 100. The sequence is defined by n2 = n1 + n0. - === Answers (def grade [score] @@ -1519,7 +1506,6 @@ Commas are optional and treated as whitespace. (deftest factorial2-test (is (= 120 (factorial2 5)))) - (defn fib [limit] (loop [a 1 b 1] @@ -1529,10 +1515,9 @@ Commas are optional and treated as whitespace. (deftest fib-test (is (= 89 (fib 100)))) - == Functional Programming -image:img/functional.jpg[width=300] +image:functional.jpg[width=300] "If you don't love something, it's not functional, in my opinion." -- Yves Behar @@ -1582,7 +1567,6 @@ We will see some good examples of this philosophy in action later in the course We can use pure function to calculate the next value to be assigned to an atom given the current value. The logic is separate from the side effect. - === Apply and partial If you have 4 numbers and want the max, you can call @@ -1623,7 +1607,6 @@ In the previous example, we could have instead written: (map #(/ 1 %) (range 1 5)) => (1 1/2 1/3 1/4) - === Functions on sequences: map, reduce, and friends To really embrace Clojure is to think in terms of sequences and data structures. @@ -1728,12 +1711,12 @@ Anonymous functions are handy for adding small snippets of logic: (filter #(< % 3) [1 2 3 4 5])) => (0 1 2) -This keeps only numbers less than `3`. Now let's create a sequence of odd/even labels for each number in the vector: +This keeps only numbers less than `3`. +Now let's create a sequence of odd/even labels for each number in the vector: (map #(if (odd? %) "odd" "even") [1 2 3 4 5]) => ("odd" "even" "odd" "even" "odd") - Sequence abstractions are more concise and descriptive than loops, especially when filtering multiple conditions, or performing multiple operations. Clojure also has useful functions for constructing sequences: @@ -1755,8 +1738,9 @@ Using an imperative style loop we can peek into the vector at the previous value print v[i] + v[i-1]; => 3 5 7 9 - -Can we represent this as a sequence? Yes! Imagine two identical sequences offset slightly: +Can we represent this as a sequence? +Yes! +Imagine two identical sequences offset slightly: [1 2 3 4 5] [1 2 3 4 5] @@ -1769,7 +1753,6 @@ The overlapping values are the pairs we want. [2 4]) => (3 7) - Here `1` adds to `2` to make `3`, and `3` adds to `4` to make `7`. `rest` is a function which returns the input sequence without its first element: @@ -1788,11 +1771,11 @@ We called map on the addition function over both input sequences: v => (1 2 3 4 5) (rest v) => (2 3 4 5) -The input sequences were of different lengths, so map stopped when the smallest sequence was exhausted. The result was a new sequence of the pairwise sums: +The input sequences were of different lengths, so map stopped when the smallest sequence was exhausted. +The result was a new sequence of the pairwise sums: (3 5 7 9) - Why are sequence abstractions better than loops? When reading a loop you must comprehend the entire block of code to know what it does. As the loop body grows and changes you must mentally keep track of more complexity. @@ -1811,7 +1794,8 @@ The change is invasive to the imperative loop: The change occurs inside the loop with the addition and multiplication intertwined. -Contrast this with modifying the Clojure sequence. We compose a reduce with the original map expression: +Contrast this with modifying the Clojure sequence. +We compose a reduce with the original map expression: (reduce * (map + v (rest v))) => 945 @@ -1828,9 +1812,8 @@ Look out for opportunities to name your steps by identifying long expressions an Clojure exposes a sequence interface over data collections to a rich set of functions that compose well. Three important functional sequence concepts are: - `filter`, which retains each item in a sequence where some function evaluates to be truthy; - `map`, which selects new values by calling a function over input sequence(s) to create a new sequence; - and `reduce`, which aggregates a sequence and returns a single value. +`filter`, which retains each item in a sequence where some function evaluates to be truthy; +`map`, which selects new values by calling a function over input sequence(s) to create a new sequence; and `reduce`, which aggregates a sequence and returns a single value. I invite you to take the “no loops” challenge. The next time you spot a loop stop and think about what sequence operation the loop represents. @@ -1839,7 +1822,6 @@ It will take time and mental effort, but you will be rewarded with a deeper unde Whenever you see a loop, think about how it could be expressed as a sequence. Sequences are loop abstractions that allow you to ignore the implementation details. - === Threading operators By now, you should be feeling the combinatorial power functions offer. @@ -1881,7 +1863,6 @@ Note that for empty expressions, the parenthesis are optional. (-> 42 (/ 2) inc) => 22 - === Data structures are functions! Maps sets vectors and keywords are functions. @@ -1927,17 +1908,15 @@ And so do vectors: ([1 2 3] 0) => 1 - === Exercises * Write a function that takes two inputs, and returns the sum of the numbers in a range between two input integers, including the two input numbers. * Write a function that produces a sequence of powers of 2: (1 2 4 8 16 …) * Write a function that takes a string and produces a sequence of characters with no vowels. * Write a function that produces a sequence: (1 ½ ⅓ ¼ …) -* Write a function that produces a sequence: (1 ½ ¼ ⅛ …) +* Write a function that produces a sequence: (1 ½ ¼ ⅛ …) * Write a function that produces the Fibonacci sequence (1 1 2 3 5 8 13 21) - === Answers (defn sum-between [a b] @@ -1972,8 +1951,8 @@ And so do vectors: (take 10 (fib-seq)) => (1 1 2 3 5 8 13 21 34 55) - :!sectnums: + == Challenge 2: Processing files Insuricorp branches collect applications for the “corgi cover” policy and periodically send them to headquarters in a large comma separated text file. @@ -1986,7 +1965,6 @@ Create a function that opens a file called corgi-cover-applications.csv and conv Next use that data structure as an input to your validation function and print the result. See `slurp` `line-seq` `clojure.string/split`. - === Part 2: create files The downstream Insuricorp systems will only be operating on corgi cover applications that pass your eligibility check. @@ -1994,7 +1972,6 @@ But the invalid corgi cover applications need to be sent back to the branches so Create a new function that opens two output files and writes to them based upon your eligibility check. The files should be called `eligible-corgi-cover-applications.csv` and `ineligible-corgi-cover-applications.csv`. - === Part 3: validating with reasons A request has come in from several Insuricorp branches that if a person is ineligible for corgi cover, a short reason be supplied. @@ -2002,7 +1979,6 @@ That way the sales reps don't have to spend time figuring out what they need to Create a new validation function that instead of returning a boolean, returns nil if no problems are found, or returns a string with the reason if a problem is found. Create a new processing function that splits the applications into two files based on the new validator. - === Part 4: working with file formats As part of the Megacorp merger, the downstream systems are converting to JSON format. @@ -2010,9 +1986,10 @@ Create a new function that writes JSON data to an `eligible-corgi-cover-applicat :sectnums: + == Java Interop -image:img/interop.jpg[width=300] +image:interop.jpg[width=300] "Sitting in my favorite coffeehouse with a new notebook and a hot cup of java is my idea of Heaven." -- Libba Bray @@ -2033,7 +2010,6 @@ Which is equivalent to the less used variant: (new Date) (new Date 2018 02 17) - === Calling methods Calling a method on a Java object done by prepending a leading period: @@ -2055,7 +2031,6 @@ Inner classes can be accessed using the dollar symbol: java.nio.channels.FileChannel$MapMode/READ_ONLY - === reify `reify` creates an object that conforms to an interface: @@ -2066,10 +2041,10 @@ Inner classes can be accessed using the dollar symbol: (accept [this f] (.isDirectory f)))) -Notice that we didn't define a class? We directly created an object that conforms to the `FileFilter` interface. +Notice that we didn't define a class? +We directly created an object that conforms to the `FileFilter` interface. `reify` is a convenient way to provide a concrete implementation of an interface. - === gen-class and proxy `gen-class` creates a class. @@ -2080,20 +2055,17 @@ In practice the need to create a class from within Clojure is rare, so we won't Again the need for this is rare. (see https://kotka.de/blog/2010/03/proxy_gen-class_little_brother.html if you want to explore this further) - === Including Java classes in Clojure projects You can define Java classes in Java in a separate directory and add :java-source-paths ["src/java"] -To your `project.clj` file -(See https://github.com/technomancy/leiningen/blob/master/doc/MIXED_PROJECTS.md for more other options.) - +To your `project.clj` file (See https://github.com/technomancy/leiningen/blob/master/doc/MIXED_PROJECTS.md for more other options.) == Parallel Programming and Concurrency -image:img/parallel.jpg[width=300] +image:parallel.jpg[width=300] "Our moral traditions developed concurrently with our reason, not as its product." -- Friedrich August von Hayek @@ -2154,7 +2126,6 @@ Functions defined with defn are stored in Vars, allowing for the re-definition o This also enables many of the possibilities of aspect- or context-oriented programming. For instance, you could wrap a function with logging behavior only in certain call contexts or threads. - === Delays, Futures, and Promises ==== Delays @@ -2186,7 +2157,6 @@ We see that it starts in a pending state. Dereferencing `d` with `@` causes the code to run, printing `"Hello world!"` and returning `42`. Notice that the second dereference with `@` does not print `"Hello world!"` again, it only returns the already realized value of `42`. - ==== Futures Futures provide an easy way to spin off a new thread to do some computation or I/O that you will need access to in the future. @@ -2195,14 +2165,12 @@ The difference is that the work begins immediately on another thread. The flow of control is not blocked. If you dereference a future, it will block until the value is available: - (def f (future (Thread/sleep 10000) 42)) f => #object[clojure.core$future_call {:status :pending, :val nil}] - (realized? f) => false @@ -2217,7 +2185,6 @@ If you dereference a future, it will block until the value is available: f #object[clojure.core$future_call {:status :ready, :val 42}] - ==== Promises Promises are used in a similar way to delay or future in that you dereference them for a value, can check if they have a value with `realized?` and they block when you dereference them if they don't have a value until they do. @@ -2236,7 +2203,6 @@ Where they differ is that you don't immediately give them a value, but provide t Dereferencing works on futures, delays, promises, atoms, agents refs and vars. - === Atoms, Refs, and Agents Atoms provide a way to manage shared, synchronous, independent state. @@ -2296,6 +2262,7 @@ Use shutdown-agents to terminate these threads and allow shutdown. :!sectnums: + == Challenge 3: Mocking parallel web requests Insuricorp and Megacorp are integrating their IT systems. @@ -2311,7 +2278,6 @@ Set up a function called fetch-megacorp-policies to do the web request but leave Create a test that changes the behavior of fetch-megacorp-policies to behave as though it were a web request; make it pause for 100ms before returning the policies that the person has. Set up a test that exercises the eligibility checks using the mocked version of a web request. - === Part 2: Report the how long it takes In Java you might write something like this: @@ -2322,7 +2288,6 @@ In Java you might write something like this: Implement a similar solution in Clojure. - === Part 3: Make parallel requests The web service you are using can handle multiple requests faster than a series of requests. @@ -2330,7 +2295,6 @@ It operates fastest with up to 20 connections. Modify your code such that multiple requests are made simultaneously. Compare the timing results to confirm the operations are happening in parallel. - === Part 4: Error handling Modify your mock of fetch-megacorp-policies such that it throws an exception randomly about 10% of the time. @@ -2341,9 +2305,10 @@ Then create another test where the exception is thrown 100% of the time, and the :sectnums: + == Polymorphism and Types -image:img/types.jpg[width=300] +image:types.jpg[width=300] "You need a lot of different types of people to make the world better." -- Joe Louis @@ -2391,7 +2356,6 @@ Because keywords are functions, it's quite common to use a keyword as a dispatch (defmulti draw :shape) - === Protocols A protocol is a named set of named methods and their signatures, defined using defprotocol: @@ -2479,7 +2443,6 @@ Expands into: {:baz (fn ([x] ...) ([x y zs] ...)) :bar (fn [x y] ...)}) - === Creating types with defrecord and deftype `deftype`, `defrecord`, and `reify` provide the mechanism for defining implementations of abstractions, and instances of those implementations. @@ -2498,7 +2461,6 @@ It is preferred to use the built-in datatypes (vectors, maps, sets) to represent (->Circle 10) (->Square 5 11) - ==== Defrecord This example shows how to implement a Java interface in defrecord. @@ -2523,10 +2485,9 @@ Call the method on the `thing` instance and pass `"bar"`: (.getContentTypeFor thing "bar") - == Interacting with a Database -image:img/database.jpg[width=300] +image:database.jpg[width=300] "You can have data without information, but you cannot have information without data." -- Daniel Keys Moran @@ -2558,7 +2519,6 @@ In the Clojure project we require jdbc, and set up a db connection url. Now we are all set to start doing queries. - === Inserting, updating and retrieving data First we will create a table called messages inside the database with a text field named message, and then insert some rows. @@ -2608,7 +2568,6 @@ If you want to redo any steps, remember that you can always drop the table and s (jdbc/execute! db "drop table messages") - === Solutions for SQL management HoneySQL https://github.com/jkk/honeysql can be used to build SQL statements from data structures. @@ -2617,7 +2576,6 @@ For example if the user can check a checkbox to enable an additional clause in a In such cases it is more convenient to use Clojure's capabilities for manipulating data structures. However if you do not need to do such manipulation, I recommend using plain old SQL queries in their original text form, as you can run them interactively from an SQL prompt much easier that way. - === Exercises * Create and populate a table `person` with two columns; `id`, `name`. @@ -2625,7 +2583,6 @@ However if you do not need to do such manipulation, I recommend using plain old * Create and populate a table `person_policy` with two columns; `person_id`, `policy_id` * Write a function that given a person name queries all the policies associated with them. - === Answers (ns messenger.core @@ -2668,8 +2625,8 @@ However if you do not need to do such manipulation, I recommend using plain old (find-policies "Billy") => ({:name "Corgi Cover"}) - :!sectnums: + == Challenge 4: Corgi Cover Database Sending files around is proving to be problematic. @@ -2683,19 +2640,16 @@ Using the database of your choice, set up an initial database for the Corgi Cove In the code, connect to the database and create the initial table required. You can use whatever schema you like, but the first requirement is to store the applications with exactly the same data as was retrieved from the file format in Challenge 2. - === Part 2: Populate the data Modify the code to store the applications as they are processed, and the result of the eligibility check. - === Part 3: Write a spec Ensure that all records processed from the files meets your expectations for required fields. Write a spec that explicitly defines what should be in the applications. Validate the spec on the incoming records. - === Part 4: Extending to Poodle Protection Insuricorp is about to launch a new policy called “Poodle Protection”. @@ -2705,9 +2659,10 @@ For now the only difference with the rules from “Corgi Cover” is that “Poo :sectnums: + == Spec -image:img/spec.jpg[width=300] +image:spec.jpg[width=300] "Much of the essence of building a program is in fact the debugging of the specification." -- Fred Brooks @@ -2725,7 +2680,6 @@ To start working with spec, require the `clojure.spec.alpha` namespace at the RE (ns my.ns (:require [clojure.spec.alpha :as s])) - === Validation Any function that takes a single argument and returns a truthy value is a valid predicate spec. @@ -2761,7 +2715,6 @@ Let's create a spec that combines several predicates into a composite spec with (s/valid? ::big-even 100000) => true - === Conforming We can also use `s/or` to specify two alternatives: @@ -2789,7 +2742,6 @@ Explain can be used to report why a value does not conform to a spec. In addition to `explain`, you can use `explain-str` to receive the error messages as a string or `explain-data` to receive the errors as data. - === Maps Clojure programs rely heavily on passing around maps of data. @@ -2845,7 +2797,6 @@ Spec has explicit support for pre and post conditions using `fdef`. The `:ret` spec uses fspec to declare that the returning function takes and returns a number. Even more interesting, the `:fn` spec can state a general property that relates the `:args` (where we know `x`) and the result we get from invoking the function returned from adder, namely that adding `0` to it should return `x`. - === A game of cards Here's a bigger set of specs to model a game of cards: @@ -2899,7 +2850,6 @@ We can also specify a :fn spec to verify that the count of cards in the game bef :fn #(= (total-cards (-> % :args :game)) (total-cards (-> % :ret)))) - === Generators A key design constraint of spec is that all specs are also designed to act as generators of sample data that conforms to the spec (a critical requirement for property-based testing). @@ -2943,7 +2893,6 @@ We can even generate an entire game: (gen/generate (s/gen ::game)) - It's useful to spec (and generate) values in a range. For example, in the case of a range of integer values, use `int-in` to spec a range: @@ -2955,7 +2904,6 @@ Spec also includes `inst-in` for a range of Dates, and `double-in` for double ra To learn more about generators, read the test.check tutorial https://clojure.github.io/test.check/intro.html. - === Instrumentation and Testing Spec provides a set of development and testing functionality in the clojure.spec.test.alpha namespace, which we can include with: @@ -3012,7 +2960,6 @@ Imagine instead that we made an error in the `ranged-rand` code and swapped star This broken function will still create random integers, just not in the expected range. Our `:fn` spec will detect the problem when checking the var: - (stest/abbrev-result (first (stest/check `ranged-rand))) => ({... :result {... @@ -3075,14 +3022,15 @@ The first call here instruments and stubs `invoke-service`. The second and third calls demonstrate that calls to `invoke-service` now return generated results (rather than hitting a service). Finally, we can use check on the higher level function to test that it behaves properly based on the generated stub results returned from `invoke-service`. -There is even more to spec! Once you are comfortable with the basics you can learn more at https://clojure.org/guides/spec. - +There is even more to spec! +Once you are comfortable with the basics you can learn more at https://clojure.org/guides/spec. == Macros -image:img/macros.jpg[width=300] +image:macros.jpg[width=300] -"I never think about myself as an artist working in this time. I think about it in macro." +"I never think about myself as an artist working in this time. +I think about it in macro." -- Frank Ocean Macros manipulate the operand forms instead of evaluating them as input arguments. @@ -3112,7 +3060,6 @@ You can examine the expansion using `macroexpand-1`: ([my-namespace/x] (clojure.core/* my-namespace/x my-namespace/x)))) - === Defining macros Consider two different definitions of zen: @@ -3142,7 +3089,6 @@ Macros themselves are really just functions with a `:macro` flag set in their me This last part is less obvious... but think back to `zen1`... `x` was a list, we returned `x`, but the final result wasn't a list... it was `3`. The list was evaluated as a function call to `+`, resulting in `3`. - === Syntax quoting To help write macros there is a special quoting form called syntax-quote. @@ -3184,7 +3130,6 @@ Fully qualified symbols avoids one source of collisions, but there is another: (bad a) => 2 - This might seem confusing, unless you notice that: (macroexpand-1 '(bad a)) @@ -3208,7 +3153,6 @@ To avoid this situation Clojure provides a let gensyms form which will produce a The `let` binding `a#` expands out to a randomly generated symbol unlikely to collide with existing symbols. - === Code as data You may have noticed when we write a macro, we are really writing a function that produces code. @@ -3224,7 +3168,6 @@ When working on a non-trivial macro a good strategy is: Stated another way; keep the macro as small as possible, and offload transformations to functions. - === Exercises Create the following macros and test cases: @@ -3233,13 +3176,15 @@ Create the following macros and test cases: (ignore (println "hello???") (inc 42)) -* Define your own version of the when macro. When is like if, but only has one branch and allows multiple statements. +* Define your own version of the when macro. +When is like if, but only has one branch and allows multiple statements. (when2 (pos? x) (println "Positive:" x) (inc x)) -* Write a spy macro. Spy wraps an expression and prints out its value. +* Write a spy macro. +Spy wraps an expression and prints out its value. (* (spy (+ 1 2)) 3) => Expression (+ 1 2) has value 3 @@ -3249,7 +3194,6 @@ Create the following macros and test cases: (or2 (pos? 1) (println "does not execute")) - === Answers (defmacro ignore [expr] nil) @@ -3257,7 +3201,6 @@ Create the following macros and test cases: (defmacro when2 [test & body] (list 'if test (cons 'do body)) - (defmacro spy [expr] `(let [result# ~expr] (println "Expression" '~expr "has value" result#) @@ -3279,10 +3222,9 @@ Create the following macros and test cases: `(let [or# ~x] (if or# or# (or ~@next))))) - == Further reading -image:img/further-reading.jpg[width=300] +image:further-reading.jpg[width=300] "You can never get a cup of tea large enough or a book long enough to suit me." -- C.S. Lewis @@ -3292,9 +3234,9 @@ Don't feel frustrated if the code comes slowly at first. Being a great programmer requires thinking. You will only reach your true potential expressing code in ways that empower you rather than constrain you. -Further exercises: https://www.4clojure.com +Further exercises: https://www.4clojure.com Clojure for Java Programmers - Rich Hickey -* Part 1: https://www.youtube.com/watch?v=P76Vbsk_3J0 -* Part 2: https://www.youtube.com/watch?v=hb3rurFxrZ8 +* Part 1: https://www.youtube.com/watch?v=P76Vbsk_3J0 +* Part 2: https://www.youtube.com/watch?v=hb3rurFxrZ8 diff --git a/docs/talk.adoc b/docs/meta/making-enterprise-clojure.adoc similarity index 82% rename from docs/talk.adoc rename to docs/meta/making-enterprise-clojure.adoc index d8c22c8..9540782 100644 --- a/docs/talk.adoc +++ b/docs/meta/making-enterprise-clojure.adoc @@ -1,22 +1,14 @@ -= Talk += Making Enterprise Clojure :copyright: Timothy Pratley :license: Eclipse Public License http://www.eclipse.org/legal/epl-v10.html -:customcss: slides.css -:revealjs_theme: simple -:revealjs_center: false -:revealjs_controls: false -:revealjs_transition: none -:revealjs_history: true -:docinfo: shared -:notitle: -:icons: font - +include::../docinfo.attrs[] +imgagedir::..img/talk [state=title] == What are you waiting for? .Waiting patiently for Clojure adoption in the workplace -image::img/talk/corgi-waiting.jpg[Corgi waiting at door] +image::talk/corgi-waiting.jpg[Corgi waiting at door] [NOTE.speaker] -- @@ -30,12 +22,12 @@ image::img/talk/corgi-waiting.jpg[Corgi waiting at door] * Why did we build a course you might ask? -- - [state=title] == Train 28 developers in 24 hours .Challenge accepted -image::img/talk/corgi-challenge.jpg[Corgi dodging poles] +image::talk/corgi-challenge.jpg[Corgi dodging poles] + [NOTE.speaker] -- * A company asked me to take 28 developers and get them ready to start using Clojure day one @@ -44,13 +36,11 @@ image::img/talk/corgi-challenge.jpg[Corgi dodging poles] * Challenge accepted -- - [state=title] == Enterprise Clojure Training .An open source training course for Clojure -image::img/corgi.jpg[Art] - +image::corgi.jpg[Art] == Target audience @@ -61,7 +51,6 @@ image::img/corgi.jpg[Art] NOTE: This is a boot camp - == The programme * 2 days, 10 hours per day @@ -69,7 +58,6 @@ NOTE: This is a boot camp * 1 hour for lunch * 2 follow up webinars, 2 hours each - == Sections |=== @@ -80,7 +68,6 @@ NOTE: This is a boot camp |Challenges|When ahead |=== - == Course material |=== @@ -90,7 +77,6 @@ NOTE: This is a boot camp |link:https://www.youtube.com/playlist?list=PLHl2BS5yLgWE96d5Q07C5Ou8SB7QteFAN[Videos] |=== - == The value of questions * Questions and answers @@ -100,7 +86,6 @@ NOTE: This is a boot camp NOTE: *Enterprise Clojure Training is a resource for teachers* - == Adaptable course material * Easy access (open source) @@ -114,13 +99,11 @@ NOTE: *Enterprise Clojure Training is a resource for teachers* * Merge first, ask questions later -- - [state=title] == Creating a course .Under the hood of Enterprise Clojure Training -image::img/talk/corgi-hoodie.jpg[Corgi wearing a hoodie] - +image::talk/corgi-hoodie.jpg[Corgi wearing a hoodie] == Asciidoctor @@ -131,7 +114,6 @@ image::img/talk/corgi-hoodie.jpg[Corgi wearing a hoodie] NOTE: Asciidoctor is an improved implementation of Asciidoc - == Flavor of markdown ---- @@ -146,7 +128,6 @@ One sentence per line; rearrange with ease. (defn example [] "code") ---- - == The documents https://raw.githubusercontent.com/timothypratley/enterprise-clojure-training/master/docs/manual.adoc[manual.adoc] @@ -161,7 +142,6 @@ https://raw.githubusercontent.com/timothypratley/enterprise-clojure-training/mas https://raw.githubusercontent.com/timothypratley/enterprise-clojure-training/master/docs/index.adoc[index.adoc] - == Better for publishing * Automatic table of contents @@ -173,7 +153,6 @@ https://raw.githubusercontent.com/timothypratley/enterprise-clojure-training/mas TIP: Notes/tips/warnings - == Excellent tooling * Produces attractive content by default @@ -181,26 +160,22 @@ TIP: Notes/tips/warnings * Editor plugins * Chrome live reloading extension - == IntelliJ IDEA .Asciidoctor plugin -image::img/talk/idea-asciidoctor.png[] - +image::talk/idea-asciidoctor.png[] == Chrome live reloading * Renders in the browser direct from `.adoc` * Updates when the file changes - == Slides * Based on RevealJS * `asciidoctor-revealjs` plugin * Requires a build step - == Event Notify Test Runner (entr) * Watches files @@ -208,7 +183,6 @@ image::img/talk/idea-asciidoctor.png[] * Install with brew or apt * `./watch.sh` - == Deploying * Github Pages build `adoc` via Jekyll @@ -216,7 +190,6 @@ image::img/talk/idea-asciidoctor.png[] * TravisCI auto-build/deploy * Change text, push, done! - == Training options https://clojure.org/community/training @@ -229,12 +202,11 @@ https://clojure.org/community/training * If you want to add a resource to this page, submit a pull request or contact Alex Miller. -- - [state=title] == Observations .Learning Clojure is challenging -image::img/talk/corgi-training.jpg[Corgi jumping a bar] +image::talk/corgi-training.jpg[Corgi jumping a bar] [NOTE.speaker] -- @@ -245,11 +217,10 @@ image::img/talk/corgi-training.jpg[Corgi jumping a bar] * But it is definitely a challenge -- - == Change of thinking required .Woha, I get it! -image::img/talk/corgi-lights.png[Corgi in lights] +image::talk/corgi-lights.png[Corgi in lights] [NOTE.speaker] -- @@ -266,7 +237,6 @@ image::img/talk/corgi-lights.png[Corgi in lights] * Embrace the idioms -- - == Simple ain't easy "I had high hopes for Clojure for a while, but they're fairly user-hostile, even if they think (and loudly assert) that they aren't." @@ -277,14 +247,15 @@ image::img/talk/corgi-lights.png[Corgi in lights] * It's easy to forget our own path to enlightenment * New users face difficult obstacles * Example: just installing Java, Leiningen, IDEA -* Java - which version? Quite easy to choose the wrong thing +* Java - which version? +Quite easy to choose the wrong thing * Lein - doesn't work on Windows * IDEA - make sure you configure your SDK! - - And get Cursive! - - And now your parenthesis are out of your control +- And get Cursive! +- And now your parenthesis are out of your control * Objectively I think it is fair to call Clojure user hostile - - Error messages - - Tooling setup +- Error messages +- Tooling setup * We can see beyond these difficulties * But a little empathy goes a long way with beginners * They often blocked with something easily resolved by a question @@ -292,7 +263,6 @@ image::img/talk/corgi-lights.png[Corgi in lights] * Part of that is acknowledging that things aren't perfect -- - == Tips for learning * Focus on the language first (the mental shift will come later) @@ -303,7 +273,6 @@ image::img/talk/corgi-lights.png[Corgi in lights] * 4Clojure * Read and follow the style guide - == Join the community * ClojureVerse @@ -312,15 +281,14 @@ image::img/talk/corgi-lights.png[Corgi in lights] * Clojurians "I am so happy with, and proud of, the Clojure community. - People are helpful, and respectful, and positive." +People are helpful, and respectful, and positive." -- Rich Hickey - [state=title] == At work .How did I get here? -image::img/corgi.jpg[Corgi with tie and glasses] +image::corgi.jpg[Corgi with tie and glasses] [NOTE.speaker] -- @@ -330,11 +298,10 @@ image::img/corgi.jpg[Corgi with tie and glasses] * And to do that I must first begin with how I came to Clojure -- - == Rewind << 2007 .My first encounter with Lisp -image::img/talk/corgi-encounter.jpg[Corgi encounters budgie] +image::talk/corgi-encounter.jpg[Corgi encounters budgie] [NOTE.speaker] -- @@ -353,17 +320,14 @@ image::img/talk/corgi-encounter.jpg[Corgi encounters budgie] * Then this happened: -- - == Rich Hickey - 2007-10-17 [.small] -- Hello, -As someone interested in Foil or jFli, I thought you might want to -know about my latest project - Clojure, a dialect of Lisp for the -JVM. It's currently alpha, but fairly complete. I'm looking for some -feedback from some intrepid folks willing to kick the tires. +As someone interested in Foil or jFli, I thought you might want to know about my latest project - Clojure, a dialect of Lisp for the JVM. It's currently alpha, but fairly complete. +I'm looking for some feedback from some intrepid folks willing to kick the tires. http://clojure.sourceforge.net/ Please use the Google group for feedback: @@ -384,10 +348,9 @@ Rich * Clojure was for personal projects -- - == 2010 -image::img/talk/corgi-team.jpg[Corgis carrying a branch together] +image::talk/corgi-team.jpg[Corgis carrying a branch together] [NOTE.speaker] -- @@ -400,12 +363,10 @@ image::img/talk/corgi-team.jpg[Corgis carrying a branch together] * I didn't think it was possible on my own -- - == Why didn't I convert my team to Clojure? .I can't do this on my own -image::img/talk/corgi-asleep-at-vet.jpg[Corgi asleep at the vet] - +image::talk/corgi-asleep-at-vet.jpg[Corgi asleep at the vet] [NOTE.speaker] -- @@ -419,31 +380,28 @@ image::img/talk/corgi-asleep-at-vet.jpg[Corgi asleep at the vet] * What can we do about it? -- - == Many people don't know Clojure .Looking different can feel weird -image::img/talk/corgi-weird.jpeg[] +image::talk/corgi-weird.jpeg[] [NOTE.speaker] -- * It's natural to be suspicious of what we don't know * They have real concerns - - Is it really better? - - Is this for "regular" programmers? - - Can we learn it? - - Who will support it? - - Can we hire people? - - Why are you wasting your time on something so obscure? +- Is it really better? +- Is this for "regular" programmers? +- Can we learn it? +- Who will support it? +- Can we hire people? +- Why are you wasting your time on something so obscure? * Explaining isn't enough -- - == What are *developers* waiting for? .Jobs! -image::img/talk/clojure-jobs.png[Clojure job listings] - +image::talk/clojure-jobs.png[Clojure job listings] [NOTE.speaker] -- @@ -454,11 +412,10 @@ image::img/talk/clojure-jobs.png[Clojure job listings] * Clojure/conj sponsors and attendees are often recruiting -- - == What are *companies* waiting for? .Developers! -image::img/talk/corgi-developer.jpg[Corgi using a laptop] +image::talk/corgi-developer.jpg[Corgi using a laptop] [NOTE.speaker] -- @@ -469,40 +426,38 @@ image::img/talk/corgi-developer.jpg[Corgi using a laptop] * You don't have to hire for Clojure specifically; good programmers will become experts very quickly -- - == What are *learners* waiting for? .A market! -image::img/talk/corgi-student.png[Corgi student] +image::talk/corgi-student.png[Corgi student] [NOTE.speaker] -- * The first question a beginner asks is which language should I learn? * The standard answer right now is JavaScript, Java, Python * Clojure is a better tools for thought - - Maps/sets/vectors - - Concise algorithm expression - - Focus on important concepts - - Less baggage and distractions - - Reach mastery of algorithmic concepts faster +- Maps/sets/vectors +- Concise algorithm expression +- Focus on important concepts +- Less baggage and distractions +- Reach mastery of algorithmic concepts faster * Supply and demand is not as simple as "most used" - - The most popular market is also the most average market - - Supply and demand does not favor the most popular - - You will benefit from an imbalance in supply to demand found in smaller markets +- The most popular market is also the most average market +- Supply and demand does not favor the most popular +- You will benefit from an imbalance in supply to demand found in smaller markets * The future is ... the future - - I have used a different programming language in every job I have worked - - It's not certain what language you will be writing in the future - - It's not valuable to avoid learning more than one language - - Good employers value skills, knowledge and culture over specific technology experience - - Optimize for computer science mastery, not basics - - Choose a language that helps you grow +- I have used a different programming language in every job I have worked +- It's not certain what language you will be writing in the future +- It's not valuable to avoid learning more than one language +- Good employers value skills, knowledge and culture over specific technology experience +- Optimize for computer science mastery, not basics +- Choose a language that helps you grow -- - == What are *employees* waiting for? .A greenfield! -image::img/talk/corgi-on-field.jpg[Corgi on a green field] +image::talk/corgi-on-field.jpg[Corgi on a green field] [NOTE.speaker] -- @@ -511,11 +466,10 @@ image::img/talk/corgi-on-field.jpg[Corgi on a green field] * But why not do some boring stuff too? -- - == What are *employers* waiting for? .A framework! -image::img/talk/corgi-in-crate.jpg[Corgi in dog crate] +image::talk/corgi-in-crate.jpg[Corgi in dog crate] [NOTE.speaker] -- @@ -525,11 +479,10 @@ image::img/talk/corgi-in-crate.jpg[Corgi in dog crate] * That's all totally possible -- - == 2014 .Enterprise facing Clojure startup -image::img/talk/corgi-herding-ducks.jpg[Corgi herding ducks] +image::talk/corgi-herding-ducks.jpg[Corgi herding ducks] [NOTE.speaker] -- @@ -544,11 +497,10 @@ image::img/talk/corgi-herding-ducks.jpg[Corgi herding ducks] * Maintainable -- - == 2018 .Customer facing Clojure startup -image::img/talk/suiteness.png[Suiteness logo] +image::talk/suiteness.png[Suiteness logo] [NOTE.speaker] -- @@ -557,19 +509,17 @@ image::img/talk/suiteness.png[Suiteness logo] * Or you can find all the space and affordability of a vacation rental with the amenities of a hotel. * Our biggest users are families and group events. * Built on Clojure/ClojureScript - - make recruiting easy - - productive tech stack +- make recruiting easy +- productive tech stack -- - == Suiteness video::cc9Xg2uaEgA[youtube] - == Is Clojure Enterprise? -image::img/talk/popularity.png[Language popularity] +image::talk/popularity.png[Language popularity] [NOTE.speaker] -- @@ -581,22 +531,20 @@ image::img/talk/popularity.png[Language popularity] * It's getting popular -- - == Or not? -image::img/talk/most-used-jvm.jpg[Most used JVM languages] +image::talk/most-used-jvm.jpg[Most used JVM languages] [NOTE.speaker] -- * Still a niche * Secret weapon, and that's O.K. * Value principles over popularity - - simplicity - - thinking - - community +- simplicity +- thinking +- community -- - == Does it matter? * Clojure makes me happy @@ -605,11 +553,10 @@ image::img/talk/most-used-jvm.jpg[Most used JVM languages] * There is uncertainty * There is opportunity - == Clojure adoption .A challenging knot to approach -image::img/talk/corgi-rope.jpg[Corgi pulling a rope] +image::talk/corgi-rope.jpg[Corgi pulling a rope] [NOTE.speaker] -- @@ -623,7 +570,6 @@ image::img/talk/corgi-rope.jpg[Corgi pulling a rope] * Don't over-think it, see past implied limitations, be decisive -- - == Clojure is a great choice for your company * Books @@ -633,11 +579,10 @@ image::img/talk/corgi-rope.jpg[Corgi pulling a rope] TIP: *Build better stuff faster* - == So what are you waiting for? .Start using Clojure already! -image::img/talk/corgi-training.jpeg[] +image::talk/corgi-training.jpeg[] [NOTE.speaker] -- @@ -645,7 +590,6 @@ image::img/talk/corgi-training.jpeg[] * I'd love to help you get a strong start -- - [state=title] == Thank you @@ -655,4 +599,4 @@ timothypratley@gmail.com elango.cheran@gmail.com -image::img/art1.jpg[Art] +image::art1.jpg[Art] diff --git a/docs/setup.adoc b/docs/setup.adoc index 22a0760..19ae1bb 100644 --- a/docs/setup.adoc +++ b/docs/setup.adoc @@ -25,7 +25,6 @@ I recommend watching the Windows installation regardless of your operating syste O.K. let's get installing! - == Installation === Windows @@ -43,7 +42,6 @@ Install version 1.8 whichever the latest patch version is. Java 9 and higher has backward compatibility breakages that not all the tools have caught up with yet, so let's avoid those for now. Once you have installed the downloaded file open a command prompt and run `java -version` to check that the installation worked correctly. - ==== Leiningen Next we are going to install Leiningen. @@ -68,7 +66,6 @@ Right now we only have one dependency, Clojure 1.8.0. We can add other libraries that we want to use here later. The `core.clj` file is where we are going to write our code. - ==== Intellij IDEA We need an editor to edit the code with. @@ -77,7 +74,6 @@ Search for https://www.jetbrains.com/idea[Intellij IDEA]. Download and install the Community Edition because it is free. There is a checkbox to allow IDEA to download it's own JRE and I recommend you check that as they claim it will help the IDEA run better. - ==== Cursive Clojure plugin Now that IDEA is installed and running, click "configure" -> "plugins". @@ -98,7 +94,8 @@ We need to restart IDEA again. Now the words are understood so we are good to go. So what can IDEA and Cursive do for us? -Structural editing. The most important thing to know about structural editing is how to turn it off. +Structural editing. +The most important thing to know about structural editing is how to turn it off. You can do that in the bottom right hand corner. Structural editing attempts to preserve balanced parenthesis. We will be creating nested expressions in parentheses quite a lot, so this can be very helpful. diff --git a/examples/aot/little-aot/src/little_aot/bootstrap.clj b/examples/aot/little-aot/src/little_aot/bootstrap.clj index 14dcbe4..6ddce5d 100644 --- a/examples/aot/little-aot/src/little_aot/bootstrap.clj +++ b/examples/aot/little-aot/src/little_aot/bootstrap.clj @@ -1,6 +1,6 @@ (ns little-aot.bootstrap (:gen-class)) -(defn -main [& args] +(defn -main [& _args] (require (symbol "little-aot.core")) ((resolve (symbol "little-aot.core" "-main")))) diff --git a/examples/aot/little-aot/src/little_aot/core.clj b/examples/aot/little-aot/src/little_aot/core.clj index f77cbde..01d0a55 100644 --- a/examples/aot/little-aot/src/little_aot/core.clj +++ b/examples/aot/little-aot/src/little_aot/core.clj @@ -1,5 +1,5 @@ (ns little-aot.core (:require [little-aot.dont-aot-me])) -(defn -main [& args] +(defn -main [& _args] (println "Hello, World!")) diff --git a/examples/aot/no-aot/CHANGELOG.md b/examples/aot/no-aot/CHANGELOG.md deleted file mode 100644 index 244e813..0000000 --- a/examples/aot/no-aot/CHANGELOG.md +++ /dev/null @@ -1,24 +0,0 @@ -# Change Log -All notable changes to this project will be documented in this file. This change log follows the conventions of [keepachangelog.com](http://keepachangelog.com/). - -## [Unreleased] -### Changed -- Add a new arity to `make-widget-async` to provide a different widget shape. - -## [0.1.1] - 2018-04-18 -### Changed -- Documentation on how to make the widgets. - -### Removed -- `make-widget-sync` - we're all async, all the time. - -### Fixed -- Fixed widget maker to keep working when daylight savings switches over. - -## 0.1.0 - 2018-04-18 -### Added -- Files from the new template. -- Widget maker public API - `make-widget-sync`. - -[Unreleased]: https://github.com/your-name/no-aot/compare/0.1.1...HEAD -[0.1.1]: https://github.com/your-name/no-aot/compare/0.1.0...0.1.1 diff --git a/examples/aot/no-aot/src/no_aot/core.clj b/examples/aot/no-aot/src/no_aot/core.clj index d3214f0..eb99a9a 100644 --- a/examples/aot/no-aot/src/no_aot/core.clj +++ b/examples/aot/no-aot/src/no_aot/core.clj @@ -1,4 +1,4 @@ (ns no-aot.core) -(defn -main [& args] +(defn -main [& _args] (println "Hello, World!")) diff --git a/examples/aot/no-aot/test/no_aot/core_test.clj b/examples/aot/no-aot/test/no_aot/core_test.clj index fda4b3a..f6a9192 100644 --- a/examples/aot/no-aot/test/no_aot/core_test.clj +++ b/examples/aot/no-aot/test/no_aot/core_test.clj @@ -1,4 +1,4 @@ (ns no-aot.core-test - (:require [clojure.test :refer :all] - [no-aot.core :refer :all])) + #_(:require [clojure.test :refer [deftest is testing]] + [no-aot.core :as n])) diff --git a/examples/aot/too-much-aot/src/too_much_aot/core.clj b/examples/aot/too-much-aot/src/too_much_aot/core.clj index 1911c7f..fe72547 100644 --- a/examples/aot/too-much-aot/src/too_much_aot/core.clj +++ b/examples/aot/too-much-aot/src/too_much_aot/core.clj @@ -2,5 +2,5 @@ (:require [too-much-aot.dont-aot-me]) (:gen-class)) -(defn -main [& args] +(defn -main [& _args] (println "Hello, World!")) diff --git a/examples/communicating-services/customer-data-service/src/customer_data_service/db.clj b/examples/communicating-services/customer-data-service/src/customer_data_service/db.clj index 43542e4..4ea34f5 100644 --- a/examples/communicating-services/customer-data-service/src/customer_data_service/db.clj +++ b/examples/communicating-services/customer-data-service/src/customer_data_service/db.clj @@ -8,7 +8,7 @@ (defn find-by-name [s] (vec - (for [[k v] @backend + (for [[_k v] @backend :when (str/includes? (some-> v (:name) (str/lower-case)) (str/lower-case s))] diff --git a/examples/communicating-services/customer-data-service/src/customer_data_service/handler.clj b/examples/communicating-services/customer-data-service/src/customer_data_service/handler.clj index f7ef610..a0c3eaa 100644 --- a/examples/communicating-services/customer-data-service/src/customer_data_service/handler.clj +++ b/examples/communicating-services/customer-data-service/src/customer_data_service/handler.clj @@ -1,18 +1,18 @@ (ns customer-data-service.handler - (:require [compojure.core :refer :all] + (:require [compojure.core :as c] [compojure.route :as route] - [ring.middleware.defaults :refer [wrap-defaults site-defaults]] + [ring.middleware.defaults :as d] [customer-data-service.db :as db] [ring.util.response :as response])) -(defroutes app-routes - (GET "/status" [] +(c/defroutes app-routes + (c/GET "/status" [] (response/response "Customer data service is running")) - (GET "/customer/:id" [id] + (c/GET "/customer/:id" [id] (response/response (db/find-by-id id))) - (GET "/find-by-name/:customer-name" [customer-name] + (c/GET "/find-by-name/:customer-name" [customer-name] (response/response (db/find-by-name customer-name))) (route/not-found "Not Found")) (def app - (wrap-defaults app-routes site-defaults)) + (d/wrap-defaults app-routes d/site-defaults)) diff --git a/examples/communicating-services/customer-data-service/test/customer_data_service/handler_test.clj b/examples/communicating-services/customer-data-service/test/customer_data_service/handler_test.clj index 92bc724..282da7a 100644 --- a/examples/communicating-services/customer-data-service/test/customer_data_service/handler_test.clj +++ b/examples/communicating-services/customer-data-service/test/customer_data_service/handler_test.clj @@ -1,14 +1,14 @@ (ns customer-data-service.handler-test - (:require [clojure.test :refer :all] + (:require [clojure.test :refer [deftest testing is]] [ring.mock.request :as mock] - [customer-data-service.handler :refer :all])) + [customer-data-service.handler :as h])) (deftest test-app (testing "main route" - (let [response (app (mock/request :get "/"))] + (let [response (h/app (mock/request :get "/"))] (is (= (:status response) 200)) (is (= (:body response) "Hello World")))) (testing "not-found route" - (let [response (app (mock/request :get "/invalid"))] + (let [response (h/app (mock/request :get "/invalid"))] (is (= (:status response) 404))))) diff --git a/examples/communicating-services/insurance-policy-application-processor/src/insurance_policy_application_processor/core.clj b/examples/communicating-services/insurance-policy-application-processor/src/insurance_policy_application_processor/core.clj index 3f64856..c3e8812 100644 --- a/examples/communicating-services/insurance-policy-application-processor/src/insurance_policy_application_processor/core.clj +++ b/examples/communicating-services/insurance-policy-application-processor/src/insurance_policy_application_processor/core.clj @@ -5,7 +5,7 @@ (defn fetch-customer [id] (client/get (str "http://customer-data-service:3000/customer/" id))) -(defn -main [& args] +(defn -main [& _args] (println "Starting Insurance Policy Application Processor...") (Thread/sleep 5000) (prn (fetch-customer "1")) diff --git a/examples/communicating-services/insurance-policy-application-processor/test/insurance_policy_application_processor/core_test.clj b/examples/communicating-services/insurance-policy-application-processor/test/insurance_policy_application_processor/core_test.clj index ed46f1f..4f53c0f 100644 --- a/examples/communicating-services/insurance-policy-application-processor/test/insurance_policy_application_processor/core_test.clj +++ b/examples/communicating-services/insurance-policy-application-processor/test/insurance_policy_application_processor/core_test.clj @@ -1,7 +1,4 @@ (ns insurance-policy-application-processor.core-test - (:require [clojure.test :refer :all] - [insurance-policy-application-processor.core :refer :all])) + (:require #_[clojure.test :refer [deftest is testing]] + #_[insurance-policy-application-processor.core :as p])) -(deftest a-test - (testing "FIXME, I fail." - (is (= 0 1)))) diff --git a/examples/communicating-services/insurance-policy-service/src/insurance_policy_service/handler.clj b/examples/communicating-services/insurance-policy-service/src/insurance_policy_service/handler.clj index bcac7ca..c3b27ee 100644 --- a/examples/communicating-services/insurance-policy-service/src/insurance_policy_service/handler.clj +++ b/examples/communicating-services/insurance-policy-service/src/insurance_policy_service/handler.clj @@ -1,17 +1,17 @@ (ns insurance-policy-service.handler - (:require [compojure.core :refer :all] + (:require [compojure.core :as c] [compojure.route :as route] - [ring.middleware.defaults :refer [wrap-defaults site-defaults]] + [ring.middleware.defaults :as d] [ring.util.response :as response] [insurance-policy-service.db :as db])) -(defroutes app-routes - (GET "/status" [] +(c/defroutes app-routes + (c/GET "/status" [] (response/response "Insurance policy service is running!!!!")) - (GET "/policies" [] + (c/GET "/policies" [] (response/response (str (db/find-all)))) (route/not-found "Not Found")) (def app - (wrap-defaults app-routes site-defaults)) + (d/wrap-defaults app-routes d/site-defaults)) diff --git a/examples/communicating-services/insurance-policy-service/test/insurance_policy_service/handler_test.clj b/examples/communicating-services/insurance-policy-service/test/insurance_policy_service/handler_test.clj index 50650a4..7626dc2 100644 --- a/examples/communicating-services/insurance-policy-service/test/insurance_policy_service/handler_test.clj +++ b/examples/communicating-services/insurance-policy-service/test/insurance_policy_service/handler_test.clj @@ -1,14 +1,14 @@ (ns insurance-policy-service.handler-test - (:require [clojure.test :refer :all] + (:require [clojure.test :refer [deftest is testing]] [ring.mock.request :as mock] - [insurance-policy-service.handler :refer :all])) + [insurance-policy-service.handler :as h])) (deftest test-app (testing "main route" - (let [response (app (mock/request :get "/"))] + (let [response (h/app (mock/request :get "/"))] (is (= (:status response) 200)) (is (= (:body response) "Hello World")))) (testing "not-found route" - (let [response (app (mock/request :get "/invalid"))] + (let [response (h/app (mock/request :get "/invalid"))] (is (= (:status response) 404))))) diff --git a/examples/parsing-with-spec/src/parsing_with_spec/core.clj b/examples/parsing-with-spec/src/parsing_with_spec/core.clj index 9540ba7..3dfde99 100644 --- a/examples/parsing-with-spec/src/parsing_with_spec/core.clj +++ b/examples/parsing-with-spec/src/parsing_with_spec/core.clj @@ -64,7 +64,7 @@ (defn parse-int [s] (try (Integer/parseInt s) - (catch Exception ex 0))) + (catch Exception _ex 0))) (defn mapize [csv-rows] (let [[header & lines] csv-rows diff --git a/examples/parsing-with-spec/test/parsing_with_spec/core_test.clj b/examples/parsing-with-spec/test/parsing_with_spec/core_test.clj index 6265d06..529f613 100644 --- a/examples/parsing-with-spec/test/parsing_with_spec/core_test.clj +++ b/examples/parsing-with-spec/test/parsing_with_spec/core_test.clj @@ -1,5 +1,5 @@ (ns parsing-with-spec.core-test - (:require [clojure.test :refer :all] + (:require [clojure.test :refer [deftest is]] [parsing-with-spec.core :as p] [clojure.spec.alpha :as s] [expound.alpha :as e])) @@ -28,9 +28,9 @@ (let [insurance-policy-applications [good bad] valid-count (atom 0) invalid-count (atom 0) - valid (fn [x] + valid (fn [_x] (swap! valid-count inc)) - invalid (fn [x] + invalid (fn [_x] (swap! invalid-count inc))] (p/process-applications valid invalid insurance-policy-applications) (is (= 1 @valid-count)) diff --git a/examples/parsing-with-spec/test/parsing_with_spec/generated_test.clj b/examples/parsing-with-spec/test/parsing_with_spec/generated_test.clj index ac3c538..b2efce8 100644 --- a/examples/parsing-with-spec/test/parsing_with_spec/generated_test.clj +++ b/examples/parsing-with-spec/test/parsing_with_spec/generated_test.clj @@ -1,5 +1,5 @@ (ns parsing-with-spec.generated-test - (:require [clojure.test :refer :all] + #_(:require [clojure.test :refer [deftest is testing]] [clojure.spec.alpha :as s] [clojure.spec.test.alpha :as stest] [clojure.spec.gen.alpha :as gen] diff --git a/site/slides.css b/site/enterprise-clojure-training.css similarity index 61% rename from site/slides.css rename to site/enterprise-clojure-training.css index 8ae8881..0b86665 100644 --- a/site/slides.css +++ b/site/enterprise-clojure-training.css @@ -1,31 +1,34 @@ -.reveal h2, th { +.reveal h2, +.reveal th { font-size: 1.5em; padding-bottom: 0.5em; - color: #66AF40; + color: #66af40; } .reveal pre { font-size: 0.8em; box-shadow: none; padding: 0.1em; - background-color: #F8F8F8; + background-color: #f8f8f8; } .reveal section { - font-size: 1.0em; - color: #5B80D4; - /* allow vertical scrolling in slides */ - max-height: 100%; + font-size: 1em; + color: #5b80d4; + max-height: 100%; /* allow vertical scrolling in slides */ overflow-y: auto; } .reveal section img { margin: 0; - border: 10px solid #66AF40; + border: 10px solid #66af40; height: 300px; } -.reveal section p, h1, h2, div { +.reveal section p, +.reveal h1, +.reveal h2, +.reveal div { text-align: left; } @@ -34,7 +37,7 @@ } html.title body { - background: linear-gradient(to bottom left, #91B3FB, #92DA55); + background: linear-gradient(to bottom left, #91b3Fb, #92da55); } .small { @@ -42,15 +45,15 @@ html.title body { } .klipse-snippet { - border: solid #92DA55 1px !important; + border: solid #92da55 1px !important; } .klipse-result { - border: solid #91B3FB 1px !important; + border: solid #91b3fb 1px !important; } code { - background-color: #F8F8F8; + background-color: #f8f8f8; font-family: 'FiraCode', monospace !important; } diff --git a/site/img/clojure-logo-icon-32.png b/site/img/clojure-logo-icon-32.png index 32ef0db..4b4eca1 100644 Binary files a/site/img/clojure-logo-icon-32.png and b/site/img/clojure-logo-icon-32.png differ diff --git a/site/img/formatted-exception.png b/site/img/formatted-exception.png new file mode 100644 index 0000000..2f74f19 Binary files /dev/null and b/site/img/formatted-exception.png differ diff --git a/site/img/fx.png b/site/img/fx.png new file mode 100644 index 0000000..e022fd4 Binary files /dev/null and b/site/img/fx.png differ diff --git a/site/img/macarons.png b/site/img/macarons.png new file mode 100644 index 0000000..95fec9e Binary files /dev/null and b/site/img/macarons.png differ diff --git a/site/img/recipe.png b/site/img/recipe.png new file mode 100644 index 0000000..1158d94 Binary files /dev/null and b/site/img/recipe.png differ diff --git a/site/img/stem-2a16d2c60a53e530758b5fdef0742f3b.png b/site/img/stem-2a16d2c60a53e530758b5fdef0742f3b.png new file mode 100644 index 0000000..2f47f56 Binary files /dev/null and b/site/img/stem-2a16d2c60a53e530758b5fdef0742f3b.png differ diff --git a/site/img/stem-2c67dbf3cc587ee1c1d6a9e410d8ba4d.png b/site/img/stem-2c67dbf3cc587ee1c1d6a9e410d8ba4d.png new file mode 100644 index 0000000..e316360 Binary files /dev/null and b/site/img/stem-2c67dbf3cc587ee1c1d6a9e410d8ba4d.png differ diff --git a/site/img/stem-39d23ae4ab5f4a56e5f138fdd614e65c.png b/site/img/stem-39d23ae4ab5f4a56e5f138fdd614e65c.png new file mode 100644 index 0000000..01f9e41 Binary files /dev/null and b/site/img/stem-39d23ae4ab5f4a56e5f138fdd614e65c.png differ diff --git a/site/img/stem-44bc9d542a92714cac84e01cbbb7fd61.png b/site/img/stem-44bc9d542a92714cac84e01cbbb7fd61.png new file mode 100644 index 0000000..baf388f Binary files /dev/null and b/site/img/stem-44bc9d542a92714cac84e01cbbb7fd61.png differ diff --git a/site/img/stem-4eff5044a43766356ba8db60781b6188.png b/site/img/stem-4eff5044a43766356ba8db60781b6188.png new file mode 100644 index 0000000..dc54c71 Binary files /dev/null and b/site/img/stem-4eff5044a43766356ba8db60781b6188.png differ diff --git a/site/img/stem-5634492aecac8d866cd2d9b33f624307.png b/site/img/stem-5634492aecac8d866cd2d9b33f624307.png new file mode 100644 index 0000000..8eefeb6 Binary files /dev/null and b/site/img/stem-5634492aecac8d866cd2d9b33f624307.png differ diff --git a/site/img/stem-62cd49e3c428e7d37d7806797cf9811f.png b/site/img/stem-62cd49e3c428e7d37d7806797cf9811f.png new file mode 100644 index 0000000..f75d8f6 Binary files /dev/null and b/site/img/stem-62cd49e3c428e7d37d7806797cf9811f.png differ diff --git a/site/img/stem-7eec8eb09505297e3e4a78cc6867617c.png b/site/img/stem-7eec8eb09505297e3e4a78cc6867617c.png new file mode 100644 index 0000000..1948c06 Binary files /dev/null and b/site/img/stem-7eec8eb09505297e3e4a78cc6867617c.png differ diff --git a/site/img/stem-8b1ae91e44dde418d0f0f170f9b63a5c.png b/site/img/stem-8b1ae91e44dde418d0f0f170f9b63a5c.png new file mode 100644 index 0000000..8985d94 Binary files /dev/null and b/site/img/stem-8b1ae91e44dde418d0f0f170f9b63a5c.png differ diff --git a/site/img/stem-a6051b304f9cc72015f58cc85954b0ee.png b/site/img/stem-a6051b304f9cc72015f58cc85954b0ee.png new file mode 100644 index 0000000..83f1769 Binary files /dev/null and b/site/img/stem-a6051b304f9cc72015f58cc85954b0ee.png differ diff --git a/site/img/stem-b4bbe16e32e2319d5835a2ce2360eb4b.png b/site/img/stem-b4bbe16e32e2319d5835a2ce2360eb4b.png new file mode 100644 index 0000000..6b1502d Binary files /dev/null and b/site/img/stem-b4bbe16e32e2319d5835a2ce2360eb4b.png differ diff --git a/site/img/stew.png b/site/img/stew.png new file mode 100644 index 0000000..c037f4d Binary files /dev/null and b/site/img/stew.png differ diff --git a/site/reveal.js/.github/FUNDING.yml b/site/reveal.js/.github/FUNDING.yml new file mode 100644 index 0000000..972831e --- /dev/null +++ b/site/reveal.js/.github/FUNDING.yml @@ -0,0 +1 @@ +github: [hakimel] diff --git a/site/reveal.js/.github/workflows/js.yml b/site/reveal.js/.github/workflows/js.yml new file mode 100644 index 0000000..db5330d --- /dev/null +++ b/site/reveal.js/.github/workflows/js.yml @@ -0,0 +1,24 @@ +name: tests + +on: [push] + +jobs: + build: + + runs-on: ubuntu-latest + + strategy: + matrix: + node-version: [10.x, 14.x] + + steps: + - uses: actions/checkout@v2 + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v1 + with: + node-version: ${{ matrix.node-version }} + - run: npm install + - run: npm run build --if-present + - run: npm test + env: + CI: true diff --git a/site/reveal.js/.gitignore b/site/reveal.js/.gitignore index e7b4f21..7d986c3 100644 --- a/site/reveal.js/.gitignore +++ b/site/reveal.js/.gitignore @@ -9,5 +9,4 @@ log/*.log tmp/** node_modules/ .sass-cache -css/reveal.min.css -js/reveal.min.js \ No newline at end of file +dist/*.map \ No newline at end of file diff --git a/site/reveal.js/.npmignore b/site/reveal.js/.npmignore new file mode 100644 index 0000000..50c12b9 --- /dev/null +++ b/site/reveal.js/.npmignore @@ -0,0 +1,7 @@ +/test +/examples +.github +.gulpfile +.sass-cache +gulpfile.js +CONTRIBUTING.md \ No newline at end of file diff --git a/site/reveal.js/.travis.yml b/site/reveal.js/.travis.yml deleted file mode 100644 index ec3b27d..0000000 --- a/site/reveal.js/.travis.yml +++ /dev/null @@ -1,7 +0,0 @@ -language: node_js -node_js: - - 4 -before_script: - - npm install -g grunt-cli -after_script: - - grunt retire diff --git a/site/reveal.js/Gruntfile.js b/site/reveal.js/Gruntfile.js deleted file mode 100644 index b6ef63b..0000000 --- a/site/reveal.js/Gruntfile.js +++ /dev/null @@ -1,192 +0,0 @@ -/* global module:false */ -module.exports = function(grunt) { - var port = grunt.option('port') || 8000; - var root = grunt.option('root') || '.'; - - if (!Array.isArray(root)) root = [root]; - - // Project configuration - grunt.initConfig({ - pkg: grunt.file.readJSON('package.json'), - meta: { - banner: - '/*!\n' + - ' * reveal.js <%= pkg.version %> (<%= grunt.template.today("yyyy-mm-dd, HH:MM") %>)\n' + - ' * http://revealjs.com\n' + - ' * MIT licensed\n' + - ' *\n' + - ' * Copyright (C) 2017 Hakim El Hattab, http://hakim.se\n' + - ' */' - }, - - qunit: { - files: [ 'test/*.html' ] - }, - - uglify: { - options: { - banner: '<%= meta.banner %>\n', - screwIE8: false - }, - build: { - src: 'js/reveal.js', - dest: 'js/reveal.min.js' - } - }, - - sass: { - core: { - src: 'css/reveal.scss', - dest: 'css/reveal.css' - }, - themes: { - expand: true, - cwd: 'css/theme/source', - src: ['*.sass', '*.scss'], - dest: 'css/theme', - ext: '.css' - } - }, - - autoprefixer: { - core: { - src: 'css/reveal.css' - } - }, - - cssmin: { - options: { - compatibility: 'ie9' - }, - compress: { - src: 'css/reveal.css', - dest: 'css/reveal.min.css' - } - }, - - jshint: { - options: { - curly: false, - eqeqeq: true, - immed: true, - esnext: true, - latedef: 'nofunc', - newcap: true, - noarg: true, - sub: true, - undef: true, - eqnull: true, - browser: true, - expr: true, - globals: { - head: false, - module: false, - console: false, - unescape: false, - define: false, - exports: false - } - }, - files: [ 'Gruntfile.js', 'js/reveal.js' ] - }, - - connect: { - server: { - options: { - port: port, - base: root, - livereload: true, - open: true, - useAvailablePort: true - } - } - }, - - zip: { - bundle: { - src: [ - 'index.html', - 'css/**', - 'js/**', - 'lib/**', - 'images/**', - 'plugin/**', - '**.md' - ], - dest: 'reveal-js-presentation.zip' - } - }, - - watch: { - js: { - files: [ 'Gruntfile.js', 'js/reveal.js' ], - tasks: 'js' - }, - theme: { - files: [ - 'css/theme/source/*.sass', - 'css/theme/source/*.scss', - 'css/theme/template/*.sass', - 'css/theme/template/*.scss' - ], - tasks: 'css-themes' - }, - css: { - files: [ 'css/reveal.scss' ], - tasks: 'css-core' - }, - html: { - files: root.map(path => path + '/*.html') - }, - markdown: { - files: root.map(path => path + '/*.md') - }, - options: { - livereload: true - } - }, - - retire: { - js: [ 'js/reveal.js', 'lib/js/*.js', 'plugin/**/*.js' ], - node: [ '.' ] - } - - }); - - // Dependencies - grunt.loadNpmTasks( 'grunt-contrib-connect' ); - grunt.loadNpmTasks( 'grunt-contrib-cssmin' ); - grunt.loadNpmTasks( 'grunt-contrib-jshint' ); - grunt.loadNpmTasks( 'grunt-contrib-qunit' ); - grunt.loadNpmTasks( 'grunt-contrib-uglify' ); - grunt.loadNpmTasks( 'grunt-contrib-watch' ); - grunt.loadNpmTasks( 'grunt-autoprefixer' ); - grunt.loadNpmTasks( 'grunt-retire' ); - grunt.loadNpmTasks( 'grunt-sass' ); - grunt.loadNpmTasks( 'grunt-zip' ); - - // Default task - grunt.registerTask( 'default', [ 'css', 'js' ] ); - - // JS task - grunt.registerTask( 'js', [ 'jshint', 'uglify', 'qunit' ] ); - - // Theme CSS - grunt.registerTask( 'css-themes', [ 'sass:themes' ] ); - - // Core framework CSS - grunt.registerTask( 'css-core', [ 'sass:core', 'autoprefixer', 'cssmin' ] ); - - // All CSS - grunt.registerTask( 'css', [ 'sass', 'autoprefixer', 'cssmin' ] ); - - // Package presentation to archive - grunt.registerTask( 'package', [ 'default', 'zip' ] ); - - // Serve presentation locally - grunt.registerTask( 'serve', [ 'connect', 'watch' ] ); - - // Run tests - grunt.registerTask( 'test', [ 'jshint', 'qunit' ] ); - -}; diff --git a/site/reveal.js/LICENSE b/site/reveal.js/LICENSE index c3e6e5f..d15cf3b 100644 --- a/site/reveal.js/LICENSE +++ b/site/reveal.js/LICENSE @@ -1,4 +1,4 @@ -Copyright (C) 2017 Hakim El Hattab, http://hakim.se, and reveal.js contributors +Copyright (C) 2020 Hakim El Hattab, http://hakim.se, and reveal.js contributors Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/site/reveal.js/README.md b/site/reveal.js/README.md index 9d71472..fbea941 100644 --- a/site/reveal.js/README.md +++ b/site/reveal.js/README.md @@ -1,1238 +1,28 @@ -# reveal.js [![Build Status](https://travis-ci.org/hakimel/reveal.js.svg?branch=master)](https://travis-ci.org/hakimel/reveal.js) Slides +

+ + reveal.js + +

+ + Slides +

-A framework for easily creating beautiful presentations using HTML. [Check out the live demo](http://revealjs.com/). +reveal.js is an open source HTML presentation framework. It enables anyone with a web browser to create fully featured and beautiful presentations for free. [Check out the live demo](https://revealjs.com/). -reveal.js comes with a broad range of features including [nested slides](https://github.com/hakimel/reveal.js#markup), [Markdown contents](https://github.com/hakimel/reveal.js#markdown), [PDF export](https://github.com/hakimel/reveal.js#pdf-export), [speaker notes](https://github.com/hakimel/reveal.js#speaker-notes) and a [JavaScript API](https://github.com/hakimel/reveal.js#api). There's also a fully featured visual editor and platform for sharing reveal.js presentations at [slides.com](https://slides.com?ref=github). +The framework comes with a broad range of features including [nested slides](https://revealjs.com/vertical-slides/), [Markdown support](https://revealjs.com/markdown/), [Auto-Animate](https://revealjs.com/auto-animate/), [PDF export](https://revealjs.com/pdf-export/), [speaker notes](https://revealjs.com/speaker-view/), [LaTeX support](https://revealjs.com/math/), [syntax highlighted code](https://revealjs.com/code/) and much more. -## Table of contents -- [Online Editor](#online-editor) -- [Instructions](#instructions) - - [Markup](#markup) - - [Markdown](#markdown) - - [Element Attributes](#element-attributes) - - [Slide Attributes](#slide-attributes) -- [Configuration](#configuration) -- [Presentation Size](#presentation-size) -- [Dependencies](#dependencies) -- [Ready Event](#ready-event) -- [Auto-sliding](#auto-sliding) -- [Keyboard Bindings](#keyboard-bindings) -- [Touch Navigation](#touch-navigation) -- [Lazy Loading](#lazy-loading) -- [API](#api) - - [Slide Changed Event](#slide-changed-event) - - [Presentation State](#presentation-state) - - [Slide States](#slide-states) - - [Slide Backgrounds](#slide-backgrounds) - - [Parallax Background](#parallax-background) - - [Slide Transitions](#slide-transitions) - - [Internal links](#internal-links) - - [Fragments](#fragments) - - [Fragment events](#fragment-events) - - [Code syntax highlighting](#code-syntax-highlighting) - - [Slide number](#slide-number) - - [Overview mode](#overview-mode) - - [Fullscreen mode](#fullscreen-mode) - - [Embedded media](#embedded-media) - - [Stretching elements](#stretching-elements) - - [postMessage API](#postmessage-api) -- [PDF Export](#pdf-export) -- [Theming](#theming) -- [Speaker Notes](#speaker-notes) - - [Share and Print Speaker Notes](#share-and-print-speaker-notes) - - [Server Side Speaker Notes](#server-side-speaker-notes) -- [Multiplexing](#multiplexing) - - [Master presentation](#master-presentation) - - [Client presentation](#client-presentation) - - [Socket.io server](#socketio-server) -- [MathJax](#mathjax) -- [Installation](#installation) - - [Basic setup](#basic-setup) - - [Full setup](#full-setup) - - [Folder Structure](#folder-structure) -- [License](#license) +

+ Get Started +

-#### More reading -- [Changelog](https://github.com/hakimel/reveal.js/releases): Up-to-date version history. -- [Examples](https://github.com/hakimel/reveal.js/wiki/Example-Presentations): Presentations created with reveal.js, add your own! -- [Browser Support](https://github.com/hakimel/reveal.js/wiki/Browser-Support): Explanation of browser support and fallbacks. -- [Plugins](https://github.com/hakimel/reveal.js/wiki/Plugins,-Tools-and-Hardware): A list of plugins that can be used to extend reveal.js. +## Documentation +The full reveal.js documentation is available at [revealjs.com](https://revealjs.com). ## Online Editor - -Presentations are written using HTML or Markdown but there's also an online editor for those of you who prefer a graphical interface. Give it a try at [https://slides.com](https://slides.com?ref=github). - - -## Instructions - -### Markup - -Here's a barebones example of a fully working reveal.js presentation: -```html - - - - - - -
-
-
Slide 1
-
Slide 2
-
-
- - - - -``` - -The presentation markup hierarchy needs to be `.reveal > .slides > section` where the `section` represents one slide and can be repeated indefinitely. If you place multiple `section` elements inside of another `section` they will be shown as vertical slides. The first of the vertical slides is the "root" of the others (at the top), and will be included in the horizontal sequence. For example: - -```html -
-
-
Single Horizontal Slide
-
-
Vertical Slide 1
-
Vertical Slide 2
-
-
-
-``` - -### Markdown - -It's possible to write your slides using Markdown. To enable Markdown, add the `data-markdown` attribute to your `
` elements and wrap the contents in a ` -
-``` - -#### External Markdown - -You can write your content as a separate file and have reveal.js load it at runtime. Note the separator arguments which determine how slides are delimited in the external file: the `data-separator` attribute defines a regular expression for horizontal slides (defaults to `^\r?\n---\r?\n$`, a newline-bounded horizontal rule) and `data-separator-vertical` defines vertical slides (disabled by default). The `data-separator-notes` attribute is a regular expression for specifying the beginning of the current slide's speaker notes (defaults to `note:`). The `data-charset` attribute is optional and specifies which charset to use when loading the external file. - -When used locally, this feature requires that reveal.js [runs from a local web server](#full-setup). The following example customises all available options: - -```html -
-
-``` - -#### Element Attributes - -Special syntax (in html comment) is available for adding attributes to Markdown elements. This is useful for fragments, amongst other things. - -```html -
- -
-``` - -#### Slide Attributes - -Special syntax (in html comment) is available for adding attributes to the slide `
` elements generated by your Markdown. - -```html -
- -
-``` - -#### Configuring *marked* - -We use [marked](https://github.com/chjj/marked) to parse Markdown. To customise marked's rendering, you can pass in options when [configuring Reveal](#configuration): - -```javascript -Reveal.initialize({ - // Options which are passed into marked - // See https://github.com/chjj/marked#options-1 - markdown: { - smartypants: true - } -}); -``` - -### Configuration - -At the end of your page you need to initialize reveal by running the following code. Note that all config values are optional and will default as specified below. - -```javascript -Reveal.initialize({ - - // Display presentation control arrows - controls: true, - - // Help the user learn the controls by providing hints, for example by - // bouncing the down arrow when they first encounter a vertical slide - controlsTutorial: true, - - // Determines where controls appear, "edges" or "bottom-right" - controlsLayout: 'bottom-right', - - // Visibility rule for backwards navigation arrows; "faded", "hidden" - // or "visible" - controlsBackArrows: 'faded', - - // Display a presentation progress bar - progress: true, - - // Set default timing of 2 minutes per slide - defaultTiming: 120, - - // Display the page number of the current slide - slideNumber: false, - - // Push each slide change to the browser history - history: false, - - // Enable keyboard shortcuts for navigation - keyboard: true, - - // Enable the slide overview mode - overview: true, - - // Vertical centering of slides - center: true, - - // Enables touch navigation on devices with touch input - touch: true, - - // Loop the presentation - loop: false, - - // Change the presentation direction to be RTL - rtl: false, - - // Randomizes the order of slides each time the presentation loads - shuffle: false, - - // Turns fragments on and off globally - fragments: true, - - // Flags if the presentation is running in an embedded mode, - // i.e. contained within a limited portion of the screen - embedded: false, - - // Flags if we should show a help overlay when the questionmark - // key is pressed - help: true, - - // Flags if speaker notes should be visible to all viewers - showNotes: false, - - // Global override for autoplaying embedded media (video/audio/iframe) - // - null: Media will only autoplay if data-autoplay is present - // - true: All media will autoplay, regardless of individual setting - // - false: No media will autoplay, regardless of individual setting - autoPlayMedia: null, - - // Number of milliseconds between automatically proceeding to the - // next slide, disabled when set to 0, this value can be overwritten - // by using a data-autoslide attribute on your slides - autoSlide: 0, - - // Stop auto-sliding after user input - autoSlideStoppable: true, - - // Use this method for navigation when auto-sliding - autoSlideMethod: Reveal.navigateNext, - - // Enable slide navigation via mouse wheel - mouseWheel: false, - - // Hides the address bar on mobile devices - hideAddressBar: true, - - // Opens links in an iframe preview overlay - previewLinks: false, - - // Transition style - transition: 'slide', // none/fade/slide/convex/concave/zoom - - // Transition speed - transitionSpeed: 'default', // default/fast/slow - - // Transition style for full page slide backgrounds - backgroundTransition: 'fade', // none/fade/slide/convex/concave/zoom - - // Number of slides away from the current that are visible - viewDistance: 3, - - // Parallax background image - parallaxBackgroundImage: '', // e.g. "'https://s3.amazonaws.com/hakim-static/reveal-js/reveal-parallax-1.jpg'" - - // Parallax background size - parallaxBackgroundSize: '', // CSS syntax, e.g. "2100px 900px" - - // Number of pixels to move the parallax background per slide - // - Calculated automatically unless specified - // - Set to 0 to disable movement along an axis - parallaxBackgroundHorizontal: null, - parallaxBackgroundVertical: null, - - // The display mode that will be used to show slides - display: 'block' - -}); -``` - - -The configuration can be updated after initialization using the ```configure``` method: - -```javascript -// Turn autoSlide off -Reveal.configure({ autoSlide: 0 }); - -// Start auto-sliding every 5s -Reveal.configure({ autoSlide: 5000 }); -``` - - -### Presentation Size - -All presentations have a normal size, that is the resolution at which they are authored. The framework will automatically scale presentations uniformly based on this size to ensure that everything fits on any given display or viewport. - -See below for a list of configuration options related to sizing, including default values: - -```javascript -Reveal.initialize({ - - ... - - // The "normal" size of the presentation, aspect ratio will be preserved - // when the presentation is scaled to fit different resolutions. Can be - // specified using percentage units. - width: 960, - height: 700, - - // Factor of the display size that should remain empty around the content - margin: 0.1, - - // Bounds for smallest/largest possible scale to apply to content - minScale: 0.2, - maxScale: 1.5 - -}); -``` - -If you wish to disable this behavior and do your own scaling (e.g. using media queries), try these settings: - -```javascript -Reveal.initialize({ - - ... - - width: "100%", - height: "100%", - margin: 0, - minScale: 1, - maxScale: 1 -}); -``` - -### Dependencies - -Reveal.js doesn't _rely_ on any third party scripts to work but a few optional libraries are included by default. These libraries are loaded as dependencies in the order they appear, for example: - -```javascript -Reveal.initialize({ - dependencies: [ - // Cross-browser shim that fully implements classList - https://github.com/eligrey/classList.js/ - { src: 'lib/js/classList.js', condition: function() { return !document.body.classList; } }, - - // Interpret Markdown in
elements - { src: 'plugin/markdown/marked.js', condition: function() { return !!document.querySelector( '[data-markdown]' ); } }, - { src: 'plugin/markdown/markdown.js', condition: function() { return !!document.querySelector( '[data-markdown]' ); } }, - - // Syntax highlight for elements - { src: 'plugin/highlight/highlight.js', async: true, callback: function() { hljs.initHighlightingOnLoad(); } }, - - // Zoom in and out with Alt+click - { src: 'plugin/zoom-js/zoom.js', async: true }, - - // Speaker notes - { src: 'plugin/notes/notes.js', async: true }, - - // MathJax - { src: 'plugin/math/math.js', async: true } - ] -}); -``` - -You can add your own extensions using the same syntax. The following properties are available for each dependency object: -- **src**: Path to the script to load -- **async**: [optional] Flags if the script should load after reveal.js has started, defaults to false -- **callback**: [optional] Function to execute when the script has loaded -- **condition**: [optional] Function which must return true for the script to be loaded - -To load these dependencies, reveal.js requires [head.js](http://headjs.com/) *(a script loading library)* to be loaded before reveal.js. - -### Ready Event - -A 'ready' event is fired when reveal.js has loaded all non-async dependencies and is ready to start navigating. To check if reveal.js is already 'ready' you can call `Reveal.isReady()`. - -```javascript -Reveal.addEventListener( 'ready', function( event ) { - // event.currentSlide, event.indexh, event.indexv -} ); -``` - -Note that we also add a `.ready` class to the `.reveal` element so that you can hook into this with CSS. - -### Auto-sliding - -Presentations can be configured to progress through slides automatically, without any user input. To enable this you will need to tell the framework how many milliseconds it should wait between slides: - -```javascript -// Slide every five seconds -Reveal.configure({ - autoSlide: 5000 -}); -``` -When this is turned on a control element will appear that enables users to pause and resume auto-sliding. Alternatively, sliding can be paused or resumed by pressing »a« on the keyboard. Sliding is paused automatically as soon as the user starts navigating. You can disable these controls by specifying ```autoSlideStoppable: false``` in your reveal.js config. - -You can also override the slide duration for individual slides and fragments by using the ```data-autoslide``` attribute: - -```html -
-

After 2 seconds the first fragment will be shown.

-

After 10 seconds the next fragment will be shown.

-

Now, the fragment is displayed for 2 seconds before the next slide is shown.

-
-``` - -To override the method used for navigation when auto-sliding, you can specify the ```autoSlideMethod``` setting. To only navigate along the top layer and ignore vertical slides, set this to ```Reveal.navigateRight```. - -Whenever the auto-slide mode is resumed or paused the ```autoslideresumed``` and ```autoslidepaused``` events are fired. - - -### Keyboard Bindings - -If you're unhappy with any of the default keyboard bindings you can override them using the ```keyboard``` config option: - -```javascript -Reveal.configure({ - keyboard: { - 13: 'next', // go to the next slide when the ENTER key is pressed - 27: function() {}, // do something custom when ESC is pressed - 32: null // don't do anything when SPACE is pressed (i.e. disable a reveal.js default binding) - } -}); -``` - -### Touch Navigation - -You can swipe to navigate through a presentation on any touch-enabled device. Horizontal swipes change between horizontal slides, vertical swipes change between vertical slides. If you wish to disable this you can set the `touch` config option to false when initializing reveal.js. - -If there's some part of your content that needs to remain accessible to touch events you'll need to highlight this by adding a `data-prevent-swipe` attribute to the element. One common example where this is useful is elements that need to be scrolled. - - -### Lazy Loading - -When working on presentation with a lot of media or iframe content it's important to load lazily. Lazy loading means that reveal.js will only load content for the few slides nearest to the current slide. The number of slides that are preloaded is determined by the `viewDistance` configuration option. - -To enable lazy loading all you need to do is change your "src" attributes to "data-src" as shown below. This is supported for image, video, audio and iframe elements. Lazy loaded iframes will also unload when the containing slide is no longer visible. - -```html -
- - - -
-``` - - -### API - -The ``Reveal`` object exposes a JavaScript API for controlling navigation and reading state: - -```javascript -// Navigation -Reveal.slide( indexh, indexv, indexf ); -Reveal.left(); -Reveal.right(); -Reveal.up(); -Reveal.down(); -Reveal.prev(); -Reveal.next(); -Reveal.prevFragment(); -Reveal.nextFragment(); - -// Randomize the order of slides -Reveal.shuffle(); - -// Toggle presentation states, optionally pass true/false to force on/off -Reveal.toggleOverview(); -Reveal.togglePause(); -Reveal.toggleAutoSlide(); - -// Shows a help overlay with keyboard shortcuts, optionally pass true/false -// to force on/off -Reveal.toggleHelp(); - -// Change a config value at runtime -Reveal.configure({ controls: true }); - -// Returns the present configuration options -Reveal.getConfig(); - -// Fetch the current scale of the presentation -Reveal.getScale(); - -// Retrieves the previous and current slide elements -Reveal.getPreviousSlide(); -Reveal.getCurrentSlide(); - -Reveal.getIndices(); // { h: 0, v: 0 } } -Reveal.getPastSlideCount(); -Reveal.getProgress(); // (0 == first slide, 1 == last slide) -Reveal.getSlides(); // Array of all slides -Reveal.getTotalSlides(); // total number of slides - -// Returns the speaker notes for the current slide -Reveal.getSlideNotes(); - -// State checks -Reveal.isFirstSlide(); -Reveal.isLastSlide(); -Reveal.isOverview(); -Reveal.isPaused(); -Reveal.isAutoSliding(); -``` - -### Slide Changed Event - -A 'slidechanged' event is fired each time the slide is changed (regardless of state). The event object holds the index values of the current slide as well as a reference to the previous and current slide HTML nodes. - -Some libraries, like MathJax (see [#226](https://github.com/hakimel/reveal.js/issues/226#issuecomment-10261609)), get confused by the transforms and display states of slides. Often times, this can be fixed by calling their update or render function from this callback. - -```javascript -Reveal.addEventListener( 'slidechanged', function( event ) { - // event.previousSlide, event.currentSlide, event.indexh, event.indexv -} ); -``` - -### Presentation State - -The presentation's current state can be fetched by using the `getState` method. A state object contains all of the information required to put the presentation back as it was when `getState` was first called. Sort of like a snapshot. It's a simple object that can easily be stringified and persisted or sent over the wire. - -```javascript -Reveal.slide( 1 ); -// we're on slide 1 - -var state = Reveal.getState(); - -Reveal.slide( 3 ); -// we're on slide 3 - -Reveal.setState( state ); -// we're back on slide 1 -``` - -### Slide States - -If you set ``data-state="somestate"`` on a slide ``
``, "somestate" will be applied as a class on the document element when that slide is opened. This allows you to apply broad style changes to the page based on the active slide. - -Furthermore you can also listen to these changes in state via JavaScript: - -```javascript -Reveal.addEventListener( 'somestate', function() { - // TODO: Sprinkle magic -}, false ); -``` - -### Slide Backgrounds - -Slides are contained within a limited portion of the screen by default to allow them to fit any display and scale uniformly. You can apply full page backgrounds outside of the slide area by adding a ```data-background``` attribute to your ```
``` elements. Four different types of backgrounds are supported: color, image, video and iframe. - -#### Color Backgrounds -All CSS color formats are supported, like rgba() or hsl(). -```html -
-

Color

-
-``` - -#### Image Backgrounds -By default, background images are resized to cover the full page. Available options: - -| Attribute | Default | Description | -| :--------------------------- | :--------- | :---------- | -| data-background-image | | URL of the image to show. GIFs restart when the slide opens. | -| data-background-size | cover | See [background-size](https://developer.mozilla.org/docs/Web/CSS/background-size) on MDN. | -| data-background-position | center | See [background-position](https://developer.mozilla.org/docs/Web/CSS/background-position) on MDN. | -| data-background-repeat | no-repeat | See [background-repeat](https://developer.mozilla.org/docs/Web/CSS/background-repeat) on MDN. | -```html -
-

Image

-
-
-

This background image will be sized to 100px and repeated

-
-``` - -#### Video Backgrounds -Automatically plays a full size video behind the slide. - -| Attribute | Default | Description | -| :--------------------------- | :------ | :---------- | -| data-background-video | | A single video source, or a comma separated list of video sources. | -| data-background-video-loop | false | Flags if the video should play repeatedly. | -| data-background-video-muted | false | Flags if the audio should be muted. | -| data-background-size | cover | Use `cover` for full screen and some cropping or `contain` for letterboxing. | - -```html -
-

Video

-
-``` - -#### Iframe Backgrounds -Embeds a web page as a slide background that covers 100% of the reveal.js width and height. The iframe is in the background layer, behind your slides, and as such it's not possible to interact with it by default. To make your background interactive, you can add the `data-background-interactive` attribute. -```html -
-

Iframe

-
-``` - -#### Background Transitions -Backgrounds transition using a fade animation by default. This can be changed to a linear sliding transition by passing ```backgroundTransition: 'slide'``` to the ```Reveal.initialize()``` call. Alternatively you can set ```data-background-transition``` on any section with a background to override that specific transition. - - -### Parallax Background - -If you want to use a parallax scrolling background, set the first two config properties below when initializing reveal.js (the other two are optional). - -```javascript -Reveal.initialize({ - - // Parallax background image - parallaxBackgroundImage: '', // e.g. "https://s3.amazonaws.com/hakim-static/reveal-js/reveal-parallax-1.jpg" - - // Parallax background size - parallaxBackgroundSize: '', // CSS syntax, e.g. "2100px 900px" - currently only pixels are supported (don't use % or auto) - - // Number of pixels to move the parallax background per slide - // - Calculated automatically unless specified - // - Set to 0 to disable movement along an axis - parallaxBackgroundHorizontal: 200, - parallaxBackgroundVertical: 50 - -}); -``` - -Make sure that the background size is much bigger than screen size to allow for some scrolling. [View example](http://revealjs.com/?parallaxBackgroundImage=https%3A%2F%2Fs3.amazonaws.com%2Fhakim-static%2Freveal-js%2Freveal-parallax-1.jpg¶llaxBackgroundSize=2100px%20900px). - - - -### Slide Transitions -The global presentation transition is set using the ```transition``` config value. You can override the global transition for a specific slide by using the ```data-transition``` attribute: - -```html -
-

This slide will override the presentation transition and zoom!

-
- -
-

Choose from three transition speeds: default, fast or slow!

-
-``` - -You can also use different in and out transitions for the same slide: - -```html -
- The train goes on … -
-
- and on … -
-
- and stops. -
-
- (Passengers entering and leaving) -
-
- And it starts again. -
-``` - - -### Internal links - -It's easy to link between slides. The first example below targets the index of another slide whereas the second targets a slide with an ID attribute (```
```): - -```html -Link -Link -``` - -You can also add relative navigation links, similar to the built in reveal.js controls, by appending one of the following classes on any element. Note that each element is automatically given an ```enabled``` class when it's a valid navigation route based on the current slide. - -```html - - - - - - -``` - - -### Fragments -Fragments are used to highlight individual elements on a slide. Every element with the class ```fragment``` will be stepped through before moving on to the next slide. Here's an example: http://revealjs.com/#/fragments - -The default fragment style is to start out invisible and fade in. This style can be changed by appending a different class to the fragment: - -```html -
-

grow

-

shrink

-

fade-out

-

fade-up (also down, left and right!)

-

visible only once

-

blue only once

-

highlight-red

-

highlight-green

-

highlight-blue

-
-``` - -Multiple fragments can be applied to the same element sequentially by wrapping it, this will fade in the text on the first step and fade it back out on the second. - -```html -
- - I'll fade in, then out - -
-``` - -The display order of fragments can be controlled using the ```data-fragment-index``` attribute. - -```html -
-

Appears last

-

Appears first

-

Appears second

-
-``` - -### Fragment events - -When a slide fragment is either shown or hidden reveal.js will dispatch an event. - -Some libraries, like MathJax (see #505), get confused by the initially hidden fragment elements. Often times this can be fixed by calling their update or render function from this callback. - -```javascript -Reveal.addEventListener( 'fragmentshown', function( event ) { - // event.fragment = the fragment DOM element -} ); -Reveal.addEventListener( 'fragmenthidden', function( event ) { - // event.fragment = the fragment DOM element -} ); -``` - -### Code syntax highlighting - -By default, Reveal is configured with [highlight.js](https://highlightjs.org/) for code syntax highlighting. Below is an example with clojure code that will be syntax highlighted. When the `data-trim` attribute is present, surrounding whitespace is automatically removed. HTML will be escaped by default. To avoid this, for example if you are using `` to call out a line of code, add the `data-noescape` attribute to the `` element. - -```html -
-

-(def lazy-fib
-  (concat
-   [0 1]
-   ((fn rfib [a b]
-        (lazy-cons (+ a b) (rfib b (+ a b)))) 0 1)))
-	
-
-``` - -### Slide number -If you would like to display the page number of the current slide you can do so using the ```slideNumber``` and ```showSlideNumber``` configuration values. - -```javascript -// Shows the slide number using default formatting -Reveal.configure({ slideNumber: true }); - -// Slide number formatting can be configured using these variables: -// "h.v": horizontal . vertical slide number (default) -// "h/v": horizontal / vertical slide number -// "c": flattened slide number -// "c/t": flattened slide number / total slides -Reveal.configure({ slideNumber: 'c/t' }); - -// Control which views the slide number displays on using the "showSlideNumber" value: -// "all": show on all views (default) -// "speaker": only show slide numbers on speaker notes view -// "print": only show slide numbers when printing to PDF -Reveal.configure({ showSlideNumber: 'speaker' }); - -``` - - -### Overview mode - -Press "Esc" or "o" keys to toggle the overview mode on and off. While you're in this mode, you can still navigate between slides, -as if you were at 1,000 feet above your presentation. The overview mode comes with a few API hooks: - -```javascript -Reveal.addEventListener( 'overviewshown', function( event ) { /* ... */ } ); -Reveal.addEventListener( 'overviewhidden', function( event ) { /* ... */ } ); - -// Toggle the overview mode programmatically -Reveal.toggleOverview(); -``` - - -### Fullscreen mode -Just press »F« on your keyboard to show your presentation in fullscreen mode. Press the »ESC« key to exit fullscreen mode. - - -### Embedded media -Add `data-autoplay` to your media element if you want it to automatically start playing when the slide is shown: - -```html - -``` - -If you want to enable or disable autoplay globally, for all embedded media, you can use the `autoPlayMedia` configuration option. If you set this to `true` ALL media will autoplay regardless of individual `data-autoplay` attributes. If you initialize with `autoPlayMedia: false` NO media will autoplay. - -Note that embedded HTML5 `