Sunday, June 17, 2018

Lazy evaluation and list random access in Haskell

Lazy evaluation and list random access in Haskell

Today I learned about the :sprint command in GHCi, which prints the value bound to given identifier without forcing its evaluation. The unevaluated part, if any, is printed as an underscore.

Here, we can see that the value bound to s is entirely unevaluated at first:

λ> s = "0123456789"
λ> :sprint s
s = _

Now let’s peek at the first character in the string:

λ> head s
λ> :sprint s
s = '0' : _

We can see that only the first character in the string (i.e. the first element of a list of characters) was evaluated. Now, let’s use the !! operator to print the fourth character in s:

λ> s !! 3
λ> :sprint s
s = '0' : '1' : '2' : '3' : _

Interesting – we can see that this caused all of the intervening elements to be consumed, which demonstrates that random access of a list with the !! operator must walk along the cons cells and therefore can be expected to require linear time to complete.

If we print the whole string, then :sprint shows the entire string with no unevaluated component:

λ> s
λ> :sprint s
s = "0123456789"

Sunday, February 04, 2018

Controlling test execution in ExUnit

Controlling test execution in ExUnit

The Elixir programming language comes with a nice, simple testing library called ExUnit, and you can run all of your tests with mix test.

But sometimes you don’t want to run all your tests. Maybe you added integration tests or for whatever reason have a test that gives useful information but takes a long time to execute. In these situations, you can add the “skip” tag by adding an annotation before a test which shouldn’t be executed by mix test:

@tag :skip
test "should be skipped..." do

This way, a normal mix test run will be fast (and won’t break your continuous integration system), and when you want to actually run that test locally, you can:
mix test --include skip

However, this will run all tests marked skip. A more flexible approach is to add your own tag and use it to name your slow tests.

First, modify test/test_helper.exs to look like this, to prevent those tests from running by default:

ExUnit.start(exclude: :slow_test)

Next, annotate your slow tests with names:

  @tag slow_test: "backtest"
  test "Backtest should take ages" do

  @tag slow_test: "optimiser"
  test "Optimiser should waste a lot of time" do

Now mix test will still run your normal unit tests, and you can run one of the slow tests with mix test --trace --only slow_test:optimiser (the addition of the --trace option means ExUnit will let the test run as long as is necessary rather than killing it after 60 seconds).