{ "metadata": { "language": "Julia", "name": "" }, "nbformat": 3, "nbformat_minor": 0, "worksheets": [ { "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# A more thorough look at Julia's `::` syntax\n", "\n", "There are, as far as I know, a few different ways the `::` syntax could ever be interpreted:\n", "\n", "####To define argument types in function definitions:" ] }, { "cell_type": "code", "collapsed": false, "input": [ "foo(x::Real) = x\n", "\n", "y = foo(3.4)\n", "\n", "(y, typeof(y))" ], "language": "python", "metadata": {}, "outputs": [ { "metadata": {}, "output_type": "pyout", "prompt_number": 1, "text": [ "(3.4,Float64)" ] } ], "prompt_number": 1 }, { "cell_type": "markdown", "metadata": {}, "source": [ "In this case, which is maybe the most common one, `::Real` signifies that this method definition will be applicable for all arguments that fulfill `isa(x,Real)`. However, since the compiler will actually generate a definition for each type you use it with, if you call e.g. `foo(3.4)` you will actually call something compiled as `foo(x::Float64)`, which satisfies `typeof(x)==Float64`. Because of this, the output will also be in this specific type.\n", "\n", "####To assert that the value in a variable is in a specific part of the type hierarchy:" ] }, { "cell_type": "code", "collapsed": false, "input": [ "bar(x) = x::Real\n", "\n", "y = bar(3.4)\n", "\n", "(y, typeof(y))" ], "language": "python", "metadata": {}, "outputs": [ { "metadata": {}, "output_type": "pyout", "prompt_number": 2, "text": [ "(3.4,Float64)" ] } ], "prompt_number": 2 }, { "cell_type": "markdown", "metadata": {}, "source": [ "If you call `bar(3.4)` with this definition, you will (just as above) get a version which compiles for `typeof(x)==Float64` and returns `x`. However, the compiler can also compile a version for `bar(\"3.4\")`, where `typeof(x)==String`, which it couldn't do for `foo`.\n", "\n", "If you call the two versions above, you'll see different behavior for things that aren't some subtype of `Real`:" ] }, { "cell_type": "code", "collapsed": false, "input": [ "foo(\"3.4\")" ], "language": "python", "metadata": {}, "outputs": [ { "ename": "LoadError", "evalue": "no method foo(ASCIIString)\nwhile loading In[3], in expression starting on line 1", "output_type": "pyerr", "traceback": [ "no method foo(ASCIIString)\nwhile loading In[3], in expression starting on line 1" ] } ], "prompt_number": 3 }, { "cell_type": "code", "collapsed": false, "input": [ "bar(\"3.4\")" ], "language": "python", "metadata": {}, "outputs": [ { "ename": "LoadError", "evalue": "type: typeassert: expected Real, got ASCIIString\nwhile loading In[4], in expression starting on line 1", "output_type": "pyerr", "traceback": [ "type: typeassert: expected Real, got ASCIIString\nwhile loading In[4], in expression starting on line 1", " in bar at In[2]:1" ] } ], "prompt_number": 4 }, { "cell_type": "markdown", "metadata": {}, "source": [ "In the first case, you get a when MethodError because the compiler can't even generate a method with the signature you asked for. In the second case you get a failed type assert instead, because the compiler can generate the method for you, but once you actually run it, the variable `x` doesn't fulfil the assertion `x::Real`.\n", "\n", "####To define a variable (that is not a function argument) to be of some type:" ] }, { "cell_type": "code", "collapsed": false, "input": [ "function foobar(x)\n", " y::FloatingPoint = x\n", " y\n", "end\n", "\n", "z = foobar(3)\n", "(z, typeof(z))" ], "language": "python", "metadata": {}, "outputs": [ { "metadata": {}, "output_type": "pyout", "prompt_number": 5, "text": [ "(3.0,Float64)" ] } ], "prompt_number": 5 }, { "cell_type": "markdown", "metadata": {}, "source": [ "Note that `foobar(3)` works even though we don't even have `isa(3,FloatingPoint)` - there is no requirement on where in the type hierarchy the type of `x` is.\n", "\n", "#### A closer look at the last example\n", "\n", "Even though we have no requirement of where `x` is in the type hierarchy, successful execution of `y::FloatingPoint = x` is not without requirement - it works for this case because there is a conversion rule from `typeof(3)` (i.e. `Int64`) to `FloatingPoint` (which will end up being `Float64` on most architectures). Calling with an argument that does not have such a conversion still yields an error:" ] }, { "cell_type": "code", "collapsed": false, "input": [ "foobar(\"3.4\")" ], "language": "python", "metadata": {}, "outputs": [ { "ename": "LoadError", "evalue": "no method convert(Type{FloatingPoint}, ASCIIString)\nwhile loading In[6], in expression starting on line 1", "output_type": "pyerr", "traceback": [ "no method convert(Type{FloatingPoint}, ASCIIString)\nwhile loading In[6], in expression starting on line 1", " in foobar at In[5]:2" ] } ], "prompt_number": 6 }, { "cell_type": "markdown", "metadata": {}, "source": [ "We could define such a conversion, though, and make it work." ] }, { "cell_type": "code", "collapsed": false, "input": [ "import Base.convert # required to extend functions in Base with new methods\n", "\n", "function convert(::Type{FloatingPoint}, s::ASCIIString)\n", " if s==\"3.4\"\n", " return 3.4\n", " else\n", " throw(ErrorException(\"I'm too lazy to make anything else work\"))\n", " end\n", "end" ], "language": "python", "metadata": {}, "outputs": [ { "metadata": {}, "output_type": "pyout", "prompt_number": 7, "text": [ "convert (generic function with 592 methods)" ] } ], "prompt_number": 7 }, { "cell_type": "markdown", "metadata": {}, "source": [ "We've now *extended* the function `Base.convert` with another method, that tells Julia how to handle conversion from `ASCIIString` to `FloatingPoint`. (Of course, it only handles the very specific case of the string `\"3.4\"`, but it's enough for our purposes at the moment.) We make another attempt:" ] }, { "cell_type": "code", "collapsed": false, "input": [ "foobar(\"3.4\")" ], "language": "python", "metadata": {}, "outputs": [ { "ename": "LoadError", "evalue": "no method convert(Type{FloatingPoint}, ASCIIString)\nwhile loading In[8], in expression starting on line 1", "output_type": "pyerr", "traceback": [ "no method convert(Type{FloatingPoint}, ASCIIString)\nwhile loading In[8], in expression starting on line 1", " in foobar at In[5]:2" ] } ], "prompt_number": 8 }, { "cell_type": "markdown", "metadata": {}, "source": [ "Hm. Same error message... Let me copy-paste the code for the `foobar` function to here, and try again:" ] }, { "cell_type": "code", "collapsed": false, "input": [ "function foobar(x)\n", " y::FloatingPoint = x\n", " y\n", "end\n", "\n", "foobar(\"3.4\")" ], "language": "python", "metadata": {}, "outputs": [ { "metadata": {}, "output_type": "pyout", "prompt_number": 9, "text": [ "3.4" ] } ], "prompt_number": 9 }, { "cell_type": "markdown", "metadata": {}, "source": [ "It works!\n", "\n", "I might be wrong (and if I am, please let me know by filing an issue or submitting a pull-request to [the GitHub repo where this notebook is hosted][1]) but I think this is because Julia already compiled a version of `foobar` for input arguments of type `ASCIIString`, and even though we had defined a new method for `convert` that should have changed the behavior of `foobar`, there was no way for Julia to know that we needed to recompile it. When we executed the new method definition we replaced the old one with an identical copy that hadn't been compiled for `ASCIIString` arguments yet, and so when we called `foobar(\"3.4\")` the last time we could take advantage of our new conversion method. So this behavior really has nothing to do with the `::` syntax, but just with the way I structured this post.\n", "\n", "Note also that we didn't have to change the definition of `foobar` at all - it still has exactly the same declaration of a variable `y` with type `FloatingPoint`, and assigning `x` to it - but we taught Julia how to assign the value of a variable of type `ASCIIString` to a variable of type `FloatingPoint` by defining a conversion rule.\n", "\n", "[1]: https://github.com/tlycken/IJulia-Notebooks" ] } ], "metadata": {} } ] }