Hacker News new | ask | show | jobs
by otoburb 2586 days ago
"TXR Lisp programs are shorter and clearer than those written in some mainstream languages "du jour" like Python, Ruby, Clojure, Javascript or Racket. If you find that this isn't the case, the TXR project wants to hear from you; give a shout to the mailing list. If a program is significantly clearer and shorter in another language, that is considered a bug in TXR."

That section made me chuckle. Admirable if true.

2 comments

i agree that the general-purpose programming language space is fairly crowded ... the lisp dialect/user ratio especially so.

DSLs, otoh, are in short supply. while awk or plain sed are great for shell programming, this is the only (open source) DSL i'm aware of targeting certain types of NLP-esque "munging". this space is mostly full of statistical approaches, which, while conceptually pure, don't allow the kind of flexibility that would be useful in many applications.

i wonder if, eventually, the DSL portion of TXR could be sheared off (possibly via metacircular evaluation of the TXR lisp?) into something that's portable across lisps or at least to semi-standardized scheme implementations?

N. Westbury has been cloning it in Java:

https://github.com/westbury/txr-java

>That section made me chuckle. Admirable if true

Mostly true for very high level languages like Lisp/Scheme, or ML/OCaml/F#/Haskell, when faced against not-so-high-level languages like C, C++, Java.

Against Racket, i wouldn't be so sure. Nor against Ruby.

Python and Javascript are high level languages but they are crippled by some bad design decisions.

I know that Python and Javascript have their warts (as do all languages in my experience), but what decisions in particular are you thinking of?
Lambdas are limited to a single expression. Threads don't work the way anyone would ever want threads to work because o the GIL.

That said, homoiconicity (s-expressions) is not a feature I want. It makes code like reading a wall of text compared to a nicely laid out magazine.

Since Python lambdas also are expressions, they cannot contain statements, due to Python adhering to the Algol-like statement/expression syntactic paradigm. If a lambda expression could contain statements, that would mean that almost any other kind of Python expression could also contain statements by containing a lambda expression.

But, here above, I'm writing more from the angle of supporting multi-line lambdas that contain statements. Strictly speaking, you are only bemoaning the lack of multiple expression support, not multi-line lambdas.

Python could adopt something similar to the C comma operator. It almost has that in the form of list constructors, except that these return a list, instead of the rightmost value:

  [foo(), bar(), xyzzy()] #   foo, bar and xyzzy are called
Idea: a dummy function called progn could be used for this:

  >>> def progn(*rest):
  ...    if len(rest) > 0:
  ...       return rest[-1]
  ...    return None
  ... 
  >>> progn(1, 2, 3)
  3
  >>> progn(1)
  1
  >>> progn()
So now we can do:

  >>> x = lambda arg: progn(print(arg), print(arg), "done")
  >>> x(42)
  42
  42
  "done"
  >>>
There you go. Lambdas are (effectively) not limited to a single expression. If progn is too long, call it pg (Paul Graham) or pn (Peter Norvig).

Always have your Lisp hat on, even if you find yourself in Python land.

Maybe this is a common trick? I don't use Python; I hardly know anything about it. I wrote one Python program before which garbage-collects unreferenced files from a Linux "initramfs" image, making it smaller (thus reducing a kernel image size). This was in a Yocto environment, which is written in Python 3, so that choice of language made sense.

BTW does Python require left-to-right evaluation order for arguments? I would sure hope so; it would probably be "un-Pythonic" to plant such a bomb into the language as unspecified eval order.

BTW looks like a more idiomatic definition for progn is:

  >>> def progn(*rest):
  ...    return None if len(rest) == 0 else rest[-1]
> Maybe this is a common trick?

No. It's obvious and trivial, but you'd be on a verge of being called names if you tried to use it in a Python codebase. Lambdas in Python are limited to a single expression by convention - which in Python-land is scarily rigid and specific - rather than just by the language spec.

Before `... if ... else ...` was added to the language as an expression (I think around 2.5), people had to make do with some workarounds. The fact that `True` and `False` get automatically casted to ints and back allowed for writing something like `[val_if_false, val_if_true][condition]`. Or you could use `and`/`or` combination as per usual. The official stance at the time was to never do this and use an `if` statement instead, but people still sometimes resorted to it. Then, the `if` expression was introduced specifically to combat the use of such workarounds. Now you'd be lynched if you tried to use one of them.

"There should be one - and preferably only one - obvious way to do it" - from the Zen of Python[1].

In general, despite a lot of effort to eliminate them, there are still some creative ways to use the language. It will always be the case, obviously, as you demonstrate. However, that creativity is 100% rejected by the community, to the point that even mentioning inadequacy of some construct for some use case is frowned upon - because it could lead to people inventing creative workarounds. If you try to complain about something in the language, the general attitude is "write a PEP or GTFO". More often than not it results in the latter.

The saddest part of it all is that this apparently is one of the major factors that made Python as popular as it is. There are valid reasons and a lot of advantages to this strategy. Go is similar as far as I can tell. Among the dynamic languages with rich syntax, Python codebases tend to be stylistically very close to each other, and not because there is a lack of ways this rich syntax could be (ab)used, but because doing so is unpythonic.

Haaah, now I said it... I hope not many Python programmers read this thread; I can already see torches and pitchforks on the horizon...

Source: I've been writing Python for the last 12 years for pay.

[1] https://en.wikipedia.org/wiki/Zen_of_Python

My experience with Go is that the syntax is more rigid as compared to Python but the community is much less idealistic.

As long as your code is linted with gofmt and it compiles (and doesn’t abuse reflection), the community tolerates more creative uses of the syntax to get around some of the pitfalls of the language – but there is of course less opportunity for creative syntax than in a dynamic language like Python.

>Strictly speaking, you are only bemoaning the lack of multiple expression support, not multi-line lambdas.

Oh, I'm not bemoaning. I'm just answering GP about the likely limitations that the previous poster was complaining about.

>Maybe this is a common trick? I don't use Python; I hardly know anything about it.

I work around lambdas by naming internal functions. It's easier to read intent if I tell you what I'm trying to do.

Gotcha.

I do agree about homoiconicity. It's great for writing macros, and terrible for everything else. Of course, go too far in the other direction and you get perl, so... yeah.

I don't want to reopen this 50+ years old can of worms, but I have 2 questions:

1. Is it about homoiconicity in general or specifically s-expressions [EDIT: I see GP writes about s-exps specifically, missed it at first]? Prolog, Erlang, TCL, and Rebol (just some examples) are homoiconic, but not s-exps based. What do you think about them?

2. Do you often read code without syntax highlighting and proper indentation? Assuming the code is properly indented and colorized, what makes it so hard to read in your eyes? Take a look for example at snippets in: https://docs.racket-lang.org/quick/index.html#(part._.Local_... - what do you feel is wrong with them? Is it only the placement of parens, or is there something else?

1. It's homoiconicity in general. It's most obvious in arithmetic expressions, but homoiconicity (as far as I've seen) eschews semantic indicators besides function name and argument position. I find that keywords and symbols are almost indispensable for smooth eye-parsing of code.

2. No, I use syntax highlighting and indentation all the time. Trying to put words to my vague thoughts, I think the main issue is that, for example, in a let block the only indication of the meaning of all the items is the single function/macro name at the beginning of the block. Whereas in an algol-type language you have assignment operators in each item to indicate what they mean.

>I see GP writes about s-exps specifically, missed it at first

My experience is with s-expressions being a wall of text. I haven't used prolog (end erlang uses the same syntax) enough to have a beef with them. Perhaps I was being over general.

>I do agree about homoiconicity. It's great for writing macros, and terrible for everything else.

I don't know; i've written code in C, C++, C#, Java, Python, Ruby, Pascal, Delphi, Assembler x86, TCL, Javascript and Common Lisp. Lisp codebases are the cleanest and clearest i've seen by far, although ReasonML/SML/OCaml might be as clean too.

Racket also has the GIL though. It's a problem in every dynamic language I've looked at. Last I checked it was true for Ruby too.
Common Lisp implementations often have no GIL, and, btw, are able to reach C speeds in certain cases.
Tcl does not have GIL and is possibly one of the most dynamic languages that has seen nontrivial use. Guile does not have GIL. Running multiple interpreterd in different threads is a common notion in Tcl.
Running multiple sub-interpreters in different threads is easy in Tcl, but it doesn't stop there. If you want, you can run multiple threads in a single interpreter, using locks mutexes, etc., if you're inclined to take on the attendant complexity and risks.

Ability to run a separate interpreter per thread was designed as a handy simplification, so you could sidestep the usual complexity of thread programming if you wanted.

That's true about Racket, but it has a quite original way of dealing with it with their futures[1].

To be honest, I'm not sure about the details, but it should let other threads run truly in parallel as long as it's "safe" to do on the VM implementation level. So, if the code inside the future doesn't perform any "future unsafe" operations, it can execute within a separate OS thread without worrying about the main thread.

Examples of "future unsafe" actions were given as memory allocation, and JIT compilation. Further, it's mentioned that some simple (for the language users, at least) operations may be too complex internally to be "future safe". An example of this is using a generic number comparison operators - `<`, `>`, etc. Apparently, these have to handle the full numeric tower of Racket and in the process perform some future unsafe operations.

In the Mandelbrot function given as an example in the guide, simply replacing the generic comparisons with the ones specialized for work on floats specifically (and assuming that contract is not broken, which would immediately stop the future) allows the future to execute fully in parallel.

What is important to note here is that `set!` and friends, and so mutation of shared memory, is considered "future safe", ie. it's permitted to use them! (although then it's you who deals with the usual problems that brings).

I think it's worth mentioning here, because it's a novel strategy that seems to be between the two usual solutions (1. we've got GIL, live with it; 2. spawn more processes and get them to work - well, now you have many GILs...) and is showing some promising results. Plus they have a neat visualization tool!

Currently, it's limited and works best for purely numerical computations (which is also where you'd need it 99% of the time), but in some cases, it appears to work: the programmers of the language (not the implementation of the language) are given a tool to work outside the GIL in a structured manner plus a tool for closely inspecting low-level operations that happen in their code which would suspend or stop the future.

I'm not aware of any other dynamic or not language which has both the GIL and a nice, language-level tool for freeing it and running in parallel. Because what is considered "future unsafe" depends on the details of the implementation, I'm full of hopes for Racket-on-Chez, although I think I read somewhere that work on futures is not a priority at this time.

Also, to confirm the sibling comment, SBCL is happy to spawn truly parallel threads. There are other Scheme implementations (I think Chicken at least, but not sure right now) who allow the same.

[1] https://docs.racket-lang.org/guide/parallelism.html#%28part....

https://docs.racket-lang.org/reference/futures.html

null and undefined being separate values