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.
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).
- Are we talking about "X is Y" in the head or in the machine? The machine may dictate a specific implementation, but that doesn't necessarily mean we have to think and communicate about a concept in terms of the machine's original/root representation. Sometimes thinking/communicating in "X is Y" can be a UsefulLie in some circumstances, but not others. If I misused a UsefulLie somewhere, I apologize. -t
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:
- semantic junk: is something that exists in the model, but that cannot be expressed in the syntax. For example, the Real numbers are mostly semantic junk: for every number that can be expressed, there is an infinite set of UnknowableNumbers that cannot be expressed.
- semantic noise: exists when the syntax for expressing or using a model imposes more meaning than is necessary. For example, we can represent sets as lists, but doing so gives ordering properties to our sets, allows artificial distinctions between identical sets, and introduces possibility of duplicate elements. Or we could represent graphs as a pair of sets, but doing so introduces 'labels' for nodes that might make artificial distinctions between two semantically identical graphs.
- Just about all computer implementations of abstractions require unique addresses/ID's. Even a drawing of a graph uses some kind of pixel coordinate system and file path/name. The difference is what is "shown" to the viewer. If we show the viewer lines instead of address/ID's, were are not really getting rid of an identity system, just re-projecting it. -t
- syntactic noise: is something that exists in the syntax, but that has no particular meaning in the model. For example, needing to add a semicolon to the end of each statement, or a comma between tuple elements, is 'syntactic noise' because it doesn't mean anything; it's just there to help the parser.
- There may be some WetWare issues here. For example, I've found that for me personally, commas between key-value pairs (x=1, y=2) usually make the association easier to read and see over using spaces, especially if syntax coloring is not available.
- What is the issue?
- Let me back up a bit. What do you mean by "no meaning to the model"? Domain model? Being useful to "the domain model" and being useful as a "brain marker" may be two different things.
- I mean it has no meaning to whatever is being expressed, even for general purpose programming. I'm not seeing how the 'brain marker' objection is relevant. I've already noted that the comma can help the parser.
- Do you mean computer parser or human "parser"?
- Both. A parser is a parser.
- I would like further clarification. It does "mean" something if it's used to indicate to humans and/or parser where something starts and ends. When you say "meaning in the model", you appear to be anthropomorphizing the model. -t
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 KeyLanguageFeature
s 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
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
// approach B:
I would say approach B is conceptually simpler than approach A because it requires groking fewer concepts/idioms. However, it's less compact code-quantity-wise.
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(5, x) // note order change
See also: GreatLispWar