In terms of syntax, I think there's a divide between the needs of static type-systems, versus dynamic ones. LISP is at its core a dynamic language. It makes sense to treat code as being data that can be manipulated. Getting rid of all that syntax gives you great power in terms of what you can do. And personally I find Clojure code to be much saner than code written in Ruby or Python, but that's another discussion.
However, the other side of the coin are Haskell or Miranda (its predecessor) or languages in the ML family or Scala. These languages go to great length to achieve much of LISP's expressiveness, while ensuring a great amount of static type-enforcement. Haskell is especially notable because Haskell is lazy and has many goodies, such as type-classes or rank-2 types. People say that when a piece of Haskell code successfully compiles it is usually correct. LISP can't do this, even though there have been experiments with pluggable type-systems.
For an excellent rant on the choice of Scheme for SICP (and not Miranda, Haskell's predecessor), checkout: http://www.cs.kent.ac.uk/people/staff/dat/miranda/wadler87.p...
So IMHO, syntax helps if you want static type-safety, because the language and the type-system exposed need to be expressive. Whether static type-safety is useful or not, it's a matter of debate, being highly dependent on the problem domain. One could argue that static type-safety is very useful if the shape of the data you're manipulating is well defined and doesn't change much, otherwise you're better off with a dynamic language, but that's another discussion.
Every time I see this claim, I wonder what the heck people are working on that is so simple that getting the types right is all it takes to get the code right? Is it just that enough of my work is math or parsing, both of which typically have an infinite number of ways to get things wrong but with the correct types?
Or is the idea really the other way around -- if your program is sufficiently complex, you'll never get it to actually pass Haskell's type checks?
Edited to add, I love the article you linked to so far. :)
Sometimes I got a type error from some bizzare syntax mistake, usually failing to correctly delineate an expression, and sometimes the algorithm in my head was just wrong and I had to fix it. At that point, the cycle starts over, and the same benefits I mentioned above help in debugging. FWIW, the problem I was working on was fairly simple, but novel. I was a very happy camper this Christmas, working with OCaml, despite its syntax.
Out of curiosity, what language do you do your "math and parsing" in? OCaml might be a good fit.
To give a somewhat extreme example of the sort of stuff I do -- one of my current projects is to try to create a first-class fillet surface type [1] for the geometry engines I work with. My current code converts each fillet to a NURBS surface, but this is slow, consumes a lot of memory, and produces surfaces which frequently are not accurate enough for my purposes. So my hope is to be able to directly calculate points on the fillet and their derivatives.
It's easy enough to calculate points on the fillet surface, but I don't see any obvious way of directly calculating the derivative along the surface. So I've been investigating Chebyshev polynomials in two variables. But that algorithm looks a bit hairy, and I don't have any feel for whether it will be an actual improvement in practice. (And having written all this, I'm suddenly wondering if I'm making every effort to sort out the simple cases that are easy to solve exactly.)
My point here is that figuring out the correct types involved seems like a tiny, tiny part of the required work. Maybe it's because I haven't put in enough time in Haskell, but I don't see how stricter than C++ types are going to help. The real work is getting the math correct and fast enough to be useful.
By the way, my comment is not meant to be a dig at Haskell (et al). I'm not sold on it as a language, but it certainly has a lot of interesting ideas -- pattern matching and laziness spring to mind.
For domains outside of physics and applied math, I think the dominating factor is often the inherent trickiness of writing correct procedural code vs. the ease of writing strongly typed functional code.
Pattern matching is at least as much fun as it looks like, if you have complicated data types. That's a lot of what made OCaml so perfectly match my problem.
[0] https://github.com/andrewf/apatch if you're curious. The meat is in patch.ml
Why can't Lisp do this? In the Common Lisp world, there's some pretty aggressive, though still optional, static type checking. There's also Qi, Shen, Typed Racket, et cetera. The question is orthogonal to the question of syntax.
For example, how would you type the cons function? In Haskell, it has type t -> [t] -> [t] (i.e., for all types t, given a value of type t and a value of type list of t it produces a value of type list of t). In Lisp, on the other hand, you can construct a cons cell out of any two objects.
Obviously you can certainly construct a type system in which you can handle Lisp cons cells reasonably; Hindley-Milner type systems aren't the only game in town. But I don't think it'd have the 'if it compiles it probably works' property any more.
Type systems fall squarely in the "semantics, not syntax" department.
You have not reached Lisp enlightenment yet.
Because you use Emacs, you can get shortcut to enlightenment by choosing color for parentheses that almost hides them to the background.
(I think the fact that SRFI 49 is not more popular might say something, though...)
I want a semantically sane language. What do I care what code Lisp macros can autogenerate if nobody can make sure it makes any damn sense?
Ruby is not semantically sane, it's insane in a way we have all gotten used to. (If you don't know ruby, try to rewrite this for your ALGO language of choice, I picked ruby for its simplicity)
S-expressions are about as semantically sane as you can get, it's just most of us have a <5 year head start with the craziness of c-style languages.
You are talking about syntax here.
> Ruby is not semantically sane, it's insane in a way we have all gotten used to.
I hate Ruby even more. It is just as semantically insane as Lisp, and the syntax is worse.
> (If you don't know ruby, try to rewrite this for your ALGO language of choice, I picked ruby for its simplicity)
Ruby is not simple. It is full of corner cases and redundant constructs. It is hard to make any sort of reasonable guarantee about a Ruby program without resorting to some heavy testing. And no matter what nice property you might have wrestled out of the system, someone else can destroy it anyway by means of monkey patching.
> S-expressions are about as semantically sane as you can get
S-expressions are syntax with no intrinsic semantics.
> it's just most of us have a <5 year head start with the craziness of c-style languages.
What about Haskell? (And, in general, languages that have a denotational semantics that makes equational reasoning feasible.)
For more details, see: https://news.ycombinator.com/item?id=6954348
Explicitly, in lisp, everything is a stack of instructions arranged in a tree. The instructions are either functions that manipulate the input data, or functions that manipulate the underlying tree of instructions.
By naming convention and documentation, we give the various functions semantics. ("With" macros being an example).
I was just refuting the assertion that "S-expressions are about as semantically sane as you can get".
> Explicitly, in lisp, everything is a stack of instructions arranged in a tree.
I do not particularly care what data structure is internally used. That is the compiler's business, not mine. I want to reason about my code in terms of the semantics of the language I am using.
> By naming convention and documentation, we give the various functions semantics. ("With" macros being an example).
Names may be misleading, documentation may be incomplete, but types never fail to accurately guarantee properties about my code. Hence my preference for types.
His comment was equally nonsensical. S-expressions are consistent and easy to parse, that is all.
> I do not particularly care what data structure is internally used. That is the compiler's business, not mine. I want to reason about my code in terms of the semantics of the language I am using.
>I do not particularly care what data structure is internally used. That is the compiler's business, not mine. I want to reason about my code in terms of the semantics of the language I am using.
Good, that data structure generally isn't used internally in the compiler (not efficient enough). I'm talking about the data structure that is used for the programmer's brain. The semantics and way of reasoning about the code is in terms of a very simple data-structure.
My business is the compiler's business.
> Names may be misleading, documentation may be incomplete, but types never fail to accurately guarantee properties about my code. Hence my preference for types.
Why would you write such shitty code? There are many ways of skinning that cat. You can use types, you can use tests, you can use assertions.
You could make it overly complicated and long. A stupid person is going to have a bad time writing code, no matter what language you hand them. Shitty code is shitty. This is a tautology comparing the language I'm currently into to a language I'm not currently into. Don't use it. You're good at writing Haskell, good for you.
def add(x, y):
return x + y
(define (add x y)
(+ x y))
These are similar semantically -- they each define a procedure that takes two parameters, adds them, and returns that result. Hand-waving how the language is actually implemented, they're morally equivalent, anyway. But syntactically they're fairly different.Another example: https://en.wikipedia.org/wiki/Colorless_green_ideas_sleep_fu.... That sentence is semantically meaningless, but it has the correct syntax.
edit: fixed code formatting.
One of them.
> The best Haskell book I can find is next for me as soon as I finish SICP, any suggestions?
I liked Learn You a Haskell for Great Good, but that is maybe just because I am a childish person. :-)
Both are available online for free!
But in the reality using macros is no more complicated than using functions.
[1] http://www.ai.mit.edu/projects/iiip/doc/CommonLISP/HyperSpec...
[2] http://www.common-lisp.net/project/slime/doc/html/Macro_002d...
Do I have any guarantee that the resulting macro-expanded code semantically makes sense? (e.g., a guarantee that it will typecheck)
(my-macro (2 + 2))
and output valid lisp code during macro expansion time. The macro my-macro could expand the code to something like: (+ 2 2)
Some Lisp compilers such as CMUCL will issue a warning if you try to use wrong type (it has type inference), such as in this simple example: (defmacro foo ()
`(+ 1 "foo"))
(defun bar ()
(foo))
You can see the compiler notes here: http://imgur.com/3vlQm6GAs for macros, they are merely functions that transform expressions. They can be somewhat unpleasant to read, but they are basically that.
Unityping. Now every useful function is automatically not total!
> I find it has very clean semantics; it is strongly typed
Of course it is strongly typed! All expressions have type Univ.
> programs are commonly written in a way that preserves referential transparency
All my C++ programs are commonly written in a way that preserves referential transparency, and clearly distinguishes between objects that are meant to be mutated (non-const) from those that are not meant to (const). That still does not make C++ a pleasant language to program in.
> The limited dynamic scope may be somewhat unpleasant though, but that's its flexibility.
Spooky action at a distance. Me no like. :-(
> As for macros, they are merely functions that transform expressions. They can be somewhat unpleasant to read, but they are basically that.
Will the result typecheck?
> Of course it is strongly typed! All expressions have type Univ.
Have you used PHP? You'll come to appreciate the differences between the different "unityped" languages.
> All my C++ programs are commonly written in a way that preserves referential transparency, and clearly distinguishes between objects that are meant to be mutated (non-const) from those that are not meant to (const). That still does not make C++ a pleasant language to program in.
Does that say anything about Lisp? Nope, not at all. It isn't like C++ is comparable to Lisp, despite the efforts put by C++ programmers to wrest safety from a language designed as a superset of the language of cowboy-coders par excellence, its true nature as an imperative language with raw pointers may rear its head at any point. Not to say that Lisp has had more facilities up to this point than C++ to write referentially transparent programs.
> Spooky action at a distance. Me no like. :-(
Depends on the Lisp. Common Lisp's global variables are mutable and dynamic. Me no like either. In Racket and Clojure you can declare variables to be explicitly dynamic, and they can be rebound within special blocks, locally, sort of. Not that it's something anyone should use frequently.
> Will the result typecheck?
Does the if keyword typecheck?
Autolisp does not typecheck.
> I actually rather like...
> I've found that I still heavily prefer...
> I also often want...
> I want all this so that I can...
...
> That's why we need grammars.
Well, that was a twist ending.I actually find those harder to read. Generally, because I can indent Lisp code however the heck I like, I have a pretty good idea what's going on just from looking at the structure of the code and reading a few words here and there.
It's a fairly common experience for me in other languages to be scanning back and forth across a line searching for that $%^$£%£ operator rather than (in the rare event I get really lost in a Lisp file) just going, "Highlight the next s-expression up, ah that's what's going on." Or "You know what? Indent my code for me in a different way."
I wonder whether there's a divide between people who understand code initially by looking at the structure and people who understand code initially by looking at the operators in terms of who likes Lisp :/
And that's why I prefer Forth syntax
But yes, you're always going to have a grammar. Albeit simple, you need a tokenizer/parser for lisp and forth. It's simple, but it is there.
Computers don't know what 2.345 or "blah" means, we still need a way of inputting this in a friendly way.
$ rebol
>> price: 99
== 99
>> either price > 100 [10] [5]
== 5
>> ;;; and even more interesting is...
>> ten: [10]
== [10]
>> five: [5]
== [5]
>> either price > 100 ten five
== 5> Should I revisit it again some day?
I'm probably not the best person to ask here because I've only come across Rebol in last 12 months. I've had great fun playing with it and have used it for a few small scripts/projects.
The current pros would be:
- Rebol3 is now opensourced (since 12-12-2012)
- Rebol3 is faster than Rebol2 (in my simple tests about twice as fast) & with smaller footprint.
- Rebol3 comes with some design improvements, for eg. 64-bit integers, module system, async schemes & more.
However the cons are:
- Rebol3 still in alpha/beta development.
- So not all features of Rebol2 are (yet) present in Rebol3.
- Rebol3 GUI is only available for Windows & Android at this moment.
So depending on your use case then you may find yourself still needing to use Rebol2.
If you code in Ruby, Python, or Coffeescript, yeah, lisp has more parens, no doubt. That is the price of its regularity. Lisp could throw all that out the window, but you'd lose the easy macros.
Edit: added the link
A more useful question is the following: "is a simple grammar the be-all and end-all of programming language design?". I think the answer is "no". Ceteris paribus, a simpler grammar is of course to be preferred to a complex grammar. But, ultimately, people write programs for what they do (so that they can be useful) and for what they mean (so that they can be maintainable), not for how they look like. So a simple semantics takes precedence over a simple syntax.
> Of course, this is not to say that Lisp is unreadable. I actually rather like reading and writing (paredit is awesome) Lisp code. But, after quite a bit of Racket, I've found that I still heavily prefer having infix operators and a bit more syntax.
That is precisely what languages like Haskell (and to a lesser extent ML) give you: a typed lambda calculus (semantic simplicity) with infix operators (syntactic eye candy) for convenience. The type system reduces the universe of valid programs to those that typecheck, so you do not have to worry about stuff like the meaning of 2 + "potato".
As Wittgenstein remarked, many philosophical conundrums arise from the odd results that follow from using ordinary language in an odd way.
Wrong. Just because a language A is implemented by means of a translation into language B or execution by a machine C, it does not mean that A's abstractions do not exist at all. If A makes guarantees B or C cannot possibly make, then A will compile to a strict subset of B or yield a strict subset of the processes that can happen in C. In this strict subset, A's guarantees hold.
(5 . * . 3) ; 15
(5 . > . 3) ; #t
As a bonus, you even get more verbose syntax as in other languages, because Racket, being a Lisp will let you write code any old which way so long as there is a macro available to parse it.#I( x^^2 + y^^2 )
And, naturally, you can macroexpand the macro.
[1] ftp://ftp.cs.cmu.edu/user/ai/lang/lisp/code/syntax/infix/infix.cl
The grammars exist because programs are made to be read by people, not just computers. A more heterogeneous notation can make for more concise code that's easier to scan and easier to read.
Of course, this is not to say that Lisp is unreadable. I actually rather like reading and writing (paredit is awesome) Lisp code. But, after quite a bit of Racket, I've found that I still heavily prefer having infix operators and a bit more syntax. At the same time, I also often want less noise--ie no parentheses. I want all this so that I can quickly scan and comfortably read my code. That's why we need grammars.