rpn_calculator
This commit is contained in:
		
							parent
							
								
									0c15aedc09
								
							
						
					
					
						commit
						d1b894006c
					
				
							
								
								
									
										23
									
								
								elixir/rpn-calculator/.exercism/config.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								elixir/rpn-calculator/.exercism/config.json
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,23 @@ | ||||
| { | ||||
|   "authors": [ | ||||
|     "neenjaw" | ||||
|   ], | ||||
|   "contributors": [ | ||||
|     "angelikatyborska", | ||||
|     "cjmaxik" | ||||
|   ], | ||||
|   "files": { | ||||
|     "solution": [ | ||||
|       "lib/rpn_calculator.ex" | ||||
|     ], | ||||
|     "test": [ | ||||
|       "test/rpn_calculator_test.exs" | ||||
|     ], | ||||
|     "exemplar": [ | ||||
|       ".meta/exemplar.ex" | ||||
|     ] | ||||
|   }, | ||||
|   "language_versions": ">=1.10", | ||||
|   "icon": "instruments-of-texas", | ||||
|   "blurb": "Learn about errors and rescuing them by working on an experimental Reverse Polish Notation calculator." | ||||
| } | ||||
							
								
								
									
										1
									
								
								elixir/rpn-calculator/.exercism/metadata.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								elixir/rpn-calculator/.exercism/metadata.json
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1 @@ | ||||
| {"track":"elixir","exercise":"rpn-calculator","id":"ea46ce98f99e40f18d385997dfdae1de","url":"https://exercism.org/tracks/elixir/exercises/rpn-calculator","handle":"negrienko","is_requester":true,"auto_approve":false} | ||||
							
								
								
									
										4
									
								
								elixir/rpn-calculator/.formatter.exs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								elixir/rpn-calculator/.formatter.exs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,4 @@ | ||||
| # Used by "mix format" | ||||
| [ | ||||
|   inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"] | ||||
| ] | ||||
							
								
								
									
										24
									
								
								elixir/rpn-calculator/.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								elixir/rpn-calculator/.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,24 @@ | ||||
| # The directory Mix will write compiled artifacts to. | ||||
| /_build/ | ||||
| 
 | ||||
| # If you run "mix test --cover", coverage assets end up here. | ||||
| /cover/ | ||||
| 
 | ||||
| # The directory Mix downloads your dependencies sources to. | ||||
| /deps/ | ||||
| 
 | ||||
| # Where third-party dependencies like ExDoc output generated docs. | ||||
| /doc/ | ||||
| 
 | ||||
| # Ignore .fetch files in case you like to edit your project deps locally. | ||||
| /.fetch | ||||
| 
 | ||||
| # If the VM crashes, it generates a dump, let's ignore it too. | ||||
| erl_crash.dump | ||||
| 
 | ||||
| # Also ignore archive artifacts (built via "mix archive.build"). | ||||
| *.ez | ||||
| 
 | ||||
| # Ignore package tarball (built via "mix hex.build"). | ||||
| errors-*.tar | ||||
| 
 | ||||
							
								
								
									
										75
									
								
								elixir/rpn-calculator/HELP.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										75
									
								
								elixir/rpn-calculator/HELP.md
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,75 @@ | ||||
| # Help | ||||
| 
 | ||||
| ## Running the tests | ||||
| 
 | ||||
| From the terminal, change to the base directory of the exercise then execute the tests with: | ||||
| 
 | ||||
| ```bash | ||||
| $ mix test | ||||
| ``` | ||||
| 
 | ||||
| This will execute the test file found in the `test` subfolder -- a file ending in `_test.exs` | ||||
| 
 | ||||
| Documentation: | ||||
| 
 | ||||
| * [`mix test` - Elixir's test execution tool](https://hexdocs.pm/mix/Mix.Tasks.Test.html) | ||||
| * [`ExUnit` - Elixir's unit test library](https://hexdocs.pm/ex_unit/ExUnit.html) | ||||
| 
 | ||||
| ## Pending tests | ||||
| 
 | ||||
| In test suites of practice exercises, all but the first test have been tagged to be skipped. | ||||
| 
 | ||||
| Once you get a test passing, you can unskip the next one by commenting out the relevant `@tag :pending` with a `#` symbol. | ||||
| 
 | ||||
| For example: | ||||
| 
 | ||||
| ```elixir | ||||
| # @tag :pending | ||||
| test "shouting" do | ||||
|   assert Bob.hey("WATCH OUT!") == "Whoa, chill out!" | ||||
| end | ||||
| ``` | ||||
| 
 | ||||
| If you wish to run all tests at once, you can include all skipped test by using the `--include` flag on the `mix test` command: | ||||
| 
 | ||||
| ```bash | ||||
| $ mix test --include pending | ||||
| ``` | ||||
| 
 | ||||
| Or, you can enable all the tests by commenting out the `ExUnit.configure` line in the file `test/test_helper.exs`. | ||||
| 
 | ||||
| ```elixir | ||||
| # ExUnit.configure(exclude: :pending, trace: true) | ||||
| ``` | ||||
| 
 | ||||
| ## Useful `mix test` options | ||||
| 
 | ||||
| * `test/<FILE>.exs:LINENUM` - runs only a single test, the test from `<FILE>.exs` whose definition is on line `LINENUM` | ||||
| * `--failed` - runs only tests that failed the last time they ran | ||||
| * `--max-failures` - the suite stops evaluating tests when this number of test failures | ||||
| is reached | ||||
| * `--seed 0` - disables randomization so the tests in a single file will always be ran | ||||
| in the same order they were defined in | ||||
| 
 | ||||
| ## Submitting your solution | ||||
| 
 | ||||
| You can submit your solution using the `exercism submit lib/rpn_calculator.ex` command. | ||||
| This command will upload your solution to the Exercism website and print the solution page's URL. | ||||
| 
 | ||||
| It's possible to submit an incomplete solution which allows you to: | ||||
| 
 | ||||
| - See how others have completed the exercise | ||||
| - Request help from a mentor | ||||
| 
 | ||||
| ## Need to get help? | ||||
| 
 | ||||
| If you'd like help solving the exercise, check the following pages: | ||||
| 
 | ||||
| - The [Elixir track's documentation](https://exercism.org/docs/tracks/elixir) | ||||
| - The [Elixir track's programming category on the forum](https://forum.exercism.org/c/programming/elixir) | ||||
| - [Exercism's programming category on the forum](https://forum.exercism.org/c/programming/5) | ||||
| - The [Frequently Asked Questions](https://exercism.org/docs/using/faqs) | ||||
| 
 | ||||
| Should those resources not suffice, you could submit your (incomplete) solution to request mentoring. | ||||
| 
 | ||||
| If you're stuck on something, it may help to look at some of the [available resources](https://exercism.org/docs/tracks/elixir/resources) out there where answers might be found. | ||||
							
								
								
									
										23
									
								
								elixir/rpn-calculator/HINTS.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								elixir/rpn-calculator/HINTS.md
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,23 @@ | ||||
| # Hints | ||||
| 
 | ||||
| ## General | ||||
| 
 | ||||
| - Read about [errors][errors] in the Getting Started guide. | ||||
| - Read about [`try`][docs-try] in the documentation. | ||||
| 
 | ||||
| ## 1. Warn the team | ||||
| 
 | ||||
| - Allow the operation function to raise its error. | ||||
| - To invoke a function in a variable, use the `.` operator. | ||||
| 
 | ||||
| ## 2. Wrap the error | ||||
| 
 | ||||
| - Make use of try .. rescue to return the intended result. | ||||
| 
 | ||||
| ## 3. Pass on the message | ||||
| 
 | ||||
| - Make use of try .. rescue to return the intended result. | ||||
| - The rescue block allows you to pattern match on the error's Module name and also bind the error to a variable. | ||||
| 
 | ||||
| [errors]: https://hexdocs.pm/elixir/try-catch-and-rescue.html#errors | ||||
| [docs-try]: https://hexdocs.pm/elixir/Kernel.SpecialForms.html#try/1 | ||||
							
								
								
									
										121
									
								
								elixir/rpn-calculator/README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										121
									
								
								elixir/rpn-calculator/README.md
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,121 @@ | ||||
| # RPN Calculator | ||||
| 
 | ||||
| Welcome to RPN Calculator on Exercism's Elixir Track. | ||||
| If you need help running the tests or submitting your code, check out `HELP.md`. | ||||
| If you get stuck on the exercise, check out `HINTS.md`, but try and solve it without using those first :) | ||||
| 
 | ||||
| ## Introduction | ||||
| 
 | ||||
| ## Errors | ||||
| 
 | ||||
| Errors happen. In Elixir, while people often say to "let it crash", there are times when we need to rescue the function call to a known good state to fulfill a software contract. In some languages, errors are used as a method of control flow, but in Elixir, this pattern is discouraged. We can often recognize functions that may raise an error just by their name: functions that raise errors are to have `!` at the end of their name. This is in comparison with functions that return `{:ok, value}` or `:error`. Look at these library examples: | ||||
| 
 | ||||
| ```elixir | ||||
| Map.fetch(%{a: 1}, :b) | ||||
| # => :error | ||||
| Map.fetch!(%{a: 1}, :b) | ||||
| # => raises KeyError | ||||
| ``` | ||||
| 
 | ||||
| ## Try/Rescue | ||||
| 
 | ||||
| Elixir provides a construct for rescuing from errors using `try .. rescue` | ||||
| 
 | ||||
| []: # (elixir-formatter-disable-next-block) | ||||
| 
 | ||||
| ```elixir | ||||
| try do                             #1 | ||||
|   raise RuntimeError, "error"      #2 | ||||
| rescue | ||||
|   e in RuntimeError -> :error      #3 | ||||
| end | ||||
| ``` | ||||
| 
 | ||||
| Let's examine this construct: | ||||
| 
 | ||||
| - **Line 1**, the block is declared with `try`. | ||||
| - **Line 2**, the function call which may error is placed here, in this case we are calling `raise/2`. | ||||
| - **Line 3**, in the `rescue` section, we pattern match on the _Module_ name of the error raised | ||||
|   - on the left side of `->`: | ||||
|     - `e` is matched to the error struct. | ||||
|     - `in` is a keyword. | ||||
|     - `RuntimeError` is the error that we want to rescue. | ||||
|       - If we wanted to rescue from all errors, we could use `_` instead of the module name or omit the `in` keyword entirely. | ||||
|   - on the right side: | ||||
|     - the instructions to be executed if the error matches. | ||||
| 
 | ||||
| ### Error structs | ||||
| 
 | ||||
| Errors (sometimes also called "exceptions") that you rescue this way are structs. | ||||
| Rescuing errors in Elixir is done very rarely. | ||||
| Usually the rescued error is logged or sent to an external monitoring service, and then reraised. | ||||
| This means we usually don't care about the internal structure of the specific error struct. | ||||
| 
 | ||||
| In the [Exceptions concept][exercism-exceptions] you will learn more about error structs, including how to define your own custom error. | ||||
| 
 | ||||
| [exercism-exceptions]: https://exercism.org/tracks/elixir/concepts/exceptions | ||||
| 
 | ||||
| ## Instructions | ||||
| 
 | ||||
| While working at _Instruments of Texas_, you are tasked to work on an experimental Reverse Polish Notation [RPN] calculator written in Elixir. Your team is having a problem with some operations raising errors and crashing the process. You have been tasked to write a function which wraps the operation function so that the errors can be handled more elegantly with idiomatic Elixir code. | ||||
| 
 | ||||
| ## 1. Warn the team | ||||
| 
 | ||||
| Implement the function `calculate!/2` to call the operation function with the stack as the only argument. The operation function is defined elsewhere, but you know that it can either complete successfully or raise an error. | ||||
| 
 | ||||
| ```elixir | ||||
| stack = [] | ||||
| operation = fn _ -> :ok end | ||||
| RPNCalculator.calculate!(stack, operation) | ||||
| # => :ok | ||||
| 
 | ||||
| stack = [] | ||||
| operation = fn _ -> raise ArgumentError, "An error occurred" end | ||||
| RPNCalculator.calculate!(stack, operation) | ||||
| # => ** (ArgumentError) An error occurred | ||||
| ``` | ||||
| 
 | ||||
| > Function names that end in `!` are a warning to programmers that this function may raise an error | ||||
| 
 | ||||
| ## 2. Wrap the error | ||||
| 
 | ||||
| When doing more research you notice that many functions use atoms and tuples to indicate their success/failure. Implement `calculate/2` using this strategy. | ||||
| 
 | ||||
| ```elixir | ||||
| stack = [] | ||||
| operation = fn _ -> "operation completed" end | ||||
| RPNCalculator.calculate(stack, operation) | ||||
| # => {:ok, "operation completed"} | ||||
| 
 | ||||
| stack = [] | ||||
| operation = fn _ -> raise ArgumentError, "An error occurred" end | ||||
| RPNCalculator.calculate(stack, operation) | ||||
| # => :error | ||||
| ``` | ||||
| 
 | ||||
| ## 3. Pass on the message | ||||
| 
 | ||||
| Some of the errors contain important information that your coworkers need to have to ensure the correct operation of the system. Implement `calculate_verbose/2` to pass on the error message. The error is a struct that has a `:message` field. | ||||
| 
 | ||||
| ```elixir | ||||
| stack = [] | ||||
| operation = fn _ -> "operation completed" end | ||||
| RPNCalculator.calculate_verbose(stack, operation) | ||||
| # => {:ok, "operation completed"} | ||||
| 
 | ||||
| stack = [] | ||||
| operation = fn _ -> raise ArgumentError, "An error occurred" end | ||||
| RPNCalculator.calculate_verbose(stack, operation) | ||||
| # => {:error, "An error occurred"} | ||||
| ``` | ||||
| 
 | ||||
| ## Source | ||||
| 
 | ||||
| ### Created by | ||||
| 
 | ||||
| - @neenjaw | ||||
| 
 | ||||
| ### Contributed to by | ||||
| 
 | ||||
| - @angelikatyborska | ||||
| - @cjmaxik | ||||
							
								
								
									
										21
									
								
								elixir/rpn-calculator/lib/rpn_calculator.ex
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								elixir/rpn-calculator/lib/rpn_calculator.ex
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,21 @@ | ||||
| defmodule RPNCalculator do | ||||
|   def calculate!(stack, operation) do | ||||
|     operation.(stack) | ||||
|   end | ||||
| 
 | ||||
|   def calculate(stack, operation) do | ||||
|     try do | ||||
|       {:ok, operation.(stack)} | ||||
|     rescue | ||||
|       _e -> :error | ||||
|     end | ||||
|   end | ||||
| 
 | ||||
|   def calculate_verbose(stack, operation) do | ||||
|     try do | ||||
|       {:ok, operation.(stack)} | ||||
|     rescue | ||||
|       e -> {:error, e.message} | ||||
|     end | ||||
|   end | ||||
| end | ||||
							
								
								
									
										28
									
								
								elixir/rpn-calculator/mix.exs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								elixir/rpn-calculator/mix.exs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,28 @@ | ||||
| defmodule RPNCalculator.MixProject do | ||||
|   use Mix.Project | ||||
| 
 | ||||
|   def project do | ||||
|     [ | ||||
|       app: :rpn_calculator, | ||||
|       version: "0.1.0", | ||||
|       # elixir: "~> 1.10", | ||||
|       start_permanent: Mix.env() == :prod, | ||||
|       deps: deps() | ||||
|     ] | ||||
|   end | ||||
| 
 | ||||
|   # Run "mix help compile.app" to learn about applications. | ||||
|   def application do | ||||
|     [ | ||||
|       extra_applications: [:logger] | ||||
|     ] | ||||
|   end | ||||
| 
 | ||||
|   # Run "mix help deps" to learn about dependencies. | ||||
|   defp deps do | ||||
|     [ | ||||
|       # {:dep_from_hexpm, "~> 0.3.0"}, | ||||
|       # {:dep_from_git, git: "https://github.com/elixir-lang/my_dep.git", tag: "0.1.0"} | ||||
|     ] | ||||
|   end | ||||
| end | ||||
							
								
								
									
										55
									
								
								elixir/rpn-calculator/test/rpn_calculator_test.exs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										55
									
								
								elixir/rpn-calculator/test/rpn_calculator_test.exs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,55 @@ | ||||
| defmodule RPNCalculatorTest do | ||||
|   use ExUnit.Case | ||||
| 
 | ||||
|   @tag task_id: 1 | ||||
|   test "calculate! returns what the operation does: :ok atom" do | ||||
|     assert RPNCalculator.calculate!([], fn _ -> :ok end) == :ok | ||||
|   end | ||||
| 
 | ||||
|   @tag task_id: 1 | ||||
|   test "calculate! returns what the operation does: an ok string" do | ||||
|     assert RPNCalculator.calculate!([], fn _ -> "ok" end) == "ok" | ||||
|   end | ||||
| 
 | ||||
|   @tag task_id: 1 | ||||
|   test "let it crash" do | ||||
|     assert_raise(RuntimeError, fn -> | ||||
|       RPNCalculator.calculate!([], fn _ -> raise "test error" end) | ||||
|     end) | ||||
|   end | ||||
| 
 | ||||
|   @tag task_id: 2 | ||||
|   test "calculate returns the result of the operation (a string) wrapped in an :ok tuple" do | ||||
|     assert RPNCalculator.calculate([], fn _ -> "operation completed" end) == | ||||
|              {:ok, "operation completed"} | ||||
|   end | ||||
| 
 | ||||
|   @tag task_id: 2 | ||||
|   test "calculate returns the result of the operation (an atom) wrapped in an :ok tuple" do | ||||
|     assert RPNCalculator.calculate([], fn _ -> :success end) == | ||||
|              {:ok, :success} | ||||
|   end | ||||
| 
 | ||||
|   @tag task_id: 2 | ||||
|   test "rescue the crash, no message" do | ||||
|     assert RPNCalculator.calculate([], fn _ -> raise "test error" end) == :error | ||||
|   end | ||||
| 
 | ||||
|   @tag task_id: 3 | ||||
|   test "calculate_verbose returns the result of the operation (a string) wrapped in an :ok tuple" do | ||||
|     assert RPNCalculator.calculate_verbose([], fn _ -> "operation completed" end) == | ||||
|              {:ok, "operation completed"} | ||||
|   end | ||||
| 
 | ||||
|   @tag task_id: 3 | ||||
|   test "calculate_verbose returns the result of the operation (an atom) wrapped in an :ok tuple" do | ||||
|     assert RPNCalculator.calculate_verbose([], fn _ -> :success end) == | ||||
|              {:ok, :success} | ||||
|   end | ||||
| 
 | ||||
|   @tag task_id: 3 | ||||
|   test "rescue the crash, get error tuple with message" do | ||||
|     assert RPNCalculator.calculate_verbose([], fn _ -> raise ArgumentError, "test error" end) == | ||||
|              {:error, "test error"} | ||||
|   end | ||||
| end | ||||
							
								
								
									
										2
									
								
								elixir/rpn-calculator/test/test_helper.exs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								elixir/rpn-calculator/test/test_helper.exs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,2 @@ | ||||
| ExUnit.start() | ||||
| ExUnit.configure(exclude: :pending, trace: true, seed: 0) | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user