Hacker Newsnew | past | comments | ask | show | jobs | submit | horrido's commentslogin

It's Uncle Bob who says objects are bags of functions. And this is not specific to Smalltalk nor dynamic languages. In Java, C#, and C++, objects are still just bags of functions.


Java and C# allow you to expose mutable fields, so objects are not strictly (or even ordinarily) just bags of functions.


Yeah but objects in those languages are also bags of data, which he explicitly said they are not.


Nothing prevents an object's methods from being referentially transparent. It just depends on how you choose to write them.


Starting points matter, but this depends on the programming situation. That's why you use OOP or FP where it is most appropriate or suitable. Neither OOP nor FP is a panacea. Neither OOP nor FP is the universal programming tool.


Inheritance is not a strict requirement of OOP. In OOP, you can always use composition, in which case the question also never arises.

Inheritance is there if you need it. It's an available option.


Yes, but in common discussion, noone when talking about OOP is referring to inheritence-less OOP. No common programming language uses it, few to no programmers practice it. OOP/w inheritance is the default item being discussed.


And why is "common discussion" technically relevant? This is not a fault of OOP but of education.


You're talking about about OOP-B, everyone here is talking about OOP-A. OOP-B isn't really a part of what's being discussed.

Anyone can bring it up, but "education" isn't relevant here. It's not what was discussed in the article, or the comments.


As neither of you, I'm talking about OOP. Nobody's limiting the discussion to composition or inheritance. Otherwise we'd be talking about how 'inheritance sucks' rather than 'OOP sucks'.


> in which case the question also never arises.

I'm not sure I see that.

If class A and B wrap a C by composition and expose, respectively, `a` and `b`, then I still very much have the choice of which I'm building. Perhaps less so if they wrap a C by reference, so the C can be reused (it's too late at night to be sure I've thought it all the way through).


It has nothing to do with ownership. In FP, a function can only operate on data of a particular type. In OOP, a particular type (or class) gathers all the applicable functions (or methods) under one roof. Same difference.


Well, not really.. In effect, your class owns the relevant piece of data that it operates on and only allows pre-defined operations on that data.

Maybe my point is better articulated by saying [to me,] functional programming is more data oriented than OOP.


The word "own" is pretty meaningless. What does "own" even mean? Classes and objects (thought of as "types") are an organizing principle for organizing your functions. If you didn't organize them in this manner, then you'd have the same functions that operate on the same data types, only they're scattered everywhere, just like in functional programming.


> the same functions that operate on the same data types, only they're scattered everywhere

???

OO is different because you have things like private variables. Meaning that only methods of an object's class can access the variable. So you now have a special group of functions that can access the variable. FP is not like that.


Nice!

Would you mind if I used this?


Being shackled to existing file-based tools holds you back from improving the software development process. In the past several decades, we've seen incremental improvements but nothing really earth-shattering. Do you really believe we'll still be using file-based tools for software development 50 or 100 years from now? Smalltalk is the future of software development created 45 years in the past. See https://medium.com/smalltalk-talk/smalltalk-and-the-future-o....


Well, don't speak about abstract 'shackles', show me the improved process! I am especially interested in version control and collaboration. IMHO, existing file-based tools have had quite a few advances on this front:

* Tests are now common and considered a good thing. A new contributor can write code without worrying about breaking rarely-used functionality.

* Many languages have package managers. Using latest third-party library is now simple

* We have great deployment mechanisms -- one-command deployments are common, containers and version pinning help reproducibility.

* Some languages come with support for interactive REPL -- you can evaluate arbitrary commands inside running processes.

* Many languages support static checking -- a lot of errors could be caught even if the code is never exercised

* Many languages need no compilation at all -- just edit the file and run it

What can smalltalk offer? Obviously it has great REPL. Anything else? What is the cool new 'software development process' feature that you are referring to?

For example, I have found exactly one sentence about version control and sharing in the article you have linked: "We may even see Pharo 7, which promises a Git-integration tool called Iceberg for source code version control." . Is this what you wanted to show me? Because my takeaway is that the only way to work on Smalltalk software as a team is to use file-based tools (git). I see no improvements in software development process here.

The other commenter mentioned Monticello and Smalltalk Hub. Ok, this looks like a decent version control system. It is interesting how without files, you have to add a prefix to a name of every class and tag each function which you want checked in. Still, I could not find which features it has that git does not.


The improved process is not in how it can emulate the way you've done things for decades with file-based tools. It's in the conceptual nature of programming, as well-explained by Bret Victor: https://youtu.be/8pTEmbeENF4

- Software development using files and folders is absolutely antediluvian. Smalltalk does everything in a universe of objects; source code is organized spatially rather than in long reams residing in text files.

- Live coding and debugging done right is an enormous and unparalleled productivity booster.

- Persisting execution state is extremely convenient for maintaining continuity, and hence the high velocity of development.

Governments and enterprises have long used Smalltalk to write massive applications in large team environments using Smalltalk's own tooling for collaboration and version control. There's no question that historically Smalltalk has not played well with the outside (file-based) world, and this has been a major point of contention.

If you insist on using Git and similar tools with Smalltalk, then yes, this is problematic. The point is, if you view software development from only one perspective, you deny any possibility of improving the process in other ways that can lead to dramatically improved productivity, accelerated development, and lowered cognitive stress.


Sorry, I am at work right now and don't have time to watch videos. Can you tell me more about "Smalltalk's own tooling for collaboration and version control"? Are you referring to Monticello? I am not insisting on git, but Monticello seems pretty limited in term of collaboration. I see commit, diff, checkout, and remote pull/push.

Specifically, let's imagine this scenario: we have team of tens of programmers working on a project. A new team member joins and accidentally breaks the code in non-obvious way. He pushes the code to main repository. Next time, everyone else checks out the latest version of the code and starts having weird problems. If you had 20 people on team, and they each wasted 2 hours because the code was broken, well, you just wasted a week of programmer time. How do you prevent it?

In file-based word, the answer is tests and CI. What is the smalltalk way? And please do not say "It's in the conceptual nature of programming" -- if the scenario makes no sense in the smalltalk world (maybe you are not supposed to have 20 people working on the same project?) please say this.


Pharo Smalltalk, in particular, when it comes to VCS, it's very similar to git actually. It uses source code files, it distributes them via zip files, it works locally instead of centralized, it supports merges, etc.

Pharo works well also with usual VCS because it can export code into source code files.

The image plays no role in VCS whatsoever because VCS is about code, not data, and image is mostly about live data and less about live code.

So any tool will and does work with Pharo outside the image. Problem arises with a majority of people that prefer to stay in the image; in that case you gain more control because you have more Pharo code to play with, but you lose a lot of power because we are a small community not able to compete with behemoth projects like git.

Another interesting thing which Pharo does emphasize is remote debugging: though not a Pharo monopoly by a long shot, we do have several libraries that can achieve this, and because the image format retains live state and live code execution, it's easy to resolve issues.

Besides the image format, the Fuel format has the advantage of storing only a fraction of the image. You can email this or share it via git or Dropbox. Like an image, a Fuel file is a binary file and, like the image, it can store live state and live code execution. This way, you can isolate live bugs and share them with your team, each one in its own Fuel file.

STON is also another alternative format which feels familiar for those that have worked with JSON.

So you see, you get the best of both worlds. You have the fantastic Smalltalk image, and you have ways to deal with the file-based world.


A few important points:

1. Breaking code is nothing specific to a language. The usual weapon also in Smalltalk is to monitor if something breaks - for instance by using CI. Continuous integration only makes real sense when you have tests.

    One should remember that "test first", "unit testing" and "Extreme programming" (XP)
    like many other things had their roots in Smalltalk. Because in dynamic
    languages testing using code was and is part of the culture (ranging from lively verifying
    with workspace expressions and inspectors up to fully written tests).

    The first unit testing framework "SUnit" was written by Kent Beck for
    Smalltalk (SUnit), later he ported it to Java with Erich Gamma on a flight
    to OOPSLA OO conference. Java helped to push the popularity afterwards.

    Meanwhile also static language enthusiasts have understood that it is better to
    rely on tests than type checking compilers and they now hurry up to follow
    what Smalltalk already had years ago.

    One last thing you should try: try to query your system how many test methods were
    written. When you solved this easily with a Smalltalk expression retry
    this in Java ;)

 2. Commercial Smalltalks which are often used in big projects provide solutions
    which are repository based like the famous ENVY (written by OTI, was
    in VisualAge for Smalltalk from IBM, now VAST) or Store (VisualWorks). For
    more details try the commercial evaluation versions or read [1] or [2].
    A screenshot of Envy can be seen in [3]. I worked with ENVY and it is really
    good - but mostly only for internal work/teams. If I remember correctly
    ENVY once was also available for VisualWorks (VW) ... but later got replaced
    for business or license reasons. I'm not sure about that point.

    Cincom developed Store for VW as a replacement which is also nice as it allows to work
    in an occasionally-connected mode, so work offline and push packages/versions later to
    a central team repo.

    In the open source world there are different solutions (including Monticello
    which is available for nearly all Smalltalk derivates) or newer solutions
    like FileTree or Iceberg allowing to work with Git.

    The workflow depends on the tool and your requirements.

 3. Often it makes sense to automatically build and regular distribute a fresh daily
    developer images to the members of your team. This helps in later merging
    code.
    For instance Kapital (a big financial project from JP Morgan) works that
    way and I've seen that model very often. See [4]

    Again nothing special to Smalltalk. In more file based languages it also makes
    sense to stay close to the main line and merge as well as resynchronize with the
    team.

    In Pharo for instance we have the PharoLauncher that allows you to download
    any (fresh or old) image built provided by the open source community.

 3. Versioning can be done on many levels. Simplest level is the image itself.
    Smalltalk not only has an VM and image concept - but also the concept of a changes
    file. If you evaluate a code expression, create or modify a class or method
    in the system this gets logged there.
    It prevents you from loosing code and it is easy to restore quickly for instance
    an earlier method versions/editions that one has implemented.

    Most Smalltalks now also work with packages and you can define package
    dependencies as well as declaring versions that fit together to provide
    a project, goodie or app (for instance with a Configuration class in Monticello)

    While in file based languages this is often done in an XML file (Maven for instance)
    or a JSON file in Smalltalk this is usually expressed with objects and classes again.
    This also makes it more flexible as you can very easily do queries on it or
    use refactoring tools to even restructure or reconfigure.

 4. Usage of shared code repositories is very common also in Smalltalk. While you
    now can also use GitHub, GitLab, Gogs and others with Iceberg and friends in Smalltalk
    there are also repository systems implemented in Smalltalk itself like
     - SqueakSource (http://source.squeak.org, http://squeaksource.com)
     - SqueakSource3 (http://ss3.gemtalksystems.com)
     - SmalltalkHub (http://smalltalkhub.com)

 5. Beside repositories where code and goodies are hosted one often finds registries
    with infos about loadable components.

    Pharo for instance has http://catalog.pharo.org which is accessible also
    directly from the image.

 5. If you work in a team you can also use a custom update stream. This is how
    for instance open source projects like Pharo and Squeak are managed.
    So anyone can hit an "update" button to get the latest changes.

    In Pharo http://updates.pharo.org is used and you can have a look at UpdateStreamer
    class to see how easy that works over the web or how to customize it for own needs.

 7. If one requires not only collaboration for the development team (coding) but
    would like to collaborate also with other projects members on other artefacts
    (Excel, project plans, documents, ...) then one should have a look at tools like this

    http://www.3dicc.com

    which is implemented in - guess what: SMALLTALK.
This list could be endless ... the first few points should only give a glimpse on what is there and available.

[1] https://www.cambridge.org/core/books/mastering-envydeveloper... [2] http://www.cincomsmalltalk.com/main/documentation/VisualWork... [3] http://www.edm2.com/index.php/Installing_the_VisualAge_UML_D... [4] http://www.slideshare.net/esug/13-yanndmonclair


Most of those "advances" have nothing to do with files or images at all.

For testing it doesn't matter where the tests come from. Certainly some test environments look for tests in files with predetermined names, or in particular directories, but that's an implementation detail. Look at Rust for a modern compiled file-based language that completely avoids that; instead you tag individual functions with metadata to indicate that they are tests. At build time you can create both a normal binary and a test binary; the first is the program you wrote, the second runs the tests that you wrote.

REPLs and a mixed compiler/interpreter were both invented by Lisp, which is largely image-based. They go hand-in-hand; code you enter at the REPL might start out interpreted, but you can always call the compiler (at run time) to speed up functions you care about (or let the system do it automatically).

The static vs dynamic language divide has nothing to do with how the code is stored, but historically languages with REPLs have indeed been dynamic languages.

I don't know about Smalltalk specifically, but Common Lisp has a great package system, and with Quicklisp it's a fully modern one with automatic package installation, dependency management, version pinning, and everything. Of course, Common Lisp these days is a hybrid system. Your code is stored in files, and when you load them into memory to create an image. If you care about startup time you can dump that image out so that it loads all at once. Quicklisp just has to download a tarball of files and stick them in your load path for you; I don't know precisely this is done in Smalltalk.

Both Lisp and Smalltalk have a facility for writing code out to a file to be printed, sent across the network, etc. This is how collaboration is done. You write the code in your environment, test it, etc. Then you write it out to a file and send it to whoever. (Or your version control system puts it in a file for you when you commit, etc.)


Well, I was responding to specific parent's statement: "Being shackled to existing file-based tools holds you back from improving the software development process." This seems to imply to me that non-file-based tools have some other improvements to software development process.

I think both you and I agree that software development process improves mostly independently from file-based/image distinction. There are no visible ways when files are "holding back" the software development.


Everyone sees the world through their own eyes, filtered by what their past and what know.

A peculiar perspective of Smalltalk programmers is to consider that "code in files is dead code". External tools are needed to query it, modify it, compile it.

In Pharo you can evaluate... Integer methods first inspect

and in different tabs you can see this method's source code, the AST, the compiled bytecode.

Or in a similar way we can look at how PLUS is implemented by evaluating (Integer>>#+) inspect

Or look at all the methods that use PLUS

    #+ senders inspect
Within Smalltalk, classes and methods are themselves objects. You can operate on them like any other object. They are ALIVE!!! AHAhahaHaaaaa :)


> * Tests are now common and considered a good thing. A new contributor can write code without worrying about breaking rarely-used functionality.

btw, xUnit testing originated with Smalltalk. https://en.wikipedia.org/wiki/SUnit

> * Many languages have package managers. Using latest third-party library is now simple

Metacello is Pharo's tool for defining dependencies between libraries [1][2]. [1] http://sleepycoders.blogspot.com.au/2013/10/dead-simple-intr... [2] http://pharobooks.gforge.inria.fr/PharoByExampleTwo-Eng/late...

> * We have great deployment mechanisms -- one-command deployments are common, containers and version pinning help reproducibility.

Pharo Catalog provides a GUI one click installer of Metacello configurations https://pbs.twimg.com/media/CHajfSAWUAEpCBM.png

You can also load Metacello configurations from the command line per last snippet of [1]

Metacello provides version pinning.

You might consider every Smalltalk Image to be a container. Its like a uni sports tour. What happens in the Image, stays in the Image ;)

> * Some languages come with support for interactive REPL -- you can evaluate arbitrary commands inside running processes.

As you say, Smalltalk has a super REPL. How easy is it for other REPLs to modify and query the code of the system? For example in Pharo if I define a class and methods like this...

    Object subclass: #MyClass
	instanceVariableNames: 'count'
	classVariableNames: ''
	package: 'Demo'.

    MyClass compile: 'setCount: anInteger
         count := anInteger'.

    MyClass compile: 'getPlusFive
         ^count + 5'.
I can do this... obj := MyClass new. obj setCount: 3. obj getPlusFive inspect. ==> 8

    MyClass methods asString 
==> '{MyClass>>#getPlusFive. MyClass>>#setCount:}'

> What can smalltalk offer? Obviously it has great REPL. Anything else? > What is the cool new 'software development process' feature that you are referring to?

For me, the greatest thing about Smalltalk is the very tight code/compile/debug loop. Indeed, its common to "code from within the debugger" where you compile your first method calling methods that don't exist yet, and when execution hits them, the system asks you to define that method and then continues execution. This is great for protoyping and (purely subjective) I feel program architecture ends up being closer to how a "user" of the library would like it layered.

A video by someone outside the Pharo community (Avdi has published several books on Ruby) shows a bit of this [3] https://www.youtube.com/watch?v=HOuZyOKa91o&t

Now this is not "cool and new" for Smalltalk. It has had this since 1980. But its a "blub" [4] feature that not many people get exposed to. The saying goes "the only languages worth learning are those that change how you think about programming". And for me, this is what changed how I thought about programming. [4] http://paulgraham.com/avg.html

> For example, I have found exactly one sentence about version control and sharing in the article you have linked: "We may even see Pharo 7, which promises a Git-integration tool called Iceberg for source code version control." . Is this what you wanted to show me? Because my takeaway is that the only way to work on Smalltalk software as a team is to use file-based tools (git). I see no improvements in software development process here.

Git interaction is not what Pharo is selling a better development process. But its an important thing for the Pharo community to make it easier to participate in polygot projects. Historically one of the common criticisms that Smalltalk was insular and didn't play well with others. Monticello had three-way merge for a long time so we were able to live a long time without external version control. Monticello works well for small teams, but doesn't scale well to Internet size teams. We've now recognized those other advances and fixing those reasons "not to use Pharo."

The advantage of Pharo is not so much inter-developer processes. Its simliar to other languages, especially now we are integrating. Pharo's benefit is improving the "flow" of moving thoughts into code. The very tight code/compile/debug loop and minimal syntax reduces the friction of coding.

I heard of a few projects where clients initially resistant to the use of "new" technology like Pharo, but accepted using Pharo to prototype with - but in the end were very happy to leave it all in Pharo.

> The other commenter mentioned Monticello and Smalltalk Hub. Ok, this looks like a decent version control system. > It is interesting how without files, you have to add a prefix to a name of every class and tag each function which you want checked in. Still, I could not find which features it has that git does not.

This prefixing of class names has nothing to do with sans-files. It is just that Pharo does not have namespaces. This does leave the potential for name classes, but in practice the prefixing works well. btw, as another minor demonstration of Pharo REPL, you can change every reference to the class in one line like this...

    MyClass rename: 'OtherClass'.

    obj := OtherClass new.
    obj setCount: 5.
    obj getPlusFive inspect.
==> 10


>Smalltalk is the future of software development created 45 years in the past.

So doesn't that imply that Smalltalk was wrong to think we wouldn't be using file-based tools in 50 years..?


Exactly what I was thinking.


Java is currently the most popular language in the world. There are far more job postings for Java than for any other programming language. Java is the enterprise standard language. Java has a tremendous ecosystem and a vast user community. Java is pretty much unassailable.

However, in 1995 Java was nothing more than a hopeful upstart from Sun Microsystems. There were no job opportunities for it. It had no ecosystem to speak of and relatively few users. Performance-wise, it ran like a slug (today, the JVM is well-optimized). How did Java achieve today's status?

To a large extent, it was thanks to Sun Microsystem's marketing efforts. People were drawn to the promise of "write once, run everywhere." They also liked that Java was a fairly simple and easy-to-learn language. In time, Java developed a large community and widespread usage.

The point is, all programming languages have to start from zero. They either grow from grassroots, or they grow from corporate sponsorship and marketing. Incumbent languages with their large ecosystems and user communities eventually yield to newer languages with their promise of greater ease, productivity, flexibility, power, etc. Getting the job done is important, but one should also look to the future. That's the hope of languages such as Go, Swift, Dart, Julia, Rust, Elixir, Smalltalk, and so on.


You're over-generalizing. This is very much a matter of personal taste/inclination. Do ALL Lispers love the Lisp style of syntax? Of course not.

Moreover, for many programmers, it's asking too much of them to wrap their heads around a syntax where you need to give it time for the parens to "fade" away. This cognitive hurdle is real and it's why Lisp isn't more popular.

Let's not bury our heads in the sand.


> This cognitive hurdle is real and it's why Lisp isn't more popular.

Nobody has the answer why language or language family X isn't more popular.

Thousands and thousands of languages have been invented since the dawn of computing. Only a handful are popular at any one time.

Simply by probability and statistics alone, a language is unlikely to be popular.

Some languages with awful, difficult syntax have enjoyed popularity.

Perl became popular because of cryptic syntax. Cryptic syntax is what is populist, not clean syntax.

Programming wannabes outnumber actual developers N:1 for rather large N. Programming wannabes feel smart when they remember and correctly use cryptic syntax. "Hey, look, I didn't use any parentheses in this because I memorized all 17 precedence levels!" "Wow, this linenoise-like sausage of symbols totally works! That is AWESOME!" "Bubble sort in 13 characters of code; I'm floored ...".

C is popular because of the reverence for the tricks that it allows, like Duff's device:

https://en.wikipedia.org/wiki/Duff's_device

Lisp threatens these types of people whose competence is rooted in arcane syntax. It does that precisely because it's ultra-readable.

Even if you've never programmed Lisp, you can probably guess what this does with great accuracy:

  (loop for x from 1 to 10
        collecting x into list
        maximizing x into max
        finally (return (list max x)))
You certainly won't stumble over any issues of cryptic syntax, or precedence or whatever. You might face an impediment if you don't speak English, due to that language being used to name symbols like "loop" and "collecting".

People who claim that Lisp is not readable are disingenuous (promoting something else or jealously protecting their self-image of competence when the bulk of their CS and programming knowledge rests in the mastery of a handful of cryptic syntaxes), crazy/trolling or else cognitively different (such as dyslexics genuinely struggling with the syntax).


It's not just the parentheses. It's the whole code-as-data and interactive development thing. Code is different. Programming is different.

Lisp uses different working styles and tools. You have to learn the whole set. It's possible to learn that and to learn it well, but it might take some time.

Lisp code can be very readable, but it's also to write very complex code - more complex that in most languages, since Lisp provides many mechanisms under developer control.


Even if you don't care about using code as data, Lisp is simply more readable, period.

You don't have to crack open a reference manual to look at grammar rules or precedence tables to work out what the shape of the syntax tree is; you can read anything just from the symbolic prefix, plus a smattering of minor notations like sharpsign-this and sharpsign-that.

A programmer who doesn't know Lisp can still instinctively tell that in (foo (x y) (z w)), the x and y go together more tightly than foo and x, or y and z.

The meaning is determined by foo. But foo is a word. And words have the nice property that you can look them up easily in manuals (potentially with just a mouse click).

Those who say Lisp is not readable are trolls and liars. They are liars because they see it's obviously more readable, but it threatens their self-image of being a competent programmer. It does that because their self-image is rooted in having memorized a bunch of arcane syntax and believing that that is what makes a software developer.

Look at this comment, for instance:

https://news.ycombinator.com/item?id=12883362

"If only the LISP syntax looked like a programming language and made logical sense."

Very good representative comment which captures the mindset aptly.

A programming language can only possibly be legit only if programs in that look like what you already know.


> You don't have to crack open a reference manual to look at grammar rules or precedence tables to work out what the shape of the syntax tree is; you can read anything just from the symbolic prefix, plus a smattering of minor notations like sharpsign-this and sharpsign-that.

You can't. Any macro may support arbitrary syntax. If all you know is the macro name, you don't know nothing about the syntax it implements.

> (foo (x y) (z w))

There are many many much more complex forms in Lisp.

> And words have the nice property that you can look them up easily in manuals

Then you have to read the syntax FOO implements.

DEFGENERIC:

    defgeneric function-name gf-lambda-list [[option | {method-description}*]]

    => new-generic

    option::= (:argument-precedence-order parameter-name+) | 
              (declare gf-declaration+) | 
              (:documentation gf-documentation) | 
              (:method-combination method-combination method-combination-argument*) | 
              (:generic-function-class generic-function-class) | 
              (:method-class method-class) 
    method-description::= (:method method-qualifier* specialized-lambda-list [[declaration* | documentation]] form*) 
Then you need to know the set of precedence order names. What is a GF-DECLARACTION? What is a method-qualifier? what is the specialized-lambda-list? What are valid declaration expressions...?

> Those who say Lisp is not readable are trolls and liars.

I don't think you add credibility with these statements. Sorry, this is just dumb.


  (defgeneric draw (thing on-what where))

  (defmethod draw ((obj drawable) (where canvas) (location point))
    ...)
No brainer.

Even though macros can contain any syntax, and in principle we can have a macro such that:

  (mac token1 token2 token3 ... tokenN)
where the tokens are parsed according to some LALR(1) (or worse) grammar, in practice, macros are usually not designed that way. The nested list syntax is used for grouping units together.

So without knowing what mac is, you don't know the semantics, but the structure is, more often than not, crystal clear.

Of course you need the manual to know what they mean if you don't remember (gee, which is the class name and which is the instance, location or point?), but you don't have to implement the LARL(1) parser in your head.


Here is an exercise for you:

Which of the following forms are valid Common Lisp:

    (defun foo (a b)
      (declare (fixnum a))
      "foo"
      (declare (fixnum b))
      (+ a b))

    (defun foo (a b)
      "foo"
      (declare (fixnum a))
      (declare (fixnum b))
      (+ a b))

    (defun foo (a b)
      (declare (fixnum a))
      (declare (type fixnum b))
      "foo"
      (+ a b))

    (defun foo (a b)
      (declare (type fixnum b))
      "foo"
      "bar"
      (declare (fixnum a))
      (+ a b))

    (defun foo (a b)
      (declare (fixnum a))
      (declare (type fixnum b))
      "foo"
      "bar"
      (+ a b))

    (defun foo (a b)
      (declare (fixnum a))
      "foo"
      "bar"
      (+ a b))


I don't know, and who cares.

I seem to be vaguely convinced for some reason that there may be exactly one docstring, and that, if present, it must be the first item in the body; then there can be declarations. Not sure if there can be two or more declares. I would always write (declare (fixnum a) (fixnum b)) and don't recall seeing multiple declares in other people's code. (I might be suffering from a form of amnesia which merges multiple ANSI Lisp declares into one.)

This is the sort of thing I'd look into closely if I were tasked with parsing a function body (like for the sake of making some body rearranging-and-reassembling macro fully conforming with the surrounding language). I'm not going to write such code myself, and won't likely see it in other people's code.

Until I see such instances, I won't bother looking at the spec to see what the exact rules are and confirm/refute whether those examples are conforming.

In any case, whether or not some of the examples are correct, I can see what they are trying to say. Be they mistakes, they are still well-formed surface syntax and are readable. Things could plausibly work in some CL dialect such that they are all correct, if they aren't.

A literal string near the beginning of a function, which is only evaluated for its side effect (which it doesn't have) looks suspiciously like a doc string. Either it is correctly positioned or it isn't. The (declare ...) syntax is clearly a declaration, whether or not correctly positioned. (declare ...) isn't a form. I suspect it would be undefined behavior for a program to define a function or macro called declare; not sure about that. Defined or not, it would be a incredibly bad idea.

I'm a competent Lisp programmer and implementor too, and don't have to have this memorized; it's likely not an impediment to anyone else. Which kind of makes my point.


> I don't know, and who cares.

A Lisp programmer has to care, if he wants to write Lisp code.

> I seem to be vaguely convinced for some reason that there may be exactly one docstring, and that, if present, it must be the first item in the body

That's wrong.

> I won't bother looking at the spec to see what the exact rules are

Which confirms what I'm saying: even for seemingly trivial DEFUNs the syntax is not obvious.

> Be they mistakes, they are still well-formed surface syntax and are readable.

But the reader does not implement Lisp syntax. The reader just implements s-expression syntax. If the reader can read the form, then it is a valid s-expression. But not necessarily valid Lisp.

     (defun foo (a) a)  ; valid s-expression, valid Lisp
     (defun (foo) a a)  ; valid s-expression, not valid Lisp
     (defun (bar foo) (a) a)  ; valid s-expression, not valid Lisp
     (defun (setf foo) (a) a)  ; valid s-expression, valid Lisp syntax

     (defun foo (&key a &optional b) (list a b))  ; not valid Lisp

     (defun foo ($key a &optional b) (list a b))  ; valid Lisp
The Lisp interpreter/compiler and the macros implement Lisp syntax. Not the reader.

> it's likely not an impediment to anyone else.

That you don't know the syntax of DEFUN confirms what I'm saying: the syntax is non-obvious.

Explain this:

    * (defun foo (a b)
          (declare (fixnum a))
          "foo"
          "bar"
          (+ a b))

    debugger invoked on a SIMPLE-ERROR: duplicate doc string "bar"

    Type HELP for debugger help, or (SB-EXT:EXIT) to exit from SBCL.

    restarts (invokable by number or by possibly-abbreviated name):
      0: [ABORT] Exit debugger, returning to top level.

    (SB-INT:PARSE-BODY ((DECLARE (FIXNUM A)) "foo" "bar" (+ A B)) T NIL)
    0]


> That you don't know the syntax of DEFUN confirms what I'm saying.

Yes, it rather confirms that you can go far in Lisp without memorizing stuff like this!!!

If I don't have to know, few people do.

You can bet your ass that if I had to implement an ANSI-CL conforming defun/lambda I'd get it right, of course. I don't have some "requirements don't matter" attitude; but not all requirements matter all the time to all people.

I'd like to add, though, this: you can't be safe in ignorance in Common Lisp. Things you don't know can hurt you. Less than in some other languages, but they are there. You probably won't be hurt by not knowing all the possibilities for combining docstrings and declares. But if you're ignorant of some things, you may get burned. Undefined behavior situations like modifying literal tree structure, or writing a program that parses untrusted Lisp data, not knowing about the #. syntax and * read-eval* . I'm not advocating unsafe ignorance. Not knowing the full flexibility of some syntax is generally safe, though.

> Explain this:

Which aspect of that error scenario isn't self-explanatory; what is left to explain?

From that one error, the listener has just taught us several important facts about the syntax which it accepts (which may be a superset of ANSI CL for all we know). It shows us that it accepts a docstring after a declare just fine, but that there must be at most one docstring. (At least, when the two are in that order, if we are to be strict in our guessing.)

So without looking at a shred of documentation outside of that error message, I'm already better informed.

The interactive nature of Lisp teaches! (Especially if the implementation is tight on error checking and has good diagnostics.)

What's left unsettled from that error situation is whether or not there may be multiple declares, since that case isn't being probed.


> Yes, it rather confirms that you can go far in Lisp without memorizing stuff like this!!!

That you don't know the relatively trivial syntax of DEFUN, shows that it is already non-obvious and you have to look it up.

You have failed to explain the syntax of DEFUN and you can't distinguish valid from invalid forms. I would never trust a code review done by you.

> You can bet your ass that if I had to implement an ANSI-CL conforming defun/lambda I'd get it right, of course.

Maybe. Maybe not.

> Which aspect of that error scenario isn't self-explanatory; what is left to explain?

Is that valid or not, given the syntax of Common Lisp? Does SBCL implement the syntax correctly or does it reject valid programs?

The syntax of DEFUN is:

    defun function-name lambda-list
      [[declaration* | documentation]]
      form*
Which says: any number of declarations and at most one documentation, in any order.

    (defun foo (a b)
      (declare (fixnum a))    ; declaration
      "foo"                   ; documentation
      "bar"                   ; form
      (+ a b))
Which indicates that SBCL rejects a valid program. I would not trust you to get it right as an implementor, given that you haven't even tried to verify it and that you are ignorant to Lisp syntax. I would not trust my own implementation without trying to come up with an extensive syntax test suite.

Similar:

    (defun foo () "a")           ; sbcl valid
    (defun foo () "a" "b")       ; sbcl valid
    (defun foo () "a" "b" "c")   ; sbcl rejects


Hi lispm. I investigated this more deeply and I'm afraid I cannot conclude that SBCL is wrong.

The root of the problem is this splicing [[ ]] extended BNF notational concoction, in whose description we find this:

... For example, the expression

  (x [[A | B* | C]] y)
means that at most one A, any number of B's, and at most one C can occur in any order. It is a description of any of these:

  (x y)
  (x B A C y)
  (x A B B B B B C y)
  (x C B A B B B y)
but not any of these:

  (x B B A A C C y)
  (x C B C y)
In the first case, both A and C appear too often, and in the second case C appears too often.

In the case of defun, we can identify C with documentation and y with forms. But documentation is a kind of form. According to the above, if we are given documentation documentation form, it doesn't match: documentation appears "too often". That appears to rule out "foo" "bar" as ill-formed, if "bar" is interpreted as documentation rather than form.

This is woefully badly specified; it is not clear how to unambiguously determine the extent of the symbols matched by a given [[ ]] notation.

There needs to be a clearly stated requirement that [[ ]], independently of what follows it, denotes (say) the longest possible sequence of symbols which is consistent with its constraints. The material which follows [[ ]] must then match against the remaining symbols in the form. Then it will be clear that given "foo" "bar", the "bar" string isn't part of the [[ declare(star) | documentation ]] spec, because the longest match ends with "foo" (if no declares follow).

Maybe it does. I'm afraid I cannot make head or tail out of the sentence "such that if n /=m and 1<=n,m<=j, then either Oin/=Oim or Oin = Oim = Qk, where for some 1<=k <=n, Ok is of the form Qk {star} . Furthermore, for each Oin that is of the form {Qk}1 , that element is required to appear somewhere in the list to be spliced. " Perhaps that rescues it somehow.


> I would never trust a code review done by you.

Why not? A code review has topics. It could be to enforce a coding convention.

Firstly, I would not pass code that uses multiple docstrings or a funny order for docstrings and declares or multiple declares; a coding convention should forbid such pointless shenanigans, whether or not they are ANSI conforming.

If the topic of the review was to determine whether the code is ANSI CL conforming, then I wouldn't just feed it to the listener of SBCL or any other implementation. Obviously, implementations can be nonconforming. They can accept, without diagnostic, inputs that require a diagnostic, as well as inputs that are nonportable or entirely undefined.

You can't determine conformance of the code, or of the implementation, or both, without reading and interpreting the applicable standard, obviously. (You didn't have to labor this far if you just wanted me to say that.)

> Does SBCL implement the syntax correctly or does it reject valid programs?

Even if we determine that SBCL is rejecting valid programs, that version of SBCL will forever continue to do so. We have to change to a working construct to get the code working.

Once we do that, the status of the nonworking construct that we replaced with a working one is rather moot. We have found all instances of that construct and replaced it, after which it no longer occurs in our code.

We may submit a bug report against SBCL. Since we fixed the code not to interact with the bug/noncompliance, we don't care when, if ever, SBCL issues a fix.

The nonworking construct could have been avoided in the first place by sticking to the simplified "canonical" syntax:

  (defun args [ docstring ] [ (declare ...) ] {form}*)
You can program defun-s for the rest of your Lisp programming life this way and never know the full syntax. You're also vanishingly unlikely to run into an implementation which doesn't accept the variations on this canonical syntax.

Deviations from this form can be rewritten into this form.

The exception might be machine-generated deviations. Like multiple declares or docstrings that are piled on by some macrology: if you want their output to be canonicalized, you have to insert some processing pass to do that normalization. That is annoying, and so we will feel better if we can blame it on a nonconformance in the implementation, even if we still have to do this work.

> (defun foo () "a" "b" "c") ; sbcl rejects

That's somewhat nasty; yet, there is no reason to write this kind of defun by hand. "a" is obviously a docstring, and "c" the return value. But "b" is superfluous in the sense that it has no effect.

If "b" contains documentation, it should be merged into "a" to produce the canonical form (defun foo () "ab" "c"): "ab" is the doc, "c" the returned object. If that doesn't work, then I care; that is uproarious.

I might be interested in knowing whether the above "a" "b" "c" is in fact invalid ANSI CL, if I'm more invested in that rejected form; like I have some macrology that produces it (so fixing the situation requires more work than just hunting down a couple of bad defuns). That macrology could be reused in other projects and so on.

Still, the fact that SBCL rejects it means that I can't have it if we are targetting SBCL. A workaround for SBCL (if that's what it is) might as well be applied all across the board.

So is it ANSI CL or not? The syntax is:

  defun function-name lambda-list [[declaration* | documentation]] form*
This [[]] notation is a special "splicing" extension of the BNF syntax which here indicates that there may be at most one docstring and any number of declarations, and these may appear in any order (so that a docstring can come between declares).


> No brainer.

I was talking about DEFGENERIC syntax, not a trivial example of DEFGENERIC/DEFMETHOD.

> Even though macros can contain any syntax...practice, macros are usually not designed that way.

The LOOP and ITERATE macros are examples of macros with lots of syntax.

> So without knowing what mac is, you don't know the semantics, but the structure is, more often than not, crystal clear.

The syntax of DECLARE, DEFGENERIC, HANDLER-CASE, HANDLER-BIND, DEFINE-CONDITION, DEFINE-METHOD-COMBINATION, LOOP, FORMAT, ... are far from 'crystal clear'.

One of the macros which is very hard to get right and to understand what it actually does is CLIM:DEFINE-APPLICATION-FRAME

Example:

    (define-application-frame pets
        (command-queue-mixin standard-application-frame)
      ((assembly-group :initform nil
                       :accessor pets-assembly-group)
       (current-sheet :initform nil
                      :accessor pets-current-sheet)
       (show-sheet-backgrounds-p :initform t
                                 :initarg :show-sheet-backgrounds-p
                                 :accessor pets-show-sheet-backgrounds-p)
       (edit-mode-p :initform t
                    :initarg :edit-mode-p
                    :accessor pets-edit-mode-p)
       (current-prototype-element-class :initform nil
                                        :accessor 
                                        pets-current-prototype-element-class)
       (blue-elements :initform nil
                      :accessor pets-blue-elements)
       (red-elements :initform nil
                     :accessor pets-red-elements)
       (orange-elements :initform nil
                        :accessor pets-orange-elements)
       (prototype-vector :initform nil
                         :accessor pets-frame-prototype-vector)
       (scaling-factor :initform 1
                       :accessor pets-scaling-factor)
       (scaling-factor-changed-p :initform nil
                                 :accessor pets-scaling-factor-changed-p)
       (rectification-p :initform t
                        :accessor pets-rectification-p)
       (elements-to-be-checked :initform nil
                               :accessor pets-elements-to-be-checked)
       (viewport-position :initform nil
                          :accessor pets-frame-viewport-position))
      ;;(:pointer-documentation t)
      (:command-table (pets
                       :inherit-from 
                       (pets-assembly-group-commands pets-sheet-commands
                                                     pets-measurement-commands
                                                     pets-configuration-commands)
                       :menu
                       (("Assembly" :menu pets-assembly-group-commands
                                          :documentation "Assembly Commands")
                        ("Sheet" :menu pets-sheet-commands
                                 :documentation "Sheet Commands")
                        ("Configuration" :menu pets-configuration-commands
                                         :documentation "Configuration Commands")
                        ("Generator" :menu pets-measurement-commands
                                     :documentation "Test Plan Genenerator Commands"))))
      (:panes
       (prototype-elements-pane :application
                                :display-function 
                                'draw-circuit-elements-with-descriptor
                                :incremental-redisplay t
                                :max-width +prototype-elements-pane-width+
                                :min-width +prototype-elements-pane-width+
                                :width +prototype-elements-pane-width+
                                :scroll-bars nil)
       (relay-state-pane :accept-values
                         :text-style (make-text-style
                                      :fix :roman :normal)
                         :max-width +relay-state-pane-width+
                         :min-width +relay-state-pane-width+
                         :width +relay-state-pane-width+
                         :scroll-bars :vertical
                         :display-function '(accept-values-pane-displayer
                                             :displayer draw-relay-states-avv
                                             :resynchronize-every-pass t))
       (circuit-pane :application
                     :display-function 'draw-circuit
                     :output-record (make-instance 'r-tree-output-history)
                     :min-height 200
                     :incremental-redisplay t)
       (command-listener :interactor
                         :min-height 100
                         :height 100
                         :max-height 100)
       (sheet-pane (make-pane 'option-pane
                              :label "Current Sheet"
                              :value 1
                              :items '(1)
                              :value-changed-callback 'change-current-sheet))
       (scaling-factor-options-pane (make-pane 'option-pane
                                               :label "  Scaling Factor"
                                               :value 1
                                               :items '(1/2 1 2)
                                               :value-changed-callback
                                               'change-scaling-factor))
       (background-shown-toggle-button-pane
        (make-pane 'toggle-button
                   :value nil
                   :value-changed-callback 'change-show-background-p
                   :label "Hide Background"))
       (measurement-paths-pane (make-pane 'list-pane
                                          :label "Measurement Path"
                                          :name-key 'print-measurement-path
                                          :items (list *no-path-selected*)
                                          ;; :scroll-bars :both currently being ignored by CLIM
                                          :value *no-path-selected*
                                          :visible-items 39
                                          :width 150
                                          :min-width 150
                                          :max-with 200
                                          :value-changed-callback
                                          'change-measurement-path))
      (attribute-pane (make-clim-stream-pane
                        ;;:foreground +white+
                        ;;:background +black+
                        :text-style (make-text-style
                                     :sans-serif :bold :small)
                        :scroll-bars nil
                        :min-height '(1 :line)
                        :max-height '(1 :line)
                        :height '(1 :line)))
       (pointer-documentation (make-clim-stream-pane 
                               :foreground +white+
                               :background +black+
                               :text-style (make-text-style
                                            :sans-serif :bold :small)
                               :type 'pointer-documentation-pane
                               :scroll-bars nil
                               :min-height '(1 :line)
                               :max-height '(1 :line)
                               :height '(1 :line))))
      (:layouts
       (data-input (vertically ()
                     (horizontally ()
                       prototype-elements-pane
                       circuit-pane)
                     (horizontally ()
                       command-listener
                       (vertically ()
                         (horizontally () 
                           sheet-pane
                           scaling-factor-options-pane)
                         (horizontally (:max-width 150)
                           background-shown-toggle-button-pane)))
                     pointer-documentation
                     attribute-pane))
       (test-plan-generation (vertically ()
                               (horizontally ()
                                 relay-state-pane
                                 circuit-pane
                                 (scrolling ()
                                   measurement-paths-pane))
                               (horizontally ()
                                 command-listener
                                 (vertically ()
                                   (horizontally () 
                                     sheet-pane
                                     scaling-factor-options-pane)
                                   (horizontally (:max-width 150)
                                     background-shown-toggle-button-pane)))
                               pointer-documentation
                               attribute-pane))))
Here is another example:

    (define-application-frame stereo-demo ()
      ;; Displays the stereo icons, and interacts with classic to build a 
      ;; rack display. Use the more compact display method for the icons.
      ((rack-window-width :initform 680 :accessor rack-window-width)
       (rack-window-ht :initform 525 :accessor rack-window-ht)
       (cur-concept-rack-width :initform 10 :accessor cur-concept-rack-width)
       (concept-rack-zero-y :initform 530 :accessor concept-rack-zero-y)
       (concept-rack-zero-x :initform 10 :accessor concept-rack-zero-x)
       ;; There is an invisible horizontal error rack. It starts at pixel 10, 
       ;; and grows from left to right.
       (cur-error-rack-width :initform 10 :accessor cur-error-rack-width) ;; absolute x posn
       (cur-error-overflow-rack-width :initform 10 :accessor cur-error-overflow-rack-width) ;; absolute x posn
       (max-error-rack-width :initform 670 :accessor max-error-rack-width)
    ;;   (error-rack-ht :initform 100 :accessor error-rack-ht) ;; the distance between the 2 racks.
       ;; 100 is needed in case it is a speaker. Turntable is also larger, but the avg is 55.
       (error-rack-zero-y :initform 10 :accessor error-rack-zero-y) ;; y posn of top of icons
       (error-overflow-rack-zero-y :initform 110 ;; 65
                                   :accessor error-overflow-rack-zero-y) ;; y posn of 2nd row of icons
       (error-rack-zero-x :initform 10 :accessor error-rack-zero-x) ;; leftmost posn of error racks
       (stereo-system :initform nil :accessor stereo-system)
       (graph-frame :initform nil :accessor graph-frame)
    ;;   (graph-root :initform 'user::electrical-consumer-thing :accessor graph-root)
       (graph-roots :initform '(user::electrical-consumer-thing) :accessor graph-roots)
       (graph-left :initform 500 :accessor graph-left)
       (graph-top :initform 150 :accessor graph-top)
       (graph-width :initform 500 :accessor graph-width)
       (graph-height :initform 500 :accessor graph-height)
       (graph-orientation :initform ':horizontal :accessor graph-orientation)
       (non-interesting-concept-names
        :initform '(user::actual-component) :accessor non-interesting-concept-names)
       (error-overflow-rack-presentation  ;; No presentation for this, just T or nil, depending on whether
        :initform nil :accessor error-overflow-rack-presentation) ;; we have started the error overflow rack

       ;; *A NEW SLOT: USE TO STORE ALL RACK PRESENTATIONS
       (rack-presentations :initform nil :accessor rack-presentations)

       ;; *A NEW SLOT: USE TO STORE THE CURRENT RACK
       (cur-rack :initform nil :accessor cur-rack)
       ;; *A NEW SLOT: USE TO STORE THE PRESENTATION OF THE CURRENT RACK
       (cur-rack-presentation :initform nil :accessor cur-rack-presentation)

       (concept-rack-presentations :initform nil :accessor concept-rack-presentations)
       (error-info-presentations :initform nil :accessor error-info-presentations)
       (explanations :initform nil :accessor explanations)
       (last-display-fn-used
        ;; for efficiency, keep track of the display-fn that was used to 
        ;; display the last object, to store in the presentation-info slot.
        :initform nil :accessor last-display-fn-used)
       (last-display-x-used :initform nil :accessor last-display-x-used)
       (last-display-y-used :initform nil :accessor last-display-y-used)
       )
      ;; Try to make certain cmds more general, so stereo-demo-command-table
      ;; can inherit from other cmd table.
      (:command-table
        (stereo-demo-command-table
         :inherit-from (general-interface-command-table) ;;(user-command-table)
         :inherit-menu t
         ))
      (:menu-bar nil)
      (:panes
       (icons
        :application
    ;;    :label "Icons"
    ;;    :width 370 ;; 300
    ;;    :height 340 :min-height 340 :max-height 340
        :text-style '(:sans-serif :roman :small)
        :text-cursor nil
        :display-function 'draw-icon-display
        :display-after-commands nil
        :default-view 'icon-view
        :scroll-bars nil ;;:vertical
        :background *my-light-blue*
        :foreground +black+
        )
       (object-display
        :application
    ;;    :label "Object Display"
    ;;    :width 370
    ;;    :height 350 :min-height 350 :max-height 350
        :text-style '(:fix ;;:serif
                      :roman :small)
        :text-cursor nil
        :display-after-commands nil
        :end-of-page-action :allow ;; don't keep scrolling when displaying object
        :background *my-light-blue*
        :foreground +black+
        :default-view 'object-display-view
        )
       (rack
        :application
    ;;    :label "Rack Display"
    ;;    :label "Rack Display  Create Complete Parts Examples Quit QUIT THIS EXAMPLE"

        ;; ***this was removed because it caused presentations to disappear.
    ;;    :output-record (make-instance 'r-tree-output-history)

    ;;    :width 680
    ;;    :height 525 :min-height 525 ;; :max-height 350
    ;;    :max-height 525
        :text-style '(:serif ;;:fix
                      :roman :very-large) ;; use this to set cmd menus to a good
        ;; font, but must also set the font explicitly to the below one for any drawing in the pane.
    ;;    :text-style '(:sans-serif :roman :small)
    ;    :text-style '(:serif :roman :small)
        :text-cursor nil
        :display-after-commands nil
        :default-view 'rack-view
        :scroll-bars nil
        :background *living-room-background* ;; *grey-ink* ;;+black+ ;;+white+ ;;*my-light-blue*
        :foreground +black+ ;;+white+
        )
       (error-info
        ;; a sideways rack containing error objects
        :application
    ;;    :label "Error Info"
        :output-record (make-instance 'r-tree-output-history)
    ;;    :width 680
    ;;    :height 350 :min-height 350 ;; :max-height 350
    ;;    :max-height 175
    ;;    :text-style '(:sans-serif :roman :small)
        :text-style '(:serif :roman :very-large)
        :text-cursor nil
        :display-after-commands nil
        :default-view 'error-view
        :background *my-light-pink*
        :foreground +black+
        )
       (legend
        :application
        :label "Legend"
    ;;    :width 680
    ;;    :max-height 90
        :text-style '(:sans-serif :roman :small)
        :text-cursor nil
        :display-function 'draw-legend
        :display-after-commands nil
        :scroll-bars nil
        :background *my-light-blue*
        :foreground +black+
        )
       (doc
        :pointer-documentation
        :incremental-redisplay t
        :text-cursor nil
    ;    :text-style '(:sans-serif :roman :small)
        :text-style '(:sans-serif :roman :normal)
        :height 20 :min-height 20 :max-height 20
    ;    :background (classicist-default-background-ink)
    ;    :foreground (fg-ink "black")
        :background +white+
        :foreground +black+
        )
       (messages
        :application
        :text-cursor nil
        :text-style '(:sans-serif :roman :normal)
    ;    :background (classicist-default-background-ink)
    ;    :foreground (fg-ink "black")
        :background +white+
        :foreground +black+
        :scroll-bars nil
        :height 20 :min-height 20 :max-height 20
        :display-after-commands nil
        :background +white+
        :foreground +black+
        )
       (commands
         :command-menu
         :text-cursor nil
    ;     :height 22 :min-height 22 :max-height 22
         :height 28 :min-height 28 :max-height 28
    ;     :background (classicist-default-background-ink) ;; lite green, bad on ncd's?
    ;     :foreground (fg-ink "black")
        :background +white+
        :foreground +black+
         :text-style '(:serif :roman :very-large ;; :very-large ;; :normal ;; :small
                       ))
       )
    ;  (:geometry '(:height 740))
      (:layouts
       (default
    #|       (vertically ()
             commands
             (horizontally ()
               (vertically ()
                 icons
                 object-display
                 )
               (vertically ()
                 (3/4 rack)
                 (1/4 error-info)

    ;	     (3/4 rack)
    ;	     (1/8 error-info)
    ;	     (1/8 legend)
                 ))
                 doc
                 )
    |#
           (vertically ()
             commands
             (horizontally ()
               (390/1090 (vertically ()
    ;		       (340/710 icons)
    ;		       (370/710 object-display)
                           (310/710 icons)
                           (400/710 object-display)
                           ))
               (700/1090 (vertically ()
    ;		       (3/4 rack)
    ;		       (1/4 error-info)

    ;	               (3/4 rack)
    ;	               (1/8 error-info)
    ;	               (1/8 legend)

    ;		       (.72 rack)
    ;		       (.14 error-info)
    ;		       (.14 legend)

                           (.78 rack)
                           (.16 error-info)
                           (.06 legend)
                           )))
    ;	 doc
             (horizontally ()
               (.5 doc)
               (.5 messages))))))


Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: