Easy to learn and use

What facets of a programming system (language, libraries, tools, etc) make it easy to learn and use?

If you were to design from scratch a new programming system, what choices would you make in order to attract users? To what extent would you compromise the design in order to do so?

Comment viewing options

Cake and Eat It

I should hope that one would not have to compromise the design in order to make it attractive to users. Furthermore, any truly successful language has, almost by definition for me, a good (successful) design. But I think your questions can't be answered without first answering others: what kinds of users are you looking to attract? What is it you want them to be able to accomplish with your language? Etc.

Familiarity

1. Familiary: To make it easy to learn and use, the system should 'look like' something the user(developer here) already know: that's why many new systems look like C.

[ As an aside, I've often wondered if a Smalltalk clone which looked a bit more like C (using {} for code block instead of [], etc.) wouldn't be more successful. ]

2. Readability: features which improve readability in the language such as local type inference in statically typed language which reduce the clutter.

3. Maintainability: features like variable declaration which avoid frequent mistakes.

Of course all these point are somewhat contradictory:
-C's syntax for variable declaration suck for example (Limbo's one is quite good).
-some people consider explicit variable declaration clutter even though it prevents mistake (I remember that when they used some checker on Ruby's system, they found some mistakes which most likely would have been avoided if Ruby had an explicit declaration syntax): the solution is IMHO to keep explicit variable declaration, but do it in a 'low key' way: no separated section and for example use ':=' to declare a variable instead of '=' for normal assignment or a short keyword such as 'var' not 'variable'.

Supercollider

[ As an aside, I've often wondered if a Smalltalk clone which looked a bit more like C (using {} for code block instead of [], etc.) wouldn't be more successful. ]

Supercollider, James McCartney's music synthesis language, is exactly that. (BTW, James reads LTU.)

The examples I've seen looks really nice, I wonder how such jewel can stay totally unknown like this: it even has a real-time GC!

Looking at it further, I don't wonder anymore: the website is mmmh not too much informative (in comparison to say D's website) ,it used to be Mac centric (probably the platform where there is the smallest number of programmers), and it seems like the language is seen only as a part of the music synthesis environment, a very specialised topic.

a bit unfair, maybe...

The examples I've seen looks really nice, I wonder how such jewel can stay totally unknown like this: it even has a real-time GC!

Actually, I believe Supercollider is very well known, at least among those interested in music synthesis. A quick google search returns two-thirds as many results for "supercollider synthesis" as for "max/msp synthesis", which of course is a highly scientific way to verify my intuition...

I meant unknown in the

I meant unknown in the developers community: the language looks interesting in itself as it has features that 'fashionable' language don't even dream to have (a realtime GC in Ruby or D? Hah!).

But the more I have looked at the (messy) website, the more disappointed I am about the documentation :-(

OK, I've finally found some

OK, I've finally found some documentation.

The language is interesting, but they should have removed Smalltalk-like restriction to have the variable declared at the beginning of the function.
Between the declaration and the assignment, the variable is in 'nil-space' which will create a runtime error if you do something with it: it's much better to declare it when you want to use it, so any 'premature usage' of the variable wouldn't compile.

Also, by default list are ',' separated and function calls are by position not by keyword. This looks more C-ish but I'm not sure that it's the best choice.

variables and arguments

they should have removed Smalltalk-like restriction to have the variable declared at the beginning of the function.

I agree now that 'let' would have been better, but at least SC is better than scripting languages that allow assignment without declaration leading to ambiguous scoping.

function calls are by position not by keyword

Any function argument may be passed by position OR keyword at the option of the caller. Keyword arguments may appear in any order.

Thanks for your answer, note that IMHO the language is ok but the big problem is the documentation: the website is a mess, and it has broken links, so I nearly gave up searching about the documentation (took me three frustrating trials).
All the documentation talks about the music synthesiser and nearly none about the language itself..

SuperCollider

Yes I am here, but I missed this thread. There is an effort afoot on the sc mailing list to come up with a better website.

As far as using := for assignment: Once when I was proposing a few syntax changes, one of the more prominent users said "are we going to have to start using little penises for assignment too?" I will never look at colon equals the same way again !

Let ?

no separated section and for example use ':=' to declare a variable instead of '=' for normal assignment or a short keyword such as 'var' not 'variable'.

... or 'let', as in most functional programming languages and JS2.

"There's only one way to do it"

Don't give users more than one way to do something. If a new user sees that there are multiple, equally valid ways of accomplishing the same thing, not only must they expend extra mental effort to decide which one of the three ways they are going to use to accomplish their task at hand, they will also feel obligated to learn the other ways so that they can understand extant code. At best, they will recognize this as a redundancy in the language. At worst, they will be confused, learn only one way of doing something, and become frustrated when they are unable to read someone else's code which uses one of the other ways of accomplishing the same thing. Either way, the additional constructs increase the apparent complexity of the language increases, which can easily put off new users.

Some examples (both good and bad):

• I recently heard a classmate who needed to learn C for a project lament that the tutorial she was following introduced more than one way to open a file (open, fopen, C++ iostreams), all seemingly equal in her mind. She was put off by this and switched back to Java at the risk of the professor's ire.
• Python is widely heralded as an easy-to-learn language, although its power is certainly not limited by this. Why? Because above all, GvR sticks to the motto "there's only one way to do it". Newcomers to the language know that there's only one way to open a file, so they remember it easily and never have to make the extra decision newcomers to C do. (There are in fact multiple ways of opening files in Python but they are hidden in modules and not introduced to newcomers.)
• Python has another leg up in the learning wagon: its syntax is very uniform. Classes, functions, exceptions are all defined using a similar syntax, which in turn follows the same colon/indent block structure idiom that the rest of Python uses. By keeping the syntax uniform, newcomers need only learn one way of making a block and can focus instead on the semantic differences of each type of block. (C is good in this respect also.)
• As much as I love O'Caml, its syntax actually fails where Python succeeds. Each construct requires a different block structure: there's begin/end (identical to parenthesis but intended to be used with blocks of code), { } for records, do/done for imperative loops, and object/struct/sig/end for objects and modules. Although the rules for using each construct are well-defined (and make the grammar easy to parse), it's hard for a newcomer to remember to write "module Foo = struct .. end" but "class foo = object .. end"... the reasons for these seemingly arbitrary requirements become apparent only when advanced features such as anonymous modules and classes are used. (OTOH, O'Caml's standard library is a good example of the TOOWTDI paradigm.)

I rank having a solid tutorial second to "TOOWTDI". Why? If there really is only one way to do it, it's easy for an inquisitive user to discover that one way on their own, either by reading the reference manual, header files, or other people's code, and feel assured that it is the right way. Combined with a consistent syntax, the tutorial often need only contain enough examples to set them on their feet - they may then proceed directly to the manual knowing that not only will they understand the syntax of all the constructs they find, but that those constructs will be the "right ones" to use.

I would say that Python

I would say that Python doesn't even go enough "TOOWTDI": it should accept only whitespace for blocks and reject tabs.

Not related but it should also fully unite type and class and get rid of all these annoying self and it would good.

Java files are "simple"?

I recently heard a classmate who needed to learn C for a project lament that the tutorial she was following introduced more than one way to open a file (open, fopen, C++ iostreams), all seemingly equal in her mind. She was put off by this and switched back to Java at the risk of the professor's ire.

Java file I/O is "simple"? I'm sorry, but I have to laugh at this. Remind me, which is the correct combination of FileReader, BufferedReader, Reader, FormattedInputStream, FileInputStream, etc?

To me, this just shows that familiarity is what matters to most people most of the time. Actually, this is an argument against "TOOWTDI", since a language that caters to several other languages' ways to do things can be familiar to users of all of them.

I laughed too

As soon as she had said this I thought back to my Java days and all I could remember was the mess you brought up :) The source of her trouble was not that C was more difficult than Java, but that in Java she already knew the "right way" to read from a file (whatever that may be, I'm not sure myself), whereas in C, she was presented from the start with all the available methods to open a file and was confused by the choice.

He doesn't always stick to it

Python is widely heralded as an easy-to-learn language, although its power is certainly not limited by this. Why? Because above all, GvR sticks to the motto "there's only one way to do it".

Suppose you to want to do something to each element of an array in Python. You can choose from:

* for loop
* map
* list comprehension

Fairly distinct use cases

List comprehensions are used where you'd have used map, before, except that you can have an 'if' in there as well.

If you're doing...

for x in y:
out = []
blah = ...
out.append(blah)
return out

...then you should be using a list comprehension. :)

And apparently map() is going away precisely because of the duplication with
[myfunc(x) for x in y] .

Simplicity at all levels.

For me, simplicity is the most desired property of any programming system. I am not talking about simpleness. My choices are:

1. incremental development: it is quite frustrating to have to restart a program so as that you can catch a bug that happened in a place which you missed. This does not mean that the program would only be interpreted though: the final version could be compiled to assembly code.

2. a web IDE: an IDE accessible as a web application, with support for source control, databases and UI development. This would be geared towards live development at the customer's site: the various forms would be developed in the presence of the client, along with the back-end database and business logic. The final result would be downloadable and installable at the customer's site with the click of a button.

3. internet import: the programmer would be able to do a import 'www.mycompany.com':mylanguage.mycompany.mymodule, and the run-time environment would take care of downloading/updating/caching the modules on a need basis (i.e. only if an update exists). Modules would be automatically versioned and existing in a hardcoded directory for each O/S, so as that there are no issues like where is the home directory etc. Since the modules would be versioned, and the run-time system would select the most appropriate module (the most recent and compatible version), there would be no reason for the developer to do maintenance of the language, as in Java. This would also allow the distribution of rich client applications in a very easy way (just download an exe, and the rest of the modules will be downloaded when needed). All modules would include a public encryption key as well as a list of internet addresses of the origin, so as that the run-time system can do encrypted requests to the publisher of the module.

4. simple syntax: basic-like, but most of the language's constructs would actually be macros. For example, all the for/while/repeat constructs, try/catch/finally constructs, switch/select case constructs, SQL constructs, HTML/XML constructs would be provided as macros.

5. functional programming: functions as first class entities, closures, currying, a strong and static type system, tail recursion optimization etc. The language would also have assignment and garbage collection.

6. Direct interfacing with C and the host O/S: at the language's lowest levels, everything would be translated to structs, pointers and subroutines. Array accesses would be safe, and there would be no address-of operator (so either you use pointers to heap allocated structures or you don't).

7. layering of APIs: at the lowest layer, the programmer would be able to access each O/S's native API; for example, accessing files in Windows with OpenFile. The next layer would provide an abstraction over common O/S facilities with simplicity in mind; for example, a File object where the programmer can open and read various data structures from it. The final layer would be a set of macros that provide the necessary infrastructure for today's projects, allowing most tasks to be declarative; for example, declaring a DB schema or a GUI with a simple S-expression-like language. Java-like complexity would definitely be avoided.

8. automatic multithreading: since multithreaded programming is very difficult, the programmer should not have to deal with its issues. And since we are entering the age of multiple cores, we need programs than can scale in a linear fashion to the number of available cores (I do not know if it is entirely feasible, but the demo I am working on goes very well - for the curious, it involves continuations and computation job queues).

9. 'invisible' object-oriented programming: functions would be 'multimethods', i.e. all parameters of a function would be taken into account as predicates for the actual function to execute. This not only simplifies object-oriented code, but it makes syntax nicer (no need for many levels of identation) and solves a fundamental problem of OOP (where to put code that does not belong in one single object).

10. common binary interface for all architectures: 'int' would be 32-bits on all machines, for example.

Overall, such a system would solve quite a few problems, mostly not related to programming languages themselves, and it would seriously minimize the development time of projects (I think :-)).

and then you lost me

functions would be 'multimethods', i.e. all parameters of a function would be taken into account as predicates for the actual function to execute. This not only simplifies object-oriented code, but it makes syntax nicer (no need for many levels of identation) and solves a fundamental problem of OOP (where to put code that does not belong in one single object).

I was following you right up until this section. Could you elaborate a bit?

Multiple dispatch

In "traditional" OOP, the subroutine to execute is chosen by the object type. For example:

class TextBox extends Widget {
public void setContent(String s) ...
public void setContent(Int i) ...
}

class Button extends Widget {
public void setContent(String s) ...
public void setContent(Int i) ...
}

Widget a = new TextBox();
Widget b = new Button();

a.setContent("hello world");
a.setContent(1);
b.setContent("hello world");
b.setContent(1);


In the above example, the code a.setContent("hello world") invokes the function 'setContext(String)' for a TextBox, because behind the Widget 'a' lies a TextBox. Same goes for Button.

It would be simpler if we could do the following:

class Widget ...

class TextBox extends Widget ...

void setContent(TextBox tb, String s) ...
void setContent(TextBox tb, Int i) ...

class Button extends Widget ...

void setContent(Button btn, String s) ...
void setContent(Button btn, Int i) ...

Widget a = new TextBox();
Widget b = new Button();

setContent(a, "hello world");
setContent(a, 1);
setContent(b, "hello world");
setContent(b, 1);


(Excuse the Java-like syntax - only used for illustrative purposes).

In the above example, the code setContent(b, "hello world") would call the Button version of 'setContent', just like in 'tradional' object-oriented code.

Selection of the appropriate routine to call would also be done not only on the 1st argument, but on the 2nd argument as well.

The benefit of this approach is that, if, for example, wanted to code routines that handled collision between shapes, it would be very easy; for example:

void checkCollision(Rectangle a, Rectangle b) ...
void checkCollision(Rectangle a, Circle b) ...
void checkCollision(Circle a, Circle b) ...
void checkCollision(Circle a, Rectangle b) ...
...


I could easily then check collisions of different objects kept in a list:

List<Shape> list1 = new List();
for-each(a; list1) {
for-each(b; list1) {
checkCollision(a, b);
}
}


What's the difference?

The multi-method approach you're talking about looks semantically identical to the "traditional" approach, where "this" is an implicit parameter for every method call (python even requires the parameter to be explicit in the method declaration, but passes it implicitly in method invocations).

So what am I missing?

With multimethods, dispatch is based on the types of all of the arguments, not just on the type of the receiving object. See Multiple dispatch.

(The example given in the grandparent comment intended to show this happening with the checkCollision method, where dispatch to the appropriate definition of checkCollision occurs based on the second parameter as well as the first.)

In a nutshell, the basic idea of multi-methods is to unify overloading and dynamic dispatch into one concept, essentially by resolving overloading dynamically.

An even more general concept are multi-parameter type classes, as found in implementations of Haskell. They allow you to dispatch not only on arguments, but on any portion of a type signature, including the return type of a function. This is because resolution is decoupled from values/objects and does not require the presence of representatives, as is the case in OO.

Predicate dispatching

In a nutshell, the basic idea of multi-methods is to unify overloading and dynamic dispatch into one concept, essentially by resolving overloading dynamically.

http://pag.csail.mit.edu/~mernst/pubs/dispatching-ecoop98-abstract.html

to contrast with single-dispatch OOP

Suppose checkCollision (all forms) also took, say, a third argument that is *not* a part of the dispatch (e.g., the color to paint the intersection, if any, of the two shapes involved in the collision). Here's a syntax that might be more suggestive of what multiple dispatch does (i.e., it might be more helpful to the folks who are accustomed only to single-dispatch languages):

for-each(a; list1) {
for-each(b; list1) {
(a,b).checkCollision( color );
}
}


To me, this makes it slightly clearer that the checkCollision call is being dispatched depending on the types of both a and b.

Doesn't that violate encapsulation?

Surely, the consumer of a library object/method should not need to be aware of implementation details such as which arguments are involved in dispatch decisions? Suppose I later came up with an optimized implementation of checkCollision which specialized on the color White. Then all consumers of checkCollision would need to adjust their calls.

Dylan has multiple-dispatch OOP (with single-value specializations); it's worth looking at as an example of how it can work.

Yep

Yes, you're right. That syntax was intended to be a simple illustration, aimed for people used to object.method(args) syntax, that might show quickly, what multiple-dispatch OOP is about.

So if I read this right...

I take it "multi-methods" are very similar to "overloaded unbound static methods", with the critical distinction that the choice of overload is made at runtime, based on the actual types of the arguments, rather than at compile time based on their declared types.

Is that a fair capsule description?

Yes, the only real

Yes, the only real difference between C++'s operator overloading and Lisp/Dylan/Slate/etc's multimethods are the point at which they are bound. Though, for Common Lisp and Dylan, methods can be early bound by using optional type declarations.

Another language, Nice, uses a form of multimethods to make source code look better. I have to say, I think having multiple method/function declarations is a lot easier to read than a mess of if ... elseif ... else blocks, not to mention, easier to maintain.

Macrology

I was under the impression that getting macros working really well from a usability+power standpoint was not an entirely solved problem. Are there really super great macro implementations? How does it blur into generalized multi-staged programming?

Plenty of good ideas

Do you know how much I appreciate you all of a sudden ?

I'm not sure I agree with points 8. and 10., but I like everything else. I would add the following points

11. Allow constructions to enforce protocols.For instance, instead of having two functions file_open and file_close, introduce a macro file ... in ..., automatically closing the file and cleaning-up at the end of the block.

12. The possibility of switching off the type system Although I'm an advocate of strong, memory-safe, type systems, I believe it may be useful to shut it down for teaching purposes.

13. Runtime errors with causality Add an assert-like mechanism giving the possibility to tag offending values. The debugger should then be able to trace how that value came to be.

14. Simple error messages (By interaction with the IDE) make sure that the compiler reports are easy to read. If necessary, "fold" the error messages so as to make it easy to understand what category of error message this is, then when unfolded, print the details, then when unfolded print comments on the error message, then when unfolded print additional advice, then when unfolded print additional information (e.g. where the offending variable has been declared, why the compiler believes that it has such type, etc.)...

15. Type-time static checking In the IDE, check whatever is possible without having to push a single button. Use word-processor-style "underlined in red" for syntax errors, "underlined in green" for type errors, etc. Print suggestions.

16. Built-in extendable static check system Make the type system scriptable, Ã  la Uno, so as to allow the simple addition of custom static checkers.

8'. Near-transparent distribution Have distant thread creation be a one-line operation. Include Join-calculus-style (i.e. JoCaml, Polyphonic C#...) remote join-function calls.

Orthogonality

While a bit less specific than some of the other suggestions, orthogonality of features makes both learning and use easier. Learning is easier, because the novice can more easily focus attention on an individual facet of the language rather than having to deal with essentially everything right from the start. For usage, language facets can be combined without unpredictable interactions.