Jupyter Lab, Calysto Hy, Archimedes… oh my

I recently came across a really nifty looking set of tools, namely: Jupyter Lab and Calysto Hy. Former is continuation of IPython, a notebook based Python authoring tool. It lets you write notebooks online and mix in Python. As such it’s really nice for writing reports, while doing compations required for that report at the same time. Calypso Hy is metakernel, that allows Jupyter Lab (and Jupyter in general) use Hy.

Here I’m going to give Jupyter Lab and Calysto Hy a little test drive, using some familiar libraries: Hypothesis and Archimedes.

I originally wrote this in Jupyter Lab and exported resulting notebook from Jupyter Notebook to html, which I copy pasted here. It doesn’t look as neat and pretty as in Jupyter Lab, but readable enough so you can follow along. I hope I’ll find a better way in the future (I can just imagine what cool things I could do with this in regard to my game project).

First we of course want to verify that things have been installed correctly

(require archimedes)
(import [hypothesis]
        [hamcrest])

So far, so good. Now, lets come up with some contrived example how to use these two libraries. We could pretend that we’re writing a function that reverses a string, without touching the original string. A dead simple test case written in achimedes will suffice here:

(defn rev [a]
      "reverse a string"
      a)
(import [hamcrest [assert-that is- equal-to]])

(fact "reversed string is reversed"
      (assert-that (rev "foo")
                   (is- (equal-to "off"))))
But why doesn’t that fail? rev clearly isn’t properly implemented, so that archimedes test should be passing. The catch here is that while fact is used to define test case, it never gets executed. Lets try again with some helpers.
(defmacro check [fact-name &rest code]
          "define test case and execute it"
          `(do ((fact ~fact-name ~@code))
               (+ "ok: " ~fact-name)))

(check "reversed string is reversed"
       (assert-that (rev "foo")
                    (is- (equal-to "oof"))))
Traceback (most recent call last):
  File "C:\Users\Tuukka\AppData\Roaming\Python\Python34\site-packages\calysto_hy\kernel.py", line 92, in do_execute_direct
    eval(code, self.env)
  File "In [4]", line 6, in <module>
  File "In [4]", line 8, in test_reversed_string_is_reversed
  File "C:\Users\Tuukka\Anaconda3\envs\lab\lib\site-packages\hamcrest\core\assert_that.py", line 43, in assert_that
    _assert_match(actual=arg1, matcher=arg2, reason=arg3)
  File "C:\Users\Tuukka\Anaconda3\envs\lab\lib\site-packages\hamcrest\core\assert_that.py", line 57, in _assert_match
    raise AssertionError(description)
AssertionError: 
Expected: 'oof'
     but: was 'foo'
 Much, much better. Now we can write an actual implementation and see how that behaves.
(defn rev [a]
      "reverse a string"
      (slice a nil nil -1))

(check "reversed string is reversed"
       (assert-that (rev "foo")
                    (is- (equal-to "oof"))))
'ok: reversed string is reversed'
 Ok, not bad I guess. But testing things with hard coded values can take us only so far. What about if we would generalize things a bit? We do this by defining properties that always are true, regardless of given input. And then we feed random (within limits of our specification) input and observe that output still satisfies the facts:
(import [hypothesis.strategies [text]])

(check "reversed string is a long as original one"
       (variants :a (text))
       (assert-that (len (rev a))
                    (is- (equal-to (len a)))))
'ok: reversed string is a long as original one'
 Previous example is generating random pieces of text and feeding them to our rev function. Length of resulting string is compared to the original and an exception will be raised if their lengths don’t match. But can we be sure that it’s actually happening? Lets try a case that will surely fail.
(check "this check should fail"
       (variants :a (text))
       (assert-that (rev a)
                    (is- (equal-to a))))
Falsifying example: test_this_check_should_fail(a='10')
Traceback (most recent call last):
  File "C:\Users\Tuukka\AppData\Roaming\Python\Python34\site-packages\calysto_hy\kernel.py", line 92, in do_execute_direct
    eval(code, self.env)
  File "In [7]", line 1, in <module>
  File "In [7]", line 257, in test_this_check_should_fail
  File "In [7]", line 2, in test_this_check_should_fail
  File "C:\Users\Tuukka\Anaconda3\envs\lab\lib\site-packages\hypothesis\core.py", line 524, in wrapped_test
    print_example=True, is_final=True
  File "C:\Users\Tuukka\Anaconda3\envs\lab\lib\site-packages\hypothesis\executors.py", line 58, in default_new_style_executor
    return function(data)
  File "C:\Users\Tuukka\Anaconda3\envs\lab\lib\site-packages\hypothesis\core.py", line 111, in run
    return test(*args, **kwargs)
  File "In [7]", line 4, in test_this_check_should_fail
  File "C:\Users\Tuukka\Anaconda3\envs\lab\lib\site-packages\hamcrest\core\assert_that.py", line 43, in assert_that
    _assert_match(actual=arg1, matcher=arg2, reason=arg3)
  File "C:\Users\Tuukka\Anaconda3\envs\lab\lib\site-packages\hamcrest\core\assert_that.py", line 57, in _assert_match
    raise AssertionError(description)
AssertionError: 
Expected: '10'
     but: was '01'
 Not too shabby (noticed how a falsifying example is displayed too, very useful). And I’m able to do this from the comfort of my browser and share the resulting workbook with others isn’t big bonuses too (and I haven’t even touched things like displaying graphs and such).
Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s