{ "metadata": { "name": "", "signature": "sha256:3f2e5ccb28f3f520977398924acb4f544edad76dc6285d5004d40b0de2a441c5" }, "nbformat": 3, "nbformat_minor": 0, "worksheets": [ { "cells": [ { "cell_type": "heading", "level": 1, "metadata": {}, "source": [ "Exploring a Macro for Lispy Class Method Creation" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "In Hy, one usually creates a class using the following approach (Note that I prefer to use ``lambda`` rather than ``fn``. Hey you kids, get off my lawn!):" ] }, { "cell_type": "code", "collapsed": false, "input": [ "%%hylang\n", "\n", "(defclass ClassName []\n", " \"Docstring for ClassName class.\"\n", " [[--init--\n", " (lambda [self]\n", " None)]\n", "\n", " [set-x\n", " (lambda [self x]\n", " \"Set our x value\"\n", " (setv self.x x))]\n", "\n", " [get-x\n", " (lambda [self]\n", " \"Return our x value\"\n", " self.x)]])" ], "language": "python", "metadata": {}, "outputs": [], "prompt_number": 1 }, { "cell_type": "markdown", "metadata": {}, "source": [ "Which would then be used in the following manner:" ] }, { "cell_type": "code", "collapsed": false, "input": [ "%%hylang\n", "\n", "(def obj (ClassName))\n", "(obj.set-x \"Here's the 'x' value ...\")\n", "(obj.get-x)" ], "language": "python", "metadata": {}, "outputs": [ { "metadata": {}, "output_type": "pyout", "prompt_number": 2, "text": [ "\"Here's the 'x' value ...\"" ] } ], "prompt_number": 2 }, { "cell_type": "markdown", "metadata": {}, "source": [ "The class definition isn't very Lispy, though. Take a look at how [Common Lisp creates their version of methods on classes](http://www.gigamonkeys.com/book/object-reorientation-generic-functions.html) or [how Clojure does it](http://en.wikibooks.org/wiki/Clojure_Programming/Examples/API_Examples/Multimethod). It's got a very different feel (Clojure's apprach is different in many ways; I'm just talking about the syntax, not the underlying concepts or mechanics; this is, after all, an aesthetic exploration ...).\n", "\n", "In the tutorial notebook, we created a Linear Model class to neatly wrap a bunch of functionality. Here it is:" ] }, { "cell_type": "code", "collapsed": false, "input": [ "%%hylang\n", "\n", "(defclass PolynomialLinearModel []\n", " \"A pretty sweet utility Python-Lisp class for creating\n", " a polynomial curve fitting model\")\n", "\n", "(def PolynomialLinearModel.--init--\n", " (lambda [self x y degree]\n", " (setv self.x x)\n", " (setv self.y y)\n", " (setv self.degree degree)\n", " (setv self.results None)\n", " (setv self.model None)\n", " (setv [self.coeffs self.residuals self.rank self.singular-values self.rcond]\n", " [None None None None None])\n", " (self.polyfit)\n", " None))\n", "\n", "(def PolynomialLinearModel.get-y-mean\n", " (lambda [self]\n", " \"Get the mean value of the observed data\"\n", " (/ (np.sum self.y) self.y.size)))\n", "\n", "(def PolynomialLinearModel.get-ss-tot\n", " (lambda [self]\n", " \"Get total sum of the squares\"\n", " (np.sum (** (- self.y (self.get-y-mean)) 2))))\n", "\n", "(def PolynomialLinearModel.get-ss-reg\n", " (lambda [self]\n", " \"Get the regression sum of squares\"\n", " (np.sum (** (- self.y-predicted (self.get-y-mean)) 2))))\n", "\n", "(def PolynomialLinearModel.get-ss-res\n", " (lambda [self]\n", " \"Get the sum of squares of residuals\"\n", " (np.sum (** (- self.y self.y-predicted) 2))))\n", "\n", "(def PolynomialLinearModel.get-r-squared\n", " (lambda [self]\n", " \"Get the R^2 value for the polynomial fit\"\n", " (- 1 (/ (self.get-ss-res) (self.get-ss-tot)))))\n", "\n", "(def PolynomialLinearModel.polyfit\n", " (lambda [self]\n", " \"Do all the business\"\n", " (setv [self.coeffs self.residuals self.rank self.singular-values self.rcond]\n", " (apply np.polyfit [self.x self.y self.degree] {\"full\" true}))\n", " (setv self.model (np.poly1d self.coeffs))\n", " (setv self.y-predicted (self.model self.x))\n", " (setv self.r-squared (self.get-r-squared))\n", " (setv self.results {\n", " \"coeffs\" (self.coeffs.tolist)\n", " \"residuals\" (self.residuals.tolist)\n", " \"rank\" self.rank\n", " \"singular-values\" (self.singular-values.tolist)\n", " \"rcond\" self.rcond\n", " \"r-squared\" self.r-squared})))\n", "\n", "(def PolynomialLinearModel.--str--\n", " (lambda [self]\n", " \"Provide a string representation of the data\"\n", " (str self.results)))\n", "\n", "(def PolynomialLinearModel.--repr--\n", " (lambda [self]\n", " \"Provide a representation of the data\"\n", " (self.--str--)))\n", "\n", "(def PolynomialLinearModel.predict\n", " (lambda [self xs]\n", " \"Given a set of input values, produce outputs using the model\"\n", " (self.model xs)))" ], "language": "python", "metadata": {}, "outputs": [ { "metadata": {}, "output_type": "pyout", "prompt_number": 3, "text": [ "" ] } ], "prompt_number": 3 }, { "cell_type": "markdown", "metadata": {}, "source": [ "As you can see, this is quite different from the Hy standard (which is a reformulation of the Python approach). Here's the canonical example from the beginning of this notebook reformulated to use this approach:" ] }, { "cell_type": "code", "collapsed": false, "input": [ "%%hylang\n", "\n", "(defclass OtherClassName []\n", " \"Docstring for ClassName class.\")\n", "\n", "(def OtherClassName.--init--\n", " (lambda [self]\n", " None))\n", "\n", "(def OtherClassName.set-x\n", " (lambda [self x]\n", " \"Set our x value\"\n", " (setv self.x x)))\n", "\n", "(def OtherClassName.get-x\n", " (lambda [self]\n", " \"Return our x value\"\n", " self.x))" ], "language": "python", "metadata": {}, "outputs": [ { "metadata": {}, "output_type": "pyout", "prompt_number": 4, "text": [ "" ] } ], "prompt_number": 4 }, { "cell_type": "markdown", "metadata": {}, "source": [ "Or, formulated with ``defun`` instead of ``def`` + ``lambda``:" ] }, { "cell_type": "code", "collapsed": false, "input": [ "%%hylang\n", "\n", "(defclass AnotherClassName []\n", " \"Docstring for ClassName class.\")\n", "\n", "(defun AnotherClassName.--init-- [self]\n", " None)\n", "\n", "(defun AnotherClassName.set-x [self x]\n", " \"Set our x value\"\n", " (setv self.x x))\n", "\n", "(defun AnotherClassName.get-x [self]\n", " \"Return our x value\"\n", " self.x)" ], "language": "python", "metadata": {}, "outputs": [ { "metadata": {}, "output_type": "pyout", "prompt_number": 5, "text": [ "" ] } ], "prompt_number": 5 }, { "cell_type": "markdown", "metadata": {}, "source": [ "Let's confirm that this works as expected:" ] }, { "cell_type": "code", "collapsed": false, "input": [ "%%hylang\n", "\n", "(def obj (AnotherClassName))\n", "(obj.set-x \"Here's the 'x' value ...\")\n", "(obj.get-x)" ], "language": "python", "metadata": {}, "outputs": [ { "metadata": {}, "output_type": "pyout", "prompt_number": 6, "text": [ "\"Here's the 'x' value ...\"" ] } ], "prompt_number": 6 }, { "cell_type": "markdown", "metadata": {}, "source": [ "So, that's a little more lispy ... but here's what would be create:\n", " * not have to use the pre- and post-dashes on the constructor\n", " * not have to explicitly return ``None`` in the constructor\n", " * not have to explicitly pass ``self`` in the method args\n", "\n", "This would give us a form like the following:\n", "```cl\n", " \n", "(defclass ClassName []\n", " \"docstring\")\n", "\n", "(defmethod ClassName init []\n", " \"docstring\"\n", " (setv self.x None))\n", "\n", "(defmethod ClassName set-x [x]\n", " \"docstring\"\n", " (setv self.x x))\n", "\n", "(defmethod ClassName get-x []\n", " \"docstring\"\n", " self.x)\n", "```" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We've got some work to do before we get there, though. Here's a description of what we did above:\n", "\n", "```cl \n", "(defun class-name.method-name\n", " params body)\n", "```\n", "\n", "And for init method:\n", "\n", "```cl\n", "(def class-name.init\n", " params body None)\n", "```\n", " \n", " " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Let's create some helper functions for use in our macros:" ] }, { "cell_type": "code", "collapsed": false, "input": [ "%%hylang\n", "\n", "(defun has-docstring? [code]\n", " (if (and (> (len code) 4) (string? (get code 3)))\n", " True\n", " False))\n", "\n", "(defun get-class [code]\n", " (get code 0))\n", "\n", "(defun get-method [code]\n", " (let [[method-name (get code 1)]]\n", " (if (= method-name \"init\")\n", " \"--init--\"\n", " method-name)))\n", "\n", "(defun get-args [code]\n", " (get code 2))\n", "\n", "(defun get-docs [code]\n", " (if (has-docstring? code)\n", " (get code 3)\n", " \"\"))\n", "\n", "(defun get-body [code]\n", " (if (has-docstring? code)\n", " (slice code 4)\n", " (slice code 3)))\n", "\n", "(defun constructor? [code]\n", " (if (in (get-method code) [\"init\" \"--init--\" \"__init__\"])\n", " True\n", " False))" ], "language": "python", "metadata": {}, "outputs": [], "prompt_number": 7 }, { "cell_type": "markdown", "metadata": {}, "source": [ "Testing these:" ] }, { "cell_type": "code", "collapsed": false, "input": [ "%%hylang\n", "\n", "(def code1 [\"ClassName-1\" \"method-1\" [\"arg1-1\" \"arg2-1\"] \"docstring\" [\"code-body-1\"]])\n", "(def code2 [\"ClassName-2\" \"method-2\" [\"arg1-2\" \"arg2-2\"] [\"code-body-2\"]])\n", "(def code3 [\"ClassName-3\" \"init\" [] [\"code-body-3\"]])\n", "(def code4 [\"ClassName-4\" \"init\" [] [\"code1-4\" \"code2-4\" \"code3-4\"]])\n", "(def code5 [\"ClassName-5\" \"init\" [] [\"code1-5\"] [\"code2-5\"] [\"code3-5\"] [\"code4-5\"] [\"code5-5\"]])\n", "(def code6 [\"ClassName-6\" \"method-6\" [\"arg1-6\" \"arg2-6\"] \"returned string\"])\n", "[(get-class code1) (get-method code1) (get-args code1) (get-docs code1) (get-body code1)\n", " (get-class code2) (get-method code2) (get-args code2) (get-docs code2) (get-body code2)\n", " (get-class code3) (get-method code3) (get-args code3) (get-docs code3) (get-body code3)\n", " (get-class code4) (get-method code4) (get-args code4) (get-docs code4) (get-body code4)\n", " (get-class code5) (get-method code5) (get-args code5) (get-docs code5) (get-body code5)\n", " (get-class code6) (get-method code6) (get-args code6) (get-docs code6) (get-body code6)]" ], "language": "python", "metadata": {}, "outputs": [ { "metadata": {}, "output_type": "pyout", "prompt_number": 8, "text": [ "['ClassName-1',\n", " 'method-1',\n", " ['arg1-1', 'arg2-1'],\n", " 'docstring',\n", " [['code-body-1']],\n", " 'ClassName-2',\n", " 'method-2',\n", " ['arg1-2', 'arg2-2'],\n", " '',\n", " [['code-body-2']],\n", " 'ClassName-3',\n", " '--init--',\n", " [],\n", " '',\n", " [['code-body-3']],\n", " 'ClassName-4',\n", " '--init--',\n", " [],\n", " '',\n", " [['code1-4', 'code2-4', 'code3-4']],\n", " 'ClassName-5',\n", " '--init--',\n", " [],\n", " '',\n", " [['code1-5'], ['code2-5'], ['code3-5'], ['code4-5'], ['code5-5']],\n", " 'ClassName-6',\n", " 'method-6',\n", " ['arg1-6', 'arg2-6'],\n", " '',\n", " ['returned string']]" ] } ], "prompt_number": 8 }, { "cell_type": "markdown", "metadata": {}, "source": [ "Exploring possible macros:" ] }, { "cell_type": "code", "collapsed": false, "input": [ "%%hylang\n", "\n", "(defmacro defmethod-1 [&rest code]\n", " (let [[class-name (get-class code)]\n", " [method (get-method code)]\n", " [args (get-args code)]\n", " [docstring (get-docs code)]\n", " [body (get-body code)]]\n", " `(setattr ~class-name ~method\n", " (lambda [self] + ~args\n", " ~docstring\n", " (do ~@body)\n", " (if ~(constructor? method)\n", " None)))))\n", "\n", "(import [hy.models.list [HyList]]\n", " [hy.models.symbol [HySymbol]]\n", " [hy._compat [PY33 PY34]])\n", "\n", "(defmacro defmethod-2 [&rest code]\n", " (let [[class-name (get code 0)]\n", " [method (get code 1)]\n", " [args (get code 2)]\n", " [docstring \"a docstring\"]\n", " [body (slice code 3)]]\n", " `(setattr ~class-name '~method \"a\")))\n", "\n", "(defmacro defmethod-3 [&rest code]\n", " (let [[class-name (get code 0)]\n", " [method (get code 1)]\n", " [args (get code 2)]\n", " [docstring \"a docstring\"]\n", " [body (slice code 3)]]\n", " `(setattr ~class-name '~method\n", " (lambda ~args\n", " ~docstring\n", " (do ~@body)\n", " ~(if (in method [\"init\" \"--init--\" \"__init__\"])\n", " None)))))\n", "\n", "(defmacro defmethod [&rest code]\n", " (let [[class-name (get code 0)]\n", " [method (get code 1)]\n", " [args (map quote (get code 2))]\n", " [docstring \"a docstring\"]\n", " ;[body (slice code 3)]\n", " [body [1 2 3]]\n", " ]\n", " `(setattr ~class-name '~method\n", " (lambda [self x]\n", " \"wassup\"\n", " [~@args]))))" ], "language": "python", "metadata": {}, "outputs": [], "prompt_number": 9 }, { "cell_type": "markdown", "metadata": {}, "source": [ "Testing the macros:" ] }, { "cell_type": "code", "collapsed": false, "input": [ "%%hylang\n", "\n", "(defclass A []\n", " \"The A Class\")\n", "\n", "(defmethod A init [x]\n", " (setv self.x x))\n", "\n", "(defmethod A get-x []\n", " self.x)\n", "\n", "(def a (A \"apple\"))\n", "(a.get-x)" ], "language": "python", "metadata": {}, "outputs": [] } ], "metadata": {} } ] }