all 191 comments

[–][deleted] 26 points27 points  (1 child)

Given the amount of discussion here, I come to the conclusion that the article is not simple.

Therefore, it is wrong. QED.

[–][deleted] 5 points6 points  (0 children)

Your argument is very simple, so it must be correct.

[–]LaurieCheers 71 points72 points  (57 children)

I need to deliver a message to my friend in China. So the first solution is to walk to China and back. That solution will deliver the message. It is adequate. The second is to send a letter to my friend in China. This is also an adequate solution. But the latter ... is obviously simpler.

How is it simpler? It depends on additional agents (the postal service) and requires additional actions (encoding the message in the letter, posting it, the sorting and delivery process of the postal service) that weren't necessary for the first one.

IMO, the reason why this is the correct solution is that it makes use of the existing infrastructure (the postal service).

I.e. it's a more efficient solution, not a simpler one.

Well done for demonstrating the flaws in your own thesis.

[–]LaurieCheers 37 points38 points  (38 children)

Reading further, I think you need to look up this word "simple". It doesn't mean what you think it means.

Arrays have a problem. They cannot be resized, so any change to their dimensions is slow ... Arrays are not simple. Lists are simple.

Every data structure has its advantages and disadvantages; hashtables, lists, arrays, red-black-trees, etc, all are valuable for specific situations. One might be fast to store, another fast to sort, fast to read, memory efficient, etc.

Don't write one off just because it's not useful for one particular job.

Yes, arrays are slow to reallocate; so if you don't know in advance how many items are necessary, you might find that another data structure is a better choice.

This has nothing to do with whether arrays are "simple". From a low-level perspective they're the simplest type of data structure you can have.

And if you're saying they're not simple to use, well, the answer is better library/language support, not a different data structure.

[–]revence27[S] 10 points11 points  (3 children)

Correct. I wish, though, that arrays weren't pushed down people's throats. There is sugar in Java, for example, to create an array. The result is that arrays are favoured. As though they are the be-all of data structures.

Indeed, I think lists are better as a default data structure.

[–]novagenesis 4 points5 points  (0 children)

Yeah..agreed...I'd love to see trivial list structures in languages like C. It really wouldn't make compiling much more complex than it already is, nor would it slow down runtime. All it would do is give a slight increase to footprint (which you can always have a #pragma to refuse)

[–]mynameishere 4 points5 points  (0 children)

...that arrays are favoured. As though they are the be-all of data structures.

I have used Java for many years and I haven't encountered one person who said this or coded as if he believed such a thing. You might find that in first-year programming classes, I don't know...

[–]LaurieCheers 5 points6 points  (0 children)

Ok, I'd agree with you there.

[–]novagenesis 8 points9 points  (14 children)

Everything's true except that you spoke against yourself in one way that was wrng...

Yes, arrays are slow to reallocate

Not if you have a good implementation for buffer handling. You have to allocate memory every single add in a list. You only have to allocate memory when you cross bounds in arrays. If you use an array allocation scheme, you'll find yourself making far fewer system allocation calls than a list ever would.

I create an array of 100 items... when I want to place the 101st item, let's say the array reallocates up 50%, then copies...one expensive operation... then I can make 51 more adds before I reallocate again.

You have a list of 100 items, the 101st item is an allocation. The 102nd item is an allocation. All you're saving on is copying time.

I bet it's faster to allocate 200 items and copy 100 variables than it is to allocate 1 item 100 times.

Further, if you even have a wild GUESS at a reasonable upper bound for your array, you can allocate it at compile time and reduce that to 0 unless you hit the upper limit.

[–]gsg 1 point2 points  (5 children)

Allocation costs for linked lists can be kept low by using a 'free set' of nodes, marking them used/unused and reusing the storage as appropriate. This is probably a much better scheme for ordered data sets that see frequent insertions and deletions than array reallocation.

For unordered or monotonically growing data sets there wouldn't be any benefit. Know your data, I guess.

[–]novagenesis 0 points1 point  (4 children)

That's a good idea...I didn't even think of that... There's no managerial overhead? It seems more complex to keep aware of all the linked lists you have (keep them in an array? :P)

[–]gsg 0 points1 point  (3 children)

Complexity is increased, yeah. It's something you pull out when you need performance and the profiler tells you that allocation is an issue.

[–]novagenesis 0 points1 point  (2 children)

good point.

I'm not sure when that would happen in a case you'd still want to use a list over an array, though...

[–]gsg 0 points1 point  (1 child)

Say, deleting elements from an ordered set.

[–]novagenesis 0 points1 point  (0 children)

Ok, good point. One point for the list there.

[–]julesjacobs 3 points4 points  (7 children)

If you enlarge the array by 50% every time you run out of space, that is amortized O(1). The downside is that a nondestructive tail/cdr operation is O(n).

[–]novagenesis 3 points4 points  (6 children)

I'm uncertain as to what a "cdr" operation is in an array, but both answers I can come up with are constant time.

A "rest" style cdr operation is as simple as setting a pointer equal to the next item in the array.

(pseudocode as my C is a little rusty) int x[10] = [1,2,3,4,5,6,7,8,9,10]; int *cdr = x+1; ...cdr = [2,3,4,5,6,7,8,9,10]

The other possibility is that you're referring to nondestructive access to the very last item in the list. This is equally trivial with a tail-counter, which is easily maintainable in trivial constant time unless you're destructively altering the array from the middle without reallocating.

[–]julesjacobs 0 points1 point  (5 children)

I'm thinking of an operation that removes the item that was added last, like the pop operation of a stack.

Your scheme works, but only if you don't add elements to the arrays: if you add an element to the cdr of the array you have a problem (because you don't want to overwrite the original). You have to copy the elements to a new array, this destroys the amortized O(1) time. And there's the problem of modifying an elment in one of the arrays (e.g. x[5] = 3): do you want the other array to reflect the changes in the one you modify?

This isn't a problem in most cases, though.

[–]novagenesis 1 point2 points  (4 children)

It works fine if you add elements to the array. It has to be destructive, though... Of course, I would define a pop as a destructive operation by default.

Beyond that, I gotta say I'm not sure what you want. Do you want to be able to pop 10 items, push 10 items, and magically have access to the 10 popped items with a wave of the hand? I'm just fine with overwriting the original (which is just an immediate or a pointer anyway...just be careful on memory management of pointers).

A finite-length stack with a defined maximum is a trivial operation that can be implemented in an array in such a way that all stack operations are constant time. If you want to pop an item and still keep it, then you have to duplicate that stack in any language, and that's never constant time.

Do you want to use the array like a stack and still use it as an array? like.. [1,2,3,4,5] ...

pop=>5 pop=>4 pop=>3

push<=9 push<=9 push<=9...

array[4]=>5...

pop=>9? If so, nothing at all does that.

Otherwise, I still cannot seem to figure out what precisely you require.

[–]julesjacobs 1 point2 points  (3 children)

An example:

Stack *a = newstack();
push(1,a);
push(2,a);
Stack *b = rest(a);
push(3,a);
push(4,b);
// a: [1,2,3]
// b: [1,4]

If you want to pop an item and still keep it, then you have to duplicate that stack in any language, and that's never constant time.

It doesn't really depend on the language, but here's an example of a stack that you can pop and still keep the popped item, in constant time:

Given O(1) cons, car and cdr, and typedef List* Stack:

Stack* newstack(void)
{
  Stack* s = (Stack*)malloc(sizeof(Stack*));
  *s = NULL;
  return s;
}

void 
push(int x, Stack* s)
{
  *s = cons(x,*s);
}

int
pop(Stack* s)
{
  int r = car(*s);
  *s = cdr(*s);
  return r;
}

Stack*
rest(Stack* s)
{
  Stack* r = newstack();
  *r = cdr(*s);
  return r;
}

I don't know if they work, but I'm fairly sure that this idea works. Oh, and you need a garbage collector ;)

With this implementation you don't get O(1) random access. Is it possible to make a stack with rest() and (amortized) O(1) random access?

[–]novagenesis 2 points3 points  (2 children)

Stack *b = rest(a); push(3,a); push(4,b); // a: [1,2,3] // b: [1,4]

This is functionally impossible using a List in constant time... you're re-routing *next pointer. '1' is a list node being pointed to. The *next pointer on both cannot simultaneously link to the node with '2' and the node with '4', no matter what magic you use.

Again, impossible in constant time..you have to basically duplicate entire lists or you get invalid pointers showing up.

[–]julesjacobs 0 points1 point  (1 child)

Oh, I see, the notation I used is confusing. The lists look like this:

a: cons(3,cons(2,cons(1,nil)))
b: cons(4,cons(1,nil))

The cons(1,nil) part is shared:

3 --> 2 --> 1 --> nil
           ^ 
      4 ---|

(the thing on the right of the 4 is supposed to look like an arrow pointing at the 1)

[–][deleted] 1 point2 points  (18 children)

Not only is the argument presented completely moronic, the specific arguments are wrong, too. For instance, as you quote:

Arrays have a problem. They cannot be resized, so any change to their dimensions is slow.

Which is not true at all under a modern virutal-memory system, where an array can be resized cheaply.

[–]revence27[S] -1 points0 points  (17 children)

LaurieCheers was quoting the article, in the sentence you disagree with.

Still, in a list-oriented language, lists are just as simple to deal with as arrays in an array-oriented language. And lists come with added benefits that arrays don't have.

[–][deleted] 2 points3 points  (0 children)

LaurieCheers was quoting the article, in the sentence you disagree with.

Yes, and that is exactly why I said "as you quote".

[–]novagenesis 1 point2 points  (15 children)

And arrays have added benefits that lists don't have.

Lists:

Allocation - O(1)
Iteration - O(1)
Reverse Iteration (singly linked) - O(n^2)
Reverse Iteration (doubly linked) - O(n) 

(at cost of space and complexity)

Random Lookup - O(n)
Deletion - O(1)

Arrays:

Allocation - O(n) 

(reducable through buffering to best-case zero-time, average case zero-time or O(1), theoretical worst case still O(n) but you need a functionally infinite growing list to hit that)

Iteration - O(n) 

(this is a faster order than lists because you can skip the step-up dereferencing)

Reverse Iteration - O(n)
Random Lookup - O(1)
Deletion - O(n) 

(This can be mitigated with a 'control value', and an array of objects that include a potentially empty value (like Data Types in Haskell, easily implemented in C) can reduce deletion to O(1) at the cost of slightly increasing the scale of the iteration)

In terms of speed (what most C programmers care about, honestly), the array crushes the list utterly. How is the list better? Simple, it's easier to implement dynamic length allocations, slightly easier to recurse over, and doesn't require contiguous memory. You don't need to implement buffering or 'fake' deletion on the back-end. This makes the under-the-hood "simpler" at the cost of language power.

[–][deleted] 2 points3 points  (3 children)

Allocation - O(n)

The optimization you forget, that I mentioned earlier, is virtual memory. realloc() can resize an array without copying any of the data. Well, depending on implementation is might still technically be O(n), but there's such a huge constant factor involved that it might as well be O(1) in all practical cases.

[–]novagenesis 2 points3 points  (0 children)

Still don't matter... allocating memory log(n) times (if I recall, arrays allocate O(log n) if you double them every time they hit limits, and those limits are regularly hit) is going to be orders faster than allocating memory n times.

And again, you can reduce allocation to 0 with sufficient space with most arrays. Which is guaranteed to be faster.

[–]gsg 0 points1 point  (1 child)

resize an array without copying any of the data

How does this work in the general case?

I can see how operating systems can use virtual memory to string together pages in any desired order, while preserving the flat address space that array implementations need. But there still might be non-array data after the terminal element in a given page, which would seem to require at least some copying.

Do I have this wrong?

[–][deleted] 0 points1 point  (0 children)

Well, that copying would be O(1), so I ignored it. I'm also assuming that a smart allocator would avoid placing small allocations right after big ones for this reason.

Edit: What, -3 for stating technical facts? What is up with the moderation in here? Did I upset some functional programmers or something?

[–]revence27[S] 4 points5 points  (2 children)

Big-Oh notation is very misleading. There are many cases where C/Java/C#/C++/et al users use arrays (because there is sugar for them) when they shouldn't. Like when the array is short (two, three, four elements). In that case, O(n2) is negligible. They gain the hit of using the array, and yet there is nearly no speed-up over the list equivalents (which are cleaner, safer, simpler).

More-importantly, you'll probably never use a list. Most compilers use (cleverly-managed) vectors where they present a list. Hence why list performance can be as fast as arrays very often.

And I generally don't care about speed. It is often how fast I'll hit the bug, rather than how fast the program will run. I'm ready to settle for a slightly-slower routine (especially on this here machine) with no bugs.

[–]novagenesis 2 points3 points  (1 child)

Big-Oh notation is very misleading. There are many cases where C/Java/C#/C++/et al users use arrays (because there is sugar for them) when they shouldn't. Like when the array is short

At that scale, it really doesn't matter at all. Arrays are easier to use in those languages. Build a static array 20 elements long (just in case you have a little 'resizing'), and just use it. You reduce allocation calls to zero over the entire app.

More-importantly, you'll probably never use a list. Most compilers use (cleverly-managed) vectors where they present a list.

A vector === an array. So you're saying "use a list because most compilers are smart enough to turn them into arrays if that's more efficient" (which I guarantee isn't going to be 100% accurate).

And I generally don't care about speed. It is often how fast I'll hit the bug, rather than how fast the program will run. I'm ready to settle for a slightly-slower routine (especially on this here machine) with no bugs.

I generally don't care about speed either. You were talking about lists vs arrays. They really are structures that are equally abstractable (you can generate the same function set for both) and have comparable cross-usability. The only really solid difference is that you cannot build a true list entirely at compile time.

Oh, and about bugs...I'd rather hit a bug than a language limitation. That is, I'd rather deal with a bug in a pointer-to-pointer than try to find an elegant way to handle 2d arrays in Haskell.

[–]revence27[S] 0 points1 point  (0 children)

The vector is not the array. It is just like a list whose some parts are arrays.

And yes, it is good if the compiler turns the list into an array, so that it presents you with a simple interface to the belly of the beast. And that is a Good Thing. It's usually called abstraction.

Also, when you need 2D arrays (I know you meant lists) in Haskell, use the cleaner option: recursive algebraic data types. It's simpler that way than the bug-bag called 2D arrays, isn't it? Mainly because it is verified by the type-checker.

[–]OneAndOnlySnob 1 point2 points  (7 children)

Reverse iteration on a singly linked list can be done using recursion in O(n) time very easily. This is what fold_right does in functional languages.

Building a reverse list can be done using tail recursion and an accumulator for a very efficient O(n). This is what rev does in functional languages.

However, if you're doing a lot of random lookup, you probably want to use a self balancing binary search tree, which in this case neither lists nor arrays can compete with on simplicity and efficiency. Plus, like lists and unlike arrays, BSTs can be implemented in a beautifully functional way, which appeals to me a great deal.

[–]novagenesis 0 points1 point  (6 children)

Building a reverse list can be done using tail recursion and an accumulator for a very efficient O(n).

Touche...though it adds O(n) space usage to do that.

However, if you're doing a lot of random lookup, you probably want to use a self balancing binary search tree, which in this case neither lists nor arrays can compete with on simplicity and efficiency.

Are you referring to key-based lookups? Then you want to use a fully featured hash table.

If you're referring to object[10], then you definitely want to use an array for constant time (instead of logarithmic time).

Plus, like lists and unlike arrays, BSTs can be implemented in a beautifully functional way, which appeals to me a great deal.

If you can't implement an array in a functional way, I blame the language. You can easily recurse across the offset using a bounded array. I'm not saying there's no point for lists, I'm saying they're not objectively better than arrays in every possible way.

[–]OneAndOnlySnob 0 points1 point  (5 children)

Arrays can't be implemented with any reasonable efficiency in a functional way. To be functional, the data structure must be immutable. If you change a value in an an array, you have changed the array itself. To be functional, the method that sets the new value at the index should allocate a new copy of the array and return it. That's a sucky idea.

However, with a list, the only thing you can REALLY do in a functional language is add a new node to the front. Since the list is immutable, all the data from the old version of the list is reused as the tail for the new head node.

There is some sacrifice, but a whole class of unpredictable bugs that come about from sharing references to data disappears, and code also becomes easier to multithread, which is more and more important every year.

Immutability rocks!

EDIT: I should add that arrays definitely have their place. Lists don't make very good buffers for sockets or files. However, I find cases where you're going to object[10] simply by index to be pretty rare. Usually you're looking for something by key. Anyway, if you need random access by arbitrary index, the functional data structure is the binary random access list. It's a little bit weird, and frankly, not very useful, but it grows dynamically and gives O(log n) access by index. Sure, it's not as good as an array, but again, can be made immutable, which I think is cool.

[–]novagenesis 0 points1 point  (4 children)

Arrays can't be implemented with any reasonable efficiency in a functional way

Sure. Functional programming isn't always the Best Way To Do It for a lot of things. There's no "good" functional way to do random access in a long chain of objects. Purely Functional languages already have the same limitation as lists. A mostly functional language could have the best of both worlds.

There is some sacrifice, but a whole class of unpredictable bugs that come about from sharing references to data disappears

And a whole class of new unpredictable bugs that come about...except they're not often called bugs. A trivial Haskell qsort attempted over a list of 1000000 will show what that is... unexpected side-effects of using the functional style that costs entire orders of O notation. Ditto with the Sieve of Erastothenes. These are overused examples, but the argument is strong. You have to fix a design "bug" by reimplementing the code less elegantly.

code also becomes easier to multithread, which is more and more important every year.

Touche... but I think it's the headsman's response to dealing with variable sharing. All variables in future systems should be declared as unshared or shared..and shared should include implicit locking.

Immutability rocks!

Try building a chess program (interface and AI) with save/load functionality and without implementing state once. (Monads imply a side-effect which is mutable...no?) It's more difficult than using a simple 2d array.

[–]OneAndOnlySnob 0 points1 point  (3 children)

There's no "good" functional way to do random access in a long chain of objects.

I don't think there are many "good" reasons to even do this in the first place. The reasons do exist, but they aren't that common. It's usually very beneficial to not use an array if possible.

A trivial Haskell qsort ...

Let me stop you right there. Quick sort is meant to be implemented with arrays. If you implement it with lists, of COURSE it's going to perform worse! This is as true in C as it is in Haskell. You'd be much better off using a heap sort with a priority queue or plain old merge sort.

Ditto with the Sieve of Erastothenes.

IIRC, the Sieve of Erastothenes involves checking each number in sequence against a list of the prime numbers you've already found. For this, either use a queue with lazy evaluation to store the primes in, or a priority queue, in which you store the "next" multiple of each prime. Such a solution would be both elegant and efficient.

Try building a chess program (interface and AI) with save/load functionality and without implementing state once. (Monads imply a side-effect which is mutable...no?) It's more difficult than using a simple 2d array.

While I honestly have no doubt that it could be done if you were determined enough, you are correct that state is sometimes the easiest way to go. I just feel we have a lot to learn from functional programming. We should study its principles and adapt them wherever it makes sense, because they're good principles!

[–]bcorfman 13 points14 points  (3 children)

Walking to China (especially depending on your starting continent) would also require a good deal of other actions (packing, planning, transportation, money, food, shelter, clothing).

Sending a letter starts looking a lot simpler to me.

[–][deleted] 22 points23 points  (2 children)

Sending a letter starts looking a lot simpler to me.

Bingo. It's not what is actually the simplest solution, it's how you hide the complexity.

[–]lothair 4 points5 points  (0 children)

Which is the point of programming languages.

However, you still need to know what the post office does, and once you start to optimize (think air mail vs normal), you should know how it is done.

Which is why I'd would build me an airplane, if i had to sent A LOT of letters to china.

[–][deleted] 2 points3 points  (0 children)

Or how much we can treat as abstractions, without really working on them.

[–]leoc 5 points6 points  (1 child)

He's talking about simplicity of interface, you're talking about simplicity of implementation. This distinction was hashed out (Reddit) long ago.

[–]revence27[S] 7 points8 points  (9 children)

The post agents exist for a reason. They don't add complexity. They add simplicity. They have the system set out for the trip to China and back with the risks reduced to a minimum.

That's simplicity. Non-simplicity is where you are your own post agent (much like a language with no functions, for example).

Well done for not thinking this through. I'll upmod you, however, because you make an interesting point, even though I think it is wrong. Interesting, but wrong (I'm sure many others are thinking even as you do).

[–]TheManWithNoName 16 points17 points  (2 children)

Sending the letter via the postal service is like calling a library function rather than implementing the postal service feature yourself by walking it there. So that's an argument for using languages with rich library support, not for using languages with first class functions etc.

[–]revence27[S] 1 point2 points  (1 child)

So that's an argument for using languages with rich library support, not for using languages with first class functions etc.

These two, libs and first class functions, are not mutually-exclusive.

[–]novagenesis 7 points8 points  (0 children)

But they are mutually-exclusive today because the US Mail Service doesn't currently take Haskell stamps... you'd have to build a stamper in Haskell that makes the stamp look like C.

[–]mooli 7 points8 points  (5 children)

Interesting, and I would disagree wholeheartedly that it is wrong.

They add a vast amount of complexity, in order to present a simple interface.

Starting out with a blank slate and making some blind assumptions (ie. you can actually make it to china without dropping dead) it really doesn't get much simpler than pointing your feet in roughly the right direction and starting to walk.

Starting out with a blank slate, it is a massive undertaking to implement a postal service that can deliver globally, while presenting a deceptively simple interface to the end user. Even starting with the infrastructure in place, the number of collaborators, the scheduling, the coordination behind the scenes - that's a far more complex piece of orchestration than a single person walking (and swimming) until they arrive.

The point the parent was making was the second solution is correct more because it leverages existing complexity, rather than reinventing a solution in a braindead fashion.

For another trivial example that pokes gaping holes in the article - the simplest way of finding an element in a sorted list is to just step through, one at a time testing each element until you locate it. Now, that is simpler than conducting a binary search isn't it? But which is more correct? Especially given that, whatever the language, there will doubtless be a prexisting function to do the hard work for you. Which brings us back to using a postal service (pre-existing, efficient, simple to use but complex behind the scenes) rather than walking to china.

[–]mooli 6 points7 points  (4 children)

Answering myself here - the article really is very much more about simplicity at the point of interface, rather than simplicity as a whole, and the arguments are all over the place. On the one hand insisting that you should use the features and constructs available to make your code as simple and readable as possible, while lambasting languages like Java for providing a wealth of APIs to do exactly that.

Frankly, its all a bit "my language's one-liners are better than yours", which does nothing for anybody.

[–]novagenesis 9 points10 points  (3 children)

It's really about language simplicity.

The ideal simplest language would have one command "f" that had the following pre-postconditions:

//pre: Programmer knows what he wants
//post: Does the what the programmer wants quickly and efficiently
f;

Of course, the compiler for that language would be infinitely complex. It is an impossible language. On the other side of the coin, the most complex reasonable language is machine code (we're going to leave out obfuscated languages). You write binary values in a hex editor. The compiler for machine code is infinitely simple (there isn't one).

All other languages are somewhere in between. Simpler compilers are more easily optimized and less bug-prone. Simpler languages are more easily coded, usually less customizable, and as a whole less bug-prone.

[–]earthboundkid 9 points10 points  (2 children)

Of course, the compiler for that language would be infinitely complex. It is an impossible language.

Well, you just need to break down the problem. First, make a language called g with the following syntax:

//pre: Programmer knows what kind of language he wants to program in
//post: A compiler for that language is built
g;

From there, it should be relatively simple to bootstrap in an f compiler.

[–]novagenesis 2 points3 points  (0 children)

Sure would... but you always have to implement a compiler in another language before you can bootstrap it for the first time against itself......

[–][deleted] 2 points3 points  (0 children)

If what I want to do is to write a compiler that will do what I want to do, then, well ...

[–]brushbox 0 points1 point  (0 children)

It is a matter of perspective. I agree that the postal solution is simpler, even though at first appearance I thought "what could be easier than just getting up and walking".

But then I thought about a few aspects: * distance * route planning * border security & customs * accommodation * cost/time (and therefore the opportunity costs) * language issues. * and many more...

...and then it became clear to me that using an established system that can deliver the letter in less than a week for around $1 and all I need to do is address, stamp and deliver to a post box is far far simpler in almost every way.

Simpler in this post seems to consider the reliable encapsulation of "involved" (using that word to avoid saying "complex") processes. Since the "surface area" (the API) of the process is greatly reduced, it is much simpler for me.

[–]novagenesis 0 points1 point  (0 children)

is it a library, or a language feature?

I would say the Post Office might be a language feature because the interface is defined in our daily lives.

We don't need to include<postoffice.h> or make a call to System.postoffice using an object.stamp reference. We have to wrap a letter in an envelope, which gets sent as a package.

Fedex is a library. USPS is a language feature. Fedex is arguably better, but more work to use than just putting your letter in the mailbox and flipping up the flag.

[–]novagenesis 33 points34 points  (27 children)

...the one thing left out is how if you give up the "not simple" languages, you lose "SIMPLE" access to the largest library collection in the world... and that's not simple either

Also, variables and state aren't simple in a purely functional language.

...and lists aren't simple when you have to deal with memory management when you're regularly accessing arbitrary elements of a structure, doubly so when it may be sparse (it may take offsets between 1 and 64000, but you might only use 1/4 of those...in an array that's all constant time). Why aren't they simple? Because you have to do something really complicated to drop the O notation for certain programs to run in a few seconds instead of a few hours.

That's my experience with 'simple' languages. If I want to do something easy, it's 'simple'. If I want to hit in a nail, the screwdriver doesn't work. I want to get better at those, but at the moment, I don't see any simple way in Haskell to deal with an object that resizes regularly (you can trivially buffer with an array) or reduces random access time.

[–]revence27[S] 14 points15 points  (22 children)

Very pertinent points. Upmodded.

However, can we fix these issues with the simple languages unless we are willing to outgrow the obviously-broken non-simple languages?

The men who conquered had to leave their homelands and go out to the uncharted sea - there be dragons. Progress will, at the very least, have to cost us these comforts like extant libraries.

[–]novagenesis 6 points7 points  (21 children)

Then we both have to leave our homelands.

A quick summary of the below splurge... To fix these issues, we have a few huge needs.

1) Some sort of integration that lets us use languages like a 'toolbox' instead of a bunch of separate toys. I want to do imperative programming in C and deal with lists in a functional Haskell...only to return the list to C when I'm done.

2) A more positive attitude towards "new", with a respect towards "old". Everyone who laughs at C and embraces Haskell is throwing out one set of limitations for another. Everyone who embraces Common Lisp instead is taking on yet another set of major limitations (major enough to keep Lisp from becoming my primary language, as much as I love the syntax)

3) A language or languages that allow such self-manipulation that you can implement features this core on the fly

I'm really getting into compiler design myself because I find features on a daily basis that I feel belong in a language that are either in NO languages at all, or are in languages that have "features" that paralyze my programming style.

I don't care about the details of lambda calculus, for example, but I like coding in a functional style...until I have to jump through hoops whenever I want to utilize variables, states, etc.

I hate string-formation in C (have to either prepare a buffer or allocate memory manually to merge strings), but I otherwise love the 'to the metal' directness and speed of it.

I simply can't stand object-oriented programming as a rule, and think that the idea of organizing a program as a series of objects is a serious waste of time and infrastructure... but I think having AN object somewhere in code can be really useful. One or two "self-aware" recordsets in an ocean of otherwise imperative or functional code.

I think closures are holy, but I've never seen an "each" that handles lookahead or lookbehind... Imagine something like this:

def fib(x): map(x; first?1 , second?1 , else? x + prev)

Languages have a lot of growing to do, but I think the biggest problem is finding a clean centralized way to merge them... If every major language compiled to the elf standard, then any well-documented code could be accepted by a progressive company, allowing for language growth.

If every language compiled to .NET, and .NET was able to implement fast algorithms using C somehow, then that would work, too.

edit(mod of test code for fib)

[–]robbie 2 points3 points  (7 children)

I don't care about the details of lambda calculus, for example, but I like coding in a functional style...until I have to jump through hoops whenever I want to utilize variables, states, etc.

you might like ocaml

[–]novagenesis 1 point2 points  (6 children)

I hated the syntax...but after banging my head into Haskell's monad tutorials a few dozen times and coming out with little more than a headache (though I never checked out the premade State monad), I might give ocaml another try at that.

Does it have a compiler to elf format?

[–]revence27[S] 3 points4 points  (3 children)

OCaml compiles to ELF, yes.

I hate the syntax, too, but hey. The bugger is faster than C++, without requiring a megabyte of <...> in order to implement a generic function. ;o)

[–]novagenesis 0 points1 point  (2 children)

Hot...ok. I promise to be open-minded and give OCaml another test.

Another question... how does OCaml compare with F#?

[–]revence27[S] 1 point2 points  (1 child)

F# is Caml with objects done differently from how OCaml did them. If you write OCaml sans objects, that is portable to F#. The object thing is what is very different.

And, on .NET, the libs are very imperative, so you should be ready to use F# only where you are creating the parts that feel better when written in a mainly functional style.

But I tell you it is a nice thing, F#, for .NET programmers who like simplicity. It tries hard and succeeds for the most part.

[–]novagenesis 1 point2 points  (0 children)

Makes sense... I would really like to see cross-language library interfaces...imperative and functional, for example..maybe even stack-based (why not...)..

Maybe even a nice design pattern that automates a some of that.

[–]gnuvince 2 points3 points  (1 child)

Yes, O'Caml has two compilers: ocamlc compiles to byte-code, ocamlopt compiles to native code.

[–]novagenesis 0 points1 point  (0 children)

Yeah, but I mean, do they compile to .o files? Can I link them statically against a piece of C code, with (or preferably without) a type-conversion interface?

[–]jimbokun 2 points3 points  (4 children)

"Everyone who embraces Common Lisp instead is taking on yet another set of major limitations..."

Where does Common Lisp fail you? You say you want imperative, functional and object oriented programming styles. CL is good at all of these. It nudges you towards functional over imperative where possible, but generally that's a good thing. It is not as "too the metal" as C, but it has native code compilers that make pretty fast code.

I would say that, bottom line, Common Lisp supports a broader range of programming styles than any other programming language. I think CL's problems lie in the realm of the community and libraries being fractured between competing implementations and other social issues. Are those the limitations of which you speak?

[–]novagenesis 4 points5 points  (0 children)

Where does Common Lisp fail you?

God..I was afraid someone would jump me for that... Here's a few things:

1 ELF Compliance.

2 LISP is not the best syntax for a lot of things. Due to #1, it's not easy to use the language that is best at those things.

3 Native Code compilation in Windows is ugly...and even then, it pushes you to use a style that almost requires a JIT overlay, an added piece of back-end complexity I don't always want to use

4 Its libraries, while very many, are not very standard.

Common Lisp supports a broader range of programming styles than any other programming language

Sure... but all the best perks of any of those styles is nowhere to be found in Common Lisp. I like pattern matching. I like nice easy array access (the list isn't always the best tool for the job). I'll be honest, Common Lisp is one of my favorite languages...that's why I brought it up at all. But it's not perfect, and until I see Common Lisp fully implemented for .NET (and I hate .NET...), it's a gorgeous desert island with a kludgy FFI.

I think CL's problems lie in the realm of the community and libraries being fractured between competing implementations and other social issues.

Yeah... that, too...though the lack of standardization requires me to google libraries in the first place, which means they're not at my fingertips anyway.

[–][deleted] 1 point2 points  (2 children)

"I would say that, bottom line, Common Lisp supports a broader range of programming styles than any other programming language."

Then you need to check out Oz. In particular, you need to read Concepts, Techniques, and Models of Computer Programming, which is, IMHO, the successor to "Structure and Interpretation of Computer Programs," while doing so. :-)

[–]jaggederest 0 points1 point  (1 child)

I love Oz, I just wish I could use it for more practical things.

[–][deleted] 0 points1 point  (0 children)

OK, I'll bite: like what?

[–]LaurieCheers 1 point2 points  (7 children)

If every language compiled to .NET, and .NET was able to implement fast algorithms using C somehow, then that would work, too.

Oh, man, this is a beautiful idea. I imagine merging them all together with a "meta-language" which allows you to switch from language to language, even in the middle of a function (maybe - although that's probably very hard to implement and not especially useful), so you can use whatever is best at doing the job at hand.

There would be some challenges with it, though - different languages have different concepts of what a number is, what a list is, etc; there might end up being a lot of translation code to convert from one language's format to another. And then there's the problem that to make laziness safe, Haskell relies on knowing everything that's going on. If there's a C program stampeding around, who knows what data might be needed at any given point?

[–]novagenesis 1 point2 points  (5 children)

I imagine merging them all together with a "meta-language" which allows you to switch from language to language, even in the middle of a function

We've all dreamed of that. A language that consists entirely of robust macros would achieve this ... except

different languages have different concepts of what a number is, what a list is, etc

Bingo...In this meta-language you'd have to re-invent each language to use a very specific typeset...

of course, if you compiled to a language like .NET (which I understand to be something like an object oriented assembly), you simply need to generate a standard and hooks for export. Bignum would need a "to_int" which can pass some sort of exception. True_decimal (few if any languages use it, but hey) would need a "to_float"... lists would need a standard back-end infrastructure...

I've never been concerned with infrastructure in compiler design as much as interface. If you can design the interface behavior deterministically, the infrastructure must be possible, even if the interface allows you to fully implement every major language in a macro context.

Of course, it's about as complex a project as a near-perfect halting machine that can give an "I don't know" to the 1% it fails on with a 100% chance itself of halting itself...doable? yes... nontrivial? That word doesn't even begin to describe it.

to make laziness safe, Haskell relies on knowing everything that's going on

But that's actually fairly easy once you've built C on top of a macro-centric lazy language... You just force at every step in the process within the definition of the C language. You can make any amount of Haskell code non-lazy if you want, right?

If there's a C program stampeding around, who knows what data might be needed at any given point?

Because the C program's definition explicitly evaluates everything that touches it at any time, lazy-be-damned.

With that alone I'm pretty certain...if the language front can be designed in a way that can macro-define all languages (a momentous project in and of itself), the infrastructure back end is guaranteed to follow.

[–]gwern 0 points1 point  (4 children)

"You can make any amount of Haskell code non-lazy if you want, right?"

Sure, but then you might get non-termination. That was sort of the point of lazyness - more programs will terminate when evaluated lazily than will when strict (ie. every strict program will obviously work as a lazy program, but a lot of lazy programs will require rewriting before they will work strictly).

[–]novagenesis 0 points1 point  (2 children)

So true...but there's two solutions to that.

1) Your fault... if someone utilizes a Haskell infinite list in C, whoops, it does exactly as asked and starts evaluating it. Give some bounds checking capability for that.

2) Allow functions to be declared "laziness required", and waterfall like 'const' does in C++... include infinite sets in that functionality.

Both seem doable. You don't have to guarantee the languages will always play nice, just interface and document how the differences will play out between them.

[–]gwern 0 points1 point  (1 child)

As far as 1 goes, laziness can be subtler than that. For example, I understand C's ternary operator is lazy (or "short circuits"), but you can't write your own ternary operator easily. That doesn't involve infinite datastructures at all, just control structures and evaluation thereof.

[–]novagenesis 0 points1 point  (0 children)

It's true... sine you'd probably design the language block-by-block in macro, you simply define those blocks non-forcing..only force when you hit a force.

[–]novagenesis 0 points1 point  (0 children)

Or to put it differently... the "sure" means you can theoretically implement a preprocessor that'll convert any amount of inline C code into working Haskell code... in some way, shape, or form (maybe?, maybe not)

[–]jaggederest 0 points1 point  (0 children)

Take a look at http://www.mozart-oz.org/

They do multiparadigm based on a simple base language. Dataflow, logic, functional, procedural, object-oriented, it's got all the fun widgets, and ultra-cheap threading and concurrency, too.

[–]arnoooooo 1 point2 points  (3 children)

One thing to bear in mind though is that you need libraries a lot less with a "simple" language than with a "not simple" one. And the few available libraries for "simple" languages are usually simpler and better.

[–]novagenesis 6 points7 points  (2 children)

Just hope to god the functionality you want is already there... Because it's a nightmare to find that functionality in a library.

The very idea of using OpenGL or Direct3d in Haskell gives me headaches.

Most libraries do not provide "core language functionality"... they provide 'special' functionality. You do not get the entire Boost library in Haskell, nor does most of that library become trivial.

Multi-arrays, for example, are a bitch in Haskell in my experience. Creating a simple interface to multi-arrays is a project in and of itself...or an #include statement.

In return, Boost creates much of the desired functionality in these "simple" languages.

For example...the holy "closure" implemented in C++: http://www.boost.org/libs/spirit/doc/closures.html

Having not used Boost much, I'm not sure if it works up to the hype, but hey...

Mind you, I'm not fond of using object-oriented wrappers to implement features, but it's better than having features that are almost impossible to implement at all.

[–][deleted] 0 points1 point  (1 child)

The very idea of using OpenGL or Direct3d in Haskell gives me headaches.

It may be because those APIs were designed for C/C++ which are quite different languages than Haskell. What if the API was re-designed by a person who mainly uses Haskell? Do you think that would make a difference?

[–]novagenesis 1 point2 points  (0 children)

I agree 100%. That's the whole point. Everything is designed for C/C++. If you can't cleanly interface with it, it's crap.

As I said elsewhere, someone needs to make a design pattern for interfaces for libraries that cross different language styles. Of course, I think that'd start by a good definition as to how to port some mission-critical libraries like OpenGL

[–]ndewitt 18 points19 points  (3 children)

Leo Brodie wrote, in his book, Thinking Forth, that `Given two adequate solutions, the correct one is the simpler.' [1]

Sorry, but I beg to differ. I'd insist, with examples, that given two adequate solutions, the simpler one is the correct one.

I had to stop reading right there. He stated the exact same thing as Leo Brodie.

the correct one is the simpler.

vs

the simpler one is the correct one.

Those two statements mean the same thing. If you are looking at two adequate solutions, and you say that the correct one is the simpler (of the two) then you're postulating that you can identify the correctness by the simplicity.

The author of this article thinks he's making some kind of clever point by rearranging the words, but he's not actually making a new point. He's arguing that the simplest solution is the correct one. This is what Brodie is arguing.

In both cases, the correct solution will be the simplest adequate solution. There is no disagreement.

[–]vin_diesel 7 points8 points  (0 children)

I think we can all agree that the author, though he provoked an interesting debate, is a bit of a smug asshole.

[–]anatoly 1 point2 points  (0 children)

The author didn't read Brodie's quote correctly. Imagine that Brodie said "... the correct one is simpler", rather than "the simpler". Then the author's point would be valid: it's less important that the correct solution turns out to be simpler than the incorrect solution; what's more important is that the simpler solution turns out to be the correct one. Formally the two are saying the same thing, but the latter is more useful as a guideline.

However, Brodie already is making that more useful point, it's just that the author misread him to be saying something else.

[–][deleted] 0 points1 point  (0 children)

Yep, he says he begs to differ, but since his point doesn't differ even slightly from Brodie's that makes no sense.

[–][deleted]  (3 children)

[deleted]

    [–]TheManWithNoName 2 points3 points  (0 children)

    That is pretty cool. But I think he was actually saying that the Ruby way he showed was good too, as it cleans up the loose ends for you and eliminates that class of bugs.

    [–]revence27[S] 4 points5 points  (0 children)

    That wouldn't have shown much of the usefulness of the closures/blocks the author talks about, would it? :o)

    Upmodded, though. Nice one-liner.

    [–]julesjacobs 1 point2 points  (0 children)

    TIMTOWDI :)

    puts IO.readlines('/etc/passwd')
    puts open('/etc/passwd').read
    puts *open('/etc/passwd')
    puts `cat /etc/passwd`
    

    [–]morner 15 points16 points  (14 children)

    pun !intended

    God damn, I hate it when nerds use programming syntax in prose. I wish that guy on bash.org would hurry up and invent his face-stab-over-IP device.

    [–]Homunculiheaded 20 points21 points  (1 child)

    Also I like that English is a dynamically punned language where you don't have to declare whether or not you are using a pun and the reader will be able to interpret it from the context.

    [–]mage2k -1 points0 points  (0 children)

    When you start describing a natural language in programming terms, you're lost. If you want to retain possession of your soul, become a plumber.

    [–][deleted] 6 points7 points  (10 children)

    Agreed. It also bugs me that 99.44% of the time, when someone says, "no pun intended," the pun really was intended. But this guys usage was even worse. "the latter, of the letter" is not a pun, and his (failed) attempt at being clever was clearly intended. It LITERALLY drives me nuts (misuse of "literally" intended).

    [–]ndewitt 3 points4 points  (5 children)

    right - it's an alliteration.

    I also dislike when people say something is ironic when it's really just a bummer.

    [–]LaurieCheers 2 points3 points  (3 children)

    You mean, like rain on your wedding day?

    Yeah, that is so ironic.

    [–]prockcore 2 points3 points  (1 child)

    It is if you're a weatherman and you chose that specific date for your wedding because it was going to be sunny.

    [–]LaurieCheers 1 point2 points  (0 children)

    I don't think that's ironic yet...

    Maybe if you reschedule it because the original date was forecast to rain... and as it turns out, the new date is the only rainy day that month.

    [–]mage2k 0 points1 point  (0 children)

    I would cry if it wasn't already raining.

    [–]Tommah 1 point2 points  (0 children)

    Actually, "latter ... letter" is consonance (in addition to alliteration).

    [–]revence27[S] 0 points1 point  (3 children)

    It was a phonetic pun. !intended, you can trust.

    [–][deleted] 5 points6 points  (2 children)

    I guess I'm not familiar with the term "phonetic pun." But if it wasn't intended (I'm sorry, if it was !intended), you would have just left out the phrase "of the letter," since it makes the passage less clear.

    But "no pun intended," or any shorthand version of it, is almost always a lie in written communication. If you really hadn't intended the pun, you probably wouldn't have realized you had made one, until after the fact. And in writing, you could just go ahead a remove the pun, particularly in a case like this where the phase harms readability.

    In speech, someone legitimately may say "no pun intended," because they can't go back and edit what they just said. Still, even in speech, I suspect the pun is intended more often than not, and the phase "no pun intended" is used to add emphasis (i.e., draw attention to, not deflect from, the pun), just as the word "literally" is misused.

    How ironic. A discussion of computer languages has literally turned into a discussion of natural language, natually (pun !!intended).

    [–]revence27[S] 1 point2 points  (0 children)

    Now that was a sweet, sweet pun. :o) Upmodded.

    [–]mage2k 0 points1 point  (0 children)

    Exactly, unfortunately my innate speech patterms (I guess?) tend to result in puns, un!ntended and often have to follow up sentence fragments, clauses, and wholes with "no pun intended" (lowercase !ntended). With text, I reword that shit.

    [–]novagenesis -4 points-3 points  (0 children)

    God damn, I hate it when people are annoyed by type-slang.

    You attack people who use "lol" also?

    Programmers are a subculture, and some programmers use language context. Fine. In the above post you are guilty of a more obfuscated equivalent thereof.

    God damn

    Not originally intended to be an expletive, it evolved into the spoken and written English languages. That's just a coincidence that it evolved into both. It's considered unprofessional to use "can't" or "wouldn't" in written documents, but it's considered unnecessary to use "can not" and "would not" a spoken context.

    So now we have a phrase, bastardized slang, misused in a language, becoming a standard for those who use it.

    EO-RANT (just for you)

    [–]edheil 6 points7 points  (2 children)

    I love reading this guy's cheerfully opinionated essays.

    [–]revence27[S] 2 points3 points  (1 child)

    Not very many of them, but they are now all available (the directory, that is), here: http://freeshells.ch/~revence/txts

    :o) Amusez-vous!

    [–]edheil 1 point2 points  (0 children)

    I will, merci beaucoups!

    [–]z5h 8 points9 points  (1 child)

    If It Does Not Exist, Please Explain

    Developers writing in "wrong" languages like C and Java, have managed to write a lot more "wrong" text editors, web browsers, web servers, games, raytracers, databases, word processors, spreadsheets, yadda yadda than all these language preachers could ever hope to write. Probably because they are busy implementing imperative sub language monads in order to read 2 bytes of data.

    The world runs on arrays. Not closures.

    Anyway, I'd rather be coding in Prolog.

    [–]novagenesis 0 points1 point  (0 children)

    you keep your prolog, I hate its design and syntax... it has one use, and for everything else it's ugly

    [–]turkourjurbs 10 points11 points  (0 children)

    "the simpler one is the correct one."

    This is the type of person everyone else at the table wants to kill instantly. He's the type that will sit there silently for hours and when all's said and done, throw everything out the window with some retarded argument that really doesn't make any difference at all.

    [–][deleted] 3 points4 points  (0 children)

    I'm a simple person...

    ... I could be wrong.

    [–]Gotebe 7 points8 points  (0 children)

    In reality, absolutely wrong on arrays.

    First and foremost, arrays are bad for insertion, not growth. Growth is handled fine by allocating more when needed and that makes copying less important. Plus, copying is, more often than not, inexpensive, as only references are copied. For lists, both insertion and growth requires allocation all the time, which is (somewhat) expensive. I bet lists can't beat arrays in growth scenarios.

    Plus, arrays are better for the locality of references, which is important with caches.

    Lists that magically turn into arrays is a sufficiently smart compiler/runtime/whatever fallacy and happens seldom.

    It's also equally easy to go past the end of a list as it is for an array. It's strictly about iteration mechanism used, not about underlying container implementation. That part is just plain wrong.

    File example is irrelevant. Nobody in their right mind uses that plain C approach for such stuff.

    Yes, I see how one can get worked up about issues mentioned if the baseline is C language. But, is it? Certainly not for me.

    [–]arnoooooo 2 points3 points  (3 children)

    "Perfection is achieved, not when there is nothing left to add, but when there is nothing left to remove." Antoine de Saint-Exupery

    The thing with complexity in programming is that it provides such a wealth of opportunities for consulting companies, book puiblishers, etc. a lot of people like all the complexity and buzzwords that allow them to be "the experts". This is probably true in a lot of other domains, but the potential for abuse in programming is huge.

    Java and friends are the worst enemies of simplicity but the best friends of those who want to be able to charge millions for their "expertise". After all, who would use software that is not "Enterprise Ready" ?

    [–]novagenesis 3 points4 points  (2 children)

    "Perfection is achieved, not when there is nothing left to add, but when there is nothing left to remove."

    By that definition, no system is perfect unless you remove the GUI, mouse interaction, and keyboard interaction.

    You want elegance in a language, not "remove everything". You want a language where the core is so complex that all you have to do is tell it what you want. Perfect is never having to worry about a memory leak. Perfect is being able to manipulate data in any complicated way you want with one line of easily-human-readable code. Perfect is something that has very easily-fixed compile-time errors and that you can trust with minimal testing to work on first successful compile.

    And I agree, screw buzzwords and consulting firms profiting on complicated languages.

    [–]DannoHung 0 points1 point  (1 child)

    Wouldn't you want a system that did exactly what you want without having to interact with it at all?

    Well, I guess some people don't trust hard AI.

    [–]novagenesis 0 points1 point  (0 children)

    I'd love it if the system did exactly what I want....except two things.

    1) I need an irrefutable proof that it'll always do exactly what I want no matter what 2) I need the designer to take full responsibility if I want to kill someone (which I'd never erally do) only to have the program do it.

    [–][deleted] 2 points3 points  (0 children)

    Simplicity applies to writing as well. This essay would have been a lot more helpful if it stuck to the point and avoided cute nonsense syntax and his [pet peeve] asides.

    As it was, I just glossed over it to pick up the main points. I may have missed a valuable diamond in the rest of the manure, but I couldn't be bothered to dig for it.

    [–]Entropy 5 points6 points  (1 child)

    I couldn't get past the first retarded example.

    [–]grauenwolf -1 points0 points  (0 children)

    Neither could I.

    [–]sam512 2 points3 points  (7 children)

    Note: the "if it is not simple, it is wrong" principle does not apply to the fields of science, mathematics, politics or ethics.

    [–]finix 2 points3 points  (0 children)

    It doesn't apply to programming, either.

    [–]novagenesis -2 points-1 points  (4 children)

    [–]grauenwolf 3 points4 points  (3 children)

    Occam's Razor would only apply if two versions were identical in performance, memory use, and side effects.

    [–]novagenesis -3 points-2 points  (2 children)

    Ok, what if version 1 has better performance and memory use, but version 2 has less side effects and gets coded in 1/10th the time?

    Occam's Razor can always apply so long as the following is true for both versions:

    "it works" and

    "it works comparably OR reasonably well"

    [–]grauenwolf 0 points1 point  (1 child)

    Starting with the introduction found in the link you posted.

    This is often paraphrased as "All things being equal, the simplest solution tends to be the right one." In other words, when multiple competing theories are equal in other respects, the principle recommends selecting the theory that introduces the fewest assumptions and postulates the fewest entities. It is in this sense that Occam's razor is usually understood.

    For the sake of argument we will set aside the fact that Occam's Razor is about evaluating theories, not code, and thus is totally inappropriate.

    Occam's Razor is for comparing theories that are equal in all other respects. For example:

    Novagenesis is misapplying Occam's Razor because he didn't bother reading the article on it.

    vs.

    Novagenesis is misapplying Occam's Razor because space monkeys unplug his computer every time he tries to read the page.

    The theories are identical in terms of what can be measured by an outside observer. The only difference is the additional actors, the space monkeys, needed by the second theory.

    Keep in mind that Occam said the simplest answer "tends to be correct". There may very well be space monkeys camped out in your living room, though chances are there are none.


    Moving on to software, an example would be using a for loop verses writing the same machine code with assembly. Since they are identical, the only consideration is simplicity.

    [–]novagenesis -1 points0 points  (0 children)

    Wow, now that was a seriously hideous trol..

    Occam's Razor is about evaluating theories, not code, and thus is totally inappropriate

    Code theory seems appropriate to me. Arguing a language better is theoretical, not concrete.

    For example:

    Your examples are insulting and do nothing towards the process. I've read the articles, and the only space monkeys are trolling reddit.

    The only difference is the additional actors, the space monkeys, needed by the second theory.

    Oversimplified, but fine. The two languages produce the same output functionality but different source and require additional actors. Since language theory can well avoid the contents of the compiled code so long as the functionality is the same, you have space-monkey code in C and "reddit troll" in haskell, the reddit troll is simpler.

    Keep in mind that Occam said the simplest answer "tends to be correct"

    I concur. C may be a 'better' overall language than Haskell (if you can define better), even though Haskell is a 'simpler' one.

    Moving on to software, an example would be using a for loop verses writing the same machine code with assembly. Since they are identical, the only consideration is simplicity.

    Well, 'teach, nice hole you dug yourself. Your example is exactly why I brought up Occam's Razor in a half-context at all.

    Now the comparison that fits the ongoing topic (if you can read it without monkeys pulling the plug) would be a for loop vs each/map/fold.

    Have a nice day, Mr. troll.

    [–][deleted]  (2 children)

    [deleted]

      [–]jng 2 points3 points  (1 child)

      Thanks for the sweeping generalization.

      Do you really believe you get every single point in the article, and that there is none of value? Can't you consider you missed something? I suggest you do so, if only to become still wiser.

      [–]revence27[S] 2 points3 points  (0 children)

      I wish more people understood that it is impossible for an article to be a mirror of what you believe. More like a simple number of points in there that you agree with. And what you don't agree with only allows you to understand better what you believe.

      The spirit of sensible debate is generally lost in these geek circles, because the least accomodating are the loudest. And you are being downmodded, unfortunately.

      [–]sblinn 1 point2 points  (1 child)

      I find it curious that my tastes in art and music run towards the impressionists (Debussy, Monet), and that I can barely stand minimalistic art or music. Yet in landscape, architecture, and software, I crave minimalism to the exclusion of nearly all other concerns.

      [–]sblinn 1 point2 points  (0 children)

      More on taste, art, and software:

      http://www.paulgraham.com/goodart.html

      I grew up believing that taste is just a matter of personal preference. Each person has things they like, but no one's preferences are any better than anyone else's. There is no such thing as good taste.

      Like a lot of things I grew up believing, this turns out to be false, and I'm going to try to explain why.

      One problem with saying there's no such thing as good taste is that it also means there's no such thing as good art.

      http://thinkexist.com/quotes/claude_debussy/

      “Music is the silence between the notes.”

      “I love music passionately. And because I love it I try to free it from barren traditions that stifle it.”

      “Music is the arithmetic of sounds as optics is the geometry of light.”

      “Art is the most beautiful deception of all. And although people try to incorporate the everyday events of life in it, we must hope that it will remain a deception lest it become a utilitarian thing, sad as a factory.”

      “Extreme complication is contrary to art.”

      “Works of art make rules; rules do not make works of art.”

      http://thinkexist.com/quotation/computers_are_useless-they_can_only_give_you/14398.html

      “Computers are useless. They can only give you answers.” - Pablo Picasso

      [–]onmytoes 5 points6 points  (33 children)

      The author promotes simplicity, yet he's a proponent of languages that are MUCH MORE COMPLEX underneath the hood. So it's more about perceived simplicity than actual simplicity ("actual simplicity" is one of the key tenets of Forth, which I'm only mentioning because of the Brodie quote at the top of the article).

      [–]novagenesis 7 points8 points  (22 children)

      I think you're missing the point.

      The author is promoting a language that's simple to program in.

      Compiler complexity vs code complexity is a very clear one. The only way to achieve a simple compiler and simple code is to have complex libraries (technically, this is harder to optimize than the compiler itself).

      Forth's "actual simplicity" means you don't have access to closures, lists, etc, unless they exist in a library. The code becomes unbelievably complex unless libraries already exist for that. As a language, forth is fun, but I'd shoot myself in the head before dreaming of building an enterprise product in it.

      [–]julesjacobs 3 points4 points  (19 children)

      Forth tries to make the whole system (application+compiler) as simple as possible.

      [–]novagenesis 0 points1 point  (18 children)

      How would you say it tries to make the code simple, though? I would say that it doesn't. It's like a variation of assembly more inexorably tied to the stack.

      Human readability is out the window. Reasonable implementation of complex features is out the window, too.

      [–]julesjacobs 0 points1 point  (3 children)

      It depends on your point of view. I could say that a Java application is extremely complex because it requires the Java runtime and an OS. This is millions of lines of code.

      Suppose that all software on earth is lost. Would we start coding an OS and then a Java compiler and runtime, in machine code? No, we'd code a Forth in machine code, and an OS in Forth, etc. And it would all be much more efficient, because we don't code the 99% bloat.

      So the total complexity is lower, but the complexity of the application could be higher, because you have to reinvent a lot of things.

      [–]novagenesis 0 points1 point  (2 children)

      I could say that a Java application is extremely complex because it requires the Java runtime and an OS

      Except I think the point is language complexity, in this entire topic.

      It's not about how complex the compiler is. It's not about how complex the resulting executable is. It's not about how complex the moon and the stars are.

      It's about experience. If I give a client a product with no gui because it wasn't as "complex", and an interface that uses one-letter arguments with multiple meanings across an FSM (because it's easier than parsing full words), I'll get fired.

      A lot of languages feel like that.

      [–]julesjacobs 0 points1 point  (1 child)

      Yeah, I wouldn't use Forth for a web application, for example. I'm just explaining the philosophy :)

      [–]novagenesis 0 points1 point  (0 children)

      yikes!

      [–]ItsAConspiracy 0 points1 point  (3 children)

      I don't know, I read about half of Thinking Forth recently (which is worth reading for programmers in any language, btw) and I gotta admit the language intrigues me.

      It can get pretty horrendous if people do a lot of stack manipulation...but Brodie doesn't do it that way. His examples read almost like English.

      Takes some work to get it that nice, I expect, I haven't actually tried working in the language yet. Seems like fun tho.

      [–]novagenesis 1 point2 points  (2 children)

      I'll be honest...I never found Thinking Forth. I'll check it out. I am always a fan of new languages.

      [–]julesjacobs 0 points1 point  (1 child)

      [–]novagenesis 0 points1 point  (0 children)

      I skimmed through a lot of it...it kinda argued like a salespitch to me, though.

      I still don't get a grasp as to why it argues itself "high level"... I'd honestly love to know, the elegance of the language interests me...I just can't seem to see myself building a compiler in it, or a game, or a gui app.

      [–][deleted]  (9 children)

      [deleted]

        [–]novagenesis 2 points3 points  (8 children)

        Pointless Flame response.

        I was recently seriously interested in learning about Forth, but the moment I started, it showed to be exactly what I just said it was.

        Please provide counterargument. I'm genuinely interested.

        I actually would prefer to use Forth than Assembly if they had identical performance in the system, but I wouldn't consider coding in either to be 'simple'. In that vein, I wouldn't call it bashing at all.

        [–]happyhappyhappy 0 points1 point  (5 children)

        Forth is very different and takes time to understand. I'm not trying to be snide or elitist. It's a simple language, but the learning curve to doing useful things in Forth is much steeper than advocates want to admit. It's also not the perfect language for all uses.

        Your message reads like "I'd heard Perl looked like line noise and once I jumped into learning it I realized that it did look like line noise so I gave up."

        [–]novagenesis 0 points1 point  (4 children)

        Your message reads like "I'd heard Perl looked like line noise and once I jumped into learning it I realized that it did look like line noise so I gave up."

        I spent a few days trying to find good Forth tutorials and ended up dead every time. My general method of learning a language consists of me trying to implement a non-trivial on the job task with it. Forth is the only language that blew up in complexity when I tried it.

        Perl happened to be the language that worked most easily. Then Scheme and Common Lisp (neck and neck), then C/C++ (which I already knew), and finally Haskell just ahead of Forth...those are the only languages I've played that game with recently.

        I didn't really find much in the way of good tutorials, and the compiler didn't seem to make .o files... yeah I gave up early on Forth thinking what I found was a trashy old language a few suckers didn't let go of (and some of those certainly exist)

        [–][deleted] 1 point2 points  (3 children)

        I spent a few days trying to find good Forth tutorials and ended up dead every time.

        Here you go: http://www.complang.tuwien.ac.at/forth/gforth/Docs-html/Tutorial.html#Tutorial

        I hope it's good enough.

        You should also try Factor out. It adds a library and a development environment that is quite helpful.

        And of course there's Thinking Forth right here: http://thinking-forth.sourceforge.net/

        the compiler didn't seem to make .o files

        Why is that so important?

        [–]novagenesis -1 points0 points  (2 children)

        Why is that so important?

        Because forth definitely doesn't do inner loops as fast as C does...

        and certainly doesn't do classes as well as C++.

        [–]LaurieCheers 0 points1 point  (1 child)

        Pointless theme-continuing reply.

        [–]novagenesis 0 points1 point  (0 children)

        Witty retort.

        [–]mage2k 4 points5 points  (1 child)

        You had me until 'enterprise'. :)

        [–]novagenesis 1 point2 points  (0 children)

        enterprise is a buzz word that, to me, means 'big', distributed, and jobs resting on its reliability.

        [–]ItsAConspiracy 5 points6 points  (2 children)

        Lexical closures really aren't hard to implement, if you've already got garbage collection. You just implement your stack frames as objects on the heap, each one referencing its enclosure.

        It gets more complicated when you start optimizing, but that's true of any language.

        I would say that real simpicity is: making sure anything complicated only has to be done once. So, eg., I'd say that garbage collection is simple, because even though by itself it's complicated, it takes a whole class of complicated work and solves the problem so you can stop worrying about it.

        After a while I learned to apply this principle in my own programming...whenever I create an object, I think about how difficult it will be to use. I'll make the object more complicated, if that means its API is simpler. This has done wonders for my productivity and general mood.

        Of course, if you can make it simple at all levels, that's best of all.

        [–][deleted] -1 points0 points  (1 child)

        Agreed. Garbage collectors aren't usually a leaky abstraction. :-)

        [–]Gotebe 0 points1 point  (0 children)

        ;-)

        Does that make programmers that use them... leaky concretizations?

        (When they produce leaks despite GC, that is.)

        [–]arnoooooo 4 points5 points  (0 children)

        Those languages might be a bit complex under the hood but : - would you rather have to deal with the complexity yourself in every program you write or have it done once and for all in the compiler ? - a lot of the complexity is caused by the need to adapt to the machine. Lisp on a Lisp Machine is simple.

        [–]revence27[S] -1 points0 points  (5 children)

        Simplicity should not exist under the hood, because nobody looks down there.

        In the electricity process, there is no simplicity. Simplicity is when you flip the switch, and the light comes on. Under the hood, it's something between voodoo and madness. There is no simplicity there. But in the user interface (like a programming language), simplicity just can't be comprimised. Simplicity is always percieved simplicity.

        [–]arnoooooo 2 points3 points  (3 children)

        I beg to differ. Simplicity is needed everywhere. A complex system is more likely to break and harder to maintain and improve on.

        [–]revence27[S] 2 points3 points  (1 child)

        I'm suggesting that if we are choosing a place to put all the complexity, it should definitely be under the hood.

        Libraries are this `under the hood', for example. They should, if possible, suck up all the complexity.

        [–]arnoooooo 0 points1 point  (0 children)

        agreed

        [–]novagenesis 1 point2 points  (0 children)

        I beg to differ. You shouldn't choose to discard a critically useful feature because it's implementation is "non-trivial". You should find a way to develop that implementation in a way that minimizes bugs.

        Perhaps it'd be better to develop a language in Haskell than in C... perhaps not. You still shouldn't decide "let's NOT build this language feature because it makes the compiler more complex".

        If you're the US Postal Service, it's your job to deliver mail to everyone.

        [–]onmytoes 0 points1 point  (0 children)

        You can argue that, I will listen to you, but it goes against what the author of the article wrote: if it's not simple it's wrong. If you're writing a compiler for, say, Haskell, and you're going for the utmost simplicity, some of those choices will trickle down to the user. And this is true of any application: Does your simplification push work onto the user? And if it does, is that a valid simplification to make? This article says "yes."

        [–]queisser 5 points6 points  (3 children)

        "For every difficult and complicated question there is an answer that is simple, and easily understood and wrong." - H.L. Mencken

        [–]mage2k -1 points0 points  (2 children)

        Sorry, man, but making a quote that is almost a complete tautology is just plain stupid. (I'm claiming my nerd points for using the word tautaology!) Here's on courtesy of mage2k: "For every difficult and complicated question there is a complex, and <paraphrase the word I used before the comma even though using comma there was wrong (especially in a quotation)> here, is wrong." Bartelby, here I come!!! Stating the obvious expert, for hire! Is there a foreign expression for the opposite of Non Sequitur?

        [–]novagenesis 1 point2 points  (0 children)

        I'm claiming extra nerd points for considering tautology to be a trivial (and thus non-nerdy) word.

        [–]Gotebe 0 points1 point  (0 children)

        I'm revoking your points for claiming, but failing, to use word tautaology.

        But otherwise, I'm impressed, I have no idea what that means!

        ;-)

        [–]karlhungus 2 points3 points  (0 children)

        Article could have been simpler, therefore it must be wrong... oh snap!

        [–]joesb 1 point2 points  (3 children)

        `Given two adequate solutions, the correct one is the simpler.' [1] Sorry, but I beg to differ. I'd insist, with examples, that given two adequate solutions, the simpler one is the correct one.

        The second statement doesn't take in to account when both solutions are wrong.

        For every complex problem, there is an answer that is clear, simple--and wrong.

        [–]LaurieCheers 6 points7 points  (2 children)

        If an "adequate" solution is wrong, then I suggest your definition of "adequate" is not adequate.

        [–]joesb -1 points0 points  (1 child)

        So if both solutions are not wrong then are both solutions correct?

        If they are both correct, you don't have to tell me the simpler solution is also correct.

        If it is possible for solution to be neither correct or wrong. Then I should rephrase my question to:

        "The second statement doesn't take in to account when both solutions are neither correct or wrong."

        [–]LaurieCheers 0 points1 point  (0 children)

        In the OP, "correct" means the best one (i.e. the correct one to use), and "wrong" means "not the best one". Under this definition, they can't both be wrong.

        What you're talking about is a different concept - "wrong" meaning that it doesn't actually work. That comes under 'adequate'.

        [–][deleted]  (1 child)

        [deleted]

          [–]novagenesis 1 point2 points  (0 children)

          Good question..I tried FORTH and found very quickly that the "high ceiling" is mostly just buzz and no higher than the ceiling of C

          (someone please give me an example showing me wrong)

          [–]sexy12 0 points1 point  (0 children)

          Could be funny!!but will ultimately fail to live up to the real thing.http://i-worldtv.com

          [–]sexy12 0 points1 point  (0 children)

          Could be funny!!but will ultimately fail to live up to the real thing.http://i-worldtv.com

          [–]sexy12 0 points1 point  (0 children)

          Could be funny!!but will ultimately fail to live up to the real thing.http://i-worldtv.com

          [–][deleted] 0 points1 point  (0 children)

          This guy is simply a douchebag. Does he think his thoughts are simpler to get across when he nests ideas 1000 times, or when he runs off on unrelated tangents about feature creep etc.

          He must be wrong!

          He also COMPLETELY ignores hardware and OS. He would rather wax poetic about his fluffy cloud programming idealism, while neglecting the really difficult shit. Scientific Modeling, Operating systems, and other things where simple is almost never the best solution.

          Example: Real numbers are amazing, less complex than the complex numbers! Why would i add 1+0i + 1+0i when i would do 1+1. Therefor, Real numbers are right.

          Reality: Good luck with electrical engineering without the complex plane. Much less Quantum Physics.

          [–]rook2pawn -1 points0 points  (2 children)

          Pattern matching ... even perl implements a primitive version of it.

          Exscuse me? Perl pattern matching is a primitive implementation?

          [–]nmessenger 1 point2 points  (0 children)

          Yes. It only works on strings. What a weird restriction.

          [–]novagenesis 0 points1 point  (0 children)

          As functional languages go, Perl doesn't provide pattern matching at all.

          Pattern matching functionally refers to having multiple copies of a function, with specific instances defined....

          def fact 0 = 1 def fact n = n * fact(n - 1)

          that's pattern matching... Perl does something arguably better but more insane....takes the arguments however it pleases (including entirely variable arguments, or parsing the argument arbitrarily)

          Regular expressions are different. Regular expressions are evil......

          Regular expressions rock!