A Hy macro for defining functions with docstrings on each argument

| categories: python, hylang | tags:

For functions with a lot of arguments, python style docstrings leave something to be desired. For one, they are not that close to the arguments, so if you have a function with say 20 arguments, the docstring might take up a whole page! That means they are hard to keep synchronized too. Let's not argue now over the merits of a function with 20+ arguments, it is enough that they exist, and are a problem.

So what are typical documentation standards? Here is a Numpy style doc string:

def func(arg1, arg2):
    """multiply arg1 and arg2

    Parameters
    ----------
    arg1 : a number
    arg2 : a number

    """
    return arg1 * arg2

It works well for a small number of arguments with limited descriptions. This is a proper docstring that is accessible by introspection and pydoc. With much longer argument lists, this falls apart. I will not pick on any code in particular here, but suffice it to say I was inspired today to think of a better way. There are some other documentation solutions at http://stackoverflow.com/questions/9195455/how-to-document-a-method-with-parameters, but None of them are better in my opinion. I want accessible docstrings by instrospection, and only if that is unavailable do I want to read the code! Finally, if I have to read the code, I want it to be easy to figure out, which means the documentation is close to the arguments.

There is bad news, I do not have one for vanilla python. Python does not even give you a way to deal with this. But, if we had a lisp, we could make a macro to help us out. In fact, we have a lisp with hy! And we can use a macro to make a syntax that lets us keep the docstring close to the argument, and that constructs a real docstring so we get help later!

Here it is:

(defmacro mydef [func args &optional docstring &rest body]
  `(defn ~func [~@(map (lambda [x] (nth x 0)) args)]
     ~(+ (if docstring (+ docstring "\n\n") "")
         "Parameters\n----------\n"
         (.join "\n" (map (lambda [x]
                            (.format "{} : {}"
                                     (nth x 0)
                                     (nth x 1))) args)))
     ~@body))

We can checkout how it expands like this:

(print (macroexpand '(mydef f [(a "an int")
                               (b "an int")]
                            "some doc"
                            (* a b))))
('setv' 'f' ('fn' ['a' 'b'] 'some doc\n\nParameters\n----------\na : an int\nb : an int' ('*' 'a' 'b')))

That looks ok. Now, for an example of using that. Here is the same function we defined before, but I put the documentation for each argument with the argument.

(mydef func ((arg1 "a number")
             (arg2 "a number"))
  "Multiply arg1 by arg2"
  (* arg1 arg2))

We can use the function now like a regular function.

(print (func 24 3))
72

And now for the help.

(help func)
Help on function func in module __main__:

func(arg1, arg2)
    Multiply arg1 by arg2

    Parameters
    ----------
    arg1 : a number
    arg2 : a number

Now, that should amaze and astonish you if you are a vanilla Pythonista! We have our cake, and we eat it too. You just can not make up your own syntax that way in Python. Imagine, we could add type information, validation code, etc… into that macro. Maybe it could even be possible to store argument dependent documentation on the function, say in the function dictionary. That would require some conventions I guess, but they could become introspectable then. For example, in this vanilla Python:

def f(x): return x*x
f.__dict__['args'] = {'x': 'A number'}
print(f.__dict__)

{'args': {'x': 'A number'}}

In the end, this does not really solve all the problems I have with current docstrings in Python. It does solve a problem with writing and reading the code by keeping documentation close to the arguments, but ultimately the docstring from Python's point of view will basically look the same. It is pretty awesome that it is even possible. Hy lisp for the win here (again!).

Copyright (C) 2017 by John Kitchin. See the License for information about copying.

org-mode source

Org-mode version = 9.0.5

Discuss on Twitter

Enough with the hyperbole - hy does things that are not as easy in Python

| categories: python, hylang | tags:

We run a lot of molecular simulations using Python. Here is a typical script we would use. It creates an instance of a calculator inside a context manager.

from ase import Atoms, Atom
from jasp import *

co = Atoms([Atom('C',[0,   0, 0]),
            Atom('O',[1.2, 0, 0])],
            cell=(6., 6., 6.))

with jasp('molecules/simple-co', #output dir
          xc='PBE',  # the exchange-correlation functional
          nbands=6,  # number of bands
          encut=350, # planewave cutoff
          ismear=1,  # Methfessel-Paxton smearing
          sigma=0.01,# very small smearing factor for a molecule
          atoms=co) as calc:
    print 'energy = {0} eV'.format(co.get_potential_energy())
    print co.get_forces()

This basic approach has served us for more than a decade! Still, there are things about it that bug me. Most significantly is the arbitrary keyword args. We keep a list of legitimate kwargs in the module, but there is no documentation or validation to go with them that is accessible to users (short of reading the code). There are well over 100 kwargs that are possible, so documenting them in the init docstring is not that useful (we did it once, see https://gitlab.com/ase/ase/blob/master/ase/calculators/jacapo/jacapo.py#L143 , and it made a really long docstring). Providing validation for these (some can only be integers, floats, specific strings, lists, or dictionaries) is not easy. I did this for another simulation code by providing validation functions that could be looked up dynamically by name. I never did come up with a way to provide kwarg specific documentation though.

The access to documentation while writing code is becoming increasingly important to me; I don't remember all the kwargs and what values are valid. More importantly, as I teach people how to use these tools, it is not practical to tell them to "read the code". I don't even want to do that while running simulations, I just want to setup the simulation and run it.

Today, I had an idea that a macro in hy would allow me to get documentation and validation of these kwargs.

The pseudocode would look like this. Each "kwarg" will actually be a function that has a docstring, performs validation, and evaluates to its argument. "vaspm" is a macro that will expand to the calculator with the desired kwargs. We will have to be careful that these function names don't conflict with other function names, but that could be addressed in a variety of ways with namespaces and function names.

;; pseudocode of the macro
(setv calc (vaspm "molecules/simple-co"
                  (xc "PBE")
                  (nbands 6)
                  (encut 350)
                  (ismear 1)
                  (sigma 0.01)
                  (atoms co)))

This would expand to the following block, which is equivalent to what we already do today. In the process of expansion though, we gain docstrings and validation!

(setv calc (Vasp "molecules/simple-co"
                 :xc "PBE"
                 :nbands 6
                 :encut 6
                 :ismear 1
                 :sigma 0.01
                 :atoms co))

Here is a toy implementation that illustrates what the functions are, and how we build up the code from the macro.

(defn encut [cutoff]
  "The planewave cutoff energy in eV."
  (assert (integer? cutoff))
  (assert (> cutoff 0))
  (print "encut validated")
  cutoff)

(defn xc [exc]
  "The exchange correlation functional. Should be a string of PBE or LDA."
  (assert (string? exc))
  (assert (in exc ["PBE" "LDA"]))
  (print "exc validated")
  exc)


(defclass Calculator []
  "Toy class representing a calculator."
  (defn __init__ [self wd &kwargs kwargs]
    (setattr self "wd" wd)
    (for [key kwargs]
      (setattr self key (get kwargs key)))))

We tangle that block to calculator.hy so we can reuse it. First we show the traditional syntax.

(import [calculator [*]])

(setv calc (Calculator "some-dir" :encut 400 :xc "PBE"))

(print calc.wd)
(print calc.encut)
(print calc.xc)
some-dir
400
PBE

Note, we can also do this, and get the validation too. It is verbose for my taste, but shows what we need the final code to look like, and incidentally how this would be done in Python too. We just need a macro that expands to this code.

(import [calculator [*]])

(setv calc (Calculator "some-dir" :encut (encut 400) :xc (xc "PBE")))

(print calc.wd)
(print calc.encut)
(print calc.xc)
encut validated
exc validated
some-dir
400
PBE

That is what this macro below does. We build up that code by making a keyword of the function name, and setting it to the value of the form the function is in.

(defmacro vaspm [wd &rest body]
  "Macro to build a Calculator with validation of arguments in BODY"
  (let [code `(Calculator ~wd)]
    (for [form body]
      (.append code (keyword (name (car form))))
      (.append code form))
    code))

Now, lets consider the macro version.

(import [calculator [*]])
(require calculator)

(setv calc (vaspm "some-dir" (encut 400) (xc "PBE")))
(print calc.wd)
(print calc.encut)
(print calc.xc)

;; proof we can get to the encut docstring!
(help encut)
encut validated
exc validated
some-dir
400
PBE
Help on function encut in module calculator:

encut(cutoff)
    The planewave cutoff energy in eV.

Sweet. The macro allows us to simplify our notation to be approximately the same as the original function, but with validation and docstring availability. Here is a variation of the macro that even uses keywords and builds the validation in from the keyword. It is not clear we can access the docstrings so easily here (ok, we can build an eldoc function that works either way, but the function method above is "more native").

(import [calculator [*]])
(require calculator)


(defmacro vasp2 [wd &rest kwargs]
  (let [code `(Calculator ~wd)]
    (for [x (range   0 (len kwargs) 2)]
      (let [kw (nth kwargs x)
            val (nth kwargs (+ 1 x))]
        (.append code kw)
        (.append code `(~(HySymbol (name kw)) ~val))))
    code))

(print (macroexpand '(vasp2 "/tmp" :encut 1 :xc "PBE")))

(setv calc (vasp2 "some-dir"
                  :encut 400
                  :xc "PBE"))
(print calc.wd)
(print calc.encut)
(print calc.xc)
(u'Calculator' u'/tmp' u'\ufdd0:encut' (u'encut' 1L) u'\ufdd0:xc' (u'xc' u'PBE'))
encut validated
exc validated
some-dir
400
PBE

To summarize here, we have looked at some ways to incorporate validation and documentation into kwargs. There are certainly ways to do this in Python, using these auxiliary functions. In fact we use them in hy too. We could build the validation into a Python init function too, using dynamic lookup of the function names, and evaluation of the functions. The macro features of hy give different opportunities for this, and different syntactical sugars to work with. The hy approach leads to less duplication (e.g. only a keyword, not a keyword and a function name that are the same), which will lead to fewer mistakes of the type xc=xd(something). Overall, interesting differences to contemplate.

From a developer point of view there is the burden of writing all the validation functions, but the payoff is access to documentation and optionally, validation. Also, no kwargs that are not allowed will work. Right now, with **kwargs, they might silently fail.

Copyright (C) 2016 by John Kitchin. See the License for information about copying.

org-mode source

Org-mode version = 8.2.10

Discuss on Twitter

Managing contexts - Python vs hy

| categories: python, hylang | tags:

A common pattern we have in running molecular simulations is to temporarily change to a new directory, do some stuff, and then change back to the directory, even if something goes wrong and an exception is raised. Here we examine several approaches to handling this in Python.

1 a try/except/finally approach

A way to handle this is with a try/except/finally block in Python. Here we illustrate the idea. Nothing fancy happens for the exception here, other than we do get back to the original directory before the program ends. There is nothing wrong with this, but it is not that reusable, and has a lot of places to make sure the right thing happens.

import os
print(os.getcwd())
try:
    cwd = os.getcwd()
    os.chdir('/tmp')
    f = open('some-file', 'w')
    f.write('5')
    print(os.getcwd())
    1 / 0
except:
    pass
finally:
    f.close()
    os.chdir(cwd)

print(os.getcwd())

print(open('/tmp/some-file').read())
/Users/jkitchin/Dropbox/python/hyve
/private/tmp
/Users/jkitchin/Dropbox/python/hyve
5

2 A Python context manager

A more sophisticated way to handle this in Python is a context manager. We create a context manager here called cd that does the same thing. The context manager is longer, but we would presumably put this in module and import it. This allows us to to the same thing in a lot less code afterwards, and to reuse this pattern. We also use the built in context manager for opening a file. This is for the most part a syntactical sugar for the code above.

import contextlib

@contextlib.contextmanager
def cd(wd):
    import os
    cwd = os.getcwd()
    print('Started in {}'.format(os.getcwd()))
    os.chdir(wd)
    print('Entered {}'.format(os.getcwd()))
    try:
        yield
    except:
        pass
    finally:
        os.chdir(cwd)
        print('Entered {}'.format(os.getcwd()))

##################################################################
with cd('/tmp'):
    with open('some-other-file', 'w') as f:
        f.write('5')
    1 / 0

print(open('/tmp/some-other-file').read())
Started in /Users/jkitchin/Dropbox/python/hyve
Entered /private/tmp
Entered /Users/jkitchin/Dropbox/python/hyve
5

3 A python decorator

Here is an example of doing something like this with a decorator. I don't do this too often, but this does more or less the same thing. It does eliminate a with statement and provide some context to do work in. The overall indentation is identical to the context manager we looked at previously because we have to wrap our code in a function to delay its execution, which we have to ask for with f(). A downside of this is f is always decorated now. I am not sure you can undecorate it.

import os

def cd(wd):
    def outer(func):
        def inner(*args):
            cwd = os.getcwd()
            print('Started in {}'.format(os.getcwd()))
            os.chdir(wd)
            print('entered {}'.format(os.getcwd()))
            try:
                return func(*args)
            except:
                pass
            finally:
                os.chdir(cwd)
                print('entered {}'.format(os.getcwd()))
        return inner
    return outer
##################################################################

@cd('/tmp')
def f():
    with open('decorated-file', 'w') as f:
        f.write("5")
    1 / 0

f()
print(open("/tmp/decorated-file").read())
Started in /Users/jkitchin/Dropbox/python/hyve
entered /private/tmp
entered /Users/jkitchin/Dropbox/python/hyve
5

4 A hy macro approach

hy gives us yet another option: a macro. We can use a macro to construct the context for us by building up the try/except/finally code we used above. The indentation used here is just for readability.

(defmacro cd [wd &rest body]
  `(do
    (import os)
    (print "started in " (os.getcwd))
    (let [cwd (os.getcwd)]
      (try
       (do (os.chdir ~wd)
           (print "entered " (os.getcwd))
           ~@body)
       (except [e Exception] nil)
       (finally
        (os.chdir cwd)
        (print "entered " (os.getcwd)))))))


;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(cd "/tmp"
    (with [f (open "some-hy-file" "w")]
          (.write f "5")
          (/ 1 0)))

(print (.read (open "/tmp/some-hy-file")))
started in  /Users/jkitchin/Dropbox/python/hyve
entered  /private/tmp
entered  /Users/jkitchin/Dropbox/python/hyve
5

The results are the same, even down to the reduced number of lines! But the mechanism that achieves that is different. In this example, we subtly changed the syntax that was possible, eliminating the need for one of the "with" statements. This is only possible with this kind of macro construction as far as I know. It still is not a game changer of programming, but does illustrate some new ways to think about writing these programs. It is not necessary to wrap the code into a function just to delay it from being executed.

Copyright (C) 2016 by John Kitchin. See the License for information about copying.

org-mode source

Org-mode version = 8.2.10

Discuss on Twitter

More hyjinks - space - the final frontier

| categories: unknown, hylang | tags:

Apologies in advance if you haven't come across the programming language ook before now. It kind of looks like you recorded an orangutan as a program. It cracks me up to look at it. You will not be a better person for learning about it here, or for what is about to follow. This would have been a great April Fool's joke.

Here is a typical ook program. The canonical one for any language. I run it in a Python ook interpreter. You might notice this program actually converts ook to its isomorphic relative and executes that program.

from ook import *

interpreter = BFInterpreter()

program = """Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook.
Ook. Ook! Ook? Ook! Ook! Ook. Ook? Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook.
Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook? Ook. Ook? Ook! Ook. Ook? Ook.
Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook.
Ook! Ook. Ook? Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook!
Ook? Ook! Ook! Ook. Ook? Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook.
Ook? Ook. Ook? Ook! Ook. Ook? Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook!
Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook.
Ook! Ook. Ook! Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook! Ook. Ook? Ook. Ook.
Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook.
Ook! Ook? Ook! Ook! Ook. Ook? Ook! Ook! Ook! Ook! Ook! Ook! Ook! Ook! Ook!
Ook! Ook! Ook! Ook! Ook! Ook! Ook! Ook? Ook. Ook? Ook! Ook. Ook? Ook! Ook!
Ook! Ook! Ook! Ook! Ook! Ook! Ook! Ook! Ook! Ook! Ook! Ook! Ook! Ook! Ook!
Ook! Ook! Ook! Ook! Ook! Ook! Ook! Ook! Ook! Ook! Ook! Ook! Ook! Ook! Ook.
Ook? Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook.
Ook. Ook! Ook? Ook! Ook! Ook. Ook? Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook.
Ook. Ook. Ook. Ook. Ook. Ook. Ook? Ook. Ook? Ook! Ook. Ook? Ook. Ook. Ook.
Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook! Ook. Ook? Ook. Ook. Ook.
Ook. Ook. Ook. Ook. Ook. Ook. Ook! Ook? Ook! Ook! Ook. Ook? Ook. Ook. Ook.
Ook. Ook. Ook. Ook. Ook. Ook? Ook. Ook? Ook! Ook. Ook? Ook. Ook. Ook. Ook.
Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook! Ook. Ook.
Ook. Ook. Ook. Ook. Ook. Ook! Ook. Ook! Ook! Ook! Ook! Ook! Ook! Ook! Ook!
Ook! Ook! Ook! Ook! Ook! Ook. Ook! Ook! Ook! Ook! Ook! Ook! Ook! Ook! Ook!
Ook! Ook! Ook! Ook! Ook! Ook! Ook! Ook! Ook. Ook? Ook. Ook. Ook. Ook. Ook.
Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook! Ook? Ook!
Ook! Ook. Ook? Ook! Ook! Ook! Ook! Ook! Ook! Ook! Ook! Ook! Ook! Ook! Ook!
Ook! Ook! Ook! Ook! Ook? Ook. Ook? Ook! Ook. Ook? Ook! Ook! Ook! Ook! Ook!
Ook! Ook! Ook. Ook? Ook."""

interpreter.run_commands(convertOokToBF(program))
Hello World!

Yes, that odd looking program prints "Hello World!". Ook has just three syntax elements (Ook. Ook? Ook!), which when combined in pairs lead to 8 commands. You can represent any text in ook, as well as programs. It is supposedly Turing complete.

Think it could not get worse? Prepare for disappointment. There are at least 8 different unicode spaces … You see where this is going. Yes, this is just a trivial substitution exercise. Go ahead and queue Disturbed -- Down with the Sickness . Put it on repeat.

Introducing: space - the final frontier. A place where noone should ever go. Yep. A whole, Turing complete language using different kinds of unicode spaces as the commands. This idea is so bad that the only other language similar to it was an April Fool's joke more than a decade ago (https://en.wikipedia.org/wiki/Whitespace_(programming_language) )! They stuck with the ascii whitespace characters. Anyone else who ever had this idea probably had the decency to keep it to themselves. Here is probably the one and only (and hopefully last) space program in existence.

(import [space [*]])

(space "                                                                                                                                                                                                             ")
Hello World!

That's right, a program of nothing but different kinds of spaces that actually does something. Sure space can do anything any other Turing complete language can do, with an infinite sized stack, and a Sysyphean patience and endurance, and a healthy dose of self-loathing. But why would you want to?

That program is hiding out in the open there. It is a special kind of noise. Like a modern ascii art interpreted in unicode spaces. Maybe these paintings really have hidden messages in them. Forget steganography to hide your messages. Just encode them in ook, convert them to space and put them out there. Who would even know they were there if they did not know to look. Even if they did look, what do they see? space. I am probably getting on an NSA list for that suggestion. Space is so useful. You could use a unicode space version of Morse code. Three short spaces regular space three long spaces regular space three short spaces, i.e. "           ". Those quotes are just to hint they are there. A cry for help. If a space is on the page and nobody sees it, is it really there? A space by any other name would look like … a space?

This hackery is actually just a trivial substitution on ook that translates each different space to the ook command it corresponds to and then runs it. I built it off of this Python ook intepreter , which translates the ook command to another program to run it. I implemented space in hy below. As bad as this idea is, I did not want to build a native space interpreter, just show the absurdity of it. Oh, and write a bit of hy code with dictionaries and comprehension. Sorry to drag that interesting project to this dark corner. Hy sure looks reasonable compared to this! Here is the thin layer.

(setv ook_lookup {(, "Ook." "Ook?") "\u2000"
                  (, "Ook?" "Ook.") "\u2001"
                  (, "Ook." "Ook.") "\u2002"
                  (, "Ook!" "Ook!") "\u2003"
                  (, "Ook!" "Ook.") "\u2004"
                  (, "Ook." "Ook!") "\u2005"
                  (, "Ook!" "Ook?") "\u2006"
                  (, "Ook?" "Ook!") "\u2007"}
      space_lookup (dict-comp v k [[k v] (.iteritems ook_lookup)]))

(defn ook2space [program]
  "Convert an ook PROGRAM to space syntax."
  (let [chars (.split (.replace program "\n" " ") " ")
        p1 (cut chars 0 nil 2)
        p2 (cut chars 1 nil 2)
        pairs (zip p1 p2)
        sp ""]
    (for [ch pairs] (+= sp (get ook_lookup ch)))
    sp))


(defn space2ook [program]
  "Convert a space PROGRAM to ook syntax."
  (.join " " (list-comp (.join " " (get space_lookup char)) [char program])))


(import [ook [*]])
(defn space [space_program]
  "Run a space program."
  (let [interpreter (BFInterpreter)
        bf_program (convertOokToBF (space2ook space_program))]
    (.run_commands interpreter bf_program)))

We can convert an ook program to a space program. I put the brackets in just for a visual boundary of the space program.

(import [space [*]])

(let [program "Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook.
Ook. Ook! Ook? Ook! Ook! Ook. Ook? Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook.
Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook? Ook. Ook? Ook! Ook. Ook? Ook.
Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook.
Ook! Ook. Ook? Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook!
Ook? Ook! Ook! Ook. Ook? Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook.
Ook? Ook. Ook? Ook! Ook. Ook? Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook!
Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook.
Ook! Ook. Ook! Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook! Ook. Ook? Ook. Ook.
Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook.
Ook! Ook? Ook! Ook! Ook. Ook? Ook! Ook! Ook! Ook! Ook! Ook! Ook! Ook! Ook!
Ook! Ook! Ook! Ook! Ook! Ook! Ook! Ook? Ook. Ook? Ook! Ook. Ook? Ook! Ook!
Ook! Ook! Ook! Ook! Ook! Ook! Ook! Ook! Ook! Ook! Ook! Ook! Ook! Ook! Ook!
Ook! Ook! Ook! Ook! Ook! Ook! Ook! Ook! Ook! Ook! Ook! Ook! Ook! Ook! Ook.
Ook? Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook.
Ook. Ook! Ook? Ook! Ook! Ook. Ook? Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook.
Ook. Ook. Ook. Ook. Ook. Ook. Ook? Ook. Ook? Ook! Ook. Ook? Ook. Ook. Ook.
Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook! Ook. Ook? Ook. Ook. Ook.
Ook. Ook. Ook. Ook. Ook. Ook. Ook! Ook? Ook! Ook! Ook. Ook? Ook. Ook. Ook.
Ook. Ook. Ook. Ook. Ook. Ook? Ook. Ook? Ook! Ook. Ook? Ook. Ook. Ook. Ook.
Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook! Ook. Ook.
Ook. Ook. Ook. Ook. Ook. Ook! Ook. Ook! Ook! Ook! Ook! Ook! Ook! Ook! Ook!
Ook! Ook! Ook! Ook! Ook! Ook. Ook! Ook! Ook! Ook! Ook! Ook! Ook! Ook! Ook!
Ook! Ook! Ook! Ook! Ook! Ook! Ook! Ook! Ook. Ook? Ook. Ook. Ook. Ook. Ook.
Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook! Ook? Ook!
Ook! Ook. Ook? Ook! Ook! Ook! Ook! Ook! Ook! Ook! Ook! Ook! Ook! Ook! Ook!
Ook! Ook! Ook! Ook! Ook? Ook. Ook? Ook! Ook. Ook? Ook! Ook! Ook! Ook! Ook!
Ook! Ook! Ook. Ook? Ook."]
 (print (.encode (+ "[" (ook2space program) "]") "utf-8")))
[                                                                                                                                                                                                             ]

Nothing but space here folks. Move along.

What did we learn today? Evidently I have strange ideas sometimes, and I am not alone in that. Just because you can do something, certainly doesn't mean you should. Sorry for this use of your time if you got here. Please come back again!

Copyright (C) 2016 by John Kitchin. See the License for information about copying.

org-mode source

Org-mode version = 8.2.10

Discuss on Twitter

Don't get hysterical - they are just unicode function names

| categories: hylang | tags:

Hy allows us to define functions with unicode names. Here we play around with this to define logical operators with the symbols you normally see in formal papers (i.e. the LaTeX symbols). I think in Python3 you can also define unicode names for functions. It definitely does not work for Python2.7 which we use here (although remarkably via Hy it does work).

First, we define the logical operators or, and, xor and not, and a few other interesting ones. In case it is not clear why a unicode representation of and is helpful, note there are three instances of the word and in the previous sentence, and only one is a logical operator! This is just some syntactical beauty, but it will shortly make for a different representation of code. We tangle this code block to logical_operators.hy.

(defn  [a b] (or a b))

(defn  [a b] (and a b))

(defn  [a b] (xor a b))

(defn ¬ [a] (not a))

(defn  [x func]
  "(func x) is True for every x."
  (every? func x))

(defn  [x func]
  "func(x) is true for at least one x."
  (some func x))

(defn ∃! [x func]
  "func(x) is true for exactly one x."
  (= 1 (len (list (filter func x)))))

Here is an example of using those operators. At the moment, we use the prefix notation of Lisp.

(import [logical_operators [*]])
(import [serialize [stringify]])

(defmacro show [body]
  `(do
    (print (.encode (.format "{0} = {1}" (stringify '~body) ~body) "utf-8"))))

(show (¬ True))
(show (∧ True False))
(show (∧ True True))
(show (∨ True False))
(show (∨ True True))
(show (⊕ True False))
(show (⊕ True True))
(show (⊕ False False))
(show (∀ [2 4 6] even?))
(show (∃ [2 3 4] odd?))
(show (∃! [2 3 4] odd?))
(show (∃! [2 3 5] odd?))
(¬ True) = False
(∧ True False) = False
(∧ True True) = True
(∨ True False) = True
(∨ True True) = True
(⊕ True False) = True
(⊕ True True) = False
(⊕ False False) = False
(∀ [2 4 6] is_even) = True
(∃ [2 3 4] is_odd) = True
(∃_bang [2 3 4] is_odd) = True
(∃_bang [2 3 5] is_odd) = False

Note the exclamation mark got expanded to _bang. It is evidently an ordinary ascii character.

We can get an infix notation if we use our infix module and the #$ reader macro defined in it. Here are some examples. Note it doesn't make sense to use this all time, e.g. it would even be a mistake to do this with the not operator.

(import [logical_operators [*]])
(import [serialize [stringify]])
(import [infix [*]])
(require infix)

(defmacro show [body]
  `(do
    (print (.encode (.format "{0} = {1}" (stringify '~body) ~body) "utf-8"))))

(show (¬ True))
(show #$(TrueFalse))
(show #$(TrueTrue))
(show #$(TrueFalse))
(show #$(TrueTrue))
(show #$(TrueFalse))
(show #$(TrueTrue))
(show #$(FalseFalse))
(¬ True) = False
(dispatch_reader_macro "$" (True ∧ False)) = False
(dispatch_reader_macro "$" (True ∧ True)) = True
(dispatch_reader_macro "$" (True ∨ False)) = True
(dispatch_reader_macro "$" (True ∨ True)) = True
(dispatch_reader_macro "$" (True ⊕ False)) = True
(dispatch_reader_macro "$" (True ⊕ True)) = False
(dispatch_reader_macro "$" (False ⊕ False)) = False

The show macro shows us how the reader macro gets expanded into, you guessed it, regular old function calls. They are just syntactical sugar to help us be more concise. The unicode symbols are not quite as simple to type as ascii names, but there are solutions to this: abbreviations in Emacs (http://ergoemacs.org/emacs/emacs_n_unicode.html ), custom commands, learn the keystrokes (http://www.johndcook.com/blog/emacs_unicode/ ) and C-x 8 RET and the unicode name, etc… Is it worth it? That might depend on how ingrained those logical symbols are in your mental model of your work. If it is deeply ingrained, your code will be better aligned with your thoughts, and easier to understand.

Copyright (C) 2016 by John Kitchin. See the License for information about copying.

org-mode source

Org-mode version = 8.2.10

Discuss on Twitter
Next Page »