Argument By Elegance

This is the potential fallacy that if a language or concept is "elegant", then it is automatically better than the competition. I'll agree that all other things being equal, elegance is a desirable feature, but usually there are many other real-world factors involved. See WaterbedTheory.

Elegance is generally measured by "simplicity" of the math or model. If it has fewer "parts", then allegedly it's easier for both humans and machines to reason about, resulting in less mistakes or more efficiency.

Generally a conflict arises when building a DomainSpecificLanguage (DSL) or library. The domain view (or designer) may want features that "muck up" the "purity" of the elegant version, requiring additional steps are work-arounds. The DSL designer wants to remove these extra adapters or steps.

For example, the domain may use a lot of lists, but the collection system may depend on sets, which lack order and repetition. It may be argued that a "list engine" is less elegant and less efficient than a set engine and that the domain design should learn to live with adaption/translation from sets to lists and vice verse. The DSL designer may however prefer a list-centric engine regardless of the theoretical loss of some elegance, skeptical of the downsides of heavy usage of lists for this domain.

-top

It really wouldn't matter if the implementation engine used 'lists' under-the-hood so long as the users cannot come to depend upon that fact from their interface (the DSL).

One may have to reinvent a lot of list-oriented idioms if using a set engine to implement it. Sure, one can be made to look like the other with enough programming, but ideally we don't want to have to reinvent stuff.

There seems to be a misunderstanding. To clarify: your "set engine" and "list engine" phrases have no technical meaning to anyone other than yourself. It is trivial, and legitimate, to use lists to implement finite sets - and, so long as one doesn't expose this fact through the interface, it will not cause problems for later changing the underlying implementation to something other than lists.

In any case, if the domain needs lists, then use lists for those cases. Use sets where they are appropriate, such as managing data.

Interfaces may be fine for a specific thing in isolation, but re-inventing an entire engine to get a decent set of CollectionOrientedVerbs can be a lot of code and project complexity. Ideally we want to avoid impedance mismatches between domain structures and tool structures so that we don't have to build lots of translators and adapters. Using lists for sets is generally easier than using sets for lists in my experience, at least from an usage perspective. I cannot claim it would scale better for say a big-iron-type shop, but that may not be necessary for a given project. You seem to work in hardware-bottlenecked projects while I tend to work on human-labor-bottlenecked projects. Fitting the domain is thus a bigger concern than finding a structure with more potential machine optimization abilities.

I do quite a bit of work in both worlds. An example of a domain that fits list characteristics is dealing with waypoints in a route. Waypoints have explicit order, and each is associated with extra data (commands to execute on arrival or while on the trip to the next waypoint, for example). I would suggest that these waypoints be managed as data (a set of facts about a route) rather than as a list. Fundamentally, if something is true then stating it twice or stating it in a different order will make it no more true. By avoiding list operations (insert, foldl, foldr, reverse, append, etc.) you also avoid complexity. At the GUI level, operators want to manage and tweak waypoints and see sane names for them. Regardless of how it is managed, a variety of different views will need to be displayed to the operator: points with a poly-line, tables with a list of routes and waypoints with trees of commands under them, etc.

Based on your claims, it seems more like you have a single view in mind, and thus want list-like operations built into the data management from the start, whereas I recognize that dedicated view languages are necessary to deal with all the different views a human will need. I believe your appeal for pushing lists into the set operations to be short-sighted PrematureOptimization of one particular view at the expense of both human labor AND performance for all other views. You go on and on about making things easier or more practical for the human, but I've never felt that argument holds water.

Creating views/translation can be very time-consuming. It's a very large part of the programming I do: marshaling stuff in and out and back and forth between interfaces and languages. Thus, "JUST create views" is more than a "just" (JustIsaDangerousWord). Further, customers often don't know what they want.

All these are good motivations for having dedicated support for producing views of systems. My own work consists of the same, though I usually work with live data rather than batch processing. I have never been the one in our discussions to hand-wave away the value or cost of 'views' (unlike a certain TopMind who often argues X is Y because X can be viewed as Y, totally ignoring the cost and value and reality of said view).

If something can be turned into a list, then a bag, then a set, and back to a list with the least amount of fuss, then it can save a lot of time and effort as the customer learns by exposure what they really want.

I would much prefer a system that lets me view data simultaneously as a list, a tree, various graph views, and so on - than one that lets me change from one to another across multiple steps. I would also like all these views to be automatically associated with enough command-and-control options that I can issue updates or commands - i.e. without fighting the system or knowing precisely how the view was produced. I'm actually developing a new programming paradigm around these requirements: ReactiveDemandProgramming.


"Elegance" really just means "has a lot of nice properties". So, when someone claims 'elegance', you should stop them and ask for clarification on the precise properties or features they are examining.

Rather than 'elegance of expression', for example, one might aim to avoid various instances of the following properties:

One can additionally add properties such as LiveProgramming (ability to edit, debug, and integrate without stopping everything); tolerance to changes in dependencies (related to CodeChangeImpactAnalysis); tolerance to disruption; ability to analyze real-time properties, information security, safety; suitability for parallelization, persistence, scalability, distribution, redundancy; which optimizations can be achieved without relying on clever or smart tools; and more.

In any case, the list of properties or features deemed 'elegant', or the avoidance of properties deemed 'inelegant', allows a more reasonable discussion of the system being studied.

I'd argue that "elegance" generally includes some form of conservation or reuse of concepts. It's rarely used to indicate feature quantity by itself. Lisp is considered "elegant", for example, because it does so much with the concept of nested lists. It reuses this concept over again for features that would be dedicated or use-specific and hard-wired in other languages. (It's also a case where elegance may not translate to practical benefits.)

Minimalism of concepts is not a sufficient condition for elegance (confer TuringTarpit). However, minimalism is a requirement for various other features: if primitives overlap in responsibility, they introduce semantic noise; if primitives do not cover all bases, then the system isn't general purpose; thus, any general purpose language design pursuing 'elegance' will have a minimal set of concepts to cover a lot of bases. Do not confuse 'feature quantity' with 'concept quantity'.

The boundary between "Feature" and "concept" can be overlapping. For example, is a "for" loop a feature or a concept? But I otherwise agree with your statement. As an analogy, we want to pack a tool bag with tools that can do a lot of jobs efficiently [and effectively], yet keep the weight of the bag down, and cost and complexity of the tools themselves down, and be relatively easy to learn to use. An "elegant" tool bag is one that appears to score well on all these when added up. However, some problems are that different people will assign different weights to each of these, or be concerned about how the toolkit works with their particular niche, the jack-of-all-trades-master-of-none syndrome.

[The concept is "iteration". The 'for' loop is a feature that implements iteration.]

There isn't much point arguing it, as TopMind was the first to use 'concept' in this page. I took him to mean 'language primitive concepts'. For 'feature' and 'property' I've been speaking more about KeyLanguageFeatures and useful engineering properties (interactive editing, ZeroButtonTesting, plug-in extensibility, portability, runtime upgrade, RealTime support and scheduling, synchronization of effectors (audio, video, robotic motion), concurrency control with resistance or immunity to deadlock and livelock and priority inversions, PersistentLanguage, memory safety, GarbageCollection, support for data-parallel and vector operations of the CPU, etc.). Thus, my understanding is that the for loop (assuming it is a keyword) would be a language primitive concept, whereas the primary feature it represents is iteration on a collection (as opposed to the collection-level operations you would see in APL or RelationalModel). --AnonymousDonor

I don't want to get stuck into a classification debate over what a for-loop "is". (And it's not necessarily "implementation" as claimed above.) But the most "elegant" solution would be to not hard-wire the for-loop into the language syntax and instead make it a library function. In other words, being able to "roll your own" loop and block constructs is the most "elegant" in terms of conceptual reuse, abstraction, and flexibility. (Perhaps it's more efficient performance-wise to hard-wire it, but let's stay away from that right now. Also, it may not necessarily be "iteration" either, as it may be implemented or optimized to avoid explicit looping under the hood.) -t


Quantity Versus Simplicity

For me, elegance is tied up with Occam's razor: have you achieved the goal with the minimum of entities/concepts/assumptions/keystrokes (as applicable to the context)? As such, it's highly likely that a programming language will be elegant in some domains and awful in others. APL springs to mind.

Simple code is not necessarily the same as compact code.

  // approach A:
  for i = 1 to 5 {
    foo(i, x)
  }
  //
  // approach B:
  foo(1, x)
  foo(2, x)
  foo(3, x)
  foo(4, x)
  foo(5, x)
I would say approach B is conceptually simpler than approach A because it requires grokking fewer concepts/idioms. However, it's less compact code-quantity-wise. There is thus a tradeoff of factors: fewest idioms versus smallest "code size".

The "overly educated" seem to usually assume approach A is "better". But in my experience, "excess abstraction" can often confuse other team members or future maintainers so much so that a simpler but more repetitious solution is often better for the organization in the medium and long run. (For this simple example, it doesn't matter much, but for more involved or complex designs, it does.)

By the way, my design decision between A or B would depend on a SimulationOfTheFuture to estimate which would be less maintenance down the road (approx 7 years). If the org is not likely to add or significantly change the quantities of foo, then I'd lean toward B, for example. If I saw the code samples but had no idea what the future change patterns are likely to be, I'm not certain I could make a reasonably-informed choice on that. (In this case it's trivial either way, but there are more complex variations of the concept.) -t

Here's a change scenario state that clearly favors B, by the way:

  foo(1, x)
  foo(2, x)
  foo(3, yyy)
  foo(5, x)  // note "order" change in first parameter
  foo(4, x)


I believe a lot of this is an economic issue: developers who grok a sufficient quantity of abstract coding to readily take up a given project are relatively rare. Plus, masters of abstraction often have poor people skills and are not likely to be domain experts. These things have roughly equal value to most organizations:

There is a short supply of people who do relatively well in all 3 (or at least a short supply of find-able matches). Thus, organizations will sacrifice some of #1 to balance the other two, which means not pushing the abstraction envelope so that a wider variety of personnel can take on the code. (Software-only shops will tend to value #2 and #3 less than other domains such that the ideal technique for one shop may not be for another.[1]) --top


Footnotes

[1] Personally I prefer being more of a generalist developer/analyst. Being a mostly heads-down coder gets tiresome to me. I like interaction with the end-users because of the direct feedback. Some of my best memories are praises from end-users who really liked certain design or UI features that help them get their work done smoother. Personally that is far more satisfying to me than money. But one must dig into domain issues and end-user viewpoints to obtain such solution "connections". -t


See also: GreatLispWar, PractitionersRejectFormalMethodsDiscussion, ScienceAndTools, StaffingEconomicsVersusTheoreticalElegance


CategoryEvidence


EditText of this page (last edited July 30, 2014) or FindPage with title or text search