Hi,I've been programming a recommender system in Julia for a couple of weeks and overall I like much of Julia and the choices that have been made but I've also found some things that annoy me and that I'd like to see changed. I figure that at 0.2 it's still a very young language with a small user base & ecosystem so I might actually have a chance of some of my requests actually make it into a future release.* naming convention of the standard lib. For types Julia uses camelcase starting with uppercase (like Java for Classes) but for functions it's all lowercase with sometimes an underscore for separation. Having done a lot of Java programming this feels a bit weird to me. Why mix two different systems (camelcase & underscores?) Why not go for Java's system: camelcase (shorter than underscores) : function would start with lowercase (like Java methods) and types start with uppercase. I could live with all lowercase for functions but then I would like to see a consistent use of underscore for word separation. In the standard lib this is very inconsistent (many functions names have no separation where it should: e.g. haskey) and I find this very annoying since you never really know how a function name is written unless you look it up. In the absence of any code completion in the current development tool this little thing quickly becomes a source of irritation.
* modules: I find the current way how modules are used in client code confusing & lacking. Why two systems (using vs import)? using is simple enough but then you have no control over which functions are imported into your client namespace leading to possible name clashes. Import seems confusing (import <module> vs import <module.function>) & when importing function you can import any (disregarding the export list) and hence breaking encapsulation?
I 've also found that export list of functions is annoying (it feels a bit like header files in C++): it's easy to forget to add your functions to it especially when you are reworking code and since there's no compiler that complains you only find out about it at runtime with a cryptic error message.
There is no version data in the source code for a module. This could be really useful voor specifying dependencies with version numbers (with a package manager that support that) to get exactly what you need to avoid "dependency hell".
Here is what I propose:Basically I think the Java/C# system works pretty well so this solution is modeled after it.- use only one keyword/mechanism for importing modules (if we ignore include from the discussion).- annotate functions in the module with public/private keywords to mark if they are exported or not. If you want to save keystrokes one use the convention that no keyword=private so only public would be needed then. This pretty much garantees that you'll never forget to export your functions.- on the client side only public functions can be imported & used (encapsulation is enforced). If you really want to override encapsulation (e.g. for debugger tool/I.D.E., other frameworks (think Spring in Java) this can be done via reflection/metaprogramming but not regular programming.
- import <module> imports all public functions in the client namespace. import <module.function> imports just this function in the namespace. In case of name clashes, the names are fully written in the client code: <module.function> but they still need to be imported.- specify a version number next to the module name analogous to the C# mechanism & make the package manager aware of version number so that in dependencies can be precisely specified (similar to Maven,Gradle,...). This will save a lot of headaches once the library ecosystem starts to explode. R has this problem but now that it have thousands of packages already it's very hard to fix it anymore.* packages should not be just a linear list of names (like in R which sucks big time if there a thousands) but be hierarchically organised in broad categories (like in Java,C#) and this organisation is reflected as directories in the filesystem. Julia standard libs should start with julia and external lib with the name of the firm/organisation/other meaningful name and have namesegments with a separator (e.g. a dot). For example julia.core,julia.net, julia.graphics,julia.gui,julia.db,julia.io,... Logically organising the packages like this will pay off in the long run when there are many.
* When an exception is thrown, a full stacktrace should be printed starting at the place (module name, line number, function name) where it is thrown instead of printing the line number where it is caught which is pretty much useless. This would be a big improvement for debugging!* exception handling: catch clause is too primitive: since you cannot specify the type of the exception it catches all exeptions even if you do not want it to and hence then you have to test the type(s) with a bunch of if/else statements and rethrow the exception types that you do not want to catch. Java/C# is much better/compact here: just specify the exception type (or a list of types e.g. separated by ,) after catch and then only that type is caught.
* I find myself writing a lot of type annotation in my Julia code. Basically all function arguments & local vars that do not have an initial value get a type. I do this for self documentation & for helping the compiler generate efficient code. So my code is not that different than it would be from a statically typed language. Unfortunately type inconsistencies are only discovered at runtime and once you start specifying types in Julia (instead of being fully dynamic) it can be a slippery slope because it tends to go viral so that you need to narrow the types of other variables as well (e.g. String vs ASCIIString). I figure: if most of my code has type information anyway (& the rest can be easily inferred from type inference) why not go all the way and have the option of an ahead of time statically compiler that would flag type errors at compile time and that would generate a binary executable? I know this would make Julia a statically type language (or at least optionally like in Groovy) and some might just hate that idea thinking mostly of C or Java with little flexibillity but what I'm thinking of is more like F# or Scala:AOT compiled statically typed languages with type inference that also have a shell that behaves like a shell of a dynamic language. Given that Julia source code is anyway compiled (JITted) to native code using explicit type information if it is available I feel this is not such a big step and is a logical progression. By fully compiling ahead of time there might also be more time for more global optimizations (whole program optimization). If it would be statically typed (but with the flexibility of a dynamic language) it would also allow to build way more powerful I.D.E.s e.g. code completion, refactoring,...
using GtkGtk.Window
In the absence of any code completion in the current development tool this little thing quickly becomes a source of irritation.
* modules: I find the current way how modules are used in client code confusing & lacking.
There is no version data in the source code for a module
- specify a version number next to the module name analogous to the C# mechanism & make the package manager aware of version number so that in dependencies can be precisely specified
on the client side only public functions can be imported & used (encapsulation is enforced)
I find myself writing a lot of type annotation in my Julia code.
* packages should not be just a linear list of names (like in R which sucks big time if there a thousands) but be hierarchically organised in broad categories (like in Java,C#) and this organisation is reflected as directories in the filesystem. Julia standard libs should start with julia and external lib with the name of the firm/organisation/other meaningful name and have namesegments with a separator (e.g. a dot). For example julia.core,julia.net, julia.graphics,julia.gui,julia.db,julia.io,... Logically organising the packages like this will pay off in the long run when there are many.
To combine these two concepts, `require` can be used to make modules available, merging as described above, but no actual code invades the scope (the exception might be toplevel code, but I am not so sure that's a good idea).
require "foo"
This makes `Foo` available to we could now do:
Foo.bar(...)
Or we can `use` the available modules, bring their functions in, either whole clothe:
use Foo
Or piecemeal of any *exported* functions.
use Foo(bar, baz)
So now our code and have:
bar(...)
But we we can also define a compositional handle to use instead.
use Foo as f
use Foo(bar, baz) as f
Then in our code we can use it as:
f.bar(...)
The exact syntax not withstanding, that is all that is needed for the whole system.
HI Ian,Maybe it is more there than I realized, but there are also some differences.So first, as I explained about reading the docs and still being confused, I was mistaken about `import Foo` rather it is `importall Foo` that does this. So there is yet another function to add to the confusion. ;-)
The difference with `import` and `require` is that require is file based, which makes it clear what file is being loaded. If we depend on `import` for this, there needs to be enforced correspondence between module names and file names. Considering files can have multiple modules defined in them, I think it is much less confusing to use `require`, keeping the loading of code completely separate from `using` it in modules.Also, there is the difference concerning "open modules". For example:# foo.jlmodule Foofunction x ...end# foo/bar.jlmodule Foofunction y ...module Barfunction bq ...endend# main.jlrequire 'foo'require 'foo/bar'Would just work. `Foo.x`, `Foo.y` and `Foo.Bar.bq` would all be available for use.
require("Foo")using Foo
Hey, btw, what do you all think of Go's approach to exporting functions by capitalizing them?
+1 on include and import only.
I guess someone already proposed it but, why not using the Python approach here?
import Foo:***
to import all exported stuff (current using).
import Foo: bar, baz
to import exported bar and baz. Non-exported stuff throws an error.
import Foo
to import the module Foo only.
import Foo.priv_bq
to import anything from Foo, even non-exported stuff.
import Foo.***
to import everything, exported or not from Foo.
The rules are simple:
- *** means all
- : means access to the exported attributes
- . means unrestricted access to any attribute.
This is definitely an area that needs simplification. It has evolved over several years and there are some leftover bits and pieces that aren't really needed anymore. For example, there's no reason to use `require` these days – we should unexport it and deprecate it. The `importall` function is only meant to be used out of laziness when you don't want to mess around with specific imports while developing.The only functions that are really relevant are include, import and using. I've proposed merging import and using above, which would bring it down to two ways of loading code – include and import. These are quite different and both useful. The include function loads a file as if it were included in the current file. This is a simple mechanism for splitting a file up into multiple files, which one sometimes wants to do. It also allows the same code to sed in different contexts with different meanings.
The import and using keywords are both used to acquire package resources. They load a package if necessary and make it and its contents available. A package is only ever loaded once by this mechanism; after the first time, all these keywords do is manipulate bindings. The only difference between import and using (and importall) is what they do with bindings.
This is very Rubyesque – I'm assuming that's where your inspiration is coming from. Using path-based loading is ok in Ruby. The convention that paths are lowercase while classes are uppercase can be annoying at times (how exactly is the lowercased version of the camelcase class name you want is spelled – is there an underscore or not?). But mostly it's pretty good. And this is exactly how things used to work in Julia. That's why require exists. But it doesn't work well in Julia.
First, why does it work well in Ruby? In Ruby classes are namespaces. Defining a Ruby class is enough to start using it. Given the Foo class, you can construct a Foo object by doing `foo = Foo.new()`. Then you can use functionality of the Foo class by writing foo.bq(). The only thing that Ruby's require keyword needs to do is ensure that each path is only loaded a single time.Why does this not work well in Julia? There aren't any classes. The functions you want to use on types aren't "inside" the objects they apply to – they are separate, external entities. When you require("foo"), you still don't have access to any of the things that are defined in Foo – you need to have bindings to those things also.
Thus, inevitably, the code looks like this:require("Foo")using FooThis redundant code was rampant for a while. And it was really annoying. Why would I require "Foo" if I didn't want to use it? Likewise, if I write `using Foo` and there's nothing called Foo yet, it's not a wild stretch of the imagination that I want to require whatever provides Foo – typically a file named Foo.jl. Why can't this just do both for me at once: load the code if necessary *and* create the bindings I want. This is precisely what `using` now does. If you write `using Foo` and Foo does not exist, Julia looks in the usual places (Pkg.dir() and LOAD_PATH) for Foo.jl and loads it, with the expectation that it will define a module named Foo.
The ability to reopen classes in Ruby is arguably the most heavily abused feature in the language. "Monkey patching" – the colloquial term for opening a class up and altering it – is one of the biggest problems with the Ruby ecosystem. It's such a big problem that Ruby 2.0 attempted to add "refinements" – i.e. dynamically scoped monkey patching (it sounds much less, um, refined when you put it that way, right?). I say attempted, because the feature is so complex that 2.0 didn't end up fully implementing it.
Hey, btw, what do you all think of Go's approach to exporting functions by capitalizing them?I do not like it. Do all public names have to begin with an uppercase ASCII character? What about people coding in languages where there is no such thing as uppercase – do they still need to start everything with an English letter? What about operators? If you're allowed to start public names with characters in other alphabets, does the parser assume US English locale? Would being in a different locale change the meaning of code? Unicode capitalization is a horrorshow. Dealing with it in strings is bad enough, I really don't want it anywhere near the parser.
A submodule isn't anything special, and doesn't extra syntax. I'm not sure what that syntax would gain you?
I'm really not convinced these are problems. Maybe I'm being overly defensive, but while there is room for debate over the design I feel like the tone is that something is horribly wrong with Julia's module system right now. I'd suggest reading through the codebases of some of the larger Julia projects to get a feel for how these work in practice, because the only thing I can think of that is really causing any problems in them is conditional module includes, which is an open issue.
Consider if you have file a.jl made up of a bunch of functions. Then you include it in Foo and Bar modules in f.jl. How do we document the functions in a.jl? Will they be documents twice as functions of module Foo and Bar, or just once as the file a.jl, but then how clear is it how they are imported? Include is really anti-encapsulation --it isn't modular composition at all, it's just a cheap trick to copy and paste code automatically.
[include] totally destroys modular encapsulation.
In the end I had to make a choice between using the submodules or the file layout I wanted.
Consider if you have file a.jl made up of a bunch of functions. Then you include it in Foo and Bar modules in f.jl.
As Iain suggests, I think it would be worthwhile to scan over some packages and get a better feel for the Julian way of doing these things. In general if you want to take full advantage of Julia you'll probably have to embrace a slightly different way of doing things (just the same as if you switched from Ruby to Java, for example).
Well-written post.I think a lot of the things you mention are preferences based on your past experience, I can't really comment on most of that as its mostly opinion/personal preference - I like most of things you dislike, so, not much point in exploring that further.Other more actionable things:In the absence of any code completion in the current development tool this little thing quickly becomes a source of irritation.
The Julia REPL has code completion, and there are plugins for many editors - what development tool do you use? JuliaStudio. I haven't really looked for it. I just assumed it didn't exist (or would be just text search which is usually to coarse) based on experiences with other dynamic languages. sorry! I am just a Julia newbie.
* modules: I find the current way how modules are used in client code confusing & lacking.using: bring all exported names into the namespaceimport: bring only what you specifically importThe updated manual (which you might not have seen if you've only looked at the 0.2 release manual) explains this very well.The export thing is valid, as is the need to "reexport" things from nested modules. Needs work!There is no version data in the source code for a module- specify a version number next to the module name analogous to the C# mechanism & make the package manager aware of version number so that in dependencies can be precisely specifiedAll packages have version numbers? I don't get this one... We have quite good dependency handling, few rough edges but the core is pretty solid (and working well in production environments, apart from more human issues like people stopping support for old Julia version early). Maybe I'm missing something?on the client side only public functions can be imported & used (encapsulation is enforced)Well-discussed above, but this goes against a core Julia philosophy - "I like being treated like an adult." Documentation/naming is the solution to this. I've never found this policy to cause any problem in Julia code.
I find myself writing a lot of type annotation in my Julia code.I've seen people do this, and I'm not sure why. It doesn't usually help performance at all... its odd behaviour. I also see people putting `local` in their code and things like that.I only really use type annotation on function signatures to be defensive. It doesn't actually improve performance though. This is an education thing, I suppose.
well I don't do things like: local var::String="test, I write that as: var="test" but in function signatures all params that do not have a default value get a type annotation. Also once you start doing that types creep in in initalization code like : paths=String["test","test2"]
* packages should not be just a linear list of names (like in R which sucks big time if there a thousands) but be hierarchically organised in broad categories (like in Java,C#) and this organisation is reflected as directories in the filesystem. Julia standard libs should start with julia and external lib with the name of the firm/organisation/other meaningful name and have namesegments with a separator (e.g. a dot). For example julia.core,julia.net, julia.graphics,julia.gui,julia.db,julia.io,... Logically organising the packages like this will pay off in the long run when there are many.I agree that organization is good - we already have that in the human and technical senses of the word, e.g.The main benefit of this is interoperability of similar packages, and I think we've demonstrated that this is possible.I don't see any benefit to reflecting this in the file system thing, or what problem it solves. Java does this, but I've never been sure why. Certainly Python, for example, does not seem to have hit any problems with its approach.
I hope you stick with Julia! Some of the discussion in this thread is really great already, fresh users willing to share their opinions really helps kick the tires on things we just get accustomed to.
That was exactly my intention. Once you have a few months under your belt you do not mind those little idosyncracies that much anymore because you've becomes accustomed to them.
Thanks for taking the time to write all of this, it's helpful.
I agree being able to write "export function foo(x) ... end" would be
good. Having to write things in 2 places is always annoying.
> * When an exception is thrown, a full stacktrace should be printed starting at the place (module name, line number, function name) where it is thrown instead of printing the line number where it is caught which is pretty much useless. This would be a big improvement for debugging!
This one confused me: we do print stack traces from where errors are
thrown, e.g.
julia> function foo(x)
x += 1
error("oops")
end
foo (generic function with 1 method)
julia> foo(1)
ERROR: oops
in foo at none:3
If this were showing where the error was caught it would be somewhere
in the REPL code. Maybe post an example of the behavior you saw?
On Wed, Aug 13, 2014 at 7:25 AM, Steven Sagaert
I was assuming he was interested in extracting just a specific set of
functionality from a package and not the whole package. For example, with Grid
I organized it so that if you like the Counter iterator but don't need
interpolation, you can just load that one file.
On Thursday, August 14, 2014 02:22:26 AM Trans wrote:Two examples: "using Gtk.ShortNames" or "using SIUnits.ShortUnits"
> Is code re-usability being considered? Some questions:
>
> 1. How does one make a package that has more than one reusable module? For
> instance I have a Corpus program that can do word analysis, bigram analysis
> and letter analysis. As things stands importing Corpus makes all them
> available. How could I just import `Corpus.Bigrams` for example and not the
> rest?
(meaning: see how it works for these particular packages)
This is answered in the table:
>
> 2. If there is a submodule in another package, and I realize that it is
> almost exactly what I need for my program, how can I import it so that I
> can use it but with a modification (e.g. redefining a method or two)?
http://docs.julialang.org/en/latest/manual/modules/#summary-of-module-usage
> 3. If I find a useful set of functions in another package (not encapsulatedmodule MyWrappingModule
> in a module) how to I import these into my program?
include(Pkg("UsefulPackage", "src", "usefulfile.jl"))
Unless... you get rid of `module` altogether, and just make files = modules. You can do that. Then `include` and `import` become one and the same thing. But then we have to live with the one file = one module limitation.
Include is a terrible way to load code as I tried to explain before. It totally destroys modular encapsulation. I ran into this problem immediately on my first Julia program when I tried to use submodules and hit issues where the submodules could not be found. In the end I had to make a choice between using the submodules or the file layout I wanted. It doesn't work well with documentation either (which is really just a symptom of the bad encapsulation). Consider if you have file a.jl made up of a bunch of functions. Then you include it in Foo and Bar modules in f.jl. How do we document the functions in a.jl? Will they be documents twice as functions of module Foo and Bar, or just once as the file a.jl, but then how clear is it how they are imported? Include is really anti-encapsulation --it isn't modular composition at all, it's just a cheap trick to copy and paste code automatically.
First, why does it work well in Ruby? In Ruby classes are namespaces. Defining a Ruby class is enough to start using it. Given the Foo class, you can construct a Foo object by doing `foo = Foo.new()`. Then you can use functionality of the Foo class by writing foo.bq(). The only thing that Ruby's require keyword needs to do is ensure that each path is only loaded a single time.Why does this not work well in Julia? There aren't any classes. The functions you want to use on types aren't "inside" the objects they apply to – they are separate, external entities. When you require("foo"), you still don't have access to any of the things that are defined in Foo – you need to have bindings to those things also.But there are modules, and functions are defined *in* modules, i.e. units of encapsulation.
Thus, inevitably, the code looks like this:require("Foo")using FooThis redundant code was rampant for a while. And it was really annoying. Why would I require "Foo" if I didn't want to use it? Likewise, if I write `using Foo` and there's nothing called Foo yet, it's not a wild stretch of the imagination that I want to require whatever provides Foo – typically a file named Foo.jl. Why can't this just do both for me at once: load the code if necessary *and* create the bindings I want. This is precisely what `using` now does. If you write `using Foo` and Foo does not exist, Julia looks in the usual places (Pkg.dir() and LOAD_PATH) for Foo.jl and loads it, with the expectation that it will define a module named Foo.Because Bar might be defined in Foo.jl too. So you have to do this anyway to get Bar, and now things are getting confusing. It may seem annoying, but it is simple. Instead of `using Foo` looking for a file called `Foo.jl`, use `require 'Foo'` and Foo and Bar are both made available. If you really feel it necessary to load and use in one shot, `use Foo from "foo"` is very clear. So the annoyance is gone but you also don't have to worry about the other annoyance of file and module name correspondence.
The ability to reopen classes in Ruby is arguably the most heavily abused feature in the language. "Monkey patching" – the colloquial term for opening a class up and altering it – is one of the biggest problems with the Ruby ecosystem. It's such a big problem that Ruby 2.0 attempted to add "refinements" – i.e. dynamically scoped monkey patching (it sounds much less, um, refined when you put it that way, right?). I say attempted, because the feature is so complex that 2.0 didn't end up fully implementing it.I am an expert in this, and you completely misunderstand it. It is actually one of Ruby greatest strengths! It is what made Rails even possible. "Monkey patching" is a term that came from outside of the Ruby community and has been hawked by people who really didn't know what they are talking about. Refinements are just a way Matz devised that we could use to be more careful to not to step on each others toes. But many in the community actually don't think refinements are necessary b/c it's not something that is often a problem and when it is it is quickly resolved. Refinements haven't gotten much play, to say the least. But open classes are not going anywhere. I should point out too, that open classes are not just about "patching", they also make is simple to *add* functionality, including submodules, and thus conversely, simple to decompose functionality.
Yes, but that doesn't address the key difference: to conveniently use a class, you just need the class object itself. To conveniently use a module, you typically need to not just load the module, but also make bindings to things in it. The appropriate language to compare with here is probably C++, not Ruby, Java or Python. Not coincidentally, C++ also has both import and using and they work very similarly to Julia's.
include will never go away. It's the lowest-level form of evaluating a file---
everything else (require, using, import) depends on it. You need to be able to
do this, otherwise you don't have a programming language :).
It's also the correct way to write a "script." If you have a job you want to
run on a cluster, you want one machine to be the "task master" instructing all
machines what code to load, what datasets to load, etc. If you want to put
that information into a file, then "include" is the right way to launch it.
Something like "require" will cause all machines to try to be the task master,
launching N copies of the same computation.
Well-discussed above, but this goes against a core Julia philosophy - "I like being treated like an adult." Documentation/naming is the solution to this. I've never found this policy to cause any problem in Julia code.
Well I like being treated as an adult too ;) but being in industry and not academia and having worked on large multiperson projects it's nice to be able to ensure that other people are not going to use your internal code because that might change in the future and then the client code breaks... Not everybody is going to behave nicely and some people will use whatever they can get their hands on if they can take shortcuts even if they are not supposed to. It's all about being able to refactor large codebases in the future.
With modules there's no good reason for a package not to live in a single module – the namespace is decoupled from units of functionality (types and functions). If Bar is separate enough to warrant its own module, then it belongs in its own package. So the best practice is that a package provides exactly one top-level modules. That's been working out quite well.
Since Ruby was my primary programming language for much of the past decade, I rather doubt that I'm completely misunderstanding this. Call it what you will – "duck punching", "hot fixing", whatever – it seems inconsistent to consider Ruby's rampant use of monkey patching just dandy while castigating `include` for "destroy[ing] modular encapsulation". Wat? The Rails community does seem to have made peace with monkey patching, but that's largely because activesupport, which does the most extensive modifications to the standard library, staked out the territory first and you either fall in line with that, or nobody will use your package. This may explain why the Rails community doesn't seem to care much about refinements while Matz and others outside of Rails feel the need for them – refinements would free them from the de facto dictatorship that Rails has over the Ruby ecosystem.
But the way it is seems like a bad design to me, and very much reminds me of PHP.
With modules there's no good reason for a package not to live in a single module – the namespace is decoupled from units of functionality (types and functions). If Bar is separate enough to warrant its own module, then it belongs in its own package. So the best practice is that a package provides exactly one top-level modules. That's been working out quite well.
So far however not a single reply about my proposal for a catch that would be restricted to a specific type.
On Thursday, August 14, 2014 11:23:12 AM UTC-4, Stefan Karpinski wrote:With modules there's no good reason for a package not to live in a single module – the namespace is decoupled from units of functionality (types and functions). If Bar is separate enough to warrant its own module, then it belongs in its own package. So the best practice is that a package provides exactly one top-level modules. That's been working out quite well.So you are advocating for many tiny packages? Hence in the case of the Ruby ANSI library for instance, I should create a ANSIColor package, an ANSITable package, an ANSIProgressbar package, an ANSIString package, and so on?
They are generally called "core extensions". I've been programming Ruby since 2002 myself. And the fact that you call it rampant and contrast it to my castigating `include` I don't think is fitting on either point. First of all extensions are an amazingly useful and powerful feature. It is one of the main pillars that makes Ruby great. They make it possible to "extend" the functionality of preexisting objects. Extensions have one downside, that there could be name clash. And that is the only reason Ruby was given refinements. Matz could have easily given Ruby closed classes at any time if he thought they were bad, but if he had done so, so many gems would not have been possible, not just Rails. And I am pretty sure Ruby would have never have been popular.
By contrast include isn't doing anything of the sort, it's just copy and paste. So I am castigating include because it is not *modular* encapsulation. You say that's not what include is intended for. But in package after package that is basically how it is being utilized among the "consenting adults". I am seeing lots of Julia code that is not modular in design. Instead it's just a big bucket of functions using include to pour functions in from pseudo-modular files. Maybe that's what you all want. I am not sure why Julia should even bother have separate modules at all then (at least then the file would be the module and vice versa). But the way it is seems like a bad design to me, and very much reminds me of PHP.
it seems a little odd to call it safe exceptions, but then ignore all errors in the trap code.
i understand that the interpretation is that it couldn't be handled, but it feels very wrong.
also, in your example, you are also accessing a private field of an unknown exception type, which is strongly discouraged.
i think we should just strive towards having each error type be meaningful, and use rethrow when types aren't sufficient
On Friday, August 15, 2014 10:42:16 AM UTC+10, Jameson wrote:it seems a little odd to call it safe exceptions, but then ignore all errors in the trap code.On the contrary, it is called @safe try... because the catch block automatically re-trhows all errors that are not explicitly trapped.i understand that the interpretation is that it couldn't be handled, but it feels very wrong.Ignoring UVError is just an example. See here for more realistic examples: https://groups.google.com/d/msg/julia-dev/DQFEhzZcjvQ/X0WulUYOWQsJ"NoSuchKey" and "AccessDenied" are not ignored. The result "r" is set to the empty string. This implements a semantic where fetching a non-existent URL returns "" by default.
(AccessDenied means the same thing as NoSuchKey in this context, because the system returns AccessDenied instead of NoSuchKey if the caller does not have permission to list all keys. This prevents information about what keys exist leaking to a non-privleaged caller.)Again, this is deliberately very simple to keep the example short.
also, in your example, you are also accessing a private field of an unknown exception type, which is strongly discouraged.See my description of the @trap macro expansion below. This is very deliberate. You don't want to have to write a whole bunch of guard logic before the field access, that would be way too verbose.
i think we should just strive towards having each error type be meaningful, and use rethrow when types aren't sufficient
My feeling is that having a sub-type for every error is impractical for real world complex systems. If you are interfacing to a library, or a cloud API, that has hundreds of potential error conditions, you'd have to do something like dynamically create new exception types based on a XML/JSON message from the API. It makes more sense to have a FooAPIException class with codes for http_error_code, api_error_code, api_error_string, api_human_readable_message etc...
Filtering exceptions only by type seems like the kind of think you get stuck with in a statically typed system. A dynamic system should be more flexible.
adding type annotations with multiple catch blocks would be a good feature to have. i think nobody commented because this isn't controversial, just unimplemented. it would be relatively straightforward to rewrite it during lowering as a dispatch over a generic function
errors typically should occur when either the user forgot to check for a precondition (such as whether the key exists) or an unexpected event (such as an unexpected disconnect). other conditions should typically be handled by status codes in the normal flow of logic
...these should typically be normal status conditions, not errors. in fact, julia's current lack of filter exceptions partially stems from the notion that a program receiving an exception should be more focused on how to continue than what error occurred – and on avoiding errors in general. (that's not particularly well worded, but I couldn't think of a better way to phrase it)
I guess what you are saying is that many uses of include() in practice
ought to be replaced by multiple true submodules. That could be true
in some cases, but I don't see why it's such a huge deal. One package
is generally a small enough unit that nobody worries about name
collisions *within* it. Furthermore, users don't like having to write
everything as X.Y.func(). If a package needs to export hundreds of
functions from a single module, so be it. Technical computing often
involves large vocabularies, so this can be convenient. In many cases
dividing the functions into groups would not be of any use.
> I am not sure why Julia should even bother have separate modules at all thenIsn't that a bit of an overreaction? Would it really make no
difference to have no namespacing mechanism *at all*?
On Wed, Aug 13, 2014 at 7:25 AM, Steven Sagaert <steven....@gmail.com> wrote:
Hi,I've been programming a recommender system in Julia for a couple of weeks and overall I like much of Julia and the choices that have been made but I've also found some things that annoy me and that I'd like to see changed. I figure that at 0.2 it's still a very young language with a small user base & ecosystem so I might actually have a chance of some of my requests actually make it into a future release.* naming convention of the standard lib. For types Julia uses camelcase starting with uppercase (like Java for Classes) but for functions it's all lowercase with sometimes an underscore for separation. Having done a lot of Java programming this feels a bit weird to me. Why mix two different systems (camelcase & underscores?) Why not go for Java's system: camelcase (shorter than underscores) : function would start with lowercase (like Java methods) and types start with uppercase. I could live with all lowercase for functions but then I would like to see a consistent use of underscore for word separation. In the standard lib this is very inconsistent (many functions names have no separation where it should: e.g. haskey) and I find this very annoying since you never really know how a function name is written unless you look it up. In the absence of any code completion in the current development tool this little thing quickly becomes a source of irritation.
The trend has actually been moving swiftly away from any method names with underscores, (many have been removed or renamed). If there are still ones (in 0.3, which is about to be released officially any day) that you find irksome, please file an issue and I imagine there will be a good effort to rename or remove it. Apart from that, I personally really like the convention of camel case for types and all lowercase for methods.
None of these are good, and run contrary to modular programming. Which is a very important thing! Now, Stefan argues we are all "consenting adults" and we should just not do those things. I don't agree and think the system should be designed to promote good modular design. And in fact, if it were designed as such it would actually become much easier to use. Stefan worries that such "limitations" would somehow prevent us from doing some things (whatever they might be he doesn't say).
In general, I think it might be best if we stop using the "consenting adults" phrase as a counterargument. It comes across as a bit condescending – "Oh, you want restrictions on what you can do? Are you a child?" Maximal freedom isn't always the best – restrictions are sometimes a worthwhile tradeoff.
We were so close to having a productive conversation here and then this paragraph has to go and ruin it. Perhaps there is a native language issue here – this paragraph comes off as rather rude. At no point in this conversation or any other have I used "consenting adults" as an argument for anything, only ever as a description of more liberal approaches. In fact, the only time I used this phrase at all in this thread, I wrote:On Fri, Aug 15, 2014 at 12:31 AM, Trans <tran...@gmail.com> wrote:
None of these are good, and run contrary to modular programming. Which is a very important thing! Now, Stefan argues we are all "consenting adults" and we should just not do those things. I don't agree and think the system should be designed to promote good modular design. And in fact, if it were designed as such it would actually become much easier to use. Stefan worries that such "limitations" would somehow prevent us from doing some things (whatever they might be he doesn't say).
In general, I think it might be best if we stop using the "consenting adults" phrase as a counterargument. It comes across as a bit condescending – "Oh, you want restrictions on what you can do? Are you a child?" Maximal freedom isn't always the best – restrictions are sometimes a worthwhile tradeoff.Your rhetorical caricature of my position is inaccurate, a bit offensive, and I suspect that it undermines your position more than it helps it.
Steven, I'm sorry that your thread has gotten derailed. You brought up a lot of good points and much of the ensuing discussion has been very positive. If there are issues you still want to discuss, perhaps it would be best to start separate threads about them.
While we are at this point: Saying "To me the original Gtk.jl is a convoluted mess." is a pretty strong statement. I agree that this example reveals that the module system might need some more convenient ways as Jeff indicates in his last post. But seriously: Jameson (the main author of Gtk.jl) is not known to write "convoluted mess" but he is one of the smartest hackers here around.
Now I am sorry that I even tried to help make Julia a better language.
# Foo.jlmodule Fooinclude("Bar.jl")include("Baz.jl")end
# Bar.jlmodule Bar# barfy stuffend# Baz.jlmodule Baz# bazish stuffend
# Foo.jlmodule Fooimport .Bar
import .Baz
end
One thing that I think I've distilled from this conversation is that it may be good to have a mechanism besides include for loading submodules. This was actually something that we discussed when the module system was originally designed but decided it was too speculative until more experience was had. We've had that experience now and it does seem to be a thing. Consider this situation:# Foo.jlmodule Fooinclude("Bar.jl")include("Baz.jl")end
# Bar.jlmodule Bar# barfy stuffend# Baz.jlmodule Baz# bazish stuffendClearly include works here, but it would be nicer to write something like this:# Foo.jlmodule Fooimport .Barimport .BazendWe could automatically load relative imports relative (not a typo) to the current source file the way we automatically load absolute imports from LOAD_PATH. An open question is whether the file structure should be Foo/Bar.jl and Foo/Baz.jl or just Bar.jl and Baz.jl.