├── requirements.txt ├── .dockerignore ├── MANIFEST.in ├── hy_kernel ├── tests │ ├── __init__.py │ ├── capture.coffee │ ├── test_complete.coffee │ ├── test_basic.coffee │ ├── test_magic.coffee │ ├── test_notebook.py │ └── test_input.py ├── version.py ├── assets │ ├── logo-32x32.png │ ├── logo-64x64.png │ ├── kernel.json │ └── kernel.js ├── __init__.py ├── hy_setup.py └── hy_kernel.py ├── screenshot.png ├── requirements-test.txt ├── setup.cfg ├── docker-compose.yml ├── .gitignore ├── Dockerfile ├── .travis.yml ├── LICENSE ├── setup.py ├── notebooks ├── Automation.ipynb ├── Widgets.ipynb ├── Magics.ipynb └── Tutorial.ipynb └── README.md /requirements.txt: -------------------------------------------------------------------------------- 1 | hy>=0.11 2 | IPython>=3.0.0 3 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | .git 2 | .gitignore 3 | .vagrant 4 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include README.md 2 | recursive-include hy_kernel/assets * -------------------------------------------------------------------------------- /hy_kernel/tests/__init__.py: -------------------------------------------------------------------------------- 1 | ''' 2 | tests for the hy ipython kernel 3 | ''' 4 | -------------------------------------------------------------------------------- /screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bollwyvl/hy_kernel/HEAD/screenshot.png -------------------------------------------------------------------------------- /requirements-test.txt: -------------------------------------------------------------------------------- 1 | -r requirements.txt 2 | requests 3 | flake8 4 | nose 5 | coverage 6 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [nosetests] 2 | verbosity=3 3 | with-coverage=1 4 | cover-package=hy_kernel 5 | -------------------------------------------------------------------------------- /hy_kernel/version.py: -------------------------------------------------------------------------------- 1 | __version_info__ = (0, 3, 0) 2 | __version__ = "%d.%d.%d" % __version_info__ 3 | -------------------------------------------------------------------------------- /hy_kernel/assets/logo-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bollwyvl/hy_kernel/HEAD/hy_kernel/assets/logo-32x32.png -------------------------------------------------------------------------------- /hy_kernel/assets/logo-64x64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bollwyvl/hy_kernel/HEAD/hy_kernel/assets/logo-64x64.png -------------------------------------------------------------------------------- /hy_kernel/__init__.py: -------------------------------------------------------------------------------- 1 | # flake8: noqa 2 | from .hy_setup import setup_assets 3 | from .version import __version__, __version_info__ 4 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | hynotebook: 2 | build: . 3 | command: /notebook.sh 4 | environment: 5 | USE_HTTP: 1 6 | PASSWORD: "" 7 | ports: 8 | - "8888:8888" 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__ 2 | *.pyc 3 | build/ 4 | dist/ 5 | MANIFEST 6 | 7 | .ipynb_checkpoints/* 8 | */.ipynb_checkpoints/* 9 | *.egg-info 10 | *.xunit.xml 11 | hy-*.egg 12 | 13 | .coverage 14 | .eggs 15 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ipython/notebook 2 | MAINTAINER Nicholas Bollweg 3 | 4 | WORKDIR /opt/hylang/hy_kernel 5 | 6 | ADD ["requirements-test.txt", "requirements.txt", "/opt/hylang/hy_kernel/"] 7 | RUN pip install -r requirements-test.txt 8 | 9 | ADD . /opt/hylang/hy_kernel 10 | RUN python setup.py install 11 | 12 | CMD ["python", "setup.py", "nosetests"] 13 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | # http://travis-ci.org/#!/bollwyvl/hy_kernel 2 | language: python 3 | python: 4 | - 2.7 5 | - 3.4 6 | sudo: false 7 | cache: 8 | directories: 9 | - node_modules 10 | - $HOME/.cache/pip 11 | before_install: 12 | - npm install -g casperjs 13 | - phantomjs --version 14 | - casperjs --version 15 | install: 16 | - pip install -r requirements-test.txt 17 | - pip install IPython[notebook] 18 | - python setup.py install 19 | script: 20 | - flake8 setup.py hy_kernel 21 | - python setup.py nosetests 22 | -------------------------------------------------------------------------------- /hy_kernel/tests/capture.coffee: -------------------------------------------------------------------------------- 1 | # val = the value to pad 2 | # length = the length of the padded string 3 | # padChar = the character to use for padding. Defaults to '0' 4 | pad = (val, length, padChar = '0') -> 5 | val += '' 6 | numPads = length - val.length 7 | if (numPads > 0) then new Array(numPads + 1).join(padChar) + val else val 8 | 9 | module.exports = (casper, prefix="") -> 10 | _cid = 0 11 | capture = (name) -> 12 | casper.then -> 13 | @capture ["screenshots", prefix, "#{pad _cid++, 3}_#{name}.png"].join "/" 14 | -------------------------------------------------------------------------------- /hy_kernel/tests/test_complete.coffee: -------------------------------------------------------------------------------- 1 | casper.notebook_test -> 2 | cells = {} 3 | 4 | capture = require("./capture") @, "complete" 5 | 6 | @then -> 7 | @viewport 1024, 768, -> 8 | @evaluate -> IPython.kernelselector.set_kernel "hy" 9 | @wait_for_idle() 10 | 11 | @thenEvaluate -> 12 | IPython.notebook.insert_cell_at_index 0, "code" 13 | cell = IPython.notebook.get_cell 0 14 | cell.set_text "(unquote" 15 | cell.focus_editor() 16 | @then -> 17 | @page.sendEvent 'keypress', @page.event.key.End 18 | @page.sendEvent 'keypress', @page.event.key.Tab 19 | @wait 100 20 | @then -> 21 | @test.assertExists "#complete .introspection", "hy completions available" 22 | 23 | capture "complete" 24 | -------------------------------------------------------------------------------- /hy_kernel/hy_setup.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | join = os.path.join 4 | pkgroot = os.path.dirname(__file__) 5 | 6 | 7 | def setup_assets(user=False, ipython_dir=None): 8 | # Now write the kernelspec 9 | from IPython.kernel.kernelspec import ( 10 | KernelSpecManager, 11 | install_kernel_spec, 12 | ) 13 | 14 | if ipython_dir is None: 15 | install = install_kernel_spec 16 | else: 17 | ksm = KernelSpecManager(ipython_dir=ipython_dir) 18 | install = ksm.install_kernel_spec 19 | 20 | install( 21 | join(pkgroot, 'assets'), 22 | 'hy', 23 | replace=True, 24 | user=user 25 | ) 26 | 27 | 28 | if __name__ == '__main__': 29 | setup_assets() # pragma: no cover 30 | -------------------------------------------------------------------------------- /hy_kernel/assets/kernel.json: -------------------------------------------------------------------------------- 1 | { 2 | "argv": ["python", "-m", "hy_kernel.hy_kernel", "-f", "{connection_file}"], 3 | "display_name": "Hy", 4 | "language": "hy", 5 | "codemirror_mode": "hy", 6 | "language_info": { 7 | "name": "hy", 8 | "codemirror_mode": { 9 | "name": "hy" 10 | }, 11 | "mimetype": "text/x-hylang", 12 | "pygments_lexer": "ipython3" 13 | }, 14 | "help_links": [ 15 | { 16 | "text": "Hy Documentation", 17 | "link": "http://docs.hylang.org/" 18 | }, 19 | { 20 | "text": "Hy Google Group", 21 | "link": "https://groups.google.com/forum/#!forum/hylang-discuss" 22 | }, 23 | { 24 | "text": "Hy Github", 25 | "link": "https://github.com/hylang/hy" 26 | } 27 | ] 28 | } 29 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Permission is hereby granted, free of charge, to any person obtaining a 2 | copy of this software and associated documentation files (the "Software"), 3 | to deal in the Software without restriction, including without limitation 4 | the rights to use, copy, modify, merge, publish, distribute, sublicense, 5 | and/or sell copies of the Software, and to permit persons to whom the 6 | Software is furnished to do so, subject to the following conditions: 7 | 8 | The above copyright notice and this permission notice shall be included in 9 | all copies or substantial portions of the Software. 10 | 11 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 12 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 13 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 14 | THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 15 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 16 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 17 | DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /hy_kernel/tests/test_basic.coffee: -------------------------------------------------------------------------------- 1 | casper.notebook_test -> 2 | 3 | cells = {} 4 | 5 | capture = require("./capture") @, "basic" 6 | 7 | @then -> 8 | @viewport 1024, 768, -> 9 | @evaluate -> IPython.kernelselector.set_kernel "hy" 10 | @wait_for_idle() 11 | 12 | capture "hy_kernel" 13 | 14 | @then -> @execute_cell cells.addition = @append_cell "(+ 1 1)", "code" 15 | @wait_for_idle() 16 | 17 | capture "1_plus_1" 18 | 19 | @then -> 20 | @test.assertMatch @get_output_cell(cells.addition, 0).data["text/plain"], 21 | /^2L?$/, 22 | "adding 1 to 1 gives 2" 23 | 24 | @then -> 25 | @execute_cell cells.setx = @append_cell "(setv x 1)", "code" 26 | @then -> 27 | @execute_cell cells.printx = @append_cell "x", "code" 28 | 29 | @wait_for_idle() 30 | 31 | capture "setv_x_1" 32 | 33 | @then -> 34 | @test.assertMatch @get_output_cell(cells.printx , 0).data["text/plain"], 35 | /^1L?$/, 36 | "variables persist" 37 | 38 | @wait_for_idle() 39 | 40 | @then -> 41 | @execute_cell cells.usefor = @append_cell "(for [i (range 3)] (print i))", "code" 42 | 43 | @wait_for_idle() 44 | 45 | capture "use_for" 46 | 47 | @then -> 48 | @test.assertMatch @get_output_cell(cells.usefor, 0).text, 49 | /^0\n1\n2\n$/, 50 | "using `for` works" 51 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup, find_packages 2 | from setuptools.command.install import install as _install 3 | from setuptools.command.develop import develop as _develop 4 | 5 | 6 | # should be loaded below 7 | __version__ = None 8 | 9 | with open('hy_kernel/version.py') as version: 10 | exec(version.read()) 11 | 12 | 13 | with open('README.md') as f: 14 | readme = f.read() 15 | 16 | 17 | def proxy_cmd(_cmd): 18 | class Proxied(_cmd): 19 | def run(self): 20 | _cmd.run(self) 21 | from hy_kernel import setup_assets 22 | setup_assets(True) 23 | return Proxied 24 | 25 | 26 | setup( 27 | name='hy_kernel', 28 | version=__version__, 29 | description='A hy kernel for IPython', 30 | long_description=readme, 31 | author='Nicholas Bollweg', 32 | author_email='nick.bollweg@gmail.com', 33 | url='https://github.com/bollwyvl/hy_kernel', 34 | packages=find_packages(exclude=('tests', 'notebooks')), 35 | include_package_data=True, 36 | install_requires=[ 37 | 'IPython>=3.0.0', 38 | 'hy>=0.10.1', 39 | ], 40 | tests_require=[ 41 | 'coverage', 42 | 'nose', 43 | ], 44 | cmdclass={ 45 | 'install': proxy_cmd(_install), 46 | 'develop': proxy_cmd(_develop) 47 | }, 48 | classifiers=[ 49 | 'Framework :: IPython', 50 | 'License :: OSI Approved :: BSD License', 51 | 'Programming Language :: Python :: 3', 52 | 'Programming Language :: Python :: 2', 53 | 'Programming Language :: Lisp', 54 | ], 55 | test_suite='nose.collector', 56 | ) 57 | -------------------------------------------------------------------------------- /hy_kernel/tests/test_magic.coffee: -------------------------------------------------------------------------------- 1 | casper.notebook_test -> 2 | 3 | cells = {} 4 | 5 | capture = require("./capture") @, "magic" 6 | 7 | @then -> 8 | @viewport 1024, 768, -> 9 | @evaluate -> IPython.kernelselector.set_kernel "hy" 10 | @wait_for_idle() 11 | 12 | # cell magic that compiles 13 | @then -> 14 | @execute_cell cells.hymagic = @append_cell """ 15 | %%timeit 16 | (+ 1 1) 17 | """, "code" 18 | 19 | @wait_for_idle() 20 | 21 | capture "cell-magic-er" 22 | 23 | @then -> 24 | @test.assertMatch @get_output_cell(cells.hymagic, 0).text, 25 | /loops/, 26 | "a cell magic that compiles hy works" 27 | 28 | 29 | # cell magic that doesn't compile 30 | @then -> 31 | @execute_cell cells.plaincell = @append_cell """ 32 | %%%HTML 33 |

Magic!

34 | """, "code" 35 | 36 | @wait_for_idle() 37 | 38 | capture "cell-plain" 39 | 40 | @then -> 41 | @test.assertSelectorHasText "h1", "Magic!", 42 | "a cell magic that doesn't compile hy works" 43 | 44 | 45 | # ! line magic 46 | @then -> 47 | @execute_cell cells.bangline = @append_cell """ 48 | !ls . 49 | """, "code" 50 | 51 | capture "line-bang" 52 | 53 | @then -> 54 | @test.assertMatch @get_output_cell(cells.bangline, 0).text, 55 | /Untitled/, 56 | "bang line magic works" 57 | 58 | 59 | # % line magic 60 | @then -> 61 | @execute_cell cells.percline = @append_cell """ 62 | %ls . 63 | """, "code" 64 | 65 | @wait_for_idle() 66 | 67 | capture "line-perc" 68 | 69 | @then -> 70 | @test.assertMatch @get_output_cell(cells.percline, 0).text, 71 | /Untitled/, 72 | "percent line magic works" 73 | -------------------------------------------------------------------------------- /hy_kernel/tests/test_notebook.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Test the hy kernel with JavaScript (well, CoffeeScript) tests 3 | ''' 4 | import os 5 | import sys 6 | 7 | from glob import glob 8 | 9 | from hy_kernel import setup_assets 10 | 11 | from IPython.testing import iptestcontroller 12 | 13 | 14 | join = os.path.join 15 | 16 | TEST_ROOT = os.path.dirname(__file__) 17 | 18 | TESTS = glob(join(TEST_ROOT, 'test_*.coffee')) 19 | 20 | 21 | class JSController(iptestcontroller.JSController): 22 | ''' 23 | Maybe not the best way to run a specific set of tests... 24 | ''' 25 | def __init__(self, *args, **kwargs): 26 | ''' 27 | Create new test runner. 28 | ''' 29 | super(JSController, self).__init__(*args, **kwargs) 30 | # get the test dir for utils 31 | ip_test_dir = iptestcontroller.get_js_test_dir() 32 | 33 | extras = [ 34 | '--includes=%s' % join(ip_test_dir, 'util.js'), 35 | '--engine=%s' % self.engine 36 | ] 37 | 38 | self.cmd = ['casperjs', 'test'] + extras + TESTS 39 | 40 | def setup(self): 41 | ''' 42 | let the super set up the temporary ipython dir, then install assets 43 | ''' 44 | super(JSController, self).setup() 45 | # install the assets 46 | setup_assets(user=True, ipython_dir=self.ipydir.name) 47 | 48 | 49 | def test_notebook(): 50 | ''' 51 | Test with PhantomJS 52 | ''' 53 | controller = JSController('hy') 54 | exitcode = 1 55 | try: 56 | controller.setup() 57 | controller.launch(buffer_output=False) 58 | exitcode = controller.wait() 59 | except Exception as err: 60 | print(err) 61 | exitcode = 1 62 | finally: 63 | controller.cleanup() 64 | assert exitcode == 0 65 | 66 | if __name__ == '__main__': 67 | sys.exit(test_notebook()) 68 | -------------------------------------------------------------------------------- /notebooks/Automation.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Automation\n", 8 | "This IPython Notebook is what runs all of the automation for publishing the Hy kernel." 9 | ] 10 | }, 11 | { 12 | "cell_type": "markdown", 13 | "metadata": {}, 14 | "source": [ 15 | "## Run Tests" 16 | ] 17 | }, 18 | { 19 | "cell_type": "code", 20 | "execution_count": null, 21 | "metadata": { 22 | "collapsed": false 23 | }, 24 | "outputs": [], 25 | "source": [ 26 | "!cd .. && python setup.py nosetests" 27 | ] 28 | }, 29 | { 30 | "cell_type": "markdown", 31 | "metadata": {}, 32 | "source": [ 33 | "## Publish" 34 | ] 35 | }, 36 | { 37 | "cell_type": "markdown", 38 | "metadata": {}, 39 | "source": [ 40 | "## Git Hooks\n", 41 | "Expecting something like this:\n", 42 | "```bash\n", 43 | "#!/bin/bash\n", 44 | "set -e\n", 45 | "flake8 hy_kernel/*.py setup.py && python setup.py nosetests \n", 46 | "```" 47 | ] 48 | }, 49 | { 50 | "cell_type": "markdown", 51 | "metadata": {}, 52 | "source": [ 53 | "# PyPi: release dry run" 54 | ] 55 | }, 56 | { 57 | "cell_type": "code", 58 | "execution_count": null, 59 | "metadata": { 60 | "collapsed": false 61 | }, 62 | "outputs": [], 63 | "source": [ 64 | "!cd .. && python setup.py register -r pypitest\n", 65 | "!cd .. && python setup.py sdist\n", 66 | "!cd .. && python setup.py sdist upload -r pypitest" 67 | ] 68 | }, 69 | { 70 | "cell_type": "markdown", 71 | "metadata": {}, 72 | "source": [ 73 | "# PyPi: release" 74 | ] 75 | }, 76 | { 77 | "cell_type": "code", 78 | "execution_count": null, 79 | "metadata": { 80 | "collapsed": true 81 | }, 82 | "outputs": [], 83 | "source": [ 84 | "!cd .. && python setup.py register -r pypi\n", 85 | "!cd .. && python setup.py sdist\n", 86 | "!cd .. && python setup.py sdist upload -r pypi" 87 | ] 88 | } 89 | ], 90 | "metadata": { 91 | "kernelspec": { 92 | "display_name": "Hy", 93 | "language": "hy", 94 | "name": "hy" 95 | }, 96 | "language_info": { 97 | "codemirror_mode": { 98 | "name": "hy" 99 | }, 100 | "mimetype": "text/x-hylang", 101 | "name": "hy", 102 | "pygments_lexer": "ipython3" 103 | } 104 | }, 105 | "nbformat": 4, 106 | "nbformat_minor": 0 107 | } 108 | -------------------------------------------------------------------------------- /notebooks/Widgets.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "metadata": { 3 | "kernelspec": { 4 | "codemirror_mode": "hy", 5 | "display_name": "Hy", 6 | "language": "hy", 7 | "name": "hy" 8 | }, 9 | "name": "", 10 | "signature": "sha256:7a6d88c43b04613de1cd9f351f61af756fcb3cc42ea4db2845259c65da041264" 11 | }, 12 | "nbformat": 3, 13 | "nbformat_minor": 0, 14 | "worksheets": [ 15 | { 16 | "cells": [ 17 | { 18 | "cell_type": "markdown", 19 | "metadata": {}, 20 | "source": [ 21 | "# Widgets\n", 22 | "One of the best new features of the IPython Notebook is its rich [widget system](http://nbviewer.ipython.org/github/ipython/ipython/blob/master/examples/Interactive%20Widgets/Index.ipynb). `hy_kernel` can directly use all of the IPython widgets.\n", 23 | "> _NOTE: the IPython semantics of displaying a widget by having it be the last line of a cell doesn't work. Looking into this! In the meantime, use `IPython.display.display`!_" 24 | ] 25 | }, 26 | { 27 | "cell_type": "code", 28 | "collapsed": false, 29 | "input": [ 30 | "(import [IPython.html [widgets]]\n", 31 | " [IPython.display [display]]\n", 32 | " [IPython.utils [traitlets]])" 33 | ], 34 | "language": "python", 35 | "metadata": {}, 36 | "outputs": [], 37 | "prompt_number": 1 38 | }, 39 | { 40 | "cell_type": "markdown", 41 | "metadata": {}, 42 | "source": [ 43 | "The simplest, albeit pointless, invocation." 44 | ] 45 | }, 46 | { 47 | "cell_type": "code", 48 | "collapsed": false, 49 | "input": [ 50 | "(widgets.IntText)" 51 | ], 52 | "language": "python", 53 | "metadata": {}, 54 | "outputs": [], 55 | "prompt_number": 2 56 | }, 57 | { 58 | "cell_type": "markdown", 59 | "metadata": {}, 60 | "source": [ 61 | "Naming a widget." 62 | ] 63 | }, 64 | { 65 | "cell_type": "code", 66 | "collapsed": false, 67 | "input": [ 68 | "(setv x (widgets.IntText))\n", 69 | "(display x)" 70 | ], 71 | "language": "python", 72 | "metadata": {}, 73 | "outputs": [], 74 | "prompt_number": 3 75 | }, 76 | { 77 | "cell_type": "markdown", 78 | "metadata": {}, 79 | "source": [ 80 | "The binding is bidrectional." 81 | ] 82 | }, 83 | { 84 | "cell_type": "code", 85 | "collapsed": false, 86 | "input": [ 87 | "(setv x.value 1)" 88 | ], 89 | "language": "python", 90 | "metadata": {}, 91 | "outputs": [ 92 | { 93 | "metadata": {}, 94 | "output_type": "pyout", 95 | "prompt_number": 4, 96 | "text": [ 97 | "1" 98 | ] 99 | } 100 | ], 101 | "prompt_number": 4 102 | }, 103 | { 104 | "cell_type": "markdown", 105 | "metadata": {}, 106 | "source": [ 107 | "Using `widgets.interact`, a simple GUI generator for functions." 108 | ] 109 | }, 110 | { 111 | "cell_type": "code", 112 | "collapsed": false, 113 | "input": [ 114 | "(apply widgets.interact\n", 115 | " [(fn [x y] (print (* x y)))]\n", 116 | " {\"x\" 1 \"y\" 1})" 117 | ], 118 | "language": "python", 119 | "metadata": {}, 120 | "outputs": [ 121 | { 122 | "output_type": "stream", 123 | "stream": "stdout", 124 | "text": [ 125 | "1\n" 126 | ] 127 | }, 128 | { 129 | "metadata": {}, 130 | "output_type": "pyout", 131 | "prompt_number": 5, 132 | "text": [ 133 | "" 134 | ] 135 | } 136 | ], 137 | "prompt_number": 5 138 | }, 139 | { 140 | "cell_type": "markdown", 141 | "metadata": {}, 142 | "source": [ 143 | "Two linked widgets." 144 | ] 145 | }, 146 | { 147 | "cell_type": "code", 148 | "collapsed": false, 149 | "input": [ 150 | "(setv x (widgets.IntSlider))\n", 151 | "(setv y (widgets.IntSlider))\n", 152 | "(traitlets.link (, x \"value\") (, y \"value\"))\n", 153 | "(display x y)" 154 | ], 155 | "language": "python", 156 | "metadata": {}, 157 | "outputs": [], 158 | "prompt_number": 6 159 | }, 160 | { 161 | "cell_type": "markdown", 162 | "metadata": {}, 163 | "source": [ 164 | "A button." 165 | ] 166 | }, 167 | { 168 | "cell_type": "code", 169 | "collapsed": false, 170 | "input": [ 171 | "(setv btn (apply widgets.Button [] {\"description\" \"Click me\"}))\n", 172 | "(btn.on-click (fn [btn] (setv x.value (+ x.value 10))))\n", 173 | "(display btn)" 174 | ], 175 | "language": "python", 176 | "metadata": {}, 177 | "outputs": [], 178 | "prompt_number": 7 179 | } 180 | ], 181 | "metadata": {} 182 | } 183 | ] 184 | } -------------------------------------------------------------------------------- /hy_kernel/tests/test_input.py: -------------------------------------------------------------------------------- 1 | """test the Hys Kernel""" 2 | 3 | # ---------------------------------------------------------------------------- 4 | # Copyright (C) 2013 The IPython Development Team 5 | # 6 | # Distributed under the terms of the BSD License. The full license is in 7 | # the file COPYING, distributed as part of this software. 8 | # ---------------------------------------------------------------------------- 9 | 10 | # ---------------------------------------------------------------------------- 11 | # Imports 12 | # ---------------------------------------------------------------------------- 13 | import os 14 | import shutil 15 | import tempfile 16 | 17 | from contextlib import contextmanager 18 | from subprocess import PIPE 19 | 20 | import nose.tools as nt 21 | 22 | from IPython.kernel import KernelManager 23 | from IPython.kernel.tests.test_message_spec import execute, flush_channels 24 | from IPython.utils import path, py3compat 25 | 26 | from hy_kernel import setup_assets 27 | 28 | # ---------------------------------------------------------------------------- 29 | # Tests 30 | # ---------------------------------------------------------------------------- 31 | IPYTHONDIR = None 32 | save_env = None 33 | save_get_ipython_dir = None 34 | 35 | STARTUP_TIMEOUT = 60 36 | TIMEOUT = 15 37 | 38 | 39 | def setup(): 40 | """setup temporary IPYTHONDIR for tests""" 41 | global IPYTHONDIR 42 | global save_env 43 | global save_get_ipython_dir 44 | 45 | IPYTHONDIR = tempfile.mkdtemp() 46 | 47 | save_env = os.environ.copy() 48 | os.environ["IPYTHONDIR"] = IPYTHONDIR 49 | 50 | save_get_ipython_dir = path.get_ipython_dir 51 | path.get_ipython_dir = lambda: IPYTHONDIR 52 | 53 | setup_assets(user=True) 54 | 55 | 56 | def teardown(): 57 | path.get_ipython_dir = save_get_ipython_dir 58 | os.environ = save_env 59 | 60 | try: 61 | shutil.rmtree(IPYTHONDIR) 62 | except (OSError, IOError): 63 | # no such file 64 | pass 65 | 66 | 67 | @contextmanager 68 | def new_kernel(): 69 | """start a kernel in a subprocess, and wait for it to be ready 70 | 71 | Returns 72 | ------- 73 | kernel_manager: connected KernelManager instance 74 | """ 75 | KM = KernelManager(kernel_name="hy") 76 | 77 | KM.start_kernel(stdout=PIPE, stderr=PIPE) 78 | KC = KM.client() 79 | KC.start_channels() 80 | 81 | # wait for kernel to be ready 82 | KC.execute("import sys") 83 | KC.shell_channel.get_msg(block=True, timeout=STARTUP_TIMEOUT) 84 | flush_channels(KC) 85 | try: 86 | yield KC 87 | finally: 88 | KC.stop_channels() 89 | KM.shutdown_kernel() 90 | 91 | 92 | def assemble_output(iopub): 93 | """assemble stdout/err from an execution""" 94 | stdout = '' 95 | stderr = '' 96 | while True: 97 | msg = iopub.get_msg(block=True, timeout=1) 98 | msg_type = msg['msg_type'] 99 | content = msg['content'] 100 | if msg_type == 'status' and content['execution_state'] == 'idle': 101 | # idle message signals end of output 102 | break 103 | elif msg['msg_type'] == 'stream': 104 | if content['name'] == 'stdout': 105 | stdout = stdout + content['text'] 106 | elif content['name'] == 'stderr': 107 | stderr = stderr + content['text'] 108 | else: 109 | raise KeyError("bad stream: %r" % content['name']) 110 | else: 111 | # other output, ignored 112 | pass 113 | return stdout, stderr 114 | 115 | 116 | def _check_mp_mode(kc, expected=False, stream="stdout"): 117 | execute(kc=kc, code="import sys") 118 | flush_channels(kc) 119 | msg_id, content = execute( 120 | kc=kc, 121 | code="print (sys.%s._check_mp_mode())" % stream 122 | ) 123 | stdout, stderr = assemble_output(kc.iopub_channel) 124 | nt.assert_equal(eval(stdout.strip()), expected) 125 | 126 | 127 | # printing tests 128 | 129 | def test_raw_input(): 130 | """test [raw_]input""" 131 | with new_kernel() as kc: 132 | iopub = kc.iopub_channel 133 | 134 | input_f = "input" if py3compat.PY3 else "raw_input" 135 | theprompt = "prompt> " 136 | code = '(print ({input_f} "{theprompt}"))'.format(**locals()) 137 | msg_id = kc.execute(code, allow_stdin=True) 138 | msg = kc.get_stdin_msg(block=True, timeout=TIMEOUT) 139 | nt.assert_equal(msg['header']['msg_type'], u'input_request') 140 | content = msg['content'] 141 | nt.assert_equal(content['prompt'], theprompt) 142 | text = "some text" 143 | kc.input(text) 144 | reply = kc.get_shell_msg(block=True, timeout=TIMEOUT) 145 | nt.assert_equal(reply['content']['status'], 'ok') 146 | stdout, stderr = assemble_output(iopub) 147 | nt.assert_equal(stdout, text + "\n") 148 | -------------------------------------------------------------------------------- /hy_kernel/hy_kernel.py: -------------------------------------------------------------------------------- 1 | ''' 2 | A simple Hy (hylang) kernel for IPython. 3 | ''' 4 | from __future__ import print_function 5 | 6 | import __future__ # NOQA 7 | 8 | import ast 9 | import re 10 | 11 | from IPython.kernel.zmq.ipkernel import IPythonKernel 12 | from IPython.utils.py3compat import PY3 13 | 14 | import astor 15 | 16 | from hy.version import __version__ as hy_version 17 | 18 | from hy.macros import _hy_macros, load_macros 19 | 20 | from hy.lex import tokenize 21 | from hy.compiler import hy_compile, _compile_table, load_stdlib 22 | from hy.core import language 23 | 24 | from .version import __version__ 25 | 26 | CELL_MAGIC_RAW = r'^%%%' 27 | 28 | 29 | class HyKernel(IPythonKernel): 30 | ''' 31 | This may not be the recommended way to create a kernel, but seems to bring 32 | the most features along for free. 33 | 34 | Seeking a better solution! 35 | ''' 36 | implementation = 'hy' 37 | implementation_version = __version__ 38 | language = 'hy' 39 | language_version = hy_version 40 | banner = 'Hy is a wonderful dialect of Lisp that’s embedded in Python.' 41 | language_info = { 42 | 'name': 'hy', 43 | 'mimetype': 'text/x-hylang', 44 | 'codemirror_mode': { 45 | 'name': 'hy' 46 | }, 47 | # TODO: port CM to pygments? 48 | 'pygments_lexer': 'ipython3' 49 | } 50 | 51 | def __init__(self, *args, **kwargs): 52 | ''' 53 | Create the hy environment 54 | ''' 55 | super(HyKernel, self).__init__(*args, **kwargs) 56 | load_stdlib() 57 | [load_macros(m) for m in ['hy.core', 'hy.macros']] 58 | 59 | self._cell_magic_warned = False 60 | self._line_magic_warned = False 61 | 62 | def _forward_input(self, *args, **kwargs): 63 | """Forward raw_input and getpass to the current frontend. 64 | 65 | via input_request 66 | """ 67 | super(HyKernel, self)._forward_input(*args, **kwargs) 68 | 69 | if PY3: 70 | language.input = self.raw_input 71 | else: 72 | language.raw_input = self.raw_input 73 | language.input = lambda prompt='': eval(self.raw_input(prompt)) 74 | 75 | def do_execute(self, code, *args, **kwargs): 76 | ''' 77 | Generate python code, and have IPythonKernel run it, or show why we 78 | couldn't have python code. 79 | ''' 80 | 81 | try: 82 | if re.match(CELL_MAGIC_RAW, code): 83 | # this is a none-code magic cell 84 | pass 85 | else: 86 | cell = [] 87 | chunk = [] 88 | 89 | for line in code.split("\n"): 90 | if line[:2] == "%%": 91 | # cell magic 92 | cell.append(line) 93 | elif line[0] in "!%": 94 | # line magic 95 | if chunk: 96 | cell.append(self.compile_chunk(chunk)) 97 | chunk = [] 98 | cell.append(line) 99 | else: 100 | chunk.append(line) 101 | if chunk: 102 | cell.append(self.compile_chunk(chunk)) 103 | code = "\n".join(cell) 104 | except Exception as err: 105 | if (not hasattr(err, 'source')) or not err.source: 106 | err.source = code 107 | # shell will find the last exception 108 | self.shell.showsyntaxerror() 109 | # an empty code cell is basically a no-op 110 | code = '' 111 | return super(HyKernel, self).do_execute(code, *args, **kwargs) 112 | 113 | def compile_chunk(self, chunk): 114 | tokens = tokenize("\n".join(chunk)) 115 | _ast = hy_compile(tokens, '__console__', root=ast.Interactive) 116 | _ast_for_print = ast.Module() 117 | _ast_for_print.body = _ast.body 118 | return astor.codegen.to_source(_ast_for_print) 119 | 120 | def do_complete(self, code, cursor_pos): 121 | # let IPython do the heavy lifting for variables, etc. 122 | txt, matches = self.shell.complete('', code, cursor_pos) 123 | 124 | # mangle underscores into dashes 125 | matches = [match.replace('_', '-') for match in matches] 126 | 127 | for p in list(_hy_macros.values()) + [_compile_table]: 128 | p = filter(lambda x: isinstance(x, str), p.keys()) 129 | p = [x.replace('_', '-') for x in p] 130 | matches.extend([ 131 | x for x in p 132 | if x.startswith(txt) and x not in matches 133 | ]) 134 | 135 | return { 136 | 'matches': matches, 137 | 'cursor_end': cursor_pos, 138 | 'cursor_start': cursor_pos - len(txt), 139 | 'metadata': {}, 140 | 'status': 'ok' 141 | } 142 | 143 | 144 | if __name__ == '__main__': 145 | from IPython.kernel.zmq.kernelapp import IPKernelApp 146 | IPKernelApp.launch_instance(kernel_class=HyKernel) 147 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # [hy_kernel][] 2 | 3 | [![build-badge][]][build] [![pypi-badge][]][pypi] 4 | 5 | A simple [Jupyter][] kernel for [Hy](http://hylang.org), a pythonic lisp. 6 | 7 | > BIG NEWS: check out [calysto_hy](https://github.com/Calysto/calysto_hy/blob/master/calysto_hy/kernel.py), the first-class hy kernel, maintained by the Calysto project. It's already more hy-like than this repo, and will soon be far better! 8 | 9 | [![](screenshot.png) _The Hy tutorial as a Jupyter Notebook_][tutorial] 10 | 11 | 12 | ## Features 13 | - basic REPL functionality 14 | - autocomplete with most special Hy constructs 15 | - syntax highlighting from [lighttable-hylang][] 16 | - [cell and line magics][magic] 17 | - [interactive widgets][widgets] 18 | - [pretty good tests][build] 19 | 20 | 21 | ## Installation 22 | 23 | 24 | ### pip 25 | ```shell 26 | pip install hy_kernel 27 | ``` 28 | 29 | 30 | ### docker 31 | You can try out Hy Kernel in Docker with [Docker Compose][docker-compose]: 32 | 33 | ```bash 34 | git clone https://github.com/bollwyvl/hy_kernel.git 35 | cd hy_kernel && docker-compose up 36 | ``` 37 | 38 | ## Execution 39 | To start the notebook in your directory of choice, with a running Hy kernel: 40 | 41 | ```console 42 | ipython console --kernel hy 43 | ``` 44 | 45 | Or the notebook web GUI: 46 | 47 | ```shell 48 | ipython notebook 49 | ``` 50 | 51 | Or: 52 | ```shell 53 | ipython qtconsole --kernel hy 54 | ``` 55 | 56 | Or: 57 | Your GUI might have a kernel selector: In the Web GUI it's in the 58 | upper-right-hand corner. Find it, and select `Hy` kernel from the kernel 59 | selector. 60 | 61 | ![IPython Kernel Selector][kernel-selector] 62 | 63 | 64 | ## Implementation 65 | This kernel subclasses [IPythonKernel][] directly, as opposed to using 66 | [KernelBase][], which would probably the correct thing to do. This works, but 67 | might be brittle. Each cell is run through [astor][], so you're actually 68 | seeing hy → ast → py → ast. While this probably incurs additional overhead, 69 | the benefits (free magics, widgets, all the history works) are just too great to 70 | give up. 71 | 72 | 73 | ## Limitations 74 | 75 | ### QtConsole 76 | A lot of things don't work quite right in the qt console, and this will not be 77 | supported to the same extent as the HTML notebook and terminal console. 78 | 79 | ### Operators 80 | 81 | _Issue #5_ 82 | 83 | Use of operators e.g. `*`, `+`, `/` as the left-most atom in an expression appears to 84 | work: 85 | ```hylang 86 | ;; works 87 | (+ 1 1) 88 | ``` 89 | 90 | Using operators as just about anything else doesn't: 91 | ```hylang 92 | ;; breaks 93 | (reduce + [1 2 3]) 94 | ``` 95 | 96 | #### Workaround 97 | Use the `operator` module: 98 | 99 | ```hylang 100 | (import (operator (mul add))) 101 | (reduce mul [1 2 3]) 102 | ``` 103 | 104 | This will probably need to be fixed upstream. 105 | 106 | 107 | ### Magic 108 | Cell and line magics are "supported", with the following caveats. 109 | 110 | #### "Inline" Line Magics 111 | 112 | _Issue #13_ 113 | 114 | Because we don't have much whitespace control over what gets compiled, and can't 115 | do dirty tricks with comments (the hy compiler strips them), inline/indented 116 | line magics are probably not going to work. 117 | 118 | ```hylang 119 | ;; breaks 120 | (if True ( 121 | !ls 122 | )) 123 | ``` 124 | 125 | #### Raw Magics 126 | Additionally, cell magics that should _not_ be parsed as Hy need to be _extra_- 127 | magiced, with `%%%`. This is because there is no way to know whether a 128 | particular magic expects python, or some other crazy thing e.g. html, ruby, 129 | a file... not that `%%file` works anyway (see #12). 130 | 131 | ```hylang 132 | %%html 133 |

This Breaks!

134 | ``` 135 | 136 | breaks, while 137 | 138 | ```hylang 139 | %%%html 140 |

This Works!

141 | ``` 142 | 143 | works. 144 | 145 | 146 | ## Collaboration 147 | Issues, pull requests, and forks are all supported and encouraged on the [Github 148 | repository][hy_kernel]. 149 | 150 | This [discussion on `hylang-discuss`][discuss] is also a good place to chime in. 151 | 152 | Additionally, the [Jupyter list][] can provide a larger perspective on how this 153 | stuff fits into the larger picture of interactive computing. 154 | 155 | [astor]: https://github.com/berkerpeksag/astor 156 | [build-badge]: https://travis-ci.org/bollwyvl/hy_kernel.svg 157 | [build]: https://travis-ci.org/bollwyvl/hy_kernel 158 | [discuss]: https://groups.google.com/forum/#!topic/hylang-discuss/UkoET6pU5sM 159 | [docker-compose]: https://docs.docker.com/compose/ 160 | [hy_kernel]: https://github.com/bollwyvl/hy_kernel 161 | [IPythonKernel]: https://github.com/ipython/ipython/blob/master/IPython/kernel/zmq/ipkernel.py 162 | [Jupyter]: http://jupyter.org 163 | [Jupyter list]: https://groups.google.com/forum/#!forum/jupyter 164 | [kernel-selector]: http://ipython.org/ipython-doc/dev/_images/kernel_selector_screenshot.png 165 | [KernelBase]: https://github.com/ipython/ipython/blob/master/IPython/kernel/zmq/kernelbase.py 166 | [lighttable-hylang]: https://github.com/cndreisbach/lighttable-hylang 167 | [magic]: notebooks/Magics.ipynb 168 | [pypi-badge]: https://img.shields.io/pypi/v/hy_kernel.svg 169 | [pypi]: https://pypi.python.org/pypi/hy_kernel/ 170 | [tutorial]: http://nbviewer.ipython.org/github/bollwyvl/hy_kernel/blob/master/notebooks/Tutorial.ipynb 171 | [widgets]: notebooks/Widgets.ipynb 172 | -------------------------------------------------------------------------------- /notebooks/Magics.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Magics\n", 8 | "[IPython magics](http://ipython.org/ipython-doc/dev/interactive/tutorial.html) do... interesting things. Listing them is beyond the scope of this document, but here are some examples, as well as [limitations](#Limitations)." 9 | ] 10 | }, 11 | { 12 | "cell_type": "markdown", 13 | "metadata": {}, 14 | "source": [ 15 | "## Cell magics" 16 | ] 17 | }, 18 | { 19 | "cell_type": "markdown", 20 | "metadata": {}, 21 | "source": [ 22 | "### ...that take Python" 23 | ] 24 | }, 25 | { 26 | "cell_type": "markdown", 27 | "metadata": {}, 28 | "source": [ 29 | "For example, `%%timeit` will display how long a cell takes to run, running an appropriately large number of samples." 30 | ] 31 | }, 32 | { 33 | "cell_type": "code", 34 | "execution_count": 1, 35 | "metadata": { 36 | "collapsed": false 37 | }, 38 | "outputs": [ 39 | { 40 | "name": "stdout", 41 | "output_type": "stream", 42 | "text": [ 43 | "100000000 loops, best of 3: 11.5 ns per loop\n" 44 | ] 45 | }, 46 | { 47 | "name": "stderr", 48 | "output_type": "stream", 49 | "text": [ 50 | "Magics with parameters are not supported\n", 51 | "If your cell magic doesn't take code, use %%%\n" 52 | ] 53 | } 54 | ], 55 | "source": [ 56 | "%%timeit\n", 57 | "(+ 1 1)" 58 | ] 59 | }, 60 | { 61 | "cell_type": "markdown", 62 | "metadata": {}, 63 | "source": [ 64 | "### ...that don't\n", 65 | "`%%html` will just jam some html out to the frontend. To indicate that it shouldn't be compiled as hy, throw in some more magic with another `%`:" 66 | ] 67 | }, 68 | { 69 | "cell_type": "code", 70 | "execution_count": 2, 71 | "metadata": { 72 | "collapsed": false 73 | }, 74 | "outputs": [ 75 | { 76 | "name": "stderr", 77 | "output_type": "stream", 78 | "text": [ 79 | "Magics with parameters are not supported\n" 80 | ] 81 | }, 82 | { 83 | "data": { 84 | "text/html": [ 85 | "" 89 | ], 90 | "text/plain": [ 91 | "" 92 | ] 93 | }, 94 | "metadata": {}, 95 | "output_type": "display_data" 96 | } 97 | ], 98 | "source": [ 99 | "%%%html\n", 100 | "
    \n", 101 | "
  • magic
  • \n", 102 | "
  • more magic
  • \n", 103 | "
" 104 | ] 105 | }, 106 | { 107 | "cell_type": "markdown", 108 | "metadata": {}, 109 | "source": [ 110 | "## Line Magics" 111 | ] 112 | }, 113 | { 114 | "cell_type": "markdown", 115 | "metadata": {}, 116 | "source": [ 117 | "`!` will run a shell command, and show the output." 118 | ] 119 | }, 120 | { 121 | "cell_type": "code", 122 | "execution_count": 3, 123 | "metadata": { 124 | "collapsed": false 125 | }, 126 | "outputs": [ 127 | { 128 | "name": "stdout", 129 | "output_type": "stream", 130 | "text": [ 131 | "Automation.ipynb Tutorial.ipynb Untitled.ipynb\r\n", 132 | "Magics.ipynb\t Untitled1.ipynb Widgets.ipynb\r\n" 133 | ] 134 | }, 135 | { 136 | "name": "stderr", 137 | "output_type": "stream", 138 | "text": [ 139 | "Line magics inside expressions or with args probably won't work\n" 140 | ] 141 | } 142 | ], 143 | "source": [ 144 | "!ls" 145 | ] 146 | }, 147 | { 148 | "cell_type": "markdown", 149 | "metadata": {}, 150 | "source": [ 151 | "## Limitations\n", 152 | "Magic arguments aren't supported yet. This means a lot of great stuff won't work, `%%file`, `%%run`." 153 | ] 154 | }, 155 | { 156 | "cell_type": "code", 157 | "execution_count": 4, 158 | "metadata": { 159 | "collapsed": false 160 | }, 161 | "outputs": [ 162 | { 163 | "ename": "NameError", 164 | "evalue": "name 'is_print' is not defined", 165 | "output_type": "error", 166 | "traceback": [ 167 | "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m", 168 | "\u001b[1;31mNameError\u001b[0m Traceback (most recent call last)", 169 | "\u001b[1;32m\u001b[0m in \u001b[0;36m\u001b[1;34m()\u001b[0m\n\u001b[1;32m----> 1\u001b[1;33m \u001b[0mis_print\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m", 170 | "\u001b[1;31mNameError\u001b[0m: name 'is_print' is not defined" 171 | ] 172 | } 173 | ], 174 | "source": [ 175 | "print?" 176 | ] 177 | } 178 | ], 179 | "metadata": { 180 | "kernelspec": { 181 | "display_name": "Hy", 182 | "language": "hy", 183 | "name": "hy" 184 | }, 185 | "language_info": { 186 | "codemirror_mode": { 187 | "name": "hy" 188 | }, 189 | "mimetype": "text/x-hylang", 190 | "name": "hy", 191 | "pygments_lexer": "ipython3" 192 | } 193 | }, 194 | "nbformat": 4, 195 | "nbformat_minor": 0 196 | } 197 | -------------------------------------------------------------------------------- /hy_kernel/assets/kernel.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Author: Clinton Dreisbach 3 | * Branched from CodeMirror's Clojure mode (by Hans Engel) 4 | * 5 | * Author: Hans Engel 6 | * Branched from CodeMirror's Scheme mode (by Koh Zi Han, based on implementation by Koh Zi Chun) 7 | * copied from https://github.com/cndreisbach/lighttable-hylang/blob/master/codemirror/hy.js 8 | */ 9 | define(["codemirror/lib/codemirror", "base/js/namespace"], 10 | function(CodeMirror, IPython){ 11 | 12 | return {onload: function(){ 13 | 14 | 15 | CodeMirror.defineMode("hy", function () { 16 | var BUILTIN = "builtin", COMMENT = "comment", STRING = "string", CHARACTER = "string-2", 17 | ATOM = "atom", NUMBER = "number", BRACKET = "bracket", KEYWORD = "keyword"; 18 | var INDENT_WORD_SKIP = 2; 19 | 20 | function makeKeywords(str) { 21 | var obj = {}, words = str.split(" "); 22 | for (var i = 0; i < words.length; ++i) obj[words[i]] = true; 23 | return obj; 24 | } 25 | 26 | var atoms = makeKeywords("true false nil"); 27 | 28 | var keywords = makeKeywords( 29 | "defn defn- def def- defonce defmulti defmethod defmacro defstruct deftype defprotocol defrecord defproject deftest slice defalias defhinted defmacro- defn-memo defnk defnk defonce- defunbound defunbound- defvar defvar- let letfn do case cond condp for loop recur when when-not when-let when-first if if-let if-not . .. -> ->> doto and or dosync doseq dotimes dorun doall load import unimport ns in-ns refer try catch finally throw with-open with-local-vars binding gen-class gen-and-load-class gen-and-save-class handler-case handle setv input raw-input with list-comp defclass"); 30 | 31 | var builtins = makeKeywords( 32 | "* *' *1 *2 *3 *agent* *allow-unresolved-vars* *assert* *clojure-version* *command-line-args* *compile-files* *compile-path* *compiler-options* *data-readers* *e *err* *file* *flush-on-newline* *fn-loader* *in* *math-context* *ns* *out* *print-dup* *print-length* *print-level* *print-meta* *print-readably* *read-eval* *source-path* *unchecked-math* *use-context-classloader* *verbose-defrecords* *warn-on-reflection* + +' - -' -> ->> ->ArrayChunk ->Vec ->VecNode ->VecSeq -cache-protocol-fn -reset-methods .. / < <= = == > >= EMPTY-NODE accessor aclone add-classpath add-watch agent agent-error agent-errors aget alength alias all-ns alter alter-meta! alter-var-root amap ancestors and apply areduce array-map aset aset-boolean aset-byte aset-char aset-double aset-float aset-int aset-long aset-short assert assoc assoc! assoc-in associative? atom await await-for await1 bases bean bigdec bigint biginteger binding bit-and bit-and-not bit-clear bit-flip bit-not bit-or bit-set bit-shift-left bit-shift-right bit-test bit-xor boolean boolean-array booleans bound-fn bound-fn* bound? butlast byte byte-array bytes case cast char char-array char-escape-string char-name-string char? chars chunk chunk-append chunk-buffer chunk-cons chunk-first chunk-next chunk-rest chunked-seq? class class? clear-agent-errors clojure-version coll? comment commute comp comparator compare compare-and-set! compile complement concat cond condp conj conj! cons constantly construct-proxy contains? count counted? create-ns create-struct cycle dec dec' decimal? declare default-data-readers definline definterface defmacro defmethod defmulti defn defn- defonce defprotocol defrecord defstruct deftype delay delay? deliver denominator deref derive descendants destructure disj disj! dissoc dissoc! distinct distinct? doall dorun doseq dosync dotimes doto double double-array doubles drop drop-last drop-while empty empty? ensure enumeration-seq error-handler error-mode eval even? every-pred every? ex-data ex-info extend extend-protocol extend-type extenders extends? false? ffirst file-seq filter filterv find find-keyword find-ns find-protocol-impl find-protocol-method find-var first flatten float float-array float? floats flush fn fn? fnext fnil for force format frequencies future future-call future-cancel future-cancelled? future-done? future? gen-class gen-interface gensym get get-in get-method get-proxy-class get-thread-bindings get-validator group-by hash hash-combine hash-map hash-set identical? identity if-let if-not ifn? import in-ns inc inc' init-proxy instance? int int-array integer? interleave intern interpose into into-array ints io! isa? iterate iterator-seq juxt keep keep-indexed key keys keyword keyword? last lazy-cat lazy-seq let letfn line-seq list list* list? load load-file load-reader load-string loaded-libs locking long long-array longs loop macroexpand macroexpand-1 make-array make-hierarchy map map-indexed map? mapcat mapv max max-key memfn memoize merge merge-with meta method-sig methods min min-key mod munge name namespace namespace-munge neg? newline next nfirst nil? nnext not not-any? not-empty not-every? not= ns ns-aliases ns-imports ns-interns ns-map ns-name ns-publics ns-refers ns-resolve ns-unalias ns-unmap nth nthnext nthrest num number? numerator object-array odd? or parents partial partition partition-all partition-by pcalls peek persistent! pmap pop pop! pop-thread-bindings pos? pr pr-str prefer-method prefers primitives-classnames print print-ctor print-dup print-method print-simple print-str printf println println-str prn prn-str promise proxy proxy-call-with-super proxy-mappings proxy-name proxy-super push-thread-bindings pvalues quot rand rand-int rand-nth range ratio? rational? rationalize re-find re-groups re-matcher re-matches re-pattern re-seq read read-line read-string realized? reduce reduce-kv reductions ref ref-history-count ref-max-history ref-min-history ref-set refer refer-clojure reify release-pending-sends rem remove remove-all-methods remove-method remove-ns remove-watch repeat repeatedly replace replicate require reset! reset-meta! resolve rest restart-agent resultset-seq reverse reversible? rseq rsubseq satisfies? second select-keys send send-off seq seq? seque sequence sequential? set set-error-handler! set-error-mode! set-validator! set? short short-array shorts shuffle shutdown-agents slurp some some-fn sort sort-by sorted-map sorted-map-by sorted-set sorted-set-by sorted? special-symbol? spit split-at split-with str string? struct struct-map subs subseq subvec supers swap! symbol symbol? sync take take-last take-nth take-while test the-ns thread-bound? time to-array to-array-2d trampoline transient tree-seq true? type unchecked-add unchecked-add-int unchecked-byte unchecked-char unchecked-dec unchecked-dec-int unchecked-divide-int unchecked-double unchecked-float unchecked-inc unchecked-inc-int unchecked-int unchecked-long unchecked-multiply unchecked-multiply-int unchecked-negate unchecked-negate-int unchecked-remainder-int unchecked-short unchecked-subtract unchecked-subtract-int underive unquote unquote-splicing update-in update-proxy use val vals var-get var-set var? vary-meta vec vector vector-of vector? when when-first when-let when-not while with-bindings with-bindings* with-in-str with-loading-context with-local-vars with-meta with-open with-out-str with-precision with-redefs with-redefs-fn xml-seq zero? zipmap *default-data-reader-fn* as-> cond-> cond->> reduced reduced? send-via set-agent-send-executor! set-agent-send-off-executor! some-> some->> pow"); 33 | 34 | var indentKeys = makeKeywords( 35 | // Built-ins 36 | "ns fn def defn defmethod bound-fn if if-not case condp when while when-not when-first do future comment doto locking proxy with-open with-precision reify deftype defrecord defprotocol extend extend-protocol extend-type try catch " + 37 | 38 | // Binding forms 39 | "let letfn binding loop for doseq dotimes when-let if-let " + 40 | 41 | // Data structures 42 | "defstruct struct-map assoc " + 43 | 44 | // clojure.test 45 | "testing deftest " + 46 | 47 | // contrib 48 | "handler-case handle dotrace deftrace"); 49 | 50 | var tests = { 51 | digit: /\d/, 52 | digit_or_colon: /[\d:]/, 53 | hex: /[0-9a-f]/i, 54 | sign: /[+-]/, 55 | exponent: /e/i, 56 | keyword_char: /[^\s\(\[\;\)\]]/, 57 | symbol: /[\w*+!\-\._?:\/]/ 58 | }; 59 | 60 | function stateStack(indent, type, prev) { // represents a state stack object 61 | this.indent = indent; 62 | this.type = type; 63 | this.prev = prev; 64 | } 65 | 66 | function pushStack(state, indent, type) { 67 | state.indentStack = new stateStack(indent, type, state.indentStack); 68 | } 69 | 70 | function popStack(state) { 71 | state.indentStack = state.indentStack.prev; 72 | } 73 | 74 | function isNumber(ch, stream){ 75 | // hex 76 | if ( ch === '0' && stream.eat(/x/i) ) { 77 | stream.eatWhile(tests.hex); 78 | return true; 79 | } 80 | 81 | // leading sign 82 | if ( ( ch == '+' || ch == '-' ) && ( tests.digit.test(stream.peek()) ) ) { 83 | stream.eat(tests.sign); 84 | ch = stream.next(); 85 | } 86 | 87 | if ( tests.digit.test(ch) ) { 88 | stream.eat(ch); 89 | stream.eatWhile(tests.digit); 90 | 91 | if ( '.' == stream.peek() ) { 92 | stream.eat('.'); 93 | stream.eatWhile(tests.digit); 94 | } 95 | 96 | if ( stream.eat(tests.exponent) ) { 97 | stream.eat(tests.sign); 98 | stream.eatWhile(tests.digit); 99 | } 100 | 101 | return true; 102 | } 103 | 104 | return false; 105 | } 106 | 107 | // Eat character that starts after backslash \ 108 | function eatCharacter(stream) { 109 | var first = stream.next(); 110 | // Read special literals: backspace, newline, space, return. 111 | // Just read all lowercase letters. 112 | if (first.match(/[a-z]/) && stream.match(/[a-z]+/, true)) { 113 | return; 114 | } 115 | // Read unicode character: \u1000 \uA0a1 116 | if (first === "u") { 117 | stream.match(/[0-9a-z]{4}/i, true); 118 | } 119 | } 120 | 121 | return { 122 | startState: function () { 123 | return { 124 | indentStack: null, 125 | indentation: 0, 126 | mode: false 127 | }; 128 | }, 129 | 130 | token: function (stream, state) { 131 | if (state.indentStack == null && stream.sol()) { 132 | // update indentation, but only if indentStack is empty 133 | state.indentation = stream.indentation(); 134 | } 135 | 136 | // skip spaces 137 | if (stream.eatSpace()) { 138 | return null; 139 | } 140 | var returnType = null; 141 | 142 | switch(state.mode){ 143 | case "string": // multi-line string parsing mode 144 | var next, escaped = false; 145 | while ((next = stream.next()) != null) { 146 | if (next == "\"" && !escaped) { 147 | 148 | state.mode = false; 149 | break; 150 | } 151 | escaped = !escaped && next == "\\"; 152 | } 153 | returnType = STRING; // continue on in string mode 154 | break; 155 | default: // default parsing mode 156 | var ch = stream.next(); 157 | 158 | if (ch == "\"") { 159 | state.mode = "string"; 160 | returnType = STRING; 161 | } else if (ch == "\\") { 162 | eatCharacter(stream); 163 | returnType = CHARACTER; 164 | } else if (ch == "'" && !( tests.digit_or_colon.test(stream.peek()) )) { 165 | returnType = ATOM; 166 | } else if (ch == ";") { // comment 167 | stream.skipToEnd(); // rest of the line is a comment 168 | returnType = COMMENT; 169 | } else if (isNumber(ch,stream)){ 170 | returnType = NUMBER; 171 | } else if (ch == "(" || ch == "[" || ch == "{" ) { 172 | var keyWord = '', indentTemp = stream.column(), letter; 173 | /** 174 | Either 175 | (indent-word .. 176 | (non-indent-word .. 177 | (;something else, bracket, etc. 178 | */ 179 | 180 | if (ch == "(") while ((letter = stream.eat(tests.keyword_char)) != null) { 181 | keyWord += letter; 182 | } 183 | 184 | if (keyWord.length > 0 && (indentKeys.propertyIsEnumerable(keyWord) || 185 | /^(?:def|with)/.test(keyWord))) { // indent-word 186 | pushStack(state, indentTemp + INDENT_WORD_SKIP, ch); 187 | } else { // non-indent word 188 | // we continue eating the spaces 189 | stream.eatSpace(); 190 | if (stream.eol() || stream.peek() == ";") { 191 | // nothing significant after 192 | // we restart indentation 1 space after 193 | pushStack(state, indentTemp + 1, ch); 194 | } else { 195 | pushStack(state, indentTemp + stream.current().length, ch); // else we match 196 | } 197 | } 198 | stream.backUp(stream.current().length - 1); // undo all the eating 199 | 200 | returnType = BRACKET; 201 | } else if (ch == ")" || ch == "]" || ch == "}") { 202 | returnType = BRACKET; 203 | if (state.indentStack != null && state.indentStack.type == (ch == ")" ? "(" : (ch == "]" ? "[" :"{"))) { 204 | popStack(state); 205 | } 206 | } else if ( ch == ":" ) { 207 | stream.eatWhile(tests.symbol); 208 | return ATOM; 209 | } else { 210 | stream.eatWhile(tests.symbol); 211 | 212 | if (keywords && keywords.propertyIsEnumerable(stream.current())) { 213 | returnType = KEYWORD; 214 | } else if (builtins && builtins.propertyIsEnumerable(stream.current())) { 215 | returnType = BUILTIN; 216 | } else if (atoms && atoms.propertyIsEnumerable(stream.current())) { 217 | returnType = ATOM; 218 | } else returnType = null; 219 | } 220 | } 221 | 222 | return returnType; 223 | }, 224 | 225 | indent: function (state) { 226 | if (state.indentStack == null) return state.indentation; 227 | return state.indentStack.indent; 228 | }, 229 | 230 | lineComment: ";;" 231 | }; 232 | }); 233 | 234 | CodeMirror.defineMIME("text/x-hylang", "hy"); 235 | 236 | }};//returned module 237 | });//define 238 | -------------------------------------------------------------------------------- /notebooks/Tutorial.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "> _Copied from the [hy documentation](http://docs.hylang.org/en/latest/tutorial.html)._" 8 | ] 9 | }, 10 | { 11 | "cell_type": "markdown", 12 | "metadata": {}, 13 | "source": [ 14 | "# Tutorial" 15 | ] 16 | }, 17 | { 18 | "cell_type": "markdown", 19 | "metadata": {}, 20 | "source": [ 21 | "\n", 22 | "\n", 23 | "Welcome to the Hy tutorial!\n", 24 | "\n", 25 | "In a nutshell, Hy is a lisp dialect, but one that converts its structure into Python... literally a conversion into Python’s abstract syntax tree! (Or to put it in more crude terms, Hy is lisp-stick on a python!)\n", 26 | "\n", 27 | "This is pretty cool because it means Hy is several things:\n", 28 | "- A lisp that feels very pythonic\n", 29 | "- For lispers, a great way to use lisp’s crazy powers but in the wide world of Python’s libraries (why yes, you now can write a Django application in lisp!)\n", 30 | "- For pythonistas, a great way to start exploring lisp, from the comfort of python!\n", 31 | "- For everyone: a pleasant language that has a lot of neat ideas!" 32 | ] 33 | }, 34 | { 35 | "cell_type": "markdown", 36 | "metadata": {}, 37 | "source": [ 38 | "## Basic intro to lisp for pythonistas" 39 | ] 40 | }, 41 | { 42 | "cell_type": "markdown", 43 | "metadata": {}, 44 | "source": [ 45 | "Okay, maybe you’ve never used lisp before, but you’ve used python!\n", 46 | "\n", 47 | "A “hello world” in hy is actually super simple. Let’s try it:" 48 | ] 49 | }, 50 | { 51 | "cell_type": "code", 52 | "execution_count": 1, 53 | "metadata": { 54 | "collapsed": false 55 | }, 56 | "outputs": [ 57 | { 58 | "name": "stdout", 59 | "output_type": "stream", 60 | "text": [ 61 | "hello world\n" 62 | ] 63 | } 64 | ], 65 | "source": [ 66 | "(print \"hello world\")" 67 | ] 68 | }, 69 | { 70 | "cell_type": "markdown", 71 | "metadata": {}, 72 | "source": [ 73 | "See? Easy! As you may have guessed, this is the same as the python version of:\n", 74 | "```python\n", 75 | "print \"hello world\"\n", 76 | "```" 77 | ] 78 | }, 79 | { 80 | "cell_type": "markdown", 81 | "metadata": {}, 82 | "source": [ 83 | "To add up some super simple math, we could do:" 84 | ] 85 | }, 86 | { 87 | "cell_type": "code", 88 | "execution_count": 2, 89 | "metadata": { 90 | "collapsed": false 91 | }, 92 | "outputs": [ 93 | { 94 | "data": { 95 | "text/plain": [ 96 | "4L" 97 | ] 98 | }, 99 | "execution_count": 2, 100 | "metadata": {}, 101 | "output_type": "execute_result" 102 | } 103 | ], 104 | "source": [ 105 | "(+ 1 3)" 106 | ] 107 | }, 108 | { 109 | "cell_type": "markdown", 110 | "metadata": {}, 111 | "source": [ 112 | "Which would return 4 and would be the equivalent of:\n", 113 | "```python\n", 114 | "1 + 3\n", 115 | "```" 116 | ] 117 | }, 118 | { 119 | "cell_type": "markdown", 120 | "metadata": {}, 121 | "source": [ 122 | "What you’ll notice is that the first item in the list is the function being called and the rest of the arguments are the arguments being passed in. In fact, in hy (as with most lisps) we can pass in multiple arguments to the plus operator:" 123 | ] 124 | }, 125 | { 126 | "cell_type": "code", 127 | "execution_count": 3, 128 | "metadata": { 129 | "collapsed": false 130 | }, 131 | "outputs": [ 132 | { 133 | "data": { 134 | "text/plain": [ 135 | "59L" 136 | ] 137 | }, 138 | "execution_count": 3, 139 | "metadata": {}, 140 | "output_type": "execute_result" 141 | } 142 | ], 143 | "source": [ 144 | "(+ 1 3 55)" 145 | ] 146 | }, 147 | { 148 | "cell_type": "markdown", 149 | "metadata": {}, 150 | "source": [ 151 | "Which would return 59.\n", 152 | "\n", 153 | "Maybe you’ve heard of lisp before but don’t know much about it. Lisp isn’t as hard as you might think, and hy inherits from python, so hy is a great way to start learning lisp. The main thing that’s obvious about lisp is that there’s a lot of parentheses. This might seem confusing at first, but it isn’t so hard. Let’s look at some simple math that’s wrapped in a bunch of parentheses that we could enter into the hy interpreter:" 154 | ] 155 | }, 156 | { 157 | "cell_type": "code", 158 | "execution_count": 3, 159 | "metadata": { 160 | "collapsed": false 161 | }, 162 | "outputs": [ 163 | { 164 | "ename": "HyMacroExpansionError", 165 | "evalue": " File \"None\", line 1, column 6\n\n (let result (- (/ (+ 1 3 88) 2) 8))\n ^-----^\nHyMacroExpansionError: let lexical context must be a list\n\n", 166 | "output_type": "error", 167 | "traceback": [ 168 | "\u001b[1;31mHyMacroExpansionError\u001b[0m\u001b[1;31m:\u001b[0m File \"None\", line 1, column 6\n\n (let result (- (/ (+ 1 3 88) 2) 8))\n ^-----^\nHyMacroExpansionError: let lexical context must be a list\n\n\n" 169 | ] 170 | } 171 | ], 172 | "source": [ 173 | "(let result (- (/ (+ 1 3 88) 2) 8))" 174 | ] 175 | }, 176 | { 177 | "cell_type": "markdown", 178 | "metadata": {}, 179 | "source": [ 180 | "This would return 38. But why? Well, we could look at the equivalent expression in python:\n", 181 | "```python\n", 182 | "result = ((1 + 3 + 88) / 2) - 8\n", 183 | "```" 184 | ] 185 | }, 186 | { 187 | "cell_type": "markdown", 188 | "metadata": {}, 189 | "source": [ 190 | "If you were to try to figure out how the above were to work in python, you’d of course figure out the results by solving each inner parenthesis. That’s the same basic idea in hy. Let’s try this exercise first in python:\n", 191 | "```python\n", 192 | "result = ((1 + 3 + 88) / 2) - 8\n", 193 | "# simplified to...\n", 194 | "result = (92 / 2) - 8\n", 195 | "# simplified to...\n", 196 | "result = 46 - 8\n", 197 | "# simplified to...\n", 198 | "result = 38\n", 199 | "```" 200 | ] 201 | }, 202 | { 203 | "cell_type": "markdown", 204 | "metadata": {}, 205 | "source": [ 206 | "Now let’s try the same thing in hy:" 207 | ] 208 | }, 209 | { 210 | "cell_type": "code", 211 | "execution_count": 4, 212 | "metadata": { 213 | "collapsed": true 214 | }, 215 | "outputs": [], 216 | "source": [ 217 | "(setv result (- (/ (+ 1 3 88) 2) 8))\n", 218 | "; simplified to...\n", 219 | "(setv result (- (/ 92 2) 8))\n", 220 | "; simplified to...\n", 221 | "(setv result (- 46 8))\n", 222 | "; simplified to...\n", 223 | "(setv result 38)" 224 | ] 225 | }, 226 | { 227 | "cell_type": "markdown", 228 | "metadata": {}, 229 | "source": [ 230 | "As you probably guessed, this last expression with “setv” means to assign the variable “result” to 38.\n", 231 | "\n", 232 | "See? Not too hard!\n", 233 | "\n", 234 | "This is the basic premise of lisp... lisp stands for “list processing”... this means that the structure of the program is actually lists of lists. (If you’re familiar with python lists, imagine the entire same structure as above but with square brackets instead, any you’ll be able to see the structure above as both a program and a datastructure.) This is easier to understand with more examples, so let’s write a simple python program and test it and then show the equivalent hy program:\n", 235 | "```python\n", 236 | "def simple_conversation():\n", 237 | " print \"Hello! I'd like to get to know you. Tell me about yourself!\"\n", 238 | " name = raw_input(\"What is your name? \")\n", 239 | " age = raw_input(\"What is your age? \")\n", 240 | " print \"Hello \" + name + \"! I see you are \" + age + \" years old.\"\n", 241 | "\n", 242 | "simple_conversation()\n", 243 | "```\n", 244 | "\n", 245 | "If we ran this program, it might go like:\n", 246 | "\n", 247 | "```\n", 248 | "Hello! I'd like to get to know you. Tell me about yourself!\n", 249 | "What is your name? Gary\n", 250 | "What is your age? 38\n", 251 | "Hello Gary! I see you are 38 years old.\n", 252 | "```\n", 253 | "\n", 254 | "Now let’s look at the equivalent hy program:" 255 | ] 256 | }, 257 | { 258 | "cell_type": "code", 259 | "execution_count": 5, 260 | "metadata": { 261 | "collapsed": false 262 | }, 263 | "outputs": [ 264 | { 265 | "ename": "SyntaxError", 266 | "evalue": "invalid syntax (, line 7)", 267 | "output_type": "error", 268 | "traceback": [ 269 | "\u001b[1;36m File \u001b[1;32m\"\"\u001b[1;36m, line \u001b[1;32m7\u001b[0m\n\u001b[1;33m return print(((((u'Hello ' + name) + u'! I see you are ') + age) + u' years old.'))\u001b[0m\n\u001b[1;37m ^\u001b[0m\n\u001b[1;31mSyntaxError\u001b[0m\u001b[1;31m:\u001b[0m invalid syntax\n" 270 | ] 271 | } 272 | ], 273 | "source": [ 274 | "(defn simple-conversation []\n", 275 | " (print \"Hello! I'd like to get to know you. Tell me about yourself!\")\n", 276 | " (setv name (raw_input \"What is your name? \"))\n", 277 | " (setv age (raw_input \"What is your age? \"))\n", 278 | " (print (+ \"Hello \" name \"! I see you are \"\n", 279 | " age \" years old.\")))\n", 280 | "\n", 281 | "(simple-conversation)" 282 | ] 283 | }, 284 | { 285 | "cell_type": "markdown", 286 | "metadata": {}, 287 | "source": [ 288 | "If you look at the above program, as long as you remember that the first element in each list of the program is the function (or macro... we’ll get to those later) being called and that the rest are the arguments, it’s pretty easy to figure out what this all means. (As you probably also guessed, defn is the hy method of defining methods.)\n", 289 | "\n", 290 | "Still, lots of people find this confusing at first because there’s so many parentheses, but there are plenty of things that can help make this easier: keep indentation nice and use an editor with parenthesis matching (this will help you figure out what each parenthesis pairs up with) and things will start to feel comfortable.\n", 291 | "\n", 292 | "There are some advantages to having a code structure that’s actually a very simple datastructure as the core of lisp is based on. For one thing, it means that your programs are easy to parse and that the entire actual structure of the program is very clearly exposed to you. (There’s an extra step in hy where the structure you see is converted to python’s own representations... in more “pure” lisps such as common lisp or emacs lisp, the data structure you see for the code and the data structure that is executed is much more literally close.)\n", 293 | "\n", 294 | "Another implication of this is macros: if a program’s structure is a simple data structure, that means you can write code that can write code very easily, meaning that implementing entirely new language features can be very fast. Previous to hy, this wasn’t very possible for python programmers... now you too can make use of macros’ incredible power (just be careful to not aim them footward)!" 295 | ] 296 | }, 297 | { 298 | "cell_type": "markdown", 299 | "metadata": {}, 300 | "source": [ 301 | "## Hy is a Lisp flavored Python" 302 | ] 303 | }, 304 | { 305 | "cell_type": "markdown", 306 | "metadata": {}, 307 | "source": [ 308 | "Hy converts to Python’s own abstract syntax tree, so you’ll soon start to find that all the familiar power of python is at your fingertips.\n", 309 | "\n", 310 | "You have full access to Python’s data types and standard library in Hy. Let’s experiment with this in the hy interpreter:" 311 | ] 312 | }, 313 | { 314 | "cell_type": "code", 315 | "execution_count": 6, 316 | "metadata": { 317 | "collapsed": false 318 | }, 319 | "outputs": [ 320 | { 321 | "data": { 322 | "text/plain": [ 323 | "[1L, 2L, 3L]" 324 | ] 325 | }, 326 | "execution_count": 6, 327 | "metadata": {}, 328 | "output_type": "execute_result" 329 | } 330 | ], 331 | "source": [ 332 | "[1 2 3]" 333 | ] 334 | }, 335 | { 336 | "cell_type": "code", 337 | "execution_count": 7, 338 | "metadata": { 339 | "collapsed": false 340 | }, 341 | "outputs": [ 342 | { 343 | "data": { 344 | "text/plain": [ 345 | "{u'cat': u'meow', u'dog': u'bark'}" 346 | ] 347 | }, 348 | "execution_count": 7, 349 | "metadata": {}, 350 | "output_type": "execute_result" 351 | } 352 | ], 353 | "source": [ 354 | "{\"dog\" \"bark\"\n", 355 | " \"cat\" \"meow\"}" 356 | ] 357 | }, 358 | { 359 | "cell_type": "code", 360 | "execution_count": 8, 361 | "metadata": { 362 | "collapsed": false 363 | }, 364 | "outputs": [ 365 | { 366 | "data": { 367 | "text/plain": [ 368 | "(1L, 2L, 3L)" 369 | ] 370 | }, 371 | "execution_count": 8, 372 | "metadata": {}, 373 | "output_type": "execute_result" 374 | } 375 | ], 376 | "source": [ 377 | "(, 1 2 3)" 378 | ] 379 | }, 380 | { 381 | "cell_type": "markdown", 382 | "metadata": {}, 383 | "source": [ 384 | "If you are familiar with other lisps, you may be interested that Hy supports the Common Lisp method of quoting:" 385 | ] 386 | }, 387 | { 388 | "cell_type": "code", 389 | "execution_count": 9, 390 | "metadata": { 391 | "collapsed": false 392 | }, 393 | "outputs": [ 394 | { 395 | "data": { 396 | "text/plain": [ 397 | "(1L 2L 3L)" 398 | ] 399 | }, 400 | "execution_count": 9, 401 | "metadata": {}, 402 | "output_type": "execute_result" 403 | } 404 | ], 405 | "source": [ 406 | "'(1 2 3)" 407 | ] 408 | }, 409 | { 410 | "cell_type": "markdown", 411 | "metadata": {}, 412 | "source": [ 413 | "You also have access to all the builtin types’ nice methods:" 414 | ] 415 | }, 416 | { 417 | "cell_type": "code", 418 | "execution_count": 10, 419 | "metadata": { 420 | "collapsed": false 421 | }, 422 | "outputs": [ 423 | { 424 | "data": { 425 | "text/plain": [ 426 | "u'fooooo'" 427 | ] 428 | }, 429 | "execution_count": 10, 430 | "metadata": {}, 431 | "output_type": "execute_result" 432 | } 433 | ], 434 | "source": [ 435 | "(.strip \" fooooo \")" 436 | ] 437 | }, 438 | { 439 | "cell_type": "markdown", 440 | "metadata": {}, 441 | "source": [ 442 | "What’s this? Yes indeed, this is precisely the same as:\n", 443 | "\n", 444 | "```python\n", 445 | "\" fooooo \".strip()\n", 446 | "```\n", 447 | "\n", 448 | "That’s right... lisp with dot notation! If we have this string assigned as a variable, we can also do the following:" 449 | ] 450 | }, 451 | { 452 | "cell_type": "code", 453 | "execution_count": 11, 454 | "metadata": { 455 | "collapsed": false 456 | }, 457 | "outputs": [ 458 | { 459 | "data": { 460 | "text/plain": [ 461 | "u'fooooo'" 462 | ] 463 | }, 464 | "execution_count": 11, 465 | "metadata": {}, 466 | "output_type": "execute_result" 467 | } 468 | ], 469 | "source": [ 470 | "(setv this-string \" fooooo \")\n", 471 | "(this-string.strip)" 472 | ] 473 | }, 474 | { 475 | "cell_type": "markdown", 476 | "metadata": {}, 477 | "source": [ 478 | "What about conditionals?:" 479 | ] 480 | }, 481 | { 482 | "cell_type": "code", 483 | "execution_count": 15, 484 | "metadata": { 485 | "collapsed": false 486 | }, 487 | "outputs": [ 488 | { 489 | "ename": "SyntaxError", 490 | "evalue": "invalid syntax (, line 1)", 491 | "output_type": "error", 492 | "traceback": [ 493 | "\u001b[1;36m File \u001b[1;32m\"\"\u001b[1;36m, line \u001b[1;32m1\u001b[0m\n\u001b[1;33m (print(u'this is if true') if try_some_thing() else print(u'this is if false'))\u001b[0m\n\u001b[1;37m ^\u001b[0m\n\u001b[1;31mSyntaxError\u001b[0m\u001b[1;31m:\u001b[0m invalid syntax\n" 494 | ] 495 | } 496 | ], 497 | "source": [ 498 | "(if (try-some-thing)\n", 499 | " (print \"this is if true\")\n", 500 | " (print \"this is if false\"))" 501 | ] 502 | }, 503 | { 504 | "cell_type": "markdown", 505 | "metadata": {}, 506 | "source": [ 507 | "As you can tell above, the first argument to if is a truth test, the second argument is a body if true, and the third argument (optional!) is if false (ie, “else”!).\n", 508 | "\n", 509 | "If you need to do more complex conditionals, you’ll find that you don’t have elif available in hy. Instead, you should use something called “cond”. In python, you might do something like:\n", 510 | "\n", 511 | "```python\n", 512 | "somevar = 33\n", 513 | "if somevar > 50:\n", 514 | " print \"That variable is too big!\"\n", 515 | "elif somevar < 10:\n", 516 | " print \"That variable is too small!\"\n", 517 | "else:\n", 518 | " print \"That variable is jussssst right!\"\n", 519 | "```\n", 520 | "\n", 521 | "In hy, you would do:" 522 | ] 523 | }, 524 | { 525 | "cell_type": "code", 526 | "execution_count": 16, 527 | "metadata": { 528 | "collapsed": false 529 | }, 530 | "outputs": [ 531 | { 532 | "ename": "SyntaxError", 533 | "evalue": "invalid syntax (, line 2)", 534 | "output_type": "error", 535 | "traceback": [ 536 | "\u001b[1;36m File \u001b[1;32m\"\"\u001b[1;36m, line \u001b[1;32m2\u001b[0m\n\u001b[1;33m (print(u'That variable is too big!') if (somevar > 50L) else (print(u'That variable is too small!') if (somevar < 10L) else (print(u'That variable is jussssst right!') if True else None)))\u001b[0m\n\u001b[1;37m ^\u001b[0m\n\u001b[1;31mSyntaxError\u001b[0m\u001b[1;31m:\u001b[0m invalid syntax\n" 537 | ] 538 | } 539 | ], 540 | "source": [ 541 | "(setv somevar 33)\n", 542 | "(cond\n", 543 | " [(> somevar 50)\n", 544 | " (print \"That variable is too big!\")]\n", 545 | " [(< somevar 10)\n", 546 | " (print \"That variable is too small!\")]\n", 547 | " [true\n", 548 | " (print \"That variable is jussssst right!\")])" 549 | ] 550 | }, 551 | { 552 | "cell_type": "markdown", 553 | "metadata": {}, 554 | "source": [ 555 | "What you’ll notice is that cond switches off between a some statement that is executed and checked conditionally for true or falseness, and then a bit of code to execute if it turns out to be true. You’ll also notice that the “else” is implemented at the end simply by checking for “true”... that’s because true will always be true, so if we get this far, we’ll always run that one!\n", 556 | "\n", 557 | "You might notice above that if you have code like:" 558 | ] 559 | }, 560 | { 561 | "cell_type": "code", 562 | "execution_count": 17, 563 | "metadata": { 564 | "collapsed": false 565 | }, 566 | "outputs": [ 567 | { 568 | "ename": "NameError", 569 | "evalue": "name 'some_condition' is not defined", 570 | "output_type": "error", 571 | "traceback": [ 572 | "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m", 573 | "\u001b[1;31mNameError\u001b[0m Traceback (most recent call last)", 574 | "\u001b[1;32m\u001b[0m in \u001b[0;36m\u001b[1;34m()\u001b[0m\n\u001b[1;32m----> 1\u001b[1;33m \u001b[1;33m(\u001b[0m\u001b[0mbody_if_true\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;33m)\u001b[0m \u001b[1;32mif\u001b[0m \u001b[0msome_condition\u001b[0m \u001b[1;32melse\u001b[0m \u001b[0mbody_if_false\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m", 575 | "\u001b[1;31mNameError\u001b[0m: name 'some_condition' is not defined" 576 | ] 577 | } 578 | ], 579 | "source": [ 580 | "(if some-condition\n", 581 | " (body-if-true)\n", 582 | " (body-if-false))" 583 | ] 584 | }, 585 | { 586 | "cell_type": "markdown", 587 | "metadata": {}, 588 | "source": [ 589 | "But wait! What if you want to execute more than one statement in the body of one of these?\n", 590 | "\n", 591 | "You can do the following:" 592 | ] 593 | }, 594 | { 595 | "cell_type": "code", 596 | "execution_count": 18, 597 | "metadata": { 598 | "collapsed": false 599 | }, 600 | "outputs": [ 601 | { 602 | "ename": "SyntaxError", 603 | "evalue": "invalid syntax (, line 3)", 604 | "output_type": "error", 605 | "traceback": [ 606 | "\u001b[1;36m File \u001b[1;32m\"\"\u001b[1;36m, line \u001b[1;32m3\u001b[0m\n\u001b[1;33m _hy_anon_var_1 = print(u\"and why not, let's keep talking about how true it is!\")\u001b[0m\n\u001b[1;37m ^\u001b[0m\n\u001b[1;31mSyntaxError\u001b[0m\u001b[1;31m:\u001b[0m invalid syntax\n" 607 | ] 608 | } 609 | ], 610 | "source": [ 611 | "(if (try-some-thing)\n", 612 | " (do\n", 613 | " (print \"this is if true\")\n", 614 | " (print \"and why not, let's keep talking about how true it is!\"))\n", 615 | " (print \"this one's still simply just false\"))" 616 | ] 617 | }, 618 | { 619 | "cell_type": "markdown", 620 | "metadata": {}, 621 | "source": [ 622 | "You can see that we used “do” to wrap multiple statements. If you’re familiar with other lisps, this is the equivalent of “progn” elsewhere.\n", 623 | "\n", 624 | "Comments start with semicolons:" 625 | ] 626 | }, 627 | { 628 | "cell_type": "code", 629 | "execution_count": 19, 630 | "metadata": { 631 | "collapsed": false 632 | }, 633 | "outputs": [ 634 | { 635 | "name": "stdout", 636 | "output_type": "stream", 637 | "text": [ 638 | "this will run\n" 639 | ] 640 | }, 641 | { 642 | "data": { 643 | "text/plain": [ 644 | "6L" 645 | ] 646 | }, 647 | "execution_count": 19, 648 | "metadata": {}, 649 | "output_type": "execute_result" 650 | } 651 | ], 652 | "source": [ 653 | "(print \"this will run\")\n", 654 | "; (print \"but this will not\")\n", 655 | "(+ 1 2 3) ; we'll execute the addition, but not this comment!" 656 | ] 657 | }, 658 | { 659 | "cell_type": "markdown", 660 | "metadata": {}, 661 | "source": [ 662 | "Looping is not hard but has a kind of special structure. In python, we might do:\n", 663 | "\n", 664 | "```python\n", 665 | "for i in range(10):\n", 666 | " print \"'i' is now at \" + str(i)\n", 667 | "```\n", 668 | "\n", 669 | "The equivalent in hy would be:" 670 | ] 671 | }, 672 | { 673 | "cell_type": "code", 674 | "execution_count": 19, 675 | "metadata": { 676 | "collapsed": false 677 | }, 678 | "outputs": [ 679 | { 680 | "ename": "HyMacroExpansionError", 681 | "evalue": " File \"None\", line 1, column 1\n\n (for [i (range 10)]\n ^------------------\n (print (+ \"'i' is now at \" (str i))))\n --------------------------------------^\nHyMacroExpansionError: `for' name 'is_odd' is not defined\n\n", 682 | "output_type": "error", 683 | "traceback": [ 684 | "\u001b[1;31mHyMacroExpansionError\u001b[0m\u001b[1;31m:\u001b[0m File \"None\", line 1, column 1\n\n (for [i (range 10)]\n ^------------------\n (print (+ \"'i' is now at \" (str i))))\n --------------------------------------^\nHyMacroExpansionError: `for' name 'is_odd' is not defined\n\n\n" 685 | ] 686 | } 687 | ], 688 | "source": [ 689 | "(for [i (range 10)]\n", 690 | " (print (+ \"'i' is now at \" (str i))))" 691 | ] 692 | }, 693 | { 694 | "cell_type": "markdown", 695 | "metadata": {}, 696 | "source": [ 697 | "You can also import and make use of various python libraries. For example:" 698 | ] 699 | }, 700 | { 701 | "cell_type": "code", 702 | "execution_count": 20, 703 | "metadata": { 704 | "collapsed": false 705 | }, 706 | "outputs": [ 707 | { 708 | "ename": "SyntaxError", 709 | "evalue": "invalid syntax (, line 2)", 710 | "output_type": "error", 711 | "traceback": [ 712 | "\u001b[1;36m File \u001b[1;32m\"\"\u001b[1;36m, line \u001b[1;32m2\u001b[0m\n\u001b[1;33m (os.mkdir(u'/tmp/somedir/anotherdir') if os.path.isdir(u'/tmp/somedir') else print(u\"Hey, that path isn't there!\"))\u001b[0m\n\u001b[1;37m ^\u001b[0m\n\u001b[1;31mSyntaxError\u001b[0m\u001b[1;31m:\u001b[0m invalid syntax\n" 713 | ] 714 | } 715 | ], 716 | "source": [ 717 | "(import os)\n", 718 | "\n", 719 | "(if (os.path.isdir \"/tmp/somedir\")\n", 720 | " (os.mkdir \"/tmp/somedir/anotherdir\")\n", 721 | " (print \"Hey, that path isn't there!\"))" 722 | ] 723 | }, 724 | { 725 | "cell_type": "markdown", 726 | "metadata": {}, 727 | "source": [ 728 | "Python’s context managers (‘with’ statements) are used like this:" 729 | ] 730 | }, 731 | { 732 | "cell_type": "code", 733 | "execution_count": 20, 734 | "metadata": { 735 | "collapsed": false 736 | }, 737 | "outputs": [ 738 | { 739 | "ename": "HyMacroExpansionError", 740 | "evalue": " File \"None\", line 1, column 1\n\n (with [[f (open \"/tmp/data.in\")]]\n ^--------------------------------\n (print (.read f)))\n -------------------^\nHyMacroExpansionError: `with' name 'is_empty' is not defined\n\n", 741 | "output_type": "error", 742 | "traceback": [ 743 | "\u001b[1;31mHyMacroExpansionError\u001b[0m\u001b[1;31m:\u001b[0m File \"None\", line 1, column 1\n\n (with [[f (open \"/tmp/data.in\")]]\n ^--------------------------------\n (print (.read f)))\n -------------------^\nHyMacroExpansionError: `with' name 'is_empty' is not defined\n\n\n" 744 | ] 745 | } 746 | ], 747 | "source": [ 748 | "(with [[f (open \"/tmp/data.in\")]]\n", 749 | " (print (.read f)))" 750 | ] 751 | }, 752 | { 753 | "cell_type": "markdown", 754 | "metadata": {}, 755 | "source": [ 756 | "which is equivalent to:\n", 757 | "\n", 758 | "```python\n", 759 | "with open(\"/tmp/data.in\") as f:\n", 760 | " print f.read()\n", 761 | "```\n", 762 | "\n", 763 | "And yes, we do have lisp comprehensions! In Python you might do:\n", 764 | "\n", 765 | "```python\n", 766 | "odds_squared = [\n", 767 | " pow(num, 2)\n", 768 | " for num in range(100)\n", 769 | " if num % 2 == 1]\n", 770 | "```\n", 771 | "\n", 772 | "In hy, you could do these like:" 773 | ] 774 | }, 775 | { 776 | "cell_type": "code", 777 | "execution_count": 21, 778 | "metadata": { 779 | "collapsed": false 780 | }, 781 | "outputs": [ 782 | { 783 | "data": { 784 | "text/plain": [ 785 | "[(0, u'A'),\n", 786 | " (0, u'B'),\n", 787 | " (0, u'C'),\n", 788 | " (0, u'D'),\n", 789 | " (0, u'E'),\n", 790 | " (0, u'F'),\n", 791 | " (0, u'G'),\n", 792 | " (0, u'H'),\n", 793 | " (1, u'A'),\n", 794 | " (1, u'B'),\n", 795 | " (1, u'C'),\n", 796 | " (1, u'D'),\n", 797 | " (1, u'E'),\n", 798 | " (1, u'F'),\n", 799 | " (1, u'G'),\n", 800 | " (1, u'H'),\n", 801 | " (2, u'A'),\n", 802 | " (2, u'B'),\n", 803 | " (2, u'C'),\n", 804 | " (2, u'D'),\n", 805 | " (2, u'E'),\n", 806 | " (2, u'F'),\n", 807 | " (2, u'G'),\n", 808 | " (2, u'H'),\n", 809 | " (3, u'A'),\n", 810 | " (3, u'B'),\n", 811 | " (3, u'C'),\n", 812 | " (3, u'D'),\n", 813 | " (3, u'E'),\n", 814 | " (3, u'F'),\n", 815 | " (3, u'G'),\n", 816 | " (3, u'H'),\n", 817 | " (4, u'A'),\n", 818 | " (4, u'B'),\n", 819 | " (4, u'C'),\n", 820 | " (4, u'D'),\n", 821 | " (4, u'E'),\n", 822 | " (4, u'F'),\n", 823 | " (4, u'G'),\n", 824 | " (4, u'H'),\n", 825 | " (5, u'A'),\n", 826 | " (5, u'B'),\n", 827 | " (5, u'C'),\n", 828 | " (5, u'D'),\n", 829 | " (5, u'E'),\n", 830 | " (5, u'F'),\n", 831 | " (5, u'G'),\n", 832 | " (5, u'H'),\n", 833 | " (6, u'A'),\n", 834 | " (6, u'B'),\n", 835 | " (6, u'C'),\n", 836 | " (6, u'D'),\n", 837 | " (6, u'E'),\n", 838 | " (6, u'F'),\n", 839 | " (6, u'G'),\n", 840 | " (6, u'H'),\n", 841 | " (7, u'A'),\n", 842 | " (7, u'B'),\n", 843 | " (7, u'C'),\n", 844 | " (7, u'D'),\n", 845 | " (7, u'E'),\n", 846 | " (7, u'F'),\n", 847 | " (7, u'G'),\n", 848 | " (7, u'H')]" 849 | ] 850 | }, 851 | "execution_count": 21, 852 | "metadata": {}, 853 | "output_type": "execute_result" 854 | } 855 | ], 856 | "source": [ 857 | "(setv odds-squared\n", 858 | " (list-comp\n", 859 | " (pow num 2)\n", 860 | " (num (range 100))\n", 861 | " (= (% num 2) 1)))\n", 862 | "\n", 863 | "; And, an example stolen shamelessly from a Clojure page:\n", 864 | "; Let's list all the blocks of a Chessboard:\n", 865 | "\n", 866 | "(list-comp\n", 867 | " (, x y)\n", 868 | " (x (range 8)\n", 869 | " y \"ABCDEFGH\"))" 870 | ] 871 | }, 872 | { 873 | "cell_type": "markdown", 874 | "metadata": {}, 875 | "source": [ 876 | "Python has support for various fancy argument and keyword arguments. In python we might see:\n", 877 | "\n", 878 | "```python\n", 879 | ">>> def optional_arg(pos1, pos2, keyword1=None, keyword2=42):\n", 880 | "... return [pos1, pos2, keyword1, keyword2]\n", 881 | "...\n", 882 | ">>> optional_arg(1, 2)\n", 883 | "[1, 2, None, 42]\n", 884 | ">>> optional_arg(1, 2, 3, 4)\n", 885 | "[1, 2, 3, 4]\n", 886 | ">>> optional_arg(keyword1=1, pos2=2, pos1=3, keyword2=4)\n", 887 | "[3, 2, 1, 4]\n", 888 | "```\n", 889 | "\n", 890 | "The same thing in Hy:" 891 | ] 892 | }, 893 | { 894 | "cell_type": "code", 895 | "execution_count": 22, 896 | "metadata": { 897 | "collapsed": true 898 | }, 899 | "outputs": [], 900 | "source": [ 901 | "(defn optional_arg [pos1 pos2 &optional keyword1 [keyword2 42]]\n", 902 | " [pos1 pos2 keyword1 keyword2])" 903 | ] 904 | }, 905 | { 906 | "cell_type": "code", 907 | "execution_count": 23, 908 | "metadata": { 909 | "collapsed": false 910 | }, 911 | "outputs": [ 912 | { 913 | "data": { 914 | "text/plain": [ 915 | "[1L, 2L, None, 42L]" 916 | ] 917 | }, 918 | "execution_count": 23, 919 | "metadata": {}, 920 | "output_type": "execute_result" 921 | } 922 | ], 923 | "source": [ 924 | "(optional_arg 1 2)" 925 | ] 926 | }, 927 | { 928 | "cell_type": "code", 929 | "execution_count": 24, 930 | "metadata": { 931 | "collapsed": false 932 | }, 933 | "outputs": [ 934 | { 935 | "data": { 936 | "text/plain": [ 937 | "[1L, 2L, 3L, 4L]" 938 | ] 939 | }, 940 | "execution_count": 24, 941 | "metadata": {}, 942 | "output_type": "execute_result" 943 | } 944 | ], 945 | "source": [ 946 | "(optional_arg 1 2 3 4)" 947 | ] 948 | }, 949 | { 950 | "cell_type": "code", 951 | "execution_count": 25, 952 | "metadata": { 953 | "collapsed": false 954 | }, 955 | "outputs": [ 956 | { 957 | "data": { 958 | "text/plain": [ 959 | "[3L, 2L, 1L, 4L]" 960 | ] 961 | }, 962 | "execution_count": 25, 963 | "metadata": {}, 964 | "output_type": "execute_result" 965 | } 966 | ], 967 | "source": [ 968 | "(apply optional_arg []\n", 969 | " {\"keyword1\" 1\n", 970 | " \"pos2\" 2\n", 971 | " \"pos1\" 3\n", 972 | " \"keyword2\" 4})" 973 | ] 974 | }, 975 | { 976 | "cell_type": "markdown", 977 | "metadata": {}, 978 | "source": [ 979 | "See how we use apply to handle the fancy passing? :)\n", 980 | "\n", 981 | "There’s also a dictionary-style keyword arguments construction that looks like:" 982 | ] 983 | }, 984 | { 985 | "cell_type": "code", 986 | "execution_count": 26, 987 | "metadata": { 988 | "collapsed": true 989 | }, 990 | "outputs": [], 991 | "source": [ 992 | "(defn another_style [&key {\"key1\" \"val1\" \"key2\" \"val2\"}]\n", 993 | " [key1 key2])" 994 | ] 995 | }, 996 | { 997 | "cell_type": "markdown", 998 | "metadata": {}, 999 | "source": [ 1000 | "The difference here is that since it’s a dictionary, you can’t rely on any specific ordering to the arguments.\n", 1001 | "\n", 1002 | "Hy also supports `*args` and `**kwargs`. In Python:\n", 1003 | "\n", 1004 | "```python\n", 1005 | "def some_func(foo, bar, *args, **kwargs):\n", 1006 | " import pprint\n", 1007 | " pprint.pprint((foo, bar, args, kwargs))\n", 1008 | "```" 1009 | ] 1010 | }, 1011 | { 1012 | "cell_type": "markdown", 1013 | "metadata": {}, 1014 | "source": [ 1015 | "The Hy equivalent:" 1016 | ] 1017 | }, 1018 | { 1019 | "cell_type": "code", 1020 | "execution_count": 27, 1021 | "metadata": { 1022 | "collapsed": true 1023 | }, 1024 | "outputs": [], 1025 | "source": [ 1026 | "(defn some_func [foo bar &rest args &kwargs kwargs]\n", 1027 | " (import pprint)\n", 1028 | " (pprint.pprint (, foo bar args kwargs)))" 1029 | ] 1030 | }, 1031 | { 1032 | "cell_type": "markdown", 1033 | "metadata": {}, 1034 | "source": [ 1035 | "Finally, of course we need classes! In python we might have a class like:\n", 1036 | "\n", 1037 | "```python\n", 1038 | "class FooBar(object):\n", 1039 | " \"\"\"\n", 1040 | " Yet Another Example Class\n", 1041 | " \"\"\"\n", 1042 | " def __init__(self, x):\n", 1043 | " self.x = x\n", 1044 | "\n", 1045 | " def get_x(self):\n", 1046 | " \"\"\"\n", 1047 | " Return our copy of x\n", 1048 | " \"\"\"\n", 1049 | " return self.x\n", 1050 | "```\n", 1051 | "\n", 1052 | "In Hy:" 1053 | ] 1054 | }, 1055 | { 1056 | "cell_type": "code", 1057 | "execution_count": 28, 1058 | "metadata": { 1059 | "collapsed": true 1060 | }, 1061 | "outputs": [], 1062 | "source": [ 1063 | "(defclass FooBar [object]\n", 1064 | " \"Yet Another Example Class\"\n", 1065 | " [[--init--\n", 1066 | " (fn [self x]\n", 1067 | " (setv self.x x)\n", 1068 | " ; Currently needed for --init-- because __init__ needs None\n", 1069 | " ; Hopefully this will go away :)\n", 1070 | " None)]\n", 1071 | "\n", 1072 | " [get-x\n", 1073 | " (fn [self]\n", 1074 | " \"Return our copy of x\"\n", 1075 | " self.x)]])" 1076 | ] 1077 | }, 1078 | { 1079 | "cell_type": "markdown", 1080 | "metadata": {}, 1081 | "source": [ 1082 | "You can also do class-level attributes. In Python:\n", 1083 | "\n", 1084 | "```python\n", 1085 | "class Customer(models.Model):\n", 1086 | " name = models.CharField(max_length=255)\n", 1087 | " address = models.TextField()\n", 1088 | " notes = models.TextField()\n", 1089 | "```\n", 1090 | "\n", 1091 | "In Hy:" 1092 | ] 1093 | }, 1094 | { 1095 | "cell_type": "code", 1096 | "execution_count": 29, 1097 | "metadata": { 1098 | "collapsed": false 1099 | }, 1100 | "outputs": [ 1101 | { 1102 | "ename": "NameError", 1103 | "evalue": "name 'models' is not defined", 1104 | "output_type": "error", 1105 | "traceback": [ 1106 | "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m", 1107 | "\u001b[1;31mNameError\u001b[0m Traceback (most recent call last)", 1108 | "\u001b[1;32m\u001b[0m in \u001b[0;36m\u001b[1;34m()\u001b[0m\n\u001b[0;32m 2\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 3\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m----> 4\u001b[1;33m \u001b[1;32mclass\u001b[0m \u001b[0mCustomer\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mmodels\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mModel\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m 5\u001b[0m \u001b[0mname\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mmodels\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mCharField\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;33m*\u001b[0m\u001b[1;33m[\u001b[0m\u001b[1;33m]\u001b[0m\u001b[1;33m,\u001b[0m \u001b[1;33m**\u001b[0m\u001b[1;33m{\u001b[0m\u001b[1;34mu'max_length'\u001b[0m\u001b[1;33m:\u001b[0m \u001b[1;36m255L\u001b[0m\u001b[1;33m,\u001b[0m \u001b[1;33m}\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 6\u001b[0m \u001b[0maddress\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mmodels\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mTextField\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", 1109 | "\u001b[1;31mNameError\u001b[0m: name 'models' is not defined" 1110 | ] 1111 | } 1112 | ], 1113 | "source": [ 1114 | "(defclass Customer [models.Model]\n", 1115 | " [[name (apply models.CharField [] {\"max_length\" 255})]\n", 1116 | " [address (models.TextField)]\n", 1117 | " [notes (models.TextField)]])" 1118 | ] 1119 | }, 1120 | { 1121 | "cell_type": "markdown", 1122 | "metadata": {}, 1123 | "source": [ 1124 | "## Protips!" 1125 | ] 1126 | }, 1127 | { 1128 | "cell_type": "markdown", 1129 | "metadata": {}, 1130 | "source": [ 1131 | "Hy also features something known as the “threading macro”, a really neat feature of Clojure’s. The “threading macro” (written as “->”), is used to avoid deep nesting of expressions.\n", 1132 | "\n", 1133 | "The threading macro inserts each expression into the next expression’s first argument place.\n", 1134 | "\n", 1135 | "Let’s take the classic:" 1136 | ] 1137 | }, 1138 | { 1139 | "cell_type": "code", 1140 | "execution_count": 30, 1141 | "metadata": { 1142 | "collapsed": false 1143 | }, 1144 | "outputs": [ 1145 | { 1146 | "ename": "SyntaxError", 1147 | "evalue": "invalid syntax (, line 3)", 1148 | "output_type": "error", 1149 | "traceback": [ 1150 | "\u001b[1;36m File \u001b[1;32m\"\"\u001b[1;36m, line \u001b[1;32m3\u001b[0m\n\u001b[1;33m loop(print(hy_eval(read(), locals(), u'__console__')))\u001b[0m\n\u001b[1;37m ^\u001b[0m\n\u001b[1;31mSyntaxError\u001b[0m\u001b[1;31m:\u001b[0m invalid syntax\n" 1151 | ] 1152 | } 1153 | ], 1154 | "source": [ 1155 | "(loop (print (eval (read))))" 1156 | ] 1157 | }, 1158 | { 1159 | "cell_type": "markdown", 1160 | "metadata": {}, 1161 | "source": [ 1162 | "Rather than write it like that, we can write it as follows:" 1163 | ] 1164 | }, 1165 | { 1166 | "cell_type": "code", 1167 | "execution_count": 31, 1168 | "metadata": { 1169 | "collapsed": false 1170 | }, 1171 | "outputs": [ 1172 | { 1173 | "ename": "SyntaxError", 1174 | "evalue": "invalid syntax (, line 3)", 1175 | "output_type": "error", 1176 | "traceback": [ 1177 | "\u001b[1;36m File \u001b[1;32m\"\"\u001b[1;36m, line \u001b[1;32m3\u001b[0m\n\u001b[1;33m loop(print(hy_eval(read(), locals(), u'__console__')))\u001b[0m\n\u001b[1;37m ^\u001b[0m\n\u001b[1;31mSyntaxError\u001b[0m\u001b[1;31m:\u001b[0m invalid syntax\n" 1178 | ] 1179 | } 1180 | ], 1181 | "source": [ 1182 | "(-> (read) (eval) (print) (loop))" 1183 | ] 1184 | }, 1185 | { 1186 | "cell_type": "markdown", 1187 | "metadata": {}, 1188 | "source": [ 1189 | "Now, using [python-sh](http://amoffat.github.com/sh/), we can show how the threading macro (because of python-sh’s setup) can be used like a pipe:" 1190 | ] 1191 | }, 1192 | { 1193 | "cell_type": "code", 1194 | "execution_count": 32, 1195 | "metadata": { 1196 | "collapsed": false 1197 | }, 1198 | "outputs": [ 1199 | { 1200 | "ename": "ImportError", 1201 | "evalue": "No module named sh", 1202 | "output_type": "error", 1203 | "traceback": [ 1204 | "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m", 1205 | "\u001b[1;31mImportError\u001b[0m Traceback (most recent call last)", 1206 | "\u001b[1;32m\u001b[0m in \u001b[0;36m\u001b[1;34m()\u001b[0m\n\u001b[1;32m----> 1\u001b[1;33m \u001b[1;32mfrom\u001b[0m \u001b[0msh\u001b[0m \u001b[1;32mimport\u001b[0m \u001b[0mcat\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mgrep\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mwc\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m 2\u001b[0m \u001b[0mwc\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mgrep\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mcat\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;34mu'/usr/share/dict/words'\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m,\u001b[0m \u001b[1;34mu'-E'\u001b[0m\u001b[1;33m,\u001b[0m \u001b[1;34mu'^hy'\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m,\u001b[0m \u001b[1;34mu'-l'\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", 1207 | "\u001b[1;31mImportError\u001b[0m: No module named sh" 1208 | ] 1209 | } 1210 | ], 1211 | "source": [ 1212 | "(import [sh [cat grep wc]])\n", 1213 | "(-> (cat \"/usr/share/dict/words\") (grep \"-E\" \"^hy\") (wc \"-l\"))" 1214 | ] 1215 | }, 1216 | { 1217 | "cell_type": "markdown", 1218 | "metadata": {}, 1219 | "source": [ 1220 | "Which, of course, expands out to:" 1221 | ] 1222 | }, 1223 | { 1224 | "cell_type": "code", 1225 | "execution_count": 33, 1226 | "metadata": { 1227 | "collapsed": false 1228 | }, 1229 | "outputs": [ 1230 | { 1231 | "ename": "NameError", 1232 | "evalue": "name 'wc' is not defined", 1233 | "output_type": "error", 1234 | "traceback": [ 1235 | "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m", 1236 | "\u001b[1;31mNameError\u001b[0m Traceback (most recent call last)", 1237 | "\u001b[1;32m\u001b[0m in \u001b[0;36m\u001b[1;34m()\u001b[0m\n\u001b[1;32m----> 1\u001b[1;33m \u001b[0mwc\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mgrep\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mcat\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;34mu'/usr/share/dict/words'\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m,\u001b[0m \u001b[1;34mu'-E'\u001b[0m\u001b[1;33m,\u001b[0m \u001b[1;34mu'^hy'\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m,\u001b[0m \u001b[1;34mu'-l'\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m", 1238 | "\u001b[1;31mNameError\u001b[0m: name 'wc' is not defined" 1239 | ] 1240 | } 1241 | ], 1242 | "source": [ 1243 | "(wc (grep (cat \"/usr/share/dict/words\") \"-E\" \"^hy\") \"-l\")" 1244 | ] 1245 | }, 1246 | { 1247 | "cell_type": "markdown", 1248 | "metadata": {}, 1249 | "source": [ 1250 | "Much more readable, no? Use the threading macro!" 1251 | ] 1252 | } 1253 | ], 1254 | "metadata": { 1255 | "kernelspec": { 1256 | "display_name": "Hy", 1257 | "language": "hy", 1258 | "name": "hy" 1259 | }, 1260 | "language_info": { 1261 | "codemirror_mode": { 1262 | "name": "hy" 1263 | }, 1264 | "mimetype": "text/x-hylang", 1265 | "name": "hy", 1266 | "pygments_lexer": "ipython3" 1267 | } 1268 | }, 1269 | "nbformat": 4, 1270 | "nbformat_minor": 0 1271 | } 1272 | --------------------------------------------------------------------------------