It helps if you remember that $primes is a list of primes, and <...> is pattern-matching.
So what it does is matching of such elements (named p) that the next element (see cons) equals p+2, which would be a twin prime, since p is a prime. Then it generates an element consisting of a pair [p, p+2]. The pattern apparently matches such a list comprised of elements (see <join ... >) that are [p, p+2] (the inner <cons ...>).
You definitely remember that Lisp lists are singly-linked lists, and (cons head tail) prepends a new head element to the list tail. This is why it is matched, pertty similarly to how it's done in Haskell.
Oh, I thought it was doing some kind of prime search. But you have to give it a list of primes to start with? How about one of:
print [(x,x+2) for x in primes if (x+2 in primes)]
print [(x,y) for x, y in zip(primes, primes[1:]) if x+2 == y]
The simpler one doesn't even require the primes list to be sorted, so I thought the second one would be more equivalent. It's still a lot easier to read and write than Egison one.
Your first snippet has quadratic performance. The second is closer to the idea.
Pattern-matching would be even more obvious is a piece of Haskell:
pair :: [x] -> Maybe (x, x)
pair p:q:rest = if q = p + 2 then Just (p, q) else Nothing
pair _ = Nothing
findTwins primes = [x | x <- map pair primes]
I suppose that Eigson's power lies in matching of more complex structures, as they mention non-linear matching, matching with multiple results, matching with lexical scoping.
Yep, that's where I stopped. What I think is going on... Some people think predominantly in abstraction and don't really care about syntax. As Paul Graham once said, LISP doesn't really have syntax - you write parse trees directly. That may be fine for people who have achieved that level/style of programming, but it's a tiny minority.
Why should I write (+ p 2) when most people think (p+2)? This difference gets worse with more complex expressions. For the masses (and I mean a lot of very capable people) this is just garbage. For some it's wonderful. I can't say it's wrong, just not my thing. Perhaps it's the Blub paradox - I've never had the LISP epiphany so I can't say. I have had the python epiphany, and it's quite the opposite of this.
If you have lots of math expressions to write, you can use an infix reader in Common Lisp. It has a programmable reader. Such a thing - reading infix expressions - was already a standard feature of the MIT Lisp Machine OS in the 80s.
In Lisp you actually don't write parse trees. Lisp uses a serialized hierarchical data format of a tokenizer. S-expressions are not Lisp syntax trees, but a data format.
I relish comments like these from Lisp fans that actually KNOW Lisp.
Someday we'll resurrect it all...
Reader macros blew my mind when I first read about them. Being able to change the very laws of the universe from within my simple little programs... Insane!
> This difference gets worse with more complex expressions.
Actually, no - it's the other way around: more complex expressions really benefit from the uniform of syntax and using macros to simplify the code.
Not all macros are worthwhile, and some are detrimental to readability (and not writing those is one of the first Lisp lessons), but there are also macros (syntax extensions), which are elegant and powerful.
The common example is the `->` or `thread-first` macro, which chains a list of function calls, in the following manner:
With longer expressions, or longer chains of function applications, the difference becomes even more visible, with `->` version having way less parens (possibly less than in Python for equivalent expression) and less nesting, which makes it easier to read and modify.
But it doesn't end there - this is just one macro, out of many powerful syntax extensions, which make for much clearer code. There are macros for lazy evaluation, both sequences (like generators) or expressions (more like `lazy` in OCaml); for partial evaluation of functions, for changing name resolution policy, for declaring classes, for looping, for pattern-matching, for error handling, for FFI, for logic statements, for transactional memory, for async control flow, and so on and on.
Some of these are trivial to implement, but some are hard or just complicated, with many cases covered; so you're not necessarily expected to write them yourself. If a macro's semantics are well-defined - as in most popular examples - importing a package and reading docs is enough to use them to a good effect. You don't really have to use any of them, but they all exist to make certain patterns in code simpler, more readable, and more convenient to work with. Other languages often lack many of them.
Anyway, what I want to say is that the more complex the problem, or the larger codebase, the more readable Lisp becomes, at some point surpassing most other languages. This is what makes Lisp fans to be so good at "recursion and condescension", and is also something you can't see in a `(+ p 2)` kind of examples.
> Why should I write (+ p 2) when most people think (p+2)?
just an observation. most people don’t think this naturally. most people learned (p+2). maybe there’s an argument for whatever is more natural, but the point is that people forget that what they view as natural was once unnatural and had to be learned.
as a small anecdote, i did a calculator competition in high school. we used those hp rpn calculators. it was unnatural at first but then i flew once i gained the skill in using rpn.
I like Lisps but I still don't like this. This is like the Perl of Lisps. To my eye, this is not a parse tree, it's syntax vomit on top of a parse tree.
> LISP doesn't really have syntax - you write parse trees directly.
Lisp has plenty of syntax, it's just extensible and largely defined in
operators. Commonly used macros like DEFUN, WITH-OPEN-FILE, and LOOP all add
their own syntax to the language; that's what macros are for. When you put
them together, typical Lisp code ends up looking like
(defun count-words (filespec)
(with-open-file (f filespec)
(loop with counts = (make-hash-table :test 'equal)
for line = (read-line f nil)
while line do
(loop for word in (cl-ppcre:split "\\s+" line) do
(incf (gethash word counts 0)))
finally (return counts))))
Which I don't think is that alien. It translates pretty cleanly into Python:
def count_words(filespec):
with open(filespec) as f:
counts = collections.defaultdict(int)
for line in f:
for word in line.split():
counts[word] += 1
return counts
Do you find the former significantly harder to understand? I don't see how
it's any more like "writing a parse tree directly."
> Why should I write (+ p 2) when most people think (p+2)? This difference
gets worse with more complex expressions.
As mentioned in other comments, there are infix packages available for when
you're writing math-heavy code (or if you just need infix operators), but I
also think that a lot of people don't write very much code with complicated
inline arithmetic in it, and overstate how much the awkward math syntax would
affect them (there are of course plenty of projects that are math-heavy and
where it does make a large difference). When you're defining
variables/functions/classes/etc or calling functions or looping or
whatever, there's not much difference between Lisp and most other languages
aside from whether the bracket is { or ( and which side of the keyword it goes
on; in my experience, that sort of code tends to make up the large bulk of
most codebases in most languages.
edit: Another place where Lisp's default syntax is significantly different from a lot of languages is
something like object.f(x).g(y).h(z), which in Lisp looks like (h (g (f object
x) y) z). This can be remedied with common macros like -> that let you write
(-> object (f x) (g y) (h z)).
Sure, there's other ways to write it, the point of the post was Lisp's syntax, not the algorithm I used to demonstrate some features of it. You could write examples like yours in Lisp or Python, too. As well, your examples both slurp the whole file into memory, which mine avoided.
edit: And your C# version is only 24 non-whitespace characters shorter than the Python version, and 20 of those are because you called your variables 'f' instead of 'filespec' and 'w' instead of 'word'. A 4 non-whitespace character difference makes it exceedingly long and verbose? Or are you just advocating 1-letter variable names and avoiding newlines?
The c# version is calling only 4 methods and in C# you obviously have to declare the types.
It’s the difference between declarative style versus imperative that I wanted to highlight, obviously Python is more compact than C#, but written in that way it becomes more verbose and more difficult to read and write.
Write something like that in Lisp and let’s see how it compares.
We build a function which gets the file as a string, then tokenizes non-space-character chunks out of it, which are then group-reduced to a histogram hash. We pass the filespec to this function.
I posted this yesterday but deleted it, because grandparent's point wasn't about code golfing but just comparing Lisp and Python syntax.
There is a group-by function in TXR Lisp, but group-reduce is more efficient, because by using it we avoid building the group lists and counting. It's something I invented. Basically it performs multiple left folds in parallel, using the entries in a hash as multiple accumulators. Items from the sequence are hashed to their respective accumulator entry and injected through it. 0 is the initial value for the accumulator when it doesn't exist, functioning exactly like the initial value in a regular fold. (do inc @1) expands to a function which just increments its left argument (the accumulator) and returns it. We cannot use succ because it takes exactly one argument.
group-reduce has an added flexibility in that it doesn't construct a new hash, but takes an existing one as an argument. This adds the (hash) verbosity to the code, since we have to construct the hash ourselves. But with that we could run multiple successive group-reduce jobs that go into the same hash table. Also, the function is spared from having to provide a way to pass through hash arguments for different kinds of hash tables with different options.
The identity argument is needed because group-reduce takes a function that projects the items to keys; in this case the items themselves are the keys so we use identity.
Here is an interactive gist of how to solve the problem succinctly using group-by and then counting lengths:
This is the TXR Lisp interactive listener of TXR 198.
Quit with :quit or Ctrl-D on empty line. Ctrl-X ? for cheatsheet.
1> [group-by identity '(1 2 2 2 3 3 3 3 3 4 4)]
#H(() (1 (1)) (2 (2 2 2)) (3 (3 3 3 3 3)) (4 (4 4)))
2> [hash-update *1 len]
#H(() (1 1) (2 3) (3 5) (4 2))
Easy to follow, and brief, but wastefully conses up lists just for the sake of obtaining their lengths.
> verbose and painful to understand.
Doesn't seem honest. I don't know Python, yet I can understand what that is doing, and might even be able to spot a logic error, if it had one (though not some issue of syntax).
In lisp, nearly everything is written in the same form. I.e. everything either is a function call or looks like one. Once you know that, you pretty much know lisp syntax.
People freak out about the parens. But I think what's the big deal? Functions are called with the paren in front of the function and the args space separated.
so f(x, y) in C like syntax becomes: (f x y) - Whoop dee do, not so hard.
And since the syntax is extremely consistent, things that are math "operators" in other langs are just functions in lisp.
For "(+ 1 2 3)"
+ is actually the name of the function, so we are invoking + function and 1 2 3 are the arguments. Returns 6.
Once you know that, and that function args are evaluated before passed into the function, then you no longer have to worry about operator precedence and parenthesis to control order of operations. Once you get used to it, it's actually a lot simpler.
> In lisp, nearly everything is written in the same form. I.e. everything either is a function call or looks like one. Once you know that, you pretty much know lisp syntax.
Actually not. Lisp has several types of forms. A function call is one. There are atleast two others: macro forms and special forms.
The function name the is either a symbol or a list of (setf name). The lambda-list has complex syntax with optional, aux, rest and keyword arguments, declaration has a complex declaration syntax, etc...
The third type of form would be built-in syntax. LET is an example. The syntax for LET is:
let ({var | (var [init-form])}*) declaration* form* => result*
This means that
(let ((a 1)
b)
(setf b a))
is a valid program.
This is not a valid Lisp program, which Lisp will complain about:
* (let ((a 1 2)
b)
(setf b a))
; in: LET ((A 1 2) B)
; (A 1 2)
;
; caught ERROR:
; The LET binding spec (A 1 2) is malformed.
Reason: Lisp expects that bindings are either symbols, lists with a single symbol or pairs with a symbol and a value form. (a 1 2) thus is not valid.
Is Lisp syntax easy? Once you look deeper, it actually isn't. Only a core language with only function calls is relatively easy.
* Lisp writes programs on top of a data structure called s-expressions. S-expressions are easy, Lisp not.
* Lisp usually has the operator as the first element of a lisp -> uniform appearance.
* Lisp has some built-in syntax. Usually as little as possible.
* Lisp lets the developer add new syntax via macros -> zillions of macros and the user needs to learn a few patterns to understand Lisp code.
If you squint a bit, macros and built-ins are similar in structure to functions (they're all s expressions).
I agree with much of what you said, but I still maintain that lisp syntax is still relatively trivial compared to most languages.
I also don't think exposing newcomers to macros on a language that is homoiconic is terribly useful until they're already comfortable with the basic s-expression syntax.
> If you squint a bit, macros and built-ins are similar in structure to functions
Because they have a parenthesis in front and back and the operator as first element. Macros then implement complex code structures between those:
(foo a b)
and then
(let ((a 10)
b)
(declare (type (integer 0 100) b)
(type number a))
(declare (optimize (speed 3))
(declare (special a b)
(dynamic-extent a))
(prog ()
start
(setf b (+ a 10))
(go end)
end)
(the integer (+ a a b)))
Now remove the parentheses. Looks like a program in a typical language.
Macro forms have lots of internal structure. For an extreme example check the syntax definition of the LOOP macro. Two pages of EBNF syntax declaration. That there are an opening parentheses and a closing one does not suddenly remove the syntax:
loop for i from 1 below 10 by 2
and
for j from 2 upto 50 by 3
when foo(i, j)
collect i into is and j into js and i * j into ps
when bar(i) > baz (j)
return list(is, js, ps)
or the Lisp version:
(loop for i from 1 below 10 by 2
and
for j from 2 upto 50 by 3
when (foo i j)
collect i into is and j into js and (* i j) into ps
when (> (bar i) (> baz j))
return (list is js ps))
Does it LOOK like it has less syntax? Not really.
> lisp syntax is still relatively trivial compared to most languages
Not really. Check out the concept of a code walker in Lisp. That's a tool which understands Lisp syntax and can walk over Lisp code and do transformations.
First of all, the Blub paradox is wrong. It assumes that languages can be ranked on a one-dimensional axis labeled "power". That assumption is wrong.
To see why it's wrong, think about Lisp and Haskell. Users of both languages are sure that they're at the top of the power curve, they're sure that they're looking down when they look at the other language, and they're sure why they're looking down. "How can you get anything done in [Haskell|Lisp]? It doesn't even have [macros|a decent type system]!" But if both are sure they're looking down, then languages can't be well-ordered by "power".
Next, that syntax. I suspect that that kind of syntax "clicks" with some people, and not with others. (Almost everyone could learn it, but that's not the same thing.) And I think you're right that "the masses" - the vast majority of people - are people to whom it doesn't "click". This may be the real flaw of Lisp (and Haskell) - the syntax is just wrong for the large majority of programmers.
Note well: This is my pet theory. I have no data. All predictions guaranteed wrong or your money back.
I'm not sure there are many people who know both a Lisp and Haskell and still insist on even comparing them. The conclusion I see most often, when someone tries, is basically that "whatever, both are still centuries ahead of Java".
With a decent macro system you can implement a type-checker (CL, Clojure and Racket do), as sophisticated as you want. But you can also write a type-level interpreter (of Lisp, if you want) which would evaluate programs during compilation (I can't find the post anymore, someone was describing their job interview gone... weird...). The difference between the two, more than with other such comparisons, comes down to aesthetics and cultures.
IOW, you can't infer the lack of ordering based on two items having an ex aequo position.
IMO the problem with Haskell isn't syntax, it's jargon. Not only is the jargon dense, heady, and ubiquitous, but (in my limited understanding) it also doesn't necessarily correspond cleanly to math concepts of the same name.
I guess you could say that jargon is just another form of syntax.
Well, I thought about saying that with Haskell, it was semantics at least as much as syntax. By that, I meant what you meant, but I also meant more: I wonder if functional programming itself is a poor match to the way that most programmers think, and not just because they are untrained on FP.
But I didn't say that, because I thought it was a bit of a digression to my point, which was already a digression on phkahler's point, and the digressing has to end somewhere...
It's all good. I've rather enjoyed reading the responses. IMHO this type of thing is what really makes languages appealing or not too different people.
Egison makes programming dramatically simple!
Uh, ok?