├── .gitignore ├── .travis.yml ├── CHANGELOG.md ├── README.md ├── about_anonymous_functions.exs ├── about_asserts.exs ├── about_atoms.exs ├── about_binaries.exs ├── about_enums.exs ├── about_lists.exs ├── about_lists_and_maps.exs ├── about_maps.exs ├── about_matching.exs ├── about_named_functions.exs ├── about_numbers_and_booleans.exs ├── about_processes.exs ├── about_sigils.exs ├── about_strings.exs ├── about_tuples.exs ├── answers ├── about_anonymous_functions.exs ├── about_asserts.exs ├── about_atoms.exs ├── about_binaries.exs ├── about_enums.exs ├── about_lists.exs ├── about_lists_and_maps.exs ├── about_maps.exs ├── about_matching.exs ├── about_named_functions.exs ├── about_numbers_and_booleans.exs ├── about_processes.exs ├── about_sigils.exs ├── about_strings.exs └── about_tuples.exs ├── lib ├── koans.ex ├── koans │ ├── answerer.ex │ ├── answers.ex │ ├── dsl.ex │ ├── examples.ex │ ├── formatter.ex │ ├── lessons.ex │ ├── meditate_warning.ex │ └── runner.ex └── mix │ └── tasks │ └── learn.ex ├── mix.exs └── test ├── koans ├── answerer_test.exs └── dsl_test.exs └── test_helper.exs /.gitignore: -------------------------------------------------------------------------------- 1 | /_build 2 | /cover 3 | /deps 4 | erl_crash.dump 5 | *.ez 6 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: elixir 2 | elixir: 3 | - 1.2.2 4 | notifications: 5 | email: false 6 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | All notable changes to this project will be documented in this file. 3 | 4 | ## [Unreleased] 5 | 6 | ### Changed 7 | - Reorganize supporting code (https://github.com/iamvery/elixir-koans/pull/39) 8 | - Add supervisor, moving Koans.Examples to be supervised (https://github.com/iamvery/elixir-koans/pull/39) 9 | - Remove Koans.Check (https://github.com/iamvery/elixir-koans/pull/39) 10 | - Actual value on left, expected on right (https://github.com/iamvery/elixir-koans/pull/14) 11 | - Prettier user feedback when running koans (https://github.com/iamvery/elixir-koans/pull/37) 12 | - Various house cleanings (https://github.com/iamvery/elixir-koans/pull/36) 13 | 14 | ### Added 15 | - Dynamically inject answers when tests run to validate all lessons. I can't believe it worked! (https://github.com/iamvery/elixir-koans/pull/21) 16 | - Example of variable pinning in match (https://github.com/iamvery/elixir-koans/pull/36) 17 | - Example of accessing second key with the same name in keyword list (https://github.com/iamvery/elixir-koans/pull/36) 18 | - Lesson about binaries (https://github.com/iamvery/elixir-koans/pull/33) 19 | - Examples about dot access (https://github.com/iamvery/elixir-koans/pull/30/) 20 | - Lesson about atoms (https://github.com/iamvery/elixir-koans/pull/26) 21 | - Lesson about sigils (https://github.com/iamvery/elixir-koans/pull/27) 22 | - Lesson about lists and maps (https://github.com/iamvery/elixir-koans/pull/25) 23 | 24 | ## [v2] 25 | 26 | ### Added 27 | - Example of referencing a named function (https://github.com/iamvery/elixir-koans/pull/16) 28 | - Lesson about processes (https://github.com/iamvery/elixir-koans/pull/16) 29 | - Examples may now be skipped by adding `@tag :skip` before them. (https://github.com/iamvery/elixir-koans/pull/13) 30 | - Examples may now be focused on by adding `@tag :focus` before them. (https://github.com/iamvery/elixir-koans/pull/13) 31 | 32 | ### Changed 33 | - Various rewording, additional examples, and general housekeeping throughout remaining lessons (https://github.com/iamvery/elixir-koans/pull/17) 34 | - Rewording and additional examples throughout "asserts" and "numbers and booleans" (https://github.com/iamvery/elixir-koans/pull/12) 35 | 36 | ## [v1] 37 | 38 | ### Fixed 39 | - Warnings produced when running koans (https://github.com/iamvery/elixir-koans/pull/4) 40 | 41 | ### Added 42 | - Lessons ported from https://github.com/dojo-toulouse/elixir-koans 43 | - Improved koans runner 44 | 45 | [Unreleased]: https://github.com/iamvery/elixir-koans/compare/v2...HEAD 46 | [v2]: https://github.com/iamvery/elixir-koans/compare/v1...v2 47 | [v1]: https://github.com/iamvery/elixir-koans/compare/affa90...v1 48 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # :loudspeaker: We have decided to combine our efforts with http://elixirkoans.io/. This project may no longer receive any attention! :loudspeaker: 2 | 3 | --- 4 | 5 | Elixir Koans 6 | ============ 7 | 8 | The Elixir Koans walk you along the path to enlightenment in order to learn Elixir. 9 | The goal is to learn the Elixir language, syntax, structure, and some common functions and libraries. 10 | We also teach you culture by basing the koans on tests. 11 | Testing is not just something we pay lip service to, but something we live. 12 | Testing is essential in your quest to learn and do great things in Elixir. 13 | 14 | The Structure 15 | ------------- 16 | 17 | The koans are broken out into areas by file, strings are introduced in about_string.rb, tuples are covered in about_tuples.rb, etc. 18 | They are presented in order with the `mix learn` task. 19 | 20 | Each koan builds up your knowledge of Elixir and builds upon itself. 21 | It will stop at the first place you need to correct. 22 | 23 | Some koans simply need to have the correct answer substituted for an incorrect one. 24 | Some, however, require you to supply your own answer. 25 | If you see the method `__?` (a double underscore question mark) listed, it is a hint to you to supply your own code in order to make it work correctly. 26 | If you see the assertion `assert_?`, it is a hint that you need to supply the correct assertion or refutation. 27 | 28 | Installing Elixir 29 | ----------------- 30 | 31 | The best source of truth for installation instructions can be found [on elixir-lang.org][install]. 32 | 33 | Enlightenment 34 | ------------- 35 | 36 | The koans are run via [Mix], Elixir's task runner. 37 | 38 | 1. Clone the repository. 39 | 40 | ``` 41 | $ git clone https://github.com/iamvery/elixir-koans.git 42 | $ cd elixir-koans 43 | ``` 44 | 45 | 2. Run the mix task. 46 | 47 | ``` 48 | $ mix learn 49 | ``` 50 | 51 | Note: Currently the koans generate a lot of warnings. These can probably be ignored (https://github.com/iamvery/elixir-koans/issues/4). 52 | 53 | ### Annotations 54 | 55 | For your convenience, some annotations are available for examples. 56 | 57 | * Skipping examples 58 | 59 | ```elixir 60 | @tag :skip 61 | think "This example will not run" do 62 | # ... 63 | ``` 64 | 65 | * Focusing on examples 66 | 67 | ```elixir 68 | @tag :focus 69 | think "Only examples tagged with focus will run" do 70 | # ... 71 | ``` 72 | 73 | Inspiration 74 | ----------- 75 | 76 | The Elixir koans are inspired by the work of beloved [Jim Weirich][jim] and his [Ruby Koans][ruby-koans]. 77 | This particular set of Elixir Koans is heavily inspired by [this other koans project][other-koans] which appears to be no longer maintained. 78 | 79 | 80 | [install]: http://elixir-lang.org/install.html 81 | [Mix]: http://elixir-lang.org/getting-started/mix-otp/introduction-to-mix.html 82 | [jim]: https://github.com/jimweirich 83 | [ruby-koans]: https://github.com/neo/ruby_koans 84 | [other-koans]: https://github.com/dojo-toulouse/elixir-koans 85 | -------------------------------------------------------------------------------- /about_anonymous_functions.exs: -------------------------------------------------------------------------------- 1 | defmodule AboutAnonymousFunctions do 2 | use Koans 3 | 4 | think "Declaring an anonymous function referenced by a_variable" do 5 | a_variable = fn -> "Here lies the body of the anonymous function!" end 6 | 7 | assert a_variable.() == __? 8 | end 9 | 10 | think "Anonymous function with parameter" do 11 | a_variable = fn(name) -> "Hello #{name}!" end 12 | 13 | assert a_variable.("John") == __? 14 | end 15 | 16 | think "Anonymous function short-hand" do 17 | a_variable = &("Hello #{&1}!") 18 | 19 | assert a_variable.("John") == __? 20 | end 21 | 22 | think "Anonymous function with multiple implementation body! Amazing matching power!" do 23 | a_variable = fn 24 | "first body" -> "Running body 1" 25 | "second body" -> "Running body 2" 26 | end 27 | 28 | assert a_variable.("first body") == __? 29 | assert a_variable.("second body") == __? 30 | end 31 | 32 | think "Function as the argument of a function!" do 33 | add_five = fn(value) -> 5 + value end 34 | add_ten_to_result = fn(function, value) -> function.(value) + 10 end 35 | 36 | assert add_ten_to_result.(add_five, 5) == __? 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /about_asserts.exs: -------------------------------------------------------------------------------- 1 | defmodule AboutAsserts do 2 | use Koans 3 | 4 | think "We shall contemplate truth by testing reality, via asserts." do 5 | assert __? 6 | end 7 | 8 | think "When reality lies, we shall refute truth" do 9 | refute __? 10 | end 11 | 12 | think "Assertions are defined by ExUnit, an Elixir testing library" do 13 | ExUnit.Assertions.assert __? 14 | ExUnit.Assertions.refute __? 15 | end 16 | 17 | think "Enlightenment may be more easily achieved with appropriate messages." do 18 | assert __?, "What happens when the assertion fails? One must try it." 19 | end 20 | 21 | think "To understand reality, we must compare our expectations against reality." do 22 | actual_value = 1 + 1 23 | expected_value = __? 24 | 25 | assert actual_value == expected_value 26 | end 27 | 28 | think "Assertions are smart" do 29 | is_1_equal_2? = fn -> assert 1 == 2 end 30 | is_1_greater_than_2? = fn -> assert 1 > 2 end 31 | 32 | message = "Assertion with " <> __? <> " failed" 33 | assert_raise ExUnit.AssertionError, message, is_1_equal_2? 34 | 35 | message = "Assertion with " <> __? <> " failed" 36 | assert_raise ExUnit.AssertionError, message, is_1_greater_than_2? 37 | end 38 | 39 | think "Some values are truthy; some values are falsy" do 40 | assert_? 42 41 | assert_? :foo 42 | assert_? nil 43 | end 44 | end 45 | 46 | -------------------------------------------------------------------------------- /about_atoms.exs: -------------------------------------------------------------------------------- 1 | defmodule AboutAtoms do 2 | use Koans 3 | 4 | think "Atoms are sort of like strings" do 5 | adam = :human 6 | assert adam == __? 7 | end 8 | 9 | think "Strings can be converted to atoms, and vice versa" do 10 | assert String.to_atom("atomized") == __? 11 | assert Atom.to_string(:stringified) == __? 12 | end 13 | 14 | think "Atoms are often used as keys, because they're faster than strings" do 15 | map = %{name: "Jay"} 16 | list = [name: "Jay"] 17 | 18 | assert map[:name] == __? 19 | assert list[:name] == __? 20 | end 21 | 22 | think "Only atom keys may be accessed with dot syntax" do 23 | map = %{name: "Jay"} 24 | assert map.name == __? 25 | 26 | map = %{"name" => "Jay"} 27 | assert_raise KeyError, fn -> __? end 28 | assert map["name"] == __? 29 | end 30 | 31 | think "Dot syntax is stricter than access with brackets" do 32 | map = %{name: "Jay"} 33 | 34 | assert map[:age] == __? 35 | assert_raise KeyError, fn -> __? end 36 | end 37 | 38 | think "It is surprising to find out that booleans are atoms" do 39 | assert_? is_atom(true) 40 | assert_? is_atom(false) 41 | assert :true == __? 42 | assert :false == __? 43 | end 44 | 45 | think "Modules are also atoms" do 46 | assert_? is_atom(String) 47 | assert :"Elixir.String" == __? 48 | assert :"Elixir.String".upcase("hello") == __? 49 | end 50 | 51 | think "Atoms are used to access Erlang" do 52 | assert_? :erlang.is_list([]) 53 | assert :lists.sort([2, 3, 1]) == __? 54 | end 55 | end 56 | -------------------------------------------------------------------------------- /about_binaries.exs: -------------------------------------------------------------------------------- 1 | defmodule AboutBinaries do 2 | use Koans 3 | 4 | think "A binary is a sequence of bytes" do 5 | binary = <<1, 42, 255>> 6 | assert is_binary(binary) == __? 7 | end 8 | 9 | think "A binary's size is the number of bytes" do 10 | binary = <<1, 42, 255>> 11 | assert byte_size(binary) == __? 12 | end 13 | 14 | think "Binaries can be contatenated" do 15 | concatenated = <<1, 2>> <> <<3, 4>> 16 | assert concatenated == __? 17 | end 18 | 19 | think "Every character has a unique number (unicode codepoint)" do 20 | assert_? ?a === 97 21 | assert_? ?b === 98 22 | assert ?c === __? 23 | end 24 | 25 | think "Strings are binaries" do 26 | assert is_binary("abc") == __? 27 | assert <<97, 98, 99>> === __? 28 | end 29 | 30 | think "Thus strings can be concatenated" do 31 | contatenated = "Hello " <> "world" 32 | assert contatenated == __? 33 | end 34 | end 35 | -------------------------------------------------------------------------------- /about_enums.exs: -------------------------------------------------------------------------------- 1 | defmodule AboutEnums do 2 | use Koans 3 | 4 | think "Do something with each element" do 5 | list = [__?, __?, __?] 6 | Enum.each(list, fn (x) -> is_integer(x) end) 7 | end 8 | 9 | think "Mapping over a list" do 10 | list = [1, 2, 3] 11 | assert Enum.map(list, __?) == [2, 3, 4] 12 | # Hint: Write a function! 13 | end 14 | 15 | think "Concatenation" do 16 | list_1 = [1, 2, 3] 17 | list_2 = [4, 5, 6] 18 | assert Enum.concat(list_1, list_2) == __? 19 | end 20 | 21 | think "Is an enumerable empty?" do 22 | assert_? Enum.empty?([1, 2, 3]) 23 | assert_? Enum.empty?([]) 24 | end 25 | 26 | think "Check if all items match" do 27 | list = [1, 2, 3] 28 | assert Enum.all?(list, fn (x) -> x < 4 end) == __? 29 | end 30 | 31 | think "Check if any items match" do 32 | list = [1, 2, 3] 33 | assert Enum.any?(list, fn (x) -> x < 2 end) == __? 34 | end 35 | 36 | think "Is it in the collection?" do 37 | list = [:a, :b, :c] 38 | assert Enum.member?(list, :d) == __? 39 | end 40 | 41 | think "Find an element at an index" do 42 | list = [:a, :b, :c, :d] 43 | assert Enum.at(list, 0) == __? 44 | end 45 | 46 | think "What happens if we look outside the list?" do 47 | list = [:a, :b, :c, :d] 48 | assert Enum.at(list, 5) == __? 49 | end 50 | 51 | think "It can take a default" do 52 | list = [:a, :b, :c] 53 | assert Enum.at(list, 5, :something) == __? 54 | end 55 | 56 | think "Fetching is similar to at" do 57 | list = [:a, :b, :c] 58 | assert Enum.fetch(list, 0) == __? 59 | end 60 | 61 | think "Fetching tells you if it can't find an element" do 62 | list = [:a, :b, :c] 63 | assert Enum.fetch(list, 4) == __? 64 | end 65 | 66 | think "Fetching will raise an exception if it can't find an element" do 67 | list = [:a, :b, :c] 68 | assert_raise __?, fn -> Enum.fetch!(list, 4) end 69 | end 70 | 71 | think "Find the first element that matches" do 72 | list = [1, 2, 3, 4] 73 | assert Enum.find(list, fn (x) -> x > 2 end) == __? 74 | end 75 | 76 | think "What happens when find can't find?" do 77 | list = [1, 2, 3, 4, 5] 78 | assert Enum.find(list, fn (x) -> x > 5 end) == __? 79 | end 80 | 81 | think "Find takes a default" do 82 | list = [1, 2, 3] 83 | assert Enum.find(list, 4, fn (x) -> x > 3 end) == __? 84 | end 85 | 86 | think "What is the index of an element?" do 87 | list = [1, 2, 3] 88 | assert Enum.find_index(list, fn(x) -> x == 2 end) == __? 89 | end 90 | 91 | think "Find and manipulate a value" do 92 | list = [1, 2, 3] 93 | assert Enum.find_value(list, fn (x) -> rem(x, 2) == 1 end) == __? 94 | # TODO this seems to be a really bad example 95 | end 96 | 97 | think "Get each element with its index" do 98 | list = [:a, :b, :c] 99 | assert Enum.with_index(list) == __? 100 | end 101 | 102 | think "Chunking elements into groups" do 103 | list = [1, 2, 3, 4, 5, 6] 104 | assert Enum.chunk(list, 2) == __? 105 | end 106 | 107 | think "Chunking elements in steps" do 108 | list = [1, 2, 3, 4, 5, 6] 109 | assert Enum.chunk(list, 2, 1) == __? 110 | end 111 | 112 | think "Chunking elements in steps with padding" do 113 | list = [1, 2, 3, 4, 5, 6] 114 | assert Enum.chunk(list, 3, 2, [7]) == __? 115 | end 116 | 117 | think "Chunking elements with a function" do 118 | list = [3, 4, 5, 6, 7, 8] 119 | assert Enum.chunk_by(list, fn (x) -> x > 5 end) == __? 120 | end 121 | 122 | think "Dropping elements" do 123 | list = [1, 2, 3, 4] 124 | assert Enum.drop(list, 2) == __? 125 | assert Enum.drop(list, 10) == __? 126 | assert Enum.drop(list, -1) == __? 127 | end 128 | 129 | think "Dropping while a condition is met" do 130 | list = [1, 2, 3, 4] 131 | assert Enum.drop_while(list, fn (x) -> x < 2 end) == __? 132 | end 133 | 134 | think "Filtering" do 135 | list = [1, 2, 3, 4] 136 | assert Enum.filter(list, fn (x) -> rem(x, 2) == 1 end) == __? 137 | end 138 | 139 | think "Filtering and mapping" do 140 | list = [1, 2, 3, 4] 141 | assert Enum.filter_map(list, fn (x) -> rem(x, 2) == 1 end, &(&1 * 2)) == __? 142 | end 143 | 144 | think "Flat mapping" do 145 | list = Enum.flat_map([{1, 3}, {4, 6}], fn({x, y}) -> x..y end) 146 | assert list == __? 147 | end 148 | 149 | think "Joining into a string" do 150 | list = [1, 2, 3] 151 | assert Enum.join(list) == __? 152 | end 153 | 154 | think "Joining with a separator" do 155 | list = [1, 2, 3] 156 | assert Enum.join(list, ",") == __? 157 | end 158 | 159 | think "Mapping and joining" do 160 | list = [1, 2, 3] 161 | assert Enum.map_join(list, fn (x) -> x * 2 end) == __? 162 | end 163 | 164 | think "Map reduce" do 165 | list = [4, 5, 6] 166 | assert Enum.map_reduce(list, 0, fn (x, acc) -> {x * 2, x + acc} end) == __? 167 | # TODO this example may be unecessarily difficult to grok 168 | end 169 | 170 | think "Zipping collections together" do 171 | list_1 = [1, 2, 3] 172 | list_2 = [4, 5, 6] 173 | assert Enum.zip(list_1, list_2) == __? 174 | # TODO would be nice to use values with more meaning. Like zipping atoms 175 | # and values together to make a keyword list. 176 | end 177 | 178 | think "Find the max value in a collection" do 179 | list = [6, 1, 5, 2, 4, 3] 180 | assert Enum.max(list) == __? 181 | end 182 | 183 | think "Find the max value using a function" do 184 | list = ["the", "longest", "word", "is", "expected"] 185 | assert Enum.max_by(list, &String.length(&1)) == __? 186 | end 187 | 188 | think "Find the minimum value in a collection" do 189 | list = [6, 1, 5, 2, 4, 3] 190 | assert Enum.min(list) == __? 191 | end 192 | 193 | think "Find the minimum value using a function" do 194 | list = ["the", "shortest", "word", "is", "expected"] 195 | assert Enum.min_by(list, &String.length(&1)) == __? 196 | end 197 | 198 | defp numbers, do: 1..10 199 | 200 | think "Partitioning" do 201 | {left, right} = Enum.partition(numbers, fn(x) -> rem(x, 2) == 1 end) 202 | assert left == __? 203 | assert right == __? 204 | end 205 | 206 | think "Reduction" do 207 | result = Enum.reduce(numbers, 0, fn (x, acc) -> acc + x end) 208 | assert result == __? 209 | # TODO this could probably be illustrated before map_reduce. Also would be 210 | # nice to use values easier to compute in your head. 211 | end 212 | 213 | think "Rejection" do 214 | result = Enum.reject(numbers, fn(x) -> rem(x, 2) == 1 end) 215 | assert result == __? 216 | end 217 | 218 | think "Reversal" do 219 | assert Enum.reverse(numbers) == __? 220 | end 221 | 222 | think "Shuffle" do 223 | assert_? Enum.shuffle(numbers) == numbers 224 | # Note: It's possible for this to fail. Does shuffle guarantee difference? 225 | end 226 | 227 | think "Slicing" do 228 | assert Enum.slice(numbers, 2, 2) == __? 229 | end 230 | 231 | think "Slicing beyond length" do 232 | assert Enum.slice(numbers, 2, 100) == __? 233 | end 234 | 235 | think "Sorting" do 236 | numbers = [1, 6, 3, 8, 4, 2, 9, 5, 7] 237 | assert Enum.sort(numbers) == __? 238 | end 239 | 240 | think "Unique elements" do 241 | numbers = [1, 1, 2, 3, 3, 4] 242 | assert Enum.uniq(numbers) == __? 243 | end 244 | 245 | think "Splitting" do 246 | numbers = [1, 2, 3, 4] 247 | {left, right} = Enum.split(numbers, 2) 248 | assert left == __? 249 | assert right == __? 250 | end 251 | 252 | think "Splitting with function" do 253 | {left, right} = Enum.split_while(numbers, fn (x) -> x < 5 end) 254 | assert left == __? 255 | assert right == __? 256 | end 257 | 258 | think "Take some elements" do 259 | assert Enum.take(numbers, 2) == __? 260 | end 261 | 262 | think "Take some elements from the end" do 263 | assert Enum.take(numbers, -2) == __? 264 | end 265 | 266 | think "Take every few elements" do 267 | assert Enum.take_every(numbers, 3) == __? 268 | end 269 | 270 | think "Take while function is true" do 271 | assert Enum.take_while(numbers, fn (x) -> x < 5 end) == __? 272 | end 273 | end 274 | -------------------------------------------------------------------------------- /about_lists.exs: -------------------------------------------------------------------------------- 1 | defmodule AboutLists do 2 | use Koans 3 | 4 | think "Create your first list" do 5 | a_list = __? 6 | assert is_list(a_list) 7 | end 8 | 9 | think "Getting list length is a kernel feature" do 10 | a_list = [1, 2, 3] 11 | assert length(a_list) == __? 12 | end 13 | 14 | think "Elixir provides a special operator to concatenate lists" do 15 | assert [1, 2] ++ [3] == __? 16 | end 17 | 18 | think "Elixir provides a special operator to remove an element from a list" do 19 | assert [1, 2, 3] -- [2] == __? 20 | end 21 | 22 | think "Only the occurrence is removed with truncate operator" do 23 | assert [:foo, :bar, :foo] -- [:foo] == __? 24 | end 25 | 26 | think "The truncate operator does nothing when an element is not in the list" do 27 | assert_? [:foo, :bar] -- [:baz] == [:foo, :bar] 28 | end 29 | 30 | think "The in operator tests if an element is present in a list" do 31 | assert_? :bar in [:foo, :bar] 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /about_lists_and_maps.exs: -------------------------------------------------------------------------------- 1 | defmodule AboutListsAndMaps do 2 | use Koans 3 | 4 | think "Like maps, keyword lists are key-value pairs" do 5 | kw_list = [foo: "bar"] 6 | 7 | assert kw_list[:foo] == __? 8 | end 9 | 10 | think "Keys may be repeated, but only the first is accessed" do 11 | kw_list = [foo: "bar", foo: "baz"] 12 | 13 | assert kw_list[:foo] == __? 14 | end 15 | 16 | think "You could access a second key by removing the first" do 17 | kw_list = [foo: "bar", foo: "baz"] 18 | [_|kw_list] = kw_list 19 | 20 | assert kw_list[:foo] == __? 21 | end 22 | 23 | think "Keyword lists just special syntax for lists of two-element tuples" do 24 | assert [foo: "bar"] == [{__?, __?}] 25 | end 26 | 27 | think "But unlike maps, the keys in keyword lists must be atoms" do 28 | not_kw_list = [{"foo", "bar"}] 29 | 30 | assert_raise ArgumentError, fn -> not_kw_list[__?] end 31 | end 32 | 33 | think "Lists must be pattern matched in whole" do 34 | list = [1, 2, 3] 35 | [a, _, c] = list 36 | 37 | assert a == __? and c == __? 38 | assert_raise MatchError, fn -> __? = list end 39 | end 40 | 41 | think "Maps may be patterned matched in part" do 42 | %{name: name} = %{name: "Jay", age: 29} 43 | 44 | assert name == __? 45 | end 46 | 47 | think "Conveniently keyword lists can be used for function options" do 48 | transform = fn str, opts -> 49 | if opts[:upcase], do: String.upcase(str) 50 | end 51 | 52 | assert transform.("good", upcase: true) == __? 53 | end 54 | 55 | def foo(kw_list), do: kw_list 56 | 57 | think "Actually function bodies are a sneaky use of keyword lists" do 58 | list = foo do 59 | :good 60 | end 61 | 62 | assert list == [do: __?] 63 | end 64 | 65 | def iff(kw_list), do: kw_list 66 | 67 | think "Turns out our beloved if statements are also using keyword lists" do 68 | list = iff do 69 | :this 70 | else 71 | :that 72 | end 73 | 74 | assert list == [do: __?, else: __?] 75 | end 76 | end 77 | -------------------------------------------------------------------------------- /about_maps.exs: -------------------------------------------------------------------------------- 1 | defmodule AboutMaps do 2 | use Koans 3 | 4 | think "Maps are a key-value store" do 5 | map = %{name: "Ryan", age: 27} 6 | assert map[:name] == __? 7 | assert map[:likes] == __? 8 | end 9 | 10 | think "Any value can be used as a key" do 11 | map = %{"name" => "Ryan", {:ok} => true} 12 | assert map["name"] == __? 13 | assert map[{:ok}] == __? 14 | end 15 | 16 | think "There is more than one way to access a map" do 17 | map = %{name: "Ryan", age: 27} 18 | assert Map.get(map, :name) == __? 19 | assert Map.get(map, :likes, "programming") == __? 20 | end 21 | 22 | think "You can ask a map about its keys" do 23 | map = %{name: "Ryan", age: 27} 24 | assert Map.keys(map) == __? 25 | # Further meditation: 26 | # Why are keys not returned in the order in which they're specified? 27 | end 28 | 29 | think "You can ask a map about its values" do 30 | map = %{name: "Ryan", age: 27} 31 | assert Map.values(map) == __? 32 | end 33 | 34 | think "You can ask a map if it has a key" do 35 | map = %{name: "Ryan", age: 27} 36 | assert Map.has_key?(map, :name) == __? 37 | assert Map.has_key?(map, :likes) == __? 38 | end 39 | 40 | think "Fetching a key works if the key exists..." do 41 | map = %{name: "Ryan", age: 27} 42 | assert Map.fetch(map, :name) == __? 43 | end 44 | 45 | think "... but what happens if we try to fetch a non-existant key?" do 46 | map = %{name: "Ryan", age: 27} 47 | assert Map.fetch(map, :likes) == __? 48 | end 49 | 50 | think "You can also pop a key" do 51 | map = %{amount: 10} 52 | {value, map} = Map.pop(map, :amount) 53 | assert value == __? 54 | assert map == __? 55 | end 56 | 57 | think "Popping a non-existant key" do 58 | map = %{amount: 10} 59 | {value, map} = Map.pop(map, :age) 60 | assert value == __? 61 | assert map == __? 62 | end 63 | 64 | think "Merging together two maps" do 65 | map_1 = %{name: "Ryan", age: 27} 66 | map_2 = %{name: "Ryan B.", likes: "programming"} 67 | assert Map.merge(map_1, map_2) == __? 68 | end 69 | 70 | think "Merging together two maps and doing something with their values" do 71 | map_1 = %{today: 10, yesterday: 20} 72 | map_2 = %{today: 20, yesterday: 5} 73 | 74 | assert Map.merge(map_1, map_2, fn (_k, v1, v2) -> v1 + v2 end) == __? 75 | end 76 | 77 | think "Dropping keys" do 78 | map = %{name: "Ryan", age: 27} 79 | assert Map.drop(map, [:age]) == __? 80 | end 81 | 82 | think "There's more than one way to remove a key" do 83 | map = %{name: "Ryan", age: 27} 84 | assert Map.delete(map, :age) == __? 85 | end 86 | 87 | think "Changing a value" do 88 | map = %{name: "Ryan", age: 27} 89 | assert Map.put(map, :name, "Ryan B.") == __? 90 | end 91 | 92 | think "Adding a new pair that doesn't exist" do 93 | map = %{name: "Ryan", age: 27} 94 | assert Map.put_new(map, :likes, "Programming") == __? 95 | assert Map.put_new(map, :name, "Ryan B.") == __? 96 | end 97 | 98 | think "Updating a key with a function" do 99 | map = %{amount: 10} 100 | assert Map.update(map, :amount, 0, fn (x) -> x + 1 end) == __? 101 | assert Map.update(map, :other_amount, 10, fn (x) -> x + 1 end) == __? 102 | end 103 | 104 | think "Updating keys that don't exist may not be desirable" do 105 | map = %{amount: 10} 106 | assert Map.update!(map, :amount, fn (x) -> x + 1 end) == __? 107 | assert_raise __?, fn -> Map.update!(map, :other_amount, fn (x) -> x + 1 end) end 108 | end 109 | end 110 | -------------------------------------------------------------------------------- /about_matching.exs: -------------------------------------------------------------------------------- 1 | defmodule AboutMatching do 2 | use Koans 3 | 4 | think "The match operator (=) binds values on the right to variables on the left" do 5 | a_variable = 1 6 | assert a_variable == __? 7 | end 8 | 9 | think "It matches patterns in data structures" do 10 | [a, b] = [1, 2] 11 | [c, d, e] = [4, 5, 6] 12 | 13 | assert a == __? 14 | assert b == __? 15 | assert c == __? 16 | assert d == __? 17 | assert e == __? 18 | end 19 | 20 | think "You can ignore values in the match" do 21 | [a, _] = ["Keep me", "Drop me"] 22 | 23 | assert a == __? 24 | end 25 | 26 | think "The sides must match" do 27 | [a, a] = [1, __?] 28 | 29 | assert a == __? 30 | end 31 | 32 | think "It raises an error when the match fails" do 33 | assert_raise MatchError, fn -> [a , a] = [1 , __?] end 34 | end 35 | 36 | think "Matched values overwrite variable values" do 37 | a = 1 38 | assert a == __? 39 | 40 | [a, _] = [2, 3] 41 | assert a == __? 42 | end 43 | 44 | think "Values can be pinned to prevent them from being overwritten" do 45 | a = 1 46 | assert_raise MatchError, fn -> ^a = __? end 47 | assert_raise MatchError, fn -> [^a, _] = __? end 48 | # Note: This is a way of asserting what the right-hand side of the match 49 | # meets your expectation. Similar to the literal: {:ok, result} = some_func 50 | end 51 | 52 | think "Matching a list inside a list" do 53 | [a] = [["Hello", "World!"]] 54 | 55 | assert a == __? 56 | end 57 | 58 | think "Matching a tuple" do 59 | {:ok, result} = {:ok, 12} 60 | assert result == __? 61 | end 62 | 63 | think "Matching the head and tail of a list" do 64 | [head|tail] = [1, 2, 3] 65 | assert head == __? 66 | assert tail == __? 67 | end 68 | end 69 | -------------------------------------------------------------------------------- /about_named_functions.exs: -------------------------------------------------------------------------------- 1 | defmodule AboutNamedFunctions do 2 | use Koans 3 | 4 | def hello(name) do 5 | "Hello #{name}" 6 | end 7 | 8 | think "Calling a named function" do 9 | assert hello("world!") == __? 10 | end 11 | 12 | def hello(name, country) do 13 | "Hello #{name} from #{country}" 14 | end 15 | 16 | think "A function with the same name, but different number of arguments is a different function" do 17 | assert hello("world", "France!") == __? 18 | end 19 | 20 | think "You can reference a named function with &" do 21 | upcase = &String.upcase/1 22 | 23 | assert upcase.("orly") == __? 24 | end 25 | 26 | def factorial(0) do 1 end 27 | def factorial(n) do n * factorial(n-1) end 28 | 29 | think "Pattern matching on a named function" do 30 | assert factorial(3) == __? 31 | end 32 | 33 | def i_can_identify_type(value) when is_atom(value) do 34 | "#{value} is an atom" 35 | end 36 | 37 | def i_can_identify_type(value) when is_float(value) do 38 | "#{value} is a float" 39 | end 40 | 41 | def i_can_identify_type(value) when is_number(value) do 42 | "#{value} is a number" 43 | end 44 | 45 | think "Functions can have guard clauses" do 46 | assert i_can_identify_type(4.2) == __? 47 | assert i_can_identify_type(:atom) == __? 48 | assert i_can_identify_type(5) == __? 49 | end 50 | end 51 | -------------------------------------------------------------------------------- /about_numbers_and_booleans.exs: -------------------------------------------------------------------------------- 1 | defmodule AboutNumbersAndBooleans do 2 | use Koans 3 | 4 | think "Basics numbers types" do 5 | assert_? is_integer(42) 6 | assert_? is_integer(42.0) 7 | assert_? is_float(42.0) 8 | assert_? is_float(42) 9 | end 10 | 11 | think "Is a hexadecimal number an integer?" do 12 | assert_? is_integer(0x2A) 13 | end 14 | 15 | think "Answer to the Ultimate Question of Life, the Universe, and Everything" do 16 | a_hex = 0x20 17 | a_dec = 10 18 | assert a_hex + a_dec == __? 19 | end 20 | 21 | think "You can use _ as separator in integer" do 22 | assert 100_000_000 == __? 23 | end 24 | 25 | think "Integers and floats have value equality" do 26 | assert_? 4 == 4.0 27 | end 28 | 29 | think "Integers and floats have value inequality" do 30 | assert_? 4 != 2.0 31 | end 32 | 33 | think "Strict equality checks types" do 34 | assert_? 4 === 4.0 35 | assert_? 4.0 === 4.0 36 | end 37 | 38 | think "Strict inequality checks types" do 39 | assert_? 4 !== 4.0 40 | end 41 | 42 | think "Are integers booleans?" do 43 | assert_? is_integer(true) 44 | assert_? is_boolean(0) 45 | end 46 | 47 | think "Boolean OR returns left side if true, otherwise right side" do 48 | assert_? true or true 49 | assert_? true or false 50 | assert_? false or true 51 | assert_? false or false 52 | end 53 | 54 | think "Boolean operators check their argument's type" do 55 | message = "argument error: " <> __? 56 | assert_raise ArgumentError, message, fn -> 1 or true end 57 | end 58 | 59 | think "Other binary operators are relaxed about their argument's type" do 60 | assert __? == 42 || 84 61 | assert __? == 42 || nil 62 | assert __? == nil || 84 63 | assert __? == nil || nil 64 | end 65 | end 66 | -------------------------------------------------------------------------------- /about_processes.exs: -------------------------------------------------------------------------------- 1 | defmodule AboutProcesses do 2 | use Koans 3 | 4 | think "Spawning a process executes a function" do 5 | spawn __? 6 | # Hint: Print something to the screen so you know something happened! 7 | end 8 | 9 | think "Spawning a process returns a process ID (PID)" do 10 | pid = spawn fn -> IO.puts "I am running in another process" end 11 | 12 | assert is_pid(pid) == __? 13 | end 14 | 15 | think "You are a process" do 16 | assert_? is_pid(self) 17 | end 18 | 19 | think "Processes send and receive messages; it's like mailbox" do 20 | send self, {:hello, "world"} 21 | 22 | receive do 23 | {:hello, message} -> assert message == __? 24 | end 25 | end 26 | 27 | think "Processes communicate with one another" do 28 | echo = fn -> 29 | receive do 30 | {caller, value} -> send caller, value 31 | end 32 | end 33 | 34 | pid = spawn echo 35 | send pid, {self, "hi!"} 36 | 37 | receive do 38 | value -> assert value == __? 39 | end 40 | end 41 | 42 | def echo_loop do 43 | receive do 44 | {caller, value} -> 45 | send caller, value 46 | echo_loop 47 | end 48 | end 49 | 50 | think "Use tail recursion (calling a function as the very last statement) to receive multiple messages" do 51 | pid = spawn &echo_loop/0 52 | 53 | send pid, {self, "o"} 54 | receive do 55 | value -> assert value == __? 56 | end 57 | 58 | send pid, {self, "hai"} 59 | receive do 60 | value -> assert value == __? 61 | end 62 | 63 | Process.exit(pid, :kill) 64 | end 65 | end 66 | -------------------------------------------------------------------------------- /about_sigils.exs: -------------------------------------------------------------------------------- 1 | defmodule AboutSigils do 2 | use Koans 3 | 4 | think "The ~s sigil is a different way of expressing string literals" do 5 | assert ~s{This is a string} == __? 6 | end 7 | 8 | think "Sigils are useful to avoid escaping quotes in strings" do 9 | assert "\"Welcome to the jungle\", they said." == __? 10 | end 11 | 12 | think "Sigils can use different delimiters" do 13 | assert_? ~s{This works!} == ~s[This works!] 14 | end 15 | 16 | think "The lowercase ~s sigil supports string interpolation" do 17 | assert ~s[1 + 1 = #{1+1}] == __? 18 | end 19 | 20 | think "The ~S sigil is similar to ~s but doesn't do interpolation" do 21 | assert ~S[1 + 1 = #{1+1}] == __? 22 | end 23 | 24 | think "~w is another sigil, it creates word lists" do 25 | assert ~w(Hello world) == [__?, __?] 26 | end 27 | 28 | think "~w also allows interpolation" do 29 | assert ~w(Hello 1#{1+1}3) == [__?, __?] 30 | end 31 | 32 | think "~W behaves to ~w as ~S behaves to ~s" do 33 | assert ~W(Hello #{1+1}) == ["Hello", __?] 34 | end 35 | end 36 | 37 | -------------------------------------------------------------------------------- /about_strings.exs: -------------------------------------------------------------------------------- 1 | defmodule AboutStrings do 2 | use Koans 3 | 4 | think "Creating a new string" do 5 | a_string = "hello world!" 6 | assert a_string == __? 7 | end 8 | 9 | think "Values can be interpolated into strings" do 10 | a_string = "1 + 1 = #{1+1}" 11 | assert a_string == __? 12 | end 13 | 14 | think "How long is a piece of string?" do 15 | a_string = "Hello there" 16 | assert String.length(a_string) == __? 17 | end 18 | 19 | think "What does the string start with?" do 20 | a_string = "Hello there" 21 | assert String.starts_with?(a_string, "H") == __? 22 | assert String.starts_with?(a_string, ["Bonjour", "Hello"]) == __? 23 | assert String.starts_with?(a_string, ["Bonjour", "Greetings"]) == __? 24 | end 25 | 26 | think "What does the string end with?" do 27 | a_string = "Live long and prosper" 28 | assert String.ends_with?(a_string, "prosper") == __? 29 | assert String.ends_with?(a_string, ["multiply", "prosper"]) == __? 30 | assert String.ends_with?(a_string, ["keep calm"]) == __? 31 | end 32 | 33 | think "Does a string contain something?" do 34 | a_string = "May the force be with you" 35 | assert String.contains?(a_string, "force") == __? 36 | assert String.contains?(a_string, ["voyager", "you"]) == __? 37 | assert String.contains?(a_string, ["prosper"]) == __? 38 | end 39 | 40 | think "Accessing letters by their positions" do 41 | a_string = "Hello world!" 42 | assert String.at(a_string, 2) == __? 43 | assert String.at(a_string, 20) == __? 44 | end 45 | 46 | think "Slicing a string" do 47 | a_string = "Hello world!" 48 | assert String.slice(a_string, 6, 5) == __? 49 | assert String.slice(a_string, -3, 6) == __? 50 | assert String.slice(a_string, 20, 5) == __? 51 | assert String.slice(a_string, 4, 0) == __? 52 | assert String.slice(a_string, 0..5) == __? 53 | end 54 | 55 | think "Capitalization" do 56 | a_string = "hello world!" 57 | assert String.capitalize(a_string) == __? 58 | end 59 | 60 | think "Upcase" do 61 | a_string = "hello world!" 62 | assert String.upcase(a_string) == __? 63 | end 64 | 65 | think "Downcase" do 66 | a_string = "SPEAK QUIETLY" 67 | assert String.downcase(a_string) == __? 68 | end 69 | 70 | think "Reversing a string" do 71 | a_string = "sdrow sdrawkcab" 72 | assert String.reverse(a_string) == __? 73 | end 74 | 75 | think "Say it again" do 76 | a_string = "repeat this" 77 | assert String.duplicate(a_string, 3) == __? 78 | end 79 | 80 | think "Stripping on the left" do 81 | a_string = " abc " 82 | assert String.lstrip(a_string) == __? 83 | end 84 | 85 | think "Stripping on the left with specific characters" do 86 | a_string = "$ abc $" 87 | assert String.lstrip(a_string, ?$) == __? 88 | end 89 | 90 | think "Stripping on the right" do 91 | a_string = " abc " 92 | assert String.rstrip(a_string) == __? 93 | end 94 | 95 | think "Stripping on the right with specific character" do 96 | a_string = " abc $" 97 | assert String.rstrip(a_string, ?$) == __? 98 | end 99 | 100 | think "Stripping on both sides" do 101 | a_string = " abc " 102 | assert String.strip(a_string) == __? 103 | end 104 | 105 | think "Stripping on both sides with a specific character" do 106 | a_string = "$ abc $" 107 | assert String.strip(a_string, ?$) == __? 108 | end 109 | 110 | think "Left justification" do 111 | a_string = "2" 112 | assert String.ljust(a_string, 3) == __? 113 | end 114 | 115 | think "Left justification with a specific character" do 116 | a_string = "2" 117 | assert String.ljust(a_string, 3, ?0) == __? 118 | end 119 | 120 | think "Right justification" do 121 | a_string = "2" 122 | assert String.rjust(a_string, 3) == __? 123 | end 124 | 125 | think "Right justification with a specific character" do 126 | a_string = "7" 127 | assert String.rjust(a_string, 3, ?0) == __? 128 | end 129 | 130 | think "Converting to an integer" do 131 | assert String.to_integer("10") == __? 132 | assert_raise ArgumentError, fn -> String.to_integer(__?) end 133 | end 134 | 135 | think "Converting to an integer using a different base" do 136 | a_string = "11" 137 | assert String.to_integer(a_string, 16) == __? 138 | assert String.to_integer(a_string, 2) == __? 139 | end 140 | 141 | think "Converting to a float" do 142 | assert String.to_float("10.99") == __? 143 | assert_raise ArgumentError, fn -> String.to_float(__?) end 144 | end 145 | end 146 | -------------------------------------------------------------------------------- /about_tuples.exs: -------------------------------------------------------------------------------- 1 | defmodule AboutTuples do 2 | use Koans 3 | 4 | think "Getting tuples size is a kernel feature" do 5 | a_tuple = {:foo, :bar} 6 | assert tuple_size(a_tuple) == __? 7 | end 8 | 9 | think "Can reach tuple element with index" do 10 | a_tuple = {:foo, :bar} 11 | assert elem(a_tuple, 0) == __? 12 | end 13 | 14 | think "In erlang tuples and lists start at index 1, which is different in Elixir" do 15 | a_tuple = {:foo, :bar} 16 | assert elem(a_tuple, 0) == :erlang.element(__?, a_tuple) 17 | end 18 | 19 | think "Can set a tuple element" do 20 | a_tuple = {:foo, :bar} 21 | baz_tuple = put_elem(a_tuple, 0, :baz) 22 | # Note: think about immutability 23 | assert elem(a_tuple, 0) == __? 24 | assert elem(baz_tuple, 0) == __? 25 | end 26 | 27 | think "Setting a tuple element that does not exist raises an argument error" do 28 | a_tuple = {:foo, :bar} 29 | 30 | assert_raise ArgumentError, fn -> put_elem(a_tuple, __?, :baz) end 31 | end 32 | 33 | think "Insert an element at a position" do 34 | a_tuple = {:foo, :bar} 35 | baz_tuple = Tuple.insert_at(a_tuple, 2, :baz) 36 | assert elem(baz_tuple, 2) == __? 37 | end 38 | 39 | think "Inserting a tuple element raises an argument error if index is invalid" do 40 | a_tuple = {:foo, :bar} 41 | assert_raise ArgumentError, fn -> Tuple.insert_at(a_tuple, __?, :baz) end 42 | end 43 | 44 | think "The building blocks of Elixir are tuples of three elements" do 45 | # Note: quote gives the representation of a block 46 | quoted_block = quote do 47 | a = 1 + 2 48 | assert a == 3 49 | end 50 | 51 | assert is_tuple(quoted_block) == __? 52 | assert tuple_size(quoted_block) == __? 53 | end 54 | 55 | think "We can use tuples to define blocks of Elixir code" do 56 | # Note: unquote is the reverse of quote 57 | # It gives a block from its representation 58 | unquoted_block = unquote {:"{}", [], [1, 2, 3]} 59 | # Note: it's in hungarian notation 60 | # the atom :"{}" represents the function 61 | # the list [] contains metadata like the line and module where code is defined 62 | # the list [1, 2, 3] is the arguments passed to the function 63 | # For more info see Macros and the quote/unquote functions 64 | assert unquoted_block == __? 65 | end 66 | 67 | think "Are tuples enumerable?" do 68 | assert_raise __?, fn -> Enum.empty?({1, 2, 3}) end 69 | # Note: 70 | # Do you smell the underlying machinery that make lists and tuple types different? 71 | end 72 | end 73 | -------------------------------------------------------------------------------- /answers/about_anonymous_functions.exs: -------------------------------------------------------------------------------- 1 | defmodule Answers.AboutAnonymousFunctions do 2 | use Koans 3 | 4 | answer "Declaring an anonymous function referenced by a_variable" do 5 | ["Here lies the body of the anonymous function!"] 6 | end 7 | 8 | answer "Anonymous function with parameter" do 9 | ["Hello John!"] 10 | end 11 | 12 | answer "Anonymous function short-hand" do 13 | ["Hello John!"] 14 | end 15 | 16 | answer "Anonymous function with multiple implementation body! Amazing matching power!" do 17 | ["Running body 1", "Running body 2"] 18 | end 19 | 20 | answer "Function as the argument of a function!" do 21 | [20] 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /answers/about_asserts.exs: -------------------------------------------------------------------------------- 1 | defmodule Answers.AboutAsserts do 2 | use Koans 3 | 4 | answer "We shall contemplate truth by testing reality, via asserts." do 5 | [true] 6 | end 7 | 8 | answer "When reality lies, we shall refute truth" do 9 | [false] 10 | end 11 | 12 | answer "Assertions are defined by ExUnit, an Elixir testing library" do 13 | [true, false] 14 | end 15 | 16 | answer "Enlightenment may be more easily achieved with appropriate messages." do 17 | [true] 18 | end 19 | 20 | answer "To understand reality, we must compare our expectations against reality." do 21 | [2] 22 | end 23 | 24 | answer "Assertions are smart" do 25 | ["==", ">"] 26 | end 27 | 28 | answer "Some values are truthy; some values are falsy" do 29 | [assert, assert, refute] 30 | end 31 | end 32 | 33 | -------------------------------------------------------------------------------- /answers/about_atoms.exs: -------------------------------------------------------------------------------- 1 | defmodule Answers.AboutAtoms do 2 | use Koans 3 | 4 | answer "Atoms are sort of like strings" do 5 | [:human] 6 | end 7 | 8 | answer "Strings can be converted to atoms, and vice versa" do 9 | [:atomized, "stringified"] 10 | end 11 | 12 | answer "Atoms are often used as keys, because they're faster than strings" do 13 | ["Jay", "Jay"] 14 | end 15 | 16 | answer "Only atom keys may be accessed with dot syntax" do 17 | ["Jay", map.name, "Jay"] 18 | end 19 | 20 | answer "Dot syntax is stricter than access with brackets" do 21 | [nil, map.age] 22 | end 23 | 24 | answer "It is surprising to find out that booleans are atoms" do 25 | [assert, assert, true, false] 26 | end 27 | 28 | answer "Modules are also atoms" do 29 | [assert, Elixir.String, "HELLO"] 30 | end 31 | 32 | answer "Atoms are used to access Erlang" do 33 | [assert, [1,2,3]] 34 | end 35 | end 36 | -------------------------------------------------------------------------------- /answers/about_binaries.exs: -------------------------------------------------------------------------------- 1 | defmodule Answers.AboutBinaries do 2 | use Koans 3 | 4 | answer "A binary is a sequence of bytes" do 5 | [true] 6 | end 7 | 8 | answer "A binary's size is the number of bytes" do 9 | [3] 10 | end 11 | 12 | answer "Binaries can be contatenated" do 13 | [<<1,2,3,4>>] 14 | end 15 | 16 | answer "Every character has a unique number (unicode codepoint)" do 17 | [assert, assert, 99] 18 | end 19 | 20 | answer "Strings are binaries" do 21 | [true, "abc"] 22 | end 23 | 24 | answer "Thus strings can be concatenated" do 25 | ["Hello world"] 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /answers/about_enums.exs: -------------------------------------------------------------------------------- 1 | defmodule Answers.AboutEnums do 2 | use Koans 3 | 4 | answer "Do something with each element" do 5 | [1,2,3] 6 | end 7 | 8 | answer "Mapping over a list" do 9 | [fn n -> n+1 end] 10 | end 11 | 12 | answer "Concatenation" do 13 | [[1,2,3,4,5,6]] 14 | end 15 | 16 | answer "Is an enumerable empty?" do 17 | [refute, assert] 18 | end 19 | 20 | answer "Check if all items match" do 21 | [true] 22 | end 23 | 24 | answer "Check if any items match" do 25 | [true] 26 | end 27 | 28 | answer "Is it in the collection?" do 29 | [false] 30 | end 31 | 32 | answer "Find an element at an index" do 33 | [:a] 34 | end 35 | 36 | answer "What happens if we look outside the list?" do 37 | [nil] 38 | end 39 | 40 | answer "It can take a default" do 41 | [:something] 42 | end 43 | 44 | answer "Fetching is similar to at" do 45 | [{:ok, :a}] 46 | end 47 | 48 | answer "Fetching tells you if it can't find an element" do 49 | [:error] 50 | end 51 | 52 | answer "Fetching will raise an exception if it can't find an element" do 53 | [Enum.OutOfBoundsError] 54 | end 55 | 56 | answer "Find the first element that matches" do 57 | [3] 58 | end 59 | 60 | answer "What happens when find can't find?" do 61 | [nil] 62 | end 63 | 64 | answer "Find takes a default" do 65 | [4] 66 | end 67 | 68 | answer "What is the index of an element?" do 69 | [1] 70 | end 71 | 72 | answer "Find and manipulate a value" do 73 | [true] 74 | end 75 | 76 | answer "Get each element with its index" do 77 | [[a: 0, b: 1, c: 2]] 78 | end 79 | 80 | answer "Chunking elements into groups" do 81 | [[[1,2],[3,4],[5,6]]] 82 | end 83 | 84 | answer "Chunking elements in steps" do 85 | [[[1,2],[2,3],[3,4],[4,5],[5,6]]] 86 | end 87 | 88 | answer "Chunking elements in steps with padding" do 89 | [[[1,2,3],[3,4,5],[5,6,7]]] 90 | end 91 | 92 | answer "Chunking elements with a function" do 93 | [[[3,4,5],[6,7,8]]] 94 | end 95 | 96 | answer "Dropping elements" do 97 | [[3,4], [], [1,2,3]] 98 | end 99 | 100 | answer "Dropping while a condition is met" do 101 | [[2,3,4]] 102 | end 103 | 104 | answer "Filtering" do 105 | [[1,3]] 106 | end 107 | 108 | answer "Filtering and mapping" do 109 | [[2,6]] 110 | end 111 | 112 | answer "Flat mapping" do 113 | [[1,2,3,4,5,6]] 114 | end 115 | 116 | answer "Joining into a string" do 117 | ["123"] 118 | end 119 | 120 | answer "Joining with a separator" do 121 | ["1,2,3"] 122 | end 123 | 124 | answer "Mapping and joining" do 125 | ["246"] 126 | end 127 | 128 | answer "Map reduce" do 129 | [{[8,10,12], 15}] 130 | end 131 | 132 | answer "Zipping collections together" do 133 | [[{1,4},{2,5},{3,6}]] 134 | end 135 | 136 | answer "Find the max value in a collection" do 137 | [6] 138 | end 139 | 140 | answer "Find the max value using a function" do 141 | ["expected"] 142 | end 143 | 144 | answer "Find the minimum value in a collection" do 145 | [1] 146 | end 147 | 148 | answer "Find the minimum value using a function" do 149 | ["is"] 150 | end 151 | 152 | answer "Partitioning" do 153 | [[1,3,5,7,9],[2,4,6,8,10]] 154 | end 155 | 156 | answer "Reduction" do 157 | [55] 158 | end 159 | 160 | answer "Rejection" do 161 | [[2,4,6,8,10]] 162 | end 163 | 164 | answer "Reversal" do 165 | [[10,9,8,7,6,5,4,3,2,1]] 166 | end 167 | 168 | answer "Shuffle" do 169 | [refute] 170 | end 171 | 172 | answer "Slicing" do 173 | [[3,4]] 174 | end 175 | 176 | answer "Slicing beyond length" do 177 | [[3,4,5,6,7,8,9,10]] 178 | end 179 | 180 | answer "Sorting" do 181 | [[1,2,3,4,5,6,7,8,9]] 182 | end 183 | 184 | answer "Unique elements" do 185 | [[1,2,3,4]] 186 | end 187 | 188 | answer "Splitting" do 189 | [[1,2],[3,4]] 190 | end 191 | 192 | answer "Splitting with function" do 193 | [[1,2,3,4],[5,6,7,8,9,10]] 194 | end 195 | 196 | answer "Take some elements" do 197 | [[1,2]] 198 | end 199 | 200 | answer "Take some elements from the end" do 201 | [[9,10]] 202 | end 203 | 204 | answer "Take every few elements" do 205 | [[1,4,7,10]] 206 | end 207 | 208 | answer "Take while function is true" do 209 | [[1,2,3,4]] 210 | end 211 | end 212 | -------------------------------------------------------------------------------- /answers/about_lists.exs: -------------------------------------------------------------------------------- 1 | defmodule Answers.AboutLists do 2 | use Koans 3 | 4 | answer "Create your first list" do 5 | [[1,2,3]] 6 | end 7 | 8 | answer "Getting list length is a kernel feature" do 9 | [3] 10 | end 11 | 12 | answer "Elixir provides a special operator to concatenate lists" do 13 | [[1,2,3]] 14 | end 15 | 16 | answer "Elixir provides a special operator to remove an element from a list" do 17 | [[1,3]] 18 | end 19 | 20 | answer "Only the occurrence is removed with truncate operator" do 21 | [[:bar, :foo]] 22 | end 23 | 24 | answer "The truncate operator does nothing when an element is not in the list" do 25 | [assert] 26 | end 27 | 28 | answer "The in operator tests if an element is present in a list" do 29 | [assert] 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /answers/about_lists_and_maps.exs: -------------------------------------------------------------------------------- 1 | defmodule Answers.AboutListsAndMaps do 2 | use Koans 3 | 4 | answer "Like maps, keyword lists are key-value pairs" do 5 | ["bar"] 6 | end 7 | 8 | answer "Keys may be repeated, but only the first is accessed" do 9 | ["bar"] 10 | end 11 | 12 | answer "You could access a second key by removing the first" do 13 | ["baz"] 14 | end 15 | 16 | answer "Keyword lists just special syntax for lists of two-element tuples" do 17 | [:foo, "bar"] 18 | end 19 | 20 | answer "But unlike maps, the keys in keyword lists must be atoms" do 21 | ["foo"] 22 | end 23 | 24 | answer "Lists must be pattern matched in whole" do 25 | [1, 3, [1,2]] 26 | end 27 | 28 | answer "Maps may be patterned matched in part" do 29 | ["Jay"] 30 | end 31 | 32 | answer "Conveniently keyword lists can be used for function options" do 33 | ["GOOD"] 34 | end 35 | 36 | answer "Actually function bodies are a sneaky use of keyword lists" do 37 | [:good] 38 | end 39 | 40 | answer "Turns out our beloved if statements are also using keyword lists" do 41 | [:this, :that] 42 | end 43 | end 44 | -------------------------------------------------------------------------------- /answers/about_maps.exs: -------------------------------------------------------------------------------- 1 | defmodule Answers.AboutMaps do 2 | use Koans 3 | 4 | answer "Maps are a key-value store" do 5 | ["Ryan", nil] 6 | end 7 | 8 | answer "Any value can be used as a key" do 9 | ["Ryan", true] 10 | end 11 | 12 | answer "There is more than one way to access a map" do 13 | ["Ryan", "programming"] 14 | end 15 | 16 | answer "You can ask a map about its keys" do 17 | [[:age, :name]] 18 | end 19 | 20 | answer "You can ask a map about its values" do 21 | [[27, "Ryan"]] 22 | end 23 | 24 | answer "You can ask a map if it has a key" do 25 | [true, false] 26 | end 27 | 28 | answer "Fetching a key works if the key exists..." do 29 | [{:ok, "Ryan"}] 30 | end 31 | 32 | answer "... but what happens if we try to fetch a non-existant key?" do 33 | [:error] 34 | end 35 | 36 | answer "You can also pop a key" do 37 | [10, %{}] 38 | end 39 | 40 | answer "Popping a non-existant key" do 41 | [nil, %{amount: 10}] 42 | end 43 | 44 | answer "Merging together two maps" do 45 | [%{name: "Ryan B.", age: 27, likes: "programming"}] 46 | end 47 | 48 | answer "Merging together two maps and doing something with their values" do 49 | [%{today: 30, yesterday: 25}] 50 | end 51 | 52 | answer "Dropping keys" do 53 | [%{name: "Ryan"}] 54 | end 55 | 56 | answer "There's more than one way to remove a key" do 57 | [%{name: "Ryan"}] 58 | end 59 | 60 | answer "Changing a value" do 61 | [%{name: "Ryan B.", age: 27}] 62 | end 63 | 64 | answer "Adding a new pair that doesn't exist" do 65 | [%{name: "Ryan", age: 27, likes: "Programming"}, %{name: "Ryan", age: 27}] 66 | end 67 | 68 | answer "Updating a key with a function" do 69 | [%{amount: 11}, %{amount: 10, other_amount: 10}] 70 | end 71 | 72 | answer "Updating keys that don't exist may not be desirable" do 73 | [%{amount: 11}, KeyError] 74 | end 75 | end 76 | -------------------------------------------------------------------------------- /answers/about_matching.exs: -------------------------------------------------------------------------------- 1 | defmodule Answers.AboutMatching do 2 | use Koans 3 | 4 | answer "The match operator (=) binds values on the right to variables on the left" do 5 | [1] 6 | end 7 | 8 | answer "It matches patterns in data structures" do 9 | [1,2,4,5,6] 10 | end 11 | 12 | answer "You can ignore values in the match" do 13 | ["Keep me"] 14 | end 15 | 16 | answer "The sides must match" do 17 | [1, 1] 18 | end 19 | 20 | answer "It raises an error when the match fails" do 21 | [2] 22 | end 23 | 24 | answer "Matched values overwrite variable values" do 25 | [1, 2] 26 | end 27 | 28 | answer "Values can be pinned to prevent them from being overwritten" do 29 | [2, [3,4]] 30 | end 31 | 32 | answer "Matching a list inside a list" do 33 | [["Hello", "World!"]] 34 | end 35 | 36 | answer "Matching a tuple" do 37 | [12] 38 | end 39 | 40 | answer "Matching the head and tail of a list" do 41 | [1, [2,3]] 42 | end 43 | end 44 | -------------------------------------------------------------------------------- /answers/about_named_functions.exs: -------------------------------------------------------------------------------- 1 | defmodule Answers.AboutNamedFunctions do 2 | use Koans 3 | 4 | answer "Calling a named function" do 5 | ["Hello world!"] 6 | end 7 | 8 | answer "A function with the same name, but different number of arguments is a different function" do 9 | ["Hello world from France!"] 10 | end 11 | 12 | answer "You can reference a named function with &" do 13 | ["ORLY"] 14 | end 15 | 16 | answer "Pattern matching on a named function" do 17 | [6] 18 | end 19 | 20 | answer "Functions can have guard clauses" do 21 | ["4.2 is a float", "atom is an atom", "5 is a number"] 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /answers/about_numbers_and_booleans.exs: -------------------------------------------------------------------------------- 1 | defmodule Answers.AboutNumbersAndBooleans do 2 | use Koans 3 | 4 | answer "Basics numbers types" do 5 | [assert, refute, assert, refute] 6 | end 7 | 8 | answer "Is a hexadecimal number an integer?" do 9 | [assert] 10 | end 11 | 12 | answer "Answer to the Ultimate Question of Life, the Universe, and Everything" do 13 | [42] 14 | end 15 | 16 | answer "You can use _ as separator in integer" do 17 | [100000000] 18 | end 19 | 20 | answer "Integers and floats have value equality" do 21 | [assert] 22 | end 23 | 24 | answer "Integers and floats have value inequality" do 25 | [assert] 26 | end 27 | 28 | answer "Strict equality checks types" do 29 | [refute, assert] 30 | end 31 | 32 | answer "Strict inequality checks types" do 33 | [assert] 34 | end 35 | 36 | answer "Are integers booleans?" do 37 | [refute, refute] 38 | end 39 | 40 | answer "Boolean OR returns left side if true, otherwise right side" do 41 | [assert, assert, assert, refute] 42 | end 43 | 44 | answer "Boolean operators check their argument's type" do 45 | ["1"] 46 | end 47 | 48 | answer "Other binary operators are relaxed about their argument's type" do 49 | [42, 42, 84, nil] 50 | end 51 | end 52 | -------------------------------------------------------------------------------- /answers/about_processes.exs: -------------------------------------------------------------------------------- 1 | defmodule Answers.AboutProcesses do 2 | use Koans 3 | 4 | answer "Spawning a process executes a function" do 5 | [fn -> end] 6 | end 7 | 8 | answer "Spawning a process returns a process ID (PID)" do 9 | [true] 10 | end 11 | 12 | answer "You are a process" do 13 | [assert] 14 | end 15 | 16 | answer "Processes send and receive messages; it's like mailbox" do 17 | ["world"] 18 | end 19 | 20 | answer "Processes communicate with one another" do 21 | ["hi!"] 22 | end 23 | 24 | answer "Use tail recursion (calling a function as the very last statement) to receive multiple messages" do 25 | ["o", "hai"] 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /answers/about_sigils.exs: -------------------------------------------------------------------------------- 1 | defmodule Answers.AboutSigils do 2 | use Koans 3 | 4 | answer "The ~s sigil is a different way of expressing string literals" do 5 | ["This is a string"] 6 | end 7 | 8 | answer "Sigils are useful to avoid escaping quotes in strings" do 9 | [~s("Welcome to the jungle", they said.)] 10 | end 11 | 12 | answer "Sigils can use different delimiters" do 13 | [assert] 14 | end 15 | 16 | answer "The lowercase ~s sigil supports string interpolation" do 17 | ["1 + 1 = 2"] 18 | end 19 | 20 | answer "The ~S sigil is similar to ~s but doesn't do interpolation" do 21 | ["1 + 1 = #\{1+1}"] 22 | end 23 | 24 | answer "~w is another sigil, it creates word lists" do 25 | ["Hello", "world"] 26 | end 27 | 28 | answer "~w also allows interpolation" do 29 | ["Hello", "123"] 30 | end 31 | 32 | answer "~W behaves to ~w as ~S behaves to ~s" do 33 | [~S(#{1+1})] 34 | end 35 | end 36 | 37 | -------------------------------------------------------------------------------- /answers/about_strings.exs: -------------------------------------------------------------------------------- 1 | defmodule Answers.AboutStrings do 2 | use Koans 3 | 4 | answer "Creating a new string" do 5 | ["hello world!"] 6 | end 7 | 8 | answer "Values can be interpolated into strings" do 9 | ["1 + 1 = 2"] 10 | end 11 | 12 | answer "How long is a piece of string?" do 13 | [11] 14 | end 15 | 16 | answer "What does the string start with?" do 17 | [true, true, false] 18 | end 19 | 20 | answer "What does the string end with?" do 21 | [true, true, false] 22 | end 23 | 24 | answer "Does a string contain something?" do 25 | [true, true, false] 26 | end 27 | 28 | answer "Accessing letters by their positions" do 29 | ["l", nil] 30 | end 31 | 32 | answer "Slicing a string" do 33 | ["world", "ld!", "", "", "Hello "] 34 | end 35 | 36 | answer "Capitalization" do 37 | ["Hello world!"] 38 | end 39 | 40 | answer "Upcase" do 41 | ["HELLO WORLD!"] 42 | end 43 | 44 | answer "Downcase" do 45 | ["speak quietly"] 46 | end 47 | 48 | answer "Reversing a string" do 49 | ["backwards words"] 50 | end 51 | 52 | answer "Say it again" do 53 | ["repeat thisrepeat thisrepeat this"] 54 | end 55 | 56 | answer "Stripping on the left" do 57 | ["abc "] 58 | end 59 | 60 | answer "Stripping on the left with specific characters" do 61 | [" abc $"] 62 | end 63 | 64 | answer "Stripping on the right" do 65 | [" abc"] 66 | end 67 | 68 | answer "Stripping on the right with specific character" do 69 | [" abc "] 70 | end 71 | 72 | answer "Stripping on both sides" do 73 | ["abc"] 74 | end 75 | 76 | answer "Stripping on both sides with a specific character" do 77 | [" abc "] 78 | end 79 | 80 | answer "Left justification" do 81 | ["2 "] 82 | end 83 | 84 | answer "Left justification with a specific character" do 85 | ["200"] 86 | end 87 | 88 | answer "Right justification" do 89 | [" 2"] 90 | end 91 | 92 | answer "Right justification with a specific character" do 93 | ["007"] 94 | end 95 | 96 | answer "Converting to an integer" do 97 | [10, " 10"] 98 | end 99 | 100 | answer "Converting to an integer using a different base" do 101 | [17, 3] 102 | end 103 | 104 | answer "Converting to a float" do 105 | [10.99, " 1.2"] 106 | end 107 | end 108 | -------------------------------------------------------------------------------- /answers/about_tuples.exs: -------------------------------------------------------------------------------- 1 | defmodule Answers.AboutTuples do 2 | use Koans 3 | 4 | answer "Getting tuples size is a kernel feature" do 5 | [2] 6 | end 7 | 8 | answer "Can reach tuple element with index" do 9 | [:foo] 10 | end 11 | 12 | answer "In erlang tuples and lists start at index 1, which is different in Elixir" do 13 | [1] 14 | end 15 | 16 | answer "Can set a tuple element" do 17 | [:foo, :baz] 18 | end 19 | 20 | answer "Setting a tuple element that does not exist raises an argument error" do 21 | [2] 22 | end 23 | 24 | answer "Insert an element at a position" do 25 | [:baz] 26 | end 27 | 28 | answer "Inserting a tuple element raises an argument error if index is invalid" do 29 | [-1] 30 | end 31 | 32 | answer "The building blocks of Elixir are tuples of three elements" do 33 | [true, 3] 34 | end 35 | 36 | answer "We can use tuples to define blocks of Elixir code" do 37 | [{1,2,3}] 38 | end 39 | 40 | answer "Are tuples enumerable?" do 41 | [Protocol.UndefinedError] 42 | end 43 | end 44 | -------------------------------------------------------------------------------- /lib/koans.ex: -------------------------------------------------------------------------------- 1 | defmodule Koans do 2 | use Application 3 | 4 | def start(_type, _args) do 5 | import Supervisor.Spec, warn: false 6 | 7 | children = [ 8 | worker(Koans.Examples, []), 9 | worker(Koans.Answers, []), 10 | ] 11 | 12 | opts = [strategy: :one_for_one, name: Koans.Supervisor] 13 | Supervisor.start_link(children, opts) 14 | end 15 | 16 | defmacro __using__([]) do 17 | quote do 18 | import ExUnit.Assertions 19 | import Koans.DSL, only: [think: 2, answer: 2, __?: 0, assert_?: 1] 20 | end 21 | end 22 | 23 | def meditate(subject) do 24 | raise Koans.MeditateWarning, message: subject 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /lib/koans/answerer.ex: -------------------------------------------------------------------------------- 1 | defmodule Koans.Answerer do 2 | def inject({:__?, _, _}, [answer|rest]) do 3 | {answer, rest} 4 | end 5 | 6 | def inject({:assert_?, _, args}, [answer|rest]) do 7 | with_assertion = put_elem(answer, 2, args) 8 | {with_assertion, rest} 9 | end 10 | 11 | def inject({_, _, args} = quoted, answers) when is_list(args) do 12 | {args, answers} = inject(args, answers) 13 | quoted = put_elem(quoted, 2, args) 14 | {quoted, answers} 15 | end 16 | 17 | def inject(quoted, answers) when is_tuple(quoted) do 18 | list = Tuple.to_list(quoted) 19 | {list, answers} = Enum.map_reduce list, answers, &inject/2 20 | {List.to_tuple(list), answers} 21 | end 22 | 23 | def inject([], answers), do: {[], answers} 24 | def inject([quoted|rest], answers) do 25 | {quoted, answers} = inject(quoted, answers) 26 | {rest, answers} = inject(rest, answers) 27 | {[quoted|rest], answers} 28 | end 29 | def inject(quoted, answers), do: {quoted, answers} 30 | end 31 | -------------------------------------------------------------------------------- /lib/koans/answers.ex: -------------------------------------------------------------------------------- 1 | defmodule Koans.Answers do 2 | @name __MODULE__ 3 | 4 | def start_link do 5 | Agent.start_link(fn -> %{} end, name: @name) 6 | end 7 | 8 | def add(lesson, answers) do 9 | Agent.update(@name, fn map -> Map.put(map, lesson, answers) end) 10 | end 11 | 12 | def get(lesson) do 13 | Agent.get(@name, fn map -> Map.fetch(map, lesson) end) 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /lib/koans/dsl.ex: -------------------------------------------------------------------------------- 1 | defmodule Koans.DSL do 2 | defmacro think(message, lesson) do 3 | name = :"#{message}" 4 | 5 | lesson = case Koans.Answers.get(message) do 6 | {:ok, answers} -> [do: Koans.Answerer.inject(lesson[:do], answers)] 7 | :error -> lesson 8 | end 9 | 10 | quote do 11 | tag = Module.get_attribute(__MODULE__, :tag) 12 | Module.put_attribute(__MODULE__, :tag, nil) 13 | 14 | unless tag == :skip do 15 | Module.put_attribute(__MODULE__, :meditation, unquote(message)) 16 | Koans.Examples.add({__MODULE__, unquote(name), tag}) 17 | def unquote(name)(), unquote(lesson) 18 | end 19 | end 20 | end 21 | 22 | defmacro answer(lesson, do: answers) do 23 | Koans.Answers.add(lesson, answers) 24 | end 25 | 26 | defmacro __? do 27 | quote do 28 | Koans.meditate @meditation 29 | end 30 | end 31 | 32 | defmacro assert_?(_) do 33 | quote do 34 | Koans.meditate @meditation <> "#{IO.ANSI.format([:red, " (replace with an assertion)"])}" 35 | end 36 | end 37 | end 38 | -------------------------------------------------------------------------------- /lib/koans/examples.ex: -------------------------------------------------------------------------------- 1 | defmodule Koans.Examples do 2 | @name __MODULE__ 3 | 4 | def start_link do 5 | Agent.start_link(fn -> [] end, name: @name) 6 | end 7 | 8 | def add(koan) do 9 | Agent.update(@name, fn koans -> [koan|koans] end) 10 | end 11 | 12 | def all do 13 | Agent.get(@name, fn koans -> koans end) 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /lib/koans/formatter.ex: -------------------------------------------------------------------------------- 1 | defmodule Koans.Formatter do 2 | @moduledoc """ 3 | This module is a minimal shim used to interface with ExUnit.Formatter for 4 | formatting lessons. 5 | """ 6 | 7 | @doc """ 8 | Format a pretty failure message from an error in meditation. 9 | """ 10 | def failure_message(error, meditation, case, counter \\ 1, width \\ 80, formatter \\ &formatter/2) do 11 | test = build_test(meditation, case) 12 | failure = build_failure(error) 13 | ExUnit.Formatter.format_test_failure(test, [failure], counter, width, formatter) 14 | end 15 | 16 | defp build_test(name, case) do 17 | %ExUnit.Test{name: name, case: case, tags: %{file: __ENV__.file, line: __ENV__.line}} 18 | end 19 | 20 | defp build_failure(error) do 21 | {:error, error, System.stacktrace} 22 | end 23 | 24 | # These functions are shamelessly ripped from ExUnit.CLIFormatter to make 25 | # things pretty. 26 | defp colorize(escape, string) do 27 | [IO.ANSI.format_fragment(escape, true), 28 | string, 29 | IO.ANSI.format_fragment(:reset, true)] |> IO.iodata_to_binary 30 | end 31 | defp formatter(:error_info, msg), do: colorize([:red], msg) 32 | defp formatter(:extra_info, msg), do: colorize([:cyan], msg) 33 | defp formatter(:location_info, msg), do: colorize([:bright, :black], msg) 34 | defp formatter(_, msg), do: msg 35 | end 36 | -------------------------------------------------------------------------------- /lib/koans/lessons.ex: -------------------------------------------------------------------------------- 1 | defmodule Koans.Lessons do 2 | def all do 3 | [ 4 | "about_asserts.exs", 5 | "about_numbers_and_booleans.exs", 6 | "about_strings.exs", 7 | "about_atoms.exs", 8 | "about_lists.exs", 9 | "about_matching.exs", 10 | "about_sigils.exs", 11 | "about_enums.exs", 12 | "about_tuples.exs", 13 | "about_maps.exs", 14 | "about_lists_and_maps.exs", 15 | "about_anonymous_functions.exs", 16 | "about_named_functions.exs", 17 | "about_binaries.exs", 18 | "about_processes.exs", 19 | ] 20 | end 21 | 22 | def load do 23 | all |> Enum.each(&Code.load_file/1) 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /lib/koans/meditate_warning.ex: -------------------------------------------------------------------------------- 1 | defmodule Koans.MeditateWarning do 2 | defexception [:message] 3 | def message(exception) do 4 | "#{format(exception, location)}" 5 | end 6 | 7 | defp location do 8 | System.stacktrace |> Enum.at(1) |> elem(3) 9 | end 10 | 11 | defp format(exception, location) do 12 | IO.ANSI.format([ 13 | :magenta, :bright, "Please meditate: ", 14 | :blue, :normal, exception.message, 15 | :yellow, " (#{location[:file]}:#{location[:line]})" 16 | ]) 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /lib/koans/runner.ex: -------------------------------------------------------------------------------- 1 | defmodule Koans.Runner do 2 | def run do 3 | Koans.Examples.all 4 | # TODO lazy iteration 5 | |> focus 6 | |> Enum.reverse 7 | |> Enum.each(&exec/1) 8 | congratulate 9 | end 10 | 11 | defp focus(koans) do 12 | focused_koans = Enum.filter(koans, fn {_,_,tag} -> tag == :focus end) 13 | 14 | if Enum.empty?(focused_koans) do 15 | koans 16 | else 17 | focused_koans 18 | end 19 | end 20 | 21 | defp exec({module, koan, _tag}) do 22 | try do 23 | apply(module, koan, []) 24 | rescue 25 | problem in [ExUnit.AssertionError, Koans.MeditateWarning] -> 26 | stop_to_learn(problem, koan, module) 27 | end 28 | success(module, koan) 29 | end 30 | 31 | defp stop_to_learn(error, meditation, case) do 32 | failure(case, meditation) 33 | Koans.Formatter.failure_message(error, meditation, case) 34 | |> IO.puts 35 | exit({:shutdown, 1}) 36 | end 37 | 38 | defp failure(module, koan) do 39 | IO.ANSI.format([:red, "✗ #{module}: #{koan}\n"]) 40 | |> IO.puts 41 | end 42 | 43 | defp success(module, koan) do 44 | IO.ANSI.format([:green, "✓ #{module}: #{koan}"]) 45 | |> IO.puts 46 | end 47 | 48 | @mountains ~S( 49 | ** You have learned much. You must find your own path now. ** 50 | 51 | _ * 52 | * (( 53 | ` 54 | ^ 55 | /*\ ^ * 56 | /***\ /*\ 57 | / \ /* *\ * 58 | / \ / \ ^ 59 | / \/ \ / \ ^ 60 | / / \/ \ / \ _ 61 | / / / \ _ / \/ \ X 62 | / / / \ _ / \ / / \/ \ 63 | --------------------------------------------------------------- 64 | ) 65 | 66 | defp congratulate do 67 | IO.puts @mountains 68 | end 69 | end 70 | -------------------------------------------------------------------------------- /lib/mix/tasks/learn.ex: -------------------------------------------------------------------------------- 1 | require Mix.Config 2 | 3 | defmodule Mix.Tasks.Learn do 4 | use Mix.Task 5 | 6 | @shortdoc "Learn Elixir through meditation" 7 | 8 | def run(_) do 9 | Application.ensure_all_started(:koans) 10 | Koans.Lessons.load 11 | Koans.Runner.run 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /mix.exs: -------------------------------------------------------------------------------- 1 | defmodule Koans.Mixfile do 2 | use Mix.Project 3 | 4 | def project do 5 | [app: :koans, 6 | version: "0.0.1", 7 | elixir: "~> 1.2", 8 | build_embedded: Mix.env == :prod, 9 | start_permanent: Mix.env == :prod, 10 | deps: deps] 11 | end 12 | 13 | # Configuration for the OTP application 14 | # 15 | # Type "mix help compile.app" for more information 16 | def application do 17 | [applications: [:logger], mod: {Koans, []}] 18 | end 19 | 20 | # Dependencies can be Hex packages: 21 | # 22 | # {:mydep, "~> 0.3.0"} 23 | # 24 | # Or git/path repositories: 25 | # 26 | # {:mydep, git: "https://github.com/elixir-lang/mydep.git", tag: "0.1.0"} 27 | # 28 | # Type "mix help deps" for more examples and options 29 | defp deps do 30 | [] 31 | end 32 | end 33 | -------------------------------------------------------------------------------- /test/koans/answerer_test.exs: -------------------------------------------------------------------------------- 1 | defmodule KoansAnswererTest do 2 | use ExUnit.Case 3 | 4 | import Koans.Answerer 5 | 6 | test "value injected" do 7 | value_is_missing = quote do: __? 8 | {with_value, []} = inject(value_is_missing, [:lol]) 9 | 10 | assert with_value == quote(do: :lol) 11 | end 12 | 13 | test "assertion injected" do 14 | assert_is_missing = quote do: assert_? true 15 | {with_assert, []} = inject(assert_is_missing, [quote do: assert]) 16 | 17 | # Unfortunately this can't pass as it's currently implemented... 18 | #assert with_assert == quote(do: assert true) 19 | assert elem(with_assert, 0) == :assert 20 | assert elem(with_assert, 2) == [true] 21 | end 22 | 23 | test "multiple values injected" do 24 | values_are_missing = quote do 25 | assert __? 26 | refute __? 27 | end 28 | {with_values, []} = inject(values_are_missing, [true, false]) 29 | expected = quote do 30 | assert true 31 | refute false 32 | end 33 | 34 | assert with_values == expected 35 | end 36 | 37 | test "handles basic values" do 38 | quoted = quote do 39 | :foo 40 | end 41 | {quoted, _} = inject(quoted, [nil]) 42 | expected = quote do 43 | :foo 44 | end 45 | 46 | assert quoted == expected 47 | end 48 | 49 | test "handles missing values in literal tuples" do 50 | quoted = quote do 51 | {__?, __?} 52 | end 53 | {with_values, []} = inject(quoted, [:foo, :bar]) 54 | expected = quote do 55 | {:foo, :bar} 56 | end 57 | 58 | assert with_values == expected 59 | end 60 | end 61 | -------------------------------------------------------------------------------- /test/koans/dsl_test.exs: -------------------------------------------------------------------------------- 1 | defmodule KoansDSLTest do 2 | require Koans.DSL 3 | use ExUnit.Case 4 | 5 | @meditation "testing" 6 | 7 | test "__?" do 8 | assert_raise Koans.MeditateWarning, fn -> 9 | Koans.DSL.__? 10 | end 11 | end 12 | 13 | test "assert_?" do 14 | assert_raise Koans.MeditateWarning, fn -> 15 | Koans.DSL.assert_? _ 16 | end 17 | end 18 | 19 | Koans.DSL.think "lolwat" do 20 | :hahaha 21 | end 22 | 23 | test "think/2 defines a method on the module" do 24 | assert lolwat == :hahaha 25 | end 26 | 27 | Koans.DSL.answer "answered" do 28 | [true, false] 29 | end 30 | Koans.DSL.think "answered" do 31 | assert __? 32 | refute __? 33 | end 34 | 35 | test "answer/2 provides answers which are injected into the lesson when it's defined" do 36 | answered 37 | end 38 | 39 | # TODO skipping 40 | end 41 | -------------------------------------------------------------------------------- /test/test_helper.exs: -------------------------------------------------------------------------------- 1 | ExUnit.start() 2 | 3 | Enum.each Koans.Lessons.all, fn lesson -> 4 | try do 5 | Code.load_file("answers/#{lesson}") 6 | Code.load_file(lesson) 7 | rescue 8 | Code.LoadError -> raise """ 9 | You must provide answers for all lessons. Make sure that any new lesson 10 | has a corresponding answer module defined in `answers/`. For example, 11 | a lesson `about_giggles.exs` should have answers defined in 12 | `answers/about_giggles.exs`. Look at existing lessons as example. 13 | """ 14 | end 15 | end 16 | System.at_exit fn 0 -> Koans.Runner.run end 17 | --------------------------------------------------------------------------------