Compare commits

..

No commits in common. "master" and "0.4.8" have entirely different histories.

37 changed files with 304 additions and 1072 deletions

205
.gitignore vendored
View file

@ -1,206 +1,9 @@
*~
bin
classes
lib
releases
target
*.jar
# Created by https://www.gitignore.io/api/clojure,osx,linux,windows,leiningen,intellij,eclipse,sublimetext
### Clojure ###
pom.xml
pom.xml.asc
*jar
/lib/
/classes/
/target/
/checkouts/
.lein-deps-sum
.lein-repl-history
.lein-plugins/
.lein-failures
.nrepl-port
### OSX ###
.DS_Store
.AppleDouble
.LSOverride
# Icon must end with two \r
Icon
# Thumbnails
._*
# Files that might appear in the root of a volume
.DocumentRevisions-V100
.fseventsd
.Spotlight-V100
.TemporaryItems
.Trashes
.VolumeIcon.icns
# Directories potentially created on remote AFP share
.AppleDB
.AppleDesktop
Network Trash Folder
Temporary Items
.apdisk
### Linux ###
*~
# KDE directory preferences
.directory
# Linux trash folder which might appear on any partition or disk
.Trash-*
### Windows ###
# Windows image file caches
Thumbs.db
ehthumbs.db
# Folder config file
Desktop.ini
# Recycle Bin used on file shares
$RECYCLE.BIN/
# Windows Installer files
*.cab
*.msi
*.msm
*.msp
# Windows shortcuts
*.lnk
### Leiningen ###
pom.xml
pom.xml.asc
*jar
/lib/
/classes/
/target/
/checkouts/
.lein-deps-sum
.lein-repl-history
.lein-plugins/
.lein-failures
.nrepl-port
### Intellij ###
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio
*.iml
## Directory-based project format:
.idea/
# if you remove the above rule, at least ignore the following:
# User-specific stuff:
# .idea/workspace.xml
# .idea/tasks.xml
# .idea/dictionaries
# Sensitive or high-churn files:
# .idea/dataSources.ids
# .idea/dataSources.xml
# .idea/sqlDataSources.xml
# .idea/dynamic.xml
# .idea/uiDesigner.xml
# Gradle:
# .idea/gradle.xml
# .idea/libraries
# Mongo Explorer plugin:
# .idea/mongoSettings.xml
## File-based project format:
*.ipr
*.iws
## Plugin-specific files:
# IntelliJ
/out/
# mpeltonen/sbt-idea plugin
.idea_modules/
# JIRA plugin
atlassian-ide-plugin.xml
# Crashlytics plugin (for Android Studio and IntelliJ)
com_crashlytics_export_strings.xml
crashlytics.properties
crashlytics-build.properties
### Eclipse ###
*.pydevproject
.metadata
.gradle
bin/
tmp/
*.tmp
*.bak
*.swp
*~.nib
local.properties
.settings/
.loadpath
# Eclipse Core
.project
# External tool builders
.externalToolBuilders/
# Locally stored "Eclipse launch configurations"
*.launch
# CDT-specific
.cproject
# JDT-specific (Eclipse Java Development Tools)
.classpath
# Java annotation processor (APT)
.factorypath
# PDT-specific
.buildpath
# sbteclipse plugin
.target
# TeXlipse plugin
.texlipse
### SublimeText ###
# cache files for sublime text
*.tmlanguage.cache
*.tmPreferences.cache
*.stTheme.cache
# workspace files are user-specific
*.sublime-workspace
# project files should be checked into the repository, unless a significant
# proportion of contributors will probably not be using SublimeText
# *.sublime-project
# sftp configuration file
sftp-config.json
# Vagrant
.vagrant/
ubuntu-xenial-16.04-cloudimg-console.log
.lein-plugins

146
README.md
View file

@ -1,88 +1,60 @@
# Clojure Koans
The Clojure Koans are a fun way to get started with Clojure - no experience
assumed or required. Follow the instructions below to start making tests pass!
The Clojure Koans are a fun and easy way to get started with Clojure - no
experience assumed or required. Just follow the instructions below to start
making tests pass!
## Getting Started
### Getting Started
I recommend starting from a cloned or forked repo. This way you'll be able to
track your progress in Git. You might want to create your own branch - that way
if you pull back the latest koans from master, it'll be a bit easier to manage
the inevitable conflicts if we make changes to exercises you've already
completed.
The easiest and fastest way to get the koans up and running is to [download the
latest zip file from Github](https://github.com/functional-koans/clojure-koans/downloads).
This way, you'll have all the dependencies you need, including Clojure itself
and JLine, and you can skip the rest of this section (skip to "Running the
Koans").
You have a few options for installation:
- Install the dependencies for the koans (such as Clojure) on your machine
- Use Vagrant and the configuration in this repository
- Use Docker
Instructions for each option are below!
### Installation on Your Machine
If you're starting from a cloned or forked repo, that's cool too. This way
you'll be able to track your progress in Git, and see how your answers compare
to others, by checking out the project's Network tab. You might want to create
your own branch - that way if you pull back the latest koans from master, it'll
be a bit easier to manage the inevitable conflicts if we make changes to
exercises you've already completed.
The only things you'll need to run the Clojure Koans are:
- JDK (I suggest version 8, but anything 6 or above should work fine)
- [Leiningen](http://github.com/technomancy/leiningen), a build tool for Clojure
- JRE 1.5 or higher
- [clojure-1.5.0.jar](http://repo1.maven.org/maven2/org/clojure/clojure/1.5.0/clojure-1.5.0.zip)
Once you've cloned this repo and installed the dependencies, you can run:
You can use [Leiningen](http://github.com/technomancy/leiningen) to
automatically install the Clojure jar in the right place. Leiningen will also
get you a couple more jarfiles, including JLine, which allows you some of the
functionality of readline (command-line history, for example).
```
lein repl
```
### Installing dependencies
to make sure all the dependencies get downloaded properly (and then `(exit)`
when you want to quit). See below for details on the REPL.
Dependencies are installed automatically with lein 2, but if you are still
using lein 1 run
`lein deps`
which will download all dependencies you need to run the Clojure koans.
### Installation with Vagrant
### Running the Koans
Make sure you have [Vagrant](https://www.vagrantup.com/) and
[VirtualBox](https://www.virtualbox.org) installed.
In the root directory of the project, execute:
If you're running from the zipfile or using lein 1, simply run
```
vagrant up
vagrant ssh
cd /vagrant
lein koan run
```
`script/run` on Mac/\*nix
`script\run` on Windows
### Installation with Docker
Once you've got [Docker](https://www.docker.com/) installed, you're basically
all set. You can run these commands to get started:
To run koans:
```
docker run --rm -it -v $(pwd):/app -w /app clojure lein koan run
```
To start up a REPL:
```
docker run --rm -it -v $(pwd):/app -w /app clojure lein repl
```
## Running the Koans
Run the koans via:
If you're running from a checkout using lein 2, run the koans via
`lein koan run`
If want to run directly from a REPL, once you are inside the `lein repl` prompt you can run the koans with
`(exec "run")`
Either way, it's an auto-runner, so as you save your files with the correct
answers, it will advance you to the next koan or file (conveniently, all files
are prefixed with the sequence that you should follow).
It's an auto-runner, so as you save your files with the correct answers, it will
advance you to the next koan or file (conveniently, all files are prefixed with
the sequence that you should follow).
You'll see something like this:
@ -93,33 +65,31 @@ You'll see something like this:
(= __ true)
The output is telling you that you have a failing test in the file named
`01_equalities.clj`, on line 3. So you need to open that file up and make
`01_equalities.clj`, on line 3. So you just need to open that file up and make
it pass! You'll always be filling in the blanks to make tests pass.
Sometimes there could be several correct answers (or even an infinite number):
any of them will work in these cases. Some tests will pass even if you replace
the blanks with whitespace (or nothing) instead of the expected answer. Make sure
you give one correct expression to replace each blank.
any of them will work in these cases.
The koans differ from normal TDD in that the tests are already written for you,
so you'll have to pay close attention to the failure messages, because up until
the very end, making a test pass means that the next failure message comes
the very end, making a test pass just means that the next failure message comes
up.
While it might be easy (especially at first) to fill in the blanks making
While it might be easy (especially at first) to just fill in the blanks making
things pass, you should work thoughtfully, making sure you understand why the
answer is what it is. Enjoy your path to Clojure enlightenment!
## Trying more things out
### Trying more things out
It's very useful to try things out in a REPL (Read-Evaluate-Print Loop)
whenever you get stuck or curious. Run:
There's a REPL (Read-Evaluate-Print Loop) included in the Clojure Koans. Just
run:
```
lein repl
```
`script/repl` on Mac/\*nix
and you'll be able to type expressions in, and see what output they produce.
`script\repl` on Windows
If you're on lein 2, `lein repl` is what you want instead.
Here are some interesting commands you might try, once you're in a running REPL:
@ -138,46 +108,42 @@ And if those still don't make sense:
will show you what those commands mean.
You can exit the REPL with `CTRL-d`, `(exit)`, or `(quit)`.
You can exit the REPL with `CTRL-d` on any OS.
## Contributing
### Contributing
Patches are encouraged! Make sure the answer sheet still passes
(`lein koan test`), and send a pull request.
(`script/test`, or `script\test` on Windows, or `lein koan test` on lein2), and
send a pull request.
The file ideaboard.txt has lots of good ideas for new koans to start, or things
to add to existing koans. So write some fun exercises, add your answers to
`resources/koans.clj`, and we'll get them in there!
Please follow the guidelines in
http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html for
commit messages, and put your code in a feature branch (not master) before
making the pull request. This makes patches easier to review.
Feel free to contact me (Colin Jones / trptcolin) on Github or elsewhere if you
have any questions or want more direction before you start pitching in.
## Contributors
### Contributors
https://github.com/functional-koans/clojure-koans/contributors
## Credits
### Credits
These exercises were started by [Aaron Bedra](http://github.com/abedra) of
[Relevance, Inc.](http://github.com/relevance) in early 2010, as a learning
tool for newcomers to functional programming. Aaron's macro-fu makes these
koans clear and fun to use and improve upon, and without Relevance's
initiative, this project would not exist.
koans extremely simple and fun to use, and to improve upon, and without
Relevance's initiative, this project would not exist.
Using the [koans](http://en.wikipedia.org/wiki/koan) metaphor as a tool for
learning a programming language started with the
[Ruby Koans](http://rubykoans.com) by [EdgeCase](http://github.com/edgecase).
## License
### License
The use and distribution terms for this software are covered by the
Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php)

31
Vagrantfile vendored
View file

@ -1,31 +0,0 @@
Vagrant.configure("2") do |config|
config.vm.box = "ubuntu/xenial64"
config.vm.network "private_network", ip: "192.168.33.33"
config.vm.provider "virtualbox" do |vb|
vb.memory = "1024"
end
config.vm.provision "shell", inline: <<-SHELL
set -x
#apt-get update
#apt-get upgrade -y
apt-get install -y openjdk-8-jdk
SHELL
config.vm.provision "shell", privileged: false, inline: <<-SHELL
pwd
mkdir bin
echo "PATH=\$PATH:~/bin" >> .bashrc
cd bin
wget https://raw.githubusercontent.com/technomancy/leiningen/stable/bin/lein
chmod +x lein
./lein
cd /vagrant
~/bin/lein deps
SHELL
end

View file

@ -1,16 +1,13 @@
Concepts / Language Features
=====
new record syntax
Agents
Vars
state identity lifetime
Metadata
immutability / side effects
type hints
Pre and Post conditions of functions
ex-info/ex-data
reducers (?)
transducers (?) - maybe just some basic ones, nothing too crazy
Particular Functions
=====
@ -21,9 +18,11 @@ flatten
frequencies
reductions
group-by
keep
keep-indexed
map-indexed
partition-all
partition-by
repeatedly

View file

@ -1,10 +1,8 @@
(defproject clojure-koans "0.5.2-SNAPSHOT"
(defproject clojure-koans "0.4.8"
:description "The Clojure koans."
:dependencies [[org.clojure/clojure "1.10.0"]
[koan-engine "0.2.5"]]
:dev-dependencies [[lein-koan "0.1.5"]]
:profiles {:dev {:dependencies [[lein-koan "0.1.5"]]}}
:repl-options {:init-ns koan-engine.runner
:init ^:displace (do (use '[koan-engine.core]))}
:plugins [[lein-koan "0.1.5"]]
:dependencies [[org.clojure/clojure "1.5.0"]
[koan-engine "0.1.3"]]
:dev-dependencies [[lein-koan "0.1.2"]]
:profiles {:dev {:dependencies [[lein-koan "0.1.2"]]}}
:plugins [[lein-koan "0.1.2"]]
:main koan-engine.runner/exec)

View file

@ -1,111 +1,77 @@
[["01_equalities" {"__" [true
2
7
5
true
4/2
false
true
true
false
"hello"
"hello"
nil
6/3
3]}]
["02_strings" {"__" ["hello"
"world"
"Cool "
"right?"
0
11
false
6 11
"123"
", "
"1" "2" "3"
"olleh"
"hello"
13
nil
"hello world"
true
false
false
"a"
true
true
false]}]
["03_lists" {"__" [1 2 3 4 5
["02_lists" {"__" [1 2 3 4 5
1
[2 3 4 5]
3
0
()
[:a :b :c :d :e]
[:e :a :b :c :d]
[0 :a :b :c :d :e]
:a
[:b :c :d :e]
"No dice!"
()]}]
["04_vectors" {"__" [1
["03_vectors" {"__" [1
[]
[1]
[nil nil]
[nil]
2
[111 222 333]
[333]
:peanut
:jelly
:jelly
[:butter :and]
3]}]
["05_sets" {"__" [[3]
["04_sets" {"__" [nil
3
#{1 2 3 4 5}
#{1 2 3 4 5}
#{2 3}
#{1 4}]}]
["06_maps" {"__" [:b 2
["05_maps" {"__" [{}
0
1
2
2
1
1
"Sochi"
"Vancouver"
nil
:key-not-found
true
false
"February"
1 "January"
:c 3
2
2010 2014 2018
"PyeongChang" "Sochi" "Vancouver"
2 3]}]
2006 2010 2014
"Vancouver"]}]
["07_functions" {"__" [81
20
10
60
["06_functions" {"__" [20
10 5
30 2
15
"AACC"]
"___" [+
*
(fn [f] (f 5))
20 *]
"___" [(fn [f] (f 5))
(fn [f] (f 5))]}]
["08_conditionals" {"__" [:a
["07_conditionals" {"__" [:a
[]
nil
:glory
4 6 :your-road
1
:bicycling
"is that even exercise?"]}]
'doom 0
:cocked-pistol
:say-what?]}]
["09_higher_order_functions" {"__" [4 8 12
["08_higher_order_functions" {"__" [4 8 12
(* x x)
[false false true false false]
()
@ -116,29 +82,29 @@
100
(count a) (count b)]}]
["10_runtime_polymorphism" {"__" [(str (:name a) " eats veggies.")
["09_runtime_polymorphism" {"__" [(str (:name a) " eats veggies.")
(str (:name a) " eats animals.")
(str "I don't know what " (:name a) " eats.")
"Hello World!"
"Hello, you silly world."
"Hello to this group: Peter, Paul, Mary!" ]}]
["11_lazy_sequences" {"__" [[1 2 3 4]
["10_lazy_sequences" {"__" [[1 2 3 4]
[0 1 2 3 4]
10
95
[1 2 4 8 16 32 64 128]
(range 20)
:a]
"___" [(fn [x] x)]}]
"___" [(fn [x] :foo)]}]
["12_sequence_comprehensions" {"__" [[0 1 2 3 4 5]
(* x x)
["11_sequence_comprehensions" {"__" [[0 1 2 3 4 5]
(* index index)
(range 10)
(odd? x) (* x x)
(odd? index) (* index index)
[row column]
]}]
["13_creating_functions" {"__" [true false true
["12_creating_functions" {"__" [true false true
4
:a :b :c :d
:c :d
@ -148,7 +114,7 @@
multiply-by-5
(comp dec square)]}]
["14_recursion" {"__" [true
["13_recursion" {"__" [true
acc
(loop [coll coll
acc ()]
@ -162,10 +128,10 @@
(recur (dec n) (* acc n))))]
"___" [not]}]
["15_destructuring" {"__" [":bar:foo"
(format (str "An Oxford comma list of %s, "
"%s, "
"and %s.")
["14_destructuring" {"__" [":bar:foo"
(format (str "First comes %s, "
"then comes %s, "
"then comes %s with the baby carriage")
a b c)
(apply str
(interpose " "
@ -184,7 +150,7 @@
street-address ", " city ", " state))
]}]
["16_refs" {"__" ["hello"
["15_refs" {"__" ["hello"
"hello"
"better"
"better!!!"
@ -193,7 +159,7 @@
]
"___" [(fn [x] (+ 20 x))]}]
["17_atoms" {"__" [0
["16_atoms" {"__" [0
1
(swap! atomic-clock (partial + 4))
20
@ -201,15 +167,17 @@
atomic-clock 20 :fin
]}]
["18_quote" {"__" ['(1 2 3 4 5)
(1 2 3 4 5)
'age
quote
'(+ 2 3)
1 2 3
1 5]}]
["17_macros" {"__" [~(first form)
~(nth form 2)
form
(drop 2 form)
"Hello, Macros!"
10
'(+ 9 1)
'(* 10 2)
'(+ 10 (2 * 3))]}]
["19_datatypes" {"__" [(print
["18_datatypes" {"__" [(print
(str "You're really the "
(.category this)
", " recipient "... sorry."))
@ -221,68 +189,18 @@
(str "Congratulations on your Best Picture Oscar, "
"Evil Alien Conquerors!")]}]
["20_java_interop" {"__" [java.lang.String
["19_java_interop" {"__" [java.lang.String
"SELECT * FROM"
10
1024
]
"___" [#(.toUpperCase %)
]}]
["21_partition" {"__" [partition
]
}]
["20_partition" {"__" [partition
[:a :b :c]
'((0 1 2) (3 4))
5
:hello
(6 :these :are)
]}]
["22_group_by" {"__" [odd?
{5 ["hello" "world"] 3 ["foo" "bar"]}
{1 [{:name "Bob" :id 1}
{:last-name "Smith" :id 1}]
2 [{:name "Jennifer" :id 2}]}
nil
{:naughty-list [{:name "Jimmy" :bad true}
{:name "Joe" :bad true}]
:nice-list [{:name "Jane" :bad false}]}]}]
["23_meta" {"__" [{:league "National League"}
{:division "West"}
"This doesn't implement the IObj interface"
{:foo :bar}
nil
\C
inc
:park "Oracle Park"
'Giants
"Giants"]}]
["24_macros" {"__" [~(first form)
~(nth form 2)
form
(drop 2 form)
"Hello, Macros!"
10
'(+ 9 1)]}]
["25_threading_macros" {"__" [{:a 1}
"Hello world, and moon, and stars"
"String with a trailing space"
6
1
[2 3 4]
12
[1 2 3]]}]
["26_transducers" {"__" ['(2 3 4)
[2 4]
[2 4]
[2 4]
6]}]
["27_multimethods" {"__" ["Hello, World!"
"Hello there"
1
6]}]
]
(6 :this :are)
]}]]

View file

@ -1,18 +1,20 @@
#!/bin/sh
mkdir -p releases
lein1 deps
zip -r releases/clojure-koans-`date +"%Y-%m-%d_%H-%M"`.zip \
. \
-x "./.idea/*" \
-x "./.lein-plugins/*" \
-x "./.git/*" \
-x "releases/*"
echo
echo "Don't forget to upload the zipfile (somewhere...)"
echo "Don't forget to upload the zipfile"
echo " to https://github.com/functional-koans/clojure-koans/downloads"
echo `ls -t releases/clojure-koans-*.zip | head -n1`
echo "git push"
echo "git push --tags"
echo
# TODO: use http://developer.github.com/v3/repos/downloads/
# to do the upload automatically
# GET example:
# curl https://api.github.com/repos/functional-koans/clojure-koans/downloads

View file

@ -1,6 +1,3 @@
(ns koans.01-equalities
(:require [koan-engine.core :refer :all]))
(meditations
"We shall contemplate truth by testing reality, via equality"
(= __ true)
@ -9,31 +6,16 @@
(= __ (+ 1 1))
"You can test equality of many things"
(= (+ 3 4) 7 (+ 2 __))
(= (+ 3 4) __ (+ 2 __))
"Some things may appear different, but be the same"
(= __ (= 2 2/1))
(= 2 2/1 __)
"You cannot generally float to heavens of integers"
(= __ (= 2 2.0))
"But a looser equality is also possible"
(= __ (== 2.0 2))
"Something is not equal to nothing"
(= __ (not (= 1 nil)))
"Strings, and keywords, and symbols: oh my!"
(= __ (= "hello" :hello 'hello))
"Make a keyword with your keyboard"
(= :hello (keyword __))
"Symbolism is all around us"
(= 'hello (symbol __))
"What could be equivalent to nothing?"
(= __ nil)
(== 2.0 2 __)
"When things cannot be equal, they must be different"
(not= :fill-in-the-blank __))

View file

@ -1,6 +1,3 @@
(ns koans.03-lists
(:require [koan-engine.core :refer :all]))
(meditations
"Lists can be expressed by function or a quoted form"
(= '(__ __ __ __ __) (list 1 2 3 4 5))
@ -11,20 +8,14 @@
"As well as the rest"
(= __ (rest '(1 2 3 4 5)))
"Count your blessings"
(= __ (count '(dracula dooku chocula)))
"Before they are gone"
(= __ (count '()))
"The rest, when nothing is left, is empty"
"The rest when nothing is left is empty"
(= __ (rest '(100)))
"Construction by adding an element to the front is easy"
"And construction by adding an element to the front is simple"
(= __ (cons :a '(:b :c :d :e)))
"Conjoining an element to a list isn't hard either"
(= __ (conj '(:a :b :c :d) :e))
"Conjoining an element to a list can be done in the reverse order"
(= __ (conj '(:a :b :c :d :e) 0))
"You can use a list like a stack to get the first element"
(= __ (peek '(:a :b :c :d :e)))
@ -35,11 +26,9 @@
"But watch out if you try to pop nothing"
(= __ (try
(pop '())
(catch IllegalStateException e
"No dice!")))
(catch IllegalStateException e "No dice!")))
"The rest of nothing isn't so strict"
(= __ (try
(rest '())
(catch IllegalStateException e
"No dice!"))))
(catch IllegalStateException e "No dice!"))))

View file

@ -1,70 +0,0 @@
(ns koans.02-strings
(:require [koan-engine.core :refer :all]
[clojure.string :as string]))
(meditations
"A string is nothing more than text surrounded by double quotes"
(= __ "hello")
"But double quotes are just magic on top of something deeper"
(= __ (str 'world))
"You can do more than create strings, you can put them together"
(= "Cool right?" (str __ __))
"You can even get certain characters"
(= \C (get "Characters" __))
"Or even count the characters"
(= __ (count "Hello World"))
"But strings and characters are not the same"
(= __ (= \c "c"))
"What if you only wanted to get part of a string?"
(= "World" (subs "Hello World" __ __))
"How about joining together elements in a list?"
(= __ (string/join '(1 2 3)))
"What if you wanted to separate them out?"
(= "1, 2, 3" (string/join __ '(1 2 3)))
"Maybe you want to separate out all your lines"
(= [__ __ __] (string/split-lines "1\n2\n3"))
"You may want to make sure your words are backwards"
(= __ (string/reverse "hello"))
"Maybe you want to find the index of the first occurrence of a substring"
(= 0 (string/index-of "hello world" __))
"Or maybe the last index of the same substring"
(= __ (string/last-index-of "hello world, hello" "hello"))
"But when something doesn't exist, nothing is found"
(= __ (string/index-of "hello world" "bob"))
"Sometimes you don't want whitespace cluttering the front and back"
(= __ (string/trim " \nhello world \t \n"))
"You can check if something is a char"
(= __ (char? \c))
"But it may not be"
(= __ (char? "a"))
"But chars aren't strings"
(= __ (string? \b))
"Strings are strings"
(= true (string? __))
"Some strings may be blank"
(= __ (string/blank? ""))
"Even if at first glance they aren't"
(= __ (string/blank? " \n \t "))
"However, most strings aren't blank"
(= __ (string/blank? "hello?\nare you out there?")))

View file

@ -1,21 +1,21 @@
(ns koans.04-vectors
(:require [koan-engine.core :refer :all]))
(meditations
"You can use vectors in clojure as array-like structures"
"You can use vectors in clojure to create an 'Array' like structure"
(= __ (count [42]))
"You can create a vector from a list"
"You can create a vector in several ways"
(= __ (vec nil))
"And populate it in either of these ways"
(= __ (vec '(1)))
"Or from some elements"
(= __ (vector nil nil))
"There is another way as well"
(= __ (vector nil))
"But you can populate it with any number of elements at once"
(= [1 __] (vec '(1 2)))
"Conjoining to a vector is different than to a list"
(= __ (conj [111 222] 333))
"And add to it as well"
(= __ (conj (vec nil) 333))
"You can get the first element of a vector like so"
(= __ (first [:peanut :butter :and :jelly]))

18
src/koans/04_sets.clj Normal file
View file

@ -0,0 +1,18 @@
(meditations
"You can create a set in two ways"
(= #{} (set __))
"They are another important data structure in clojure"
(= __ (count #{1 2 3}))
"Remember that a set is a 'set'"
(= __ (set '(1 1 2 2 3 3 4 4 5 5)))
"You can ask clojure for the union of two sets"
(= __ (clojure.set/union #{1 2 3 4} #{2 3 5}))
"And also the intersection"
(= __ (clojure.set/intersection #{1 2 3 4} #{2 3 5}))
"But don't forget about the difference"
(= __ (clojure.set/difference #{1 2 3 4 5} #{2 3 5})))

50
src/koans/05_maps.clj Normal file
View file

@ -0,0 +1,50 @@
(meditations
"There are two ways to create maps"
(= __ (hash-map))
"Maps in clojure associate keys with values"
(= __ (count (hash-map)))
"A value must be supplied for each key"
(= {:a 1} (hash-map :a __))
"The size is the number of entries"
(= __ (count {:a 1 :b 2}))
"You can look up the value for a given key"
(= __ (get {:a 1 :b 2} :b))
"Maps can be used as lookup functions"
(= __ ({:a 1 :b 2} :a))
"And so can keywords"
(= __ (:a {:a 1 :b 2}))
"But map keys need not be keywords"
(= __ ({2006 "Torino" 2010 "Vancouver" 2014 "Sochi"} 2010))
"You may not be able to find an entry for a key"
(= __ (get {:a 1 :b 2} :c))
"But you can provide your own default"
(= __ (get {:a 1 :b 2} :c :key-not-found))
"You can find out if a key is present"
(= __ (contains? {:a nil :b nil} :b))
"Or if it is missing"
(= __ (contains? {:a nil :b nil} :c))
"Maps are immutable, but you can create a new, 'changed' version"
(= {1 "January" 2 __} (assoc {1 "January" } 2 "February"))
"You can also 'remove' an entry"
(= {__ __} (dissoc {1 "January" 2 "February"} 2))
"Often you will need to get the keys (which will be in hash order)"
(= (list __ __ __)
(sort (keys {2006 "Torino" 2010 "Vancouver" 2014 "Sochi"})))
"Or the values"
(= (list "Sochi" "Torino" __)
(sort (vals {2006 "Torino" 2010 "Vancouver" 2014 "Sochi"}))))

View file

@ -1,22 +0,0 @@
(ns koans.05-sets
(:require [koan-engine.core :refer :all]
[clojure.set :as set]))
(meditations
"You can create a set by converting another collection"
(= #{3} (set __))
"Counting them is like counting other collections"
(= __ (count #{1 2 3}))
"Remember that a set is a *mathematical* set"
(= __ (set '(1 1 2 2 3 3 4 4 5 5)))
"You can ask Clojure for the union of two sets"
(= __ (set/union #{1 2 3 4} #{2 3 5}))
"And also the intersection"
(= __ (set/intersection #{1 2 3 4} #{2 3 5}))
"But don't forget about the difference"
(= __ (set/difference #{1 2 3 4 5} #{2 3 5})))

View file

@ -0,0 +1,29 @@
(defn multiply-by-ten [n]
(* 10 n))
(defn square [n] (* n n))
(meditations
"Functions are often defined before they are used"
(= __ (multiply-by-ten 2))
"But they can also be defined inline"
(= __ ((fn [n] (* __ n)) 2))
"Or using even shorter syntax"
(= __ (#(* 15 %) __))
"Short anonymous functions may take multiple arguments"
(= __ (#(+ %1 %2 %3) 4 5 6))
"One function can beget another"
(= __ ((fn []
((fn [a b] (__ a b))
4 5))))
"Higher-order functions take function arguments"
(= 25 (___
(fn [n] (* n n))))
"But they are often better written using the names of functions"
(= 25 (___ square)))

View file

@ -1,63 +0,0 @@
(ns koans.06-maps
(:require [koan-engine.core :refer :all]))
(meditations
"Don't get lost when creating a map"
(= {:a 1 :b 2} (hash-map :a 1 __ __))
"A value must be supplied for each key"
(= {:a 1} (hash-map :a __))
"The size is the number of entries"
(= __ (count {:a 1 :b 2}))
"You can look up the value for a given key"
(= __ (get {:a 1 :b 2} :b))
"Maps can be used as functions to do lookups"
(= __ ({:a 1 :b 2} :a))
"And so can keywords"
(= __ (:a {:a 1 :b 2}))
"But map keys need not be keywords"
(= __ ({2010 "Vancouver" 2014 "Sochi" 2018 "PyeongChang"} 2014))
"You may not be able to find an entry for a key"
(= __ (get {:a 1 :b 2} :c))
"But you can provide your own default"
(= __ (get {:a 1 :b 2} :c :key-not-found))
"You can find out if a key is present"
(= __ (contains? {:a nil :b nil} :b))
"Or if it is missing"
(= __ (contains? {:a nil :b nil} :c))
"Maps are immutable, but you can create a new and improved version"
(= {1 "January" 2 __} (assoc {1 "January"} 2 "February"))
"You can also create a new version with an entry removed"
(= {__ __} (dissoc {1 "January" 2 "February"} 2))
"Create a new map by merging"
(= {:a 1 :b 2 __ __} (merge {:a 1 :b 2} {:c 3}))
"Specify how to handle entries with same keys when merging"
(= {:a 1 :b __ :c 3} (merge-with + {:a 1 :b 1} {:b 1 :c 3}))
"Often you will need to get the keys, but the order is undependable"
(= (list __ __ __)
(sort (keys { 2014 "Sochi" 2018 "PyeongChang" 2010 "Vancouver"})))
"You can get the values in a similar way"
(= (list __ __ __)
(sort (vals {2010 "Vancouver" 2014 "Sochi" 2018 "PyeongChang"})))
"You can even iterate over the map entries as a seq"
(= {:a __ :b __}
(into {}
(map
(fn [[k v]] [k (inc v)])
{:a 1 :b 2}))))

View file

@ -1,12 +1,11 @@
(ns koans.08-conditionals
(:require [koan-engine.core :refer :all]))
(defn explain-exercise-velocity [exercise-term]
(defn explain-defcon-level [exercise-term]
(case exercise-term
:bicycling "pretty fast"
:jogging "not super fast"
:walking "not fast at all"
"is that even exercise?"))
:fade-out :you-and-what-army
:double-take :call-me-when-its-important
:round-house :o-rly
:fast-pace :thats-pretty-bad
:cocked-pistol :sirens
:say-what?))
(meditations
"You will face many decisions"
@ -34,14 +33,14 @@
:else __)))
"Or your fate may be sealed"
(= 'doom (if-not (zero? __)
(= __ (if-not (zero? __)
'doom
'more-doom))
'doom))
"In case of emergency, go fast"
(= "pretty fast"
(explain-exercise-velocity __))
"In case of emergency, sound the alarms"
(= :sirens
(explain-defcon-level __))
"But admit it when you don't know what to do"
(= __
(explain-exercise-velocity :watching-tv)))
(explain-defcon-level :yo-mama)))

View file

@ -1,40 +0,0 @@
(ns koans.07-functions
(:require [koan-engine.core :refer :all]))
(defn multiply-by-ten [n]
(* 10 n))
(defn square [n] (* n n))
(meditations
"Calling a function is like giving it a hug with parentheses"
(= __ (square 9))
"Functions are usually defined before they are used"
(= __ (multiply-by-ten 2))
"But they can also be defined inline"
(= __ ((fn [n] (* 5 n)) 2))
"Or using an even shorter syntax"
(= __ (#(* 15 %) 4))
"Even anonymous functions may take multiple arguments"
(= __ (#(+ %1 %2 %3) 4 5 6))
"Arguments can also be skipped"
(= __ (#(str "AA" %2) "bb" "CC"))
"One function can beget another"
(= 9 (((fn [] ___)) 4 5))
"Functions can also take other functions as input"
(= 20 ((fn [f] (f 4 5))
___))
"Higher-order functions take function arguments"
(= 25 (___
(fn [n] (* n n))))
"But they are often better written using the names of functions"
(= 25 (___ square)))

View file

@ -1,6 +1,3 @@
(ns koans.09-higher-order-functions
(:require [koan-engine.core :refer :all]))
(meditations
"The map function relates a sequence to another"
(= [__ __ __] (map (fn [x] (* 4 x)) [1 2 3]))

View file

@ -1,12 +1,9 @@
(ns koans.10-runtime-polymorphism
(:require [koan-engine.core :refer :all]))
(defn hello
([] "Hello World!")
([a] (str "Hello, you silly " a "."))
([a & more] (str "Hello to this group: "
(apply str
(interpose ", " (cons a more)))
(interpose ", " (concat (list a) more)))
"!")))
(defmulti diet (fn [x] (:eater x)))
@ -29,10 +26,6 @@
(= "Bambi eats veggies."
(diet {:species "deer" :name "Bambi" :age 1 :eater :herbivore}))
"Animals have different names"
(= "Thumper eats veggies."
(diet {:species "rabbit" :name "Thumper" :age 1 :eater :herbivore}))
"Different methods are used depending on the dispatch function result"
(= "Simba eats animals."
(diet {:species "lion" :name "Simba" :age 1 :eater :carnivore}))

View file

@ -1,6 +1,3 @@
(ns koans.11-lazy-sequences
(:require [koan-engine.core :refer :all]))
(meditations
"There are many ways to generate a sequence"
(= __ (range 1 5))
@ -17,12 +14,12 @@
(drop __ (range 100)))
"Iteration provides an infinite lazy sequence"
(= __ (take 8 (iterate (fn [x] (* x 2)) 1)))
(= __ (take 20 (iterate inc 0)))
"Repetition is key"
(= [:a :a :a :a :a :a :a :a :a :a ]
(repeat 10 __))
"Iteration can be used for repetition"
(= (repeat 100 "hello")
(take 100 (iterate ___ "hello"))))
(= (repeat 100 :foo)
(take 100 (iterate ___ :foo))))

View file

@ -1,30 +1,27 @@
(ns koans.12-sequence-comprehensions
(:require [koan-engine.core :refer :all]))
(meditations
"Sequence comprehensions can bind each element in turn to a symbol"
(= __
(for [x (range 6)]
x))
(for [index (range 6)]
index))
"They can easily emulate mapping"
(= '(0 1 4 9 16 25)
(map (fn [x] (* x x))
(map (fn [index] (* index index))
(range 6))
(for [x (range 6)]
(for [index (range 6)]
__))
"And also filtering"
(= '(1 3 5 7 9)
(filter odd? (range 10))
(for [x __ :when (odd? x)]
x))
(for [index __ :when (odd? index)]
index))
"Combinations of these transformations are trivial"
"Combinations these transformations is trivial"
(= '(1 9 25 49 81)
(map (fn [x] (* x x))
(map (fn [index] (* index index))
(filter odd? (range 10)))
(for [x (range 10) :when __]
(for [index (range 10) :when __]
__))
"More complex transformations simply take multiple binding forms"

View file

@ -1,6 +1,3 @@
(ns koans.13-creating-functions
(:require [koan-engine.core :refer :all]))
(defn square [x] (* x x))
(meditations

View file

@ -1,6 +1,3 @@
(ns koans.14-recursion
(:require [koan-engine.core :refer :all]))
(defn is-even? [n]
(if (= n 0)
__
@ -32,13 +29,13 @@
"Reversing directions is easy when you have not gone far"
(= '(1) (recursive-reverse [1]))
"Yet it becomes more difficult the more steps you take"
(= '(6 5 4 3 2) (recursive-reverse [2 3 4 5 6]))
"Yet more difficult the more steps you take"
(= '(5 4 3 2 1) (recursive-reverse [1 2 3 4 5]))
"Simple things may appear simple"
"Simple things may appear simple."
(= 1 (factorial 1))
"They may require other simple steps"
"They may require other simple steps."
(= 2 (factorial 2))
"Sometimes a slightly bigger step is necessary"

View file

@ -1,6 +1,3 @@
(ns koans.15-destructuring
(:require [koan-engine.core :refer :all]))
(def test-address
{:street-address "123 Test Lane"
:city "Testerville"
@ -12,24 +9,24 @@
[:foo :bar]))
"Whether in function definitions"
(= (str "An Oxford comma list of apples, "
"oranges, "
"and pears.")
(= (str "First comes love, "
"then comes marriage, "
"then comes Clojure with the baby carriage")
((fn [[a b c]] __)
["apples" "oranges" "pears"]))
["love" "marriage" "Clojure"]))
"Or in let expressions"
(= "Rich Hickey aka The Clojurer aka Go Time aka Lambda Guru"
(= "Rich Hickey aka The Clojurer aka Go Time aka Macro Killah"
(let [[first-name last-name & aliases]
(list "Rich" "Hickey" "The Clojurer" "Go Time" "Lambda Guru")]
(list "Rich" "Hickey" "The Clojurer" "Go Time" "Macro Killah")]
__))
"You can regain the full argument if you like arguing"
(= {:original-parts ["Stephen" "Hawking"] :named-parts {:first "Stephen" :last "Hawking"}}
(let [[first-name last-name :as full-name] ["Stephen" "Hawking"]]
(= {:original-parts ["Steven" "Hawking"] :named-parts {:first "Steven" :last "Hawking"}}
(let [[first-name last-name :as full-name] ["Steven" "Hawking"]]
__))
"Break up maps by keys"
"Break up maps by key"
(= "123 Test Lane, Testerville, TX"
(let [{street-address :street-address, city :city, state :state} test-address]
__))

View file

@ -1,6 +1,3 @@
(ns koans.16-refs
(:require [koan-engine.core :refer :all]))
(def the-world (ref "hello"))
(def bizarro-world (ref {}))

View file

@ -1,6 +1,3 @@
(ns koans.17-atoms
(:require [koan-engine.core :refer :all]))
(def atomic-clock (atom 0))
(meditations
@ -27,7 +24,7 @@
(compare-and-set! atomic-clock 100 :fin)
@atomic-clock))
"When your expectations are aligned with reality, things proceed that way"
"When your expectations are aligned with reality things, proceed that way"
(= :fin (do
(compare-and-set! __ __ __)
@atomic-clock)))

View file

@ -1,29 +1,26 @@
(ns koans.24-macros
(:require [koan-engine.core :refer :all]))
(defmacro hello [x]
(str "Hello, " x))
(defmacro infix [form]
(list (second form) (first form) (nth form 2)))
(defmacro infix-concise [form]
(defmacro infix-better [form]
`(~(second form) ; Note the syntax-quote (`) and unquote (~) characters!
__
__ ))
(defmacro recursive-infix [form]
(defmacro r-infix [form]
(cond (not (seq? form))
__
(= 1 (count form))
`(recursive-infix ~(first form))
`(r-infix ~(first form))
:else
(let [operator (second form)
first-arg (first form)
others __]
`(~operator
(recursive-infix ~first-arg)
(recursive-infix ~others)))))
(r-infix ~first-arg)
(r-infix ~others)))))
(meditations
"Macros are like functions created at compile time"
@ -36,10 +33,10 @@
(= __ (macroexpand '(infix (9 + 1))))
"You can do better than that - hand crafting FTW!"
(= '(* 10 2) (macroexpand '(infix-concise (10 * 2))))
(= __ (macroexpand '(infix-better (10 * 2))))
"Things don't always work as you would like them to"
(= '(+ 10 (2 * 3)) (macroexpand '(infix-concise (10 + (2 * 3)))))
"Things don't always work as you would like them to... "
(= __ (macroexpand '(infix-better ( 10 + (2 * 3)))))
"Really, you don't understand recursion until you understand recursion"
(= 36 (recursive-infix (10 + (2 * 3) + (4 * 5)))))
(= 36 (r-infix (10 + (2 * 3) + (4 * 5)))))

View file

@ -1,6 +1,3 @@
(ns koans.19-datatypes
(:require [koan-engine.core :refer :all]))
(defrecord Nobel [prize])
(deftype Pulitzer [prize])

View file

@ -1,25 +0,0 @@
(ns koans.18-quote
(:require [koan-engine.core :refer :all]))
(meditations
"Wrap a quote around a list to suppress evaluation"
(= (quote (1 2 3 4 5)) __)
"There is a shortcut too!"
(= (quote __) '(1 2 3 4 5))
"You can quote symbols as well as lists... without evaluation!"
(= __ (let [age 9] (quote age)))
"You can use a literal list as a data collection without having Clojure try to call a function"
(= (cons 1 (__ (2 3))) (list 1 2 3) (cons 1 [2 3]))
"The quote affects all of its arguments, not just the top level"
(= (list 1 __) '(1 (+ 2 3)))
"Syntax-quote (`) acts similarly to the normal quote"
(= (list __ __ __) `(1 2 3) '(1 2 3))
"Unquote (~) within a syntax-quoted expression lets you mark specific expressions as requiring evaluation"
(= (list __ __) `(1 ~(+ 2 3)) '(1 5)))

View file

@ -1,6 +1,3 @@
(ns koans.20-java-interop
(:require [koan-engine.core :refer :all]))
(meditations
"You may have done more with Java than you know"
(= __ (class "warfare")) ; hint: try typing (javadoc "warfare") in the REPL

View file

@ -1,6 +1,3 @@
(ns koans.21-partition
(:require [koan-engine.core :refer :all]))
(meditations
"To split a collection you can use the partition function"
(= '((0 1) (2 3)) (__ 2 (range 4)))
@ -8,14 +5,14 @@
"But watch out if there are not enough elements to form n sequences"
(= '(__) (partition 3 [:a :b :c :d :e]))
"You can use partition-all to include any leftovers too"
"You can use partition-all to also get partitions with less then n elements"
(= __ (partition-all 3 (range 5)))
"If you need to, you can start each sequence with an offset"
(= '((0 1 2) (5 6 7) (10 11 12)) (partition 3 __ (range 13)))
"Consider padding the last sequence with some default values"
"Consider padding the last sequence with some default values.."
(= '((0 1 2) (3 4 5) (6 :hello)) (partition 3 3 [__] (range 7)))
"But notice that they will only pad up to the given sequence length"
(= '((0 1 2) (3 4 5) __) (partition 3 3 [:these :are "my" "words"] (range 7))))
".. but notice that they will only pad up to given sequence length"
(= '((0 1 2) (3 4 5) __) (partition 3 3 [:this :are "my" "words"] (range 7))))

View file

@ -1,36 +0,0 @@
(ns koans.22-group-by
(:require [koan-engine.core :refer :all]))
(defn get-odds-and-evens [coll]
(let [{odds true evens false} (group-by __ coll)]
[odds evens]))
(meditations
"To categorize a collection by some function, use group-by"
(= __ (group-by count ["hello" "world" "foo" "bar"]))
"You can simulate filter + remove in one pass"
(= (get-odds-and-evens [1 2 3 4 5])
((juxt filter remove) odd? [1 2 3 4 5])
[[1 3 5] [2 4]])
"You can also group by a primary key"
(= __
(group-by :id [{:id 1 :name "Bob"}
{:id 2 :name "Jennifer"}
{:id 1 :last-name "Smith"} ]))
"But be careful when you group by a non-required key"
(= {"Bob" [{:name "Bob" :id 1}]
"Jennifer" [{:name "Jennifer" :id 2}]
__ [{:last-name "Smith" :id 1}]}
(group-by :name [{:id 1 :name "Bob"}
{:id 2 :name "Jennifer"}
{:id 1 :last-name "Smith"}]))
"The true power of group-by comes with custom functions"
(= __
(group-by #(if (:bad %) :naughty-list :nice-list)
[{:name "Jimmy" :bad true}
{:name "Jane" :bad false}
{:name "Joe" :bad true}])))

View file

@ -1,51 +0,0 @@
(ns koans.23-meta
(:require [koan-engine.core :refer :all]))
(def giants
(with-meta 'Giants
{:league "National League"}))
(meditations
"Some objects can be tagged using the with-meta function"
(= __ (meta giants))
"Or more succinctly with a reader macro"
(= __ (meta '^{:division "West"} Giants))
"While others can't"
(= __ (try
(with-meta
2
{:prime true})
(catch ClassCastException e
"This doesn't implement the IObj interface")))
"Notice when metadata carries over"
(= __ (meta (merge '^{:foo :bar} {:a 1 :b 2}
{:b 3 :c 4})))
"And when it doesn't"
(= __ (meta (merge {:a 1 :b 2}
'^{:foo :bar} {:b 3 :c 4})))
"Metadata can be used as a type hint to avoid reflection during runtime"
(= __ (#(.charAt ^String % 0) "Cast me"))
"You can directly update an object's metadata"
(= 8 (let [giants
(with-meta
'Giants
{:world-series-titles (atom 7)})]
(swap! (:world-series-titles (meta giants)) __)
@(:world-series-titles (meta giants))))
"You can also create a new object from another object with metadata"
(= {:league "National League" :park "Oracle Park"}
(meta (vary-meta giants
assoc __ __)))
"But it won't affect behavior like equality"
(= __ (vary-meta giants dissoc :league))
"Or the object's printed representation"
(= __ (pr-str (vary-meta giants dissoc :league))))

View file

@ -1,66 +0,0 @@
(ns koans.25-threading-macros
(:require [koan-engine.core :refer :all]))
(def a-list
'(1 2 3 4 5))
(def a-list-with-maps
'({:a 1} {:a 2} {:a 3}))
(defn function-that-takes-a-map [map a b]
(get map :a))
(defn function-that-takes-a-coll [a b coll]
(map :a coll))
(meditations
"We can use thread first for more readable sequential operations"
(= __
(-> {}
(assoc :a 1)))
"Consider also the case of strings"
(= __
(-> "Hello world"
(str ", and moon")
(str ", and stars")))
"When a function has no arguments to partially apply, just reference it"
(= __
(-> "String with a trailing space "
clojure.string/trim))
"Most operations that take a scalar value as an argument can be threaded-first"
(= __
(-> {}
(assoc :a 1)
(assoc :b 2)
(assoc :c {:d 4
:e 5})
(update-in [:c :e] inc)
(get-in [:c :e])))
"We can use functions we have written ourselves that follow this pattern"
(= __
(-> {}
(assoc :a 1)
(function-that-takes-a-map "hello" "there")))
"We can also thread last using ->>"
(= __
(->> [1 2 3]
(map inc)))
"Most operations that take a collection can be threaded-last"
(= __
(->> a-list
(map inc)
(filter even?)
(into [])
(reduce +)))
"We can use functions we have written ourselves that follow this pattern"
(= __
(->> a-list-with-maps
(function-that-takes-a-coll "hello" "there")
(into []))))

View file

@ -1,30 +0,0 @@
(ns koans.26-transducers
(:require [koan-engine.core :refer :all]))
(def example-transducer
(map inc))
(def transforms
(comp (map inc)
(filter even?)))
(meditations
"A sequence operation with only one argument often returns a transducer"
(= __
(sequence example-transducer [1 2 3]))
"Consider that sequence operations can be composed as transducers"
(= __
(transduce transforms conj [1 2 3]))
"We can do this eagerly"
(= __
(into [] transforms [1 2 3]))
"Or lazily"
(= __
(sequence transforms [1 2 3]))
"The transduce function can combine mapping and reduction"
(= __
(transduce transforms + [1 2 3])))

View file

@ -1,44 +0,0 @@
(ns koans.27-multimethods
(:require [koan-engine.core :refer :all]))
(defmulti multimethod-without-args
(fn [keyword-arg] keyword-arg))
(defmethod multimethod-without-args :first [_]
(str "Hello, World!"))
(defmethod multimethod-without-args :second [_]
(str "Hello there"))
(defmulti multimethod-with-args
(fn [opt-one opt-two] opt-one))
(defmethod multimethod-with-args :path-one [_ opts]
(:first-opt opts))
(defmethod multimethod-with-args :path-two [_ opts]
(let [numbers (:second-opt opts)]
(->> numbers
(map inc)
(reduce +))))
(defmethod multimethod-with-args :path-three [_])
(meditations
"A multimethod takes one or more arguments to dispatch on"
(= __
(multimethod-without-args :first))
"Though it can be ignored and represented by _ in defmethods"
(= __
(multimethod-without-args :second))
"Alternatively, we can use the arguments in defmethods"
(= __
(multimethod-with-args :path-one {:first-opt 1
:second-opt 2}))
"This allows us to do something different in each method implementation"
(= __
(multimethod-with-args :path-two {:first-opt 1
:second-opt [0 1 2]})))