7 ms·
Eliminating FORMAT from Lisp (2003)
- medo-bear 4y agofor better or worse, i dont think a more powerful print function exists. common lisps format function even has its own wikipedia entry https://en.m.wikipedia.org/wiki/Format_(Common_Lisp) https://en.m.wikipedia.org/wiki/Format_(Common_Lisp)
- todd8 4y agoThe description of CL format in the cited Wikipedia entry has its own charm. Here’s a sentence: “The second special character, #, is substituted by the tally of format arguments yet abiding their consumption.”
- mtreis86 4y agoFormat is awesome, it is a domain specific language for printing things. It can walk across lists, print all sorts of number radixes, make tables of data, and has its own condition system. I don't get the hate. It is not lispy at all in style, but isn't making a DSL just about the most lispy thing you could do?
- taeric 4y agoAgreed. The format string isn't an s-exp, but the general idea feels very lisp like to me. Similarly, I like LOOP more than I'm supposed to, if I go by what I read online.
- nerdponx 4y agoI feel like avoiding the use of LOOP could be a Master Foo koan. An ambitious young student of Lisp came to Master Foo, seeking to deepen her knowledge and understanding. One day, during their studies and practice, the students stopped and said, "Master, I am troubled. In Lisp we have minimal syntax, and we use S-expressions to convey structure, and this syntactic uniformity gives us great power to build our own composable syntax constructs. And yet we have LOOP in the standard, which is an ad-hoc imitation of the same arbitrary syntax of other languages that we use Lisp in order to avoid!" The next day, the Master brought the student on a hike into the hills. As the sun was setting late in the afternoon, they reached a small cabin near the summit of a hill. A village of huts was visible in the valley below them. "Observe that this building is constructed from many small pieces of wood, each nearly uniform in size and shape. With those pieces I long ago built a hut that resembles the shoddy huts of the village, yet this one is both strong and easy to modify. You could even build it yourself from plans." The student observed and agreed, and was very impressed, but was also confused. "Master, what does this teach me about Lisp?" she asked. Master Foo continued, "You shall stay here on the mountain tonight. Return to me in the morning." With that, the Master drew a key from his robe, firmly locked the door of the cabin, and hiked away. As he did so, a cold rain and steady wind began to blow. The student spent the night cold and damp on the ground beside the cabin, and awoke enlightened.
- srcreigh 4y agoRacket has for/list and even for/fold. They’re just different syntax for map and foldl. I prefer them to the functional versions sometimes so I don’t have to write lambda, the for loops have options for combining multiple lists, etc.
- taeric 4y agoThe loop macro is impressive for many of its esoteric keywords. Maximizing, collect, append, etc. There is also the for/then construct that I find much easier to work with than alternatives for the same behavior. I have no doubt much of this can be done with the racket constructs. But I still find I can more easily read loop usages I've written in the past. Whereas many similarly dense for/folds are opaque within a few minutes for me. :(
- LanceH 4y agoThe thing about functional purity is that the point of a lot of programming is to get the side effects.
- packetlost 4y agoI think that's still pretty well-understood. The real draw to FP in general is you reduce the globally modifiable state to be the things that are truly global to an application. For web applications, this may be a database. For systems applications it may be a filesystem.
- valenterry 4y agoThe point of fp is not to get rid of effects, but to make them explicit and better controllable and do that in a consistent way, not on a case by case decision.
- nyx_land 4y agoAgreed. People give a lot of hate to FORMAT and LOOP, but I think with both of them for the domains they're working in, it's kind of a natural progression of the evolution of a DSL (which as you said is very lispy since reader macros are one of the biggest strengths of CL) that you'll end up with something like FORMAT or LOOP that handles a lot of common use cases for string formatting or iteration as tersely as possible. It's inherently less flexible than pure s-exps (nothing is as flexible), but IME solves most problems where you need string formatting or iteration. Plus it's part of the ANSI CL spec, and sticking as close to the spec as possible and being conservative with external dependencies is usually a good thing in CL.
- nerdponx 4y agoWell the point is that it could have been an actual macro instead of a function that processes a syntactical-opaque control string. I do appreciate the convenience and brevity of FORMAT as it currently exists, but I also think it would be pretty cool to have a proper S-expression macro that expands to a FORMAT control string. It would be analogous to a macro that generates SQL or HTML.
- aidenn0 4y agoIt can't be a macro without requiring the string to be constant.
- nerdponx 4y agoThat's what I had in mind, which covers most use cases of FORMAT that I've seen.
- kazinator 4y agoCommon Lisp provides an explicit macro for this called formatter. The macro produces a function. The format function allows a functional argument in place of the format string for this reason. (format t (formatter "~a-~b") x y) is like (format t "~a-~b" x y) except that "~a-~b" is transformed into a lambda which takes a stream and two arguments, and which does the specified formatting.
- nerdponx 4y agoThere are so many corners of the spec that I have not yet explored, so I always appreciate trivia like this. However I was thinking more like the macro that the article was proposing.
- medo-bear 4y ago> but isn't making a DSL just about the most lispy thing you could do? 10000% agree. LOOP and FORMAT are excellent examples of using lisp to build dsls. so much so that they made it into the ANSI standard
- kazinator 4y agoThe formatter macro is an exercise in Lisp, but an interpretive format doesn't even have to be written in Lisp.
- deleted 4y ago[deleted]
- lisper 4y ago> isn't making a DSL just about the most lispy thing you could do? Yes, but with great power comes great responsibility. The problem with FORMAT and LOOP is not that they are DSL's, but rather that they are badly designed DSLs. The reasons are different in both cases. FORMAT is badly designed because it is write-only, kind of like Perl regexps. It is very, very hard to debug a complex FORMAT string. LOOP, by way of very stark contrast, is very readable, even more so than regular Lisp. But it is badly designed because it is chock-full of non-orthogonal constructs. For example, WITH is completely equivalent to a set of LET bindings outside the loop, so it doesn't actually add any functionality. All it does is give you a new non-lispy syntax for creating external bindings. The semantics of FOR depend on what comes after. "for x =" does something completely different from (for example) "for x in". And the list of problems with LOOP goes on and on and on. So the problem is not that DSLs are bad, the problem is that these DSLs are bad. But since they are part of the standard, we are stuck with them.
- lispm 4y agoThe idea is that the WITH variables are local to the LOOP construct. If one leaves the LOOP, the variables are gone. With an outside LET this would not be the case. The LOOP thus gives scope for all lexical variables inside of it. Additionally the LOOP sees the WITH definition in standard Common Lisp. It does not see any outside LET bindings, unless the Common Lisp would provide a feature to ask the current environment. This allows the LOOP to, for example, see a type declaration and provide a default value for the common cases. (let (a) ; a is initialized to NIL (declare (type integer a)) .... (loop repeat 10 do (incf a)) ... a) vs. (loop with a fixnum ; a is initialized to 0 repeat 10 do (incf a) finally (return a)) It provides a compact notation with ONE scoping construct, the LOOP macro. Similar for the LOOP name. In the same way you could argue that this is just a named BLOCK around the LOOP construct. But, again, the name inside the LOOP makes it clear that the this is the name of this LOOP construct. Btw., the ITERATE macro works in a similar way.
- lisper 4y ago> The idea is that the WITH variables are local to the LOOP construct. If one leaves the LOOP, the variables are gone. With an outside LET this would not be the case. Of course it would. This: (loop with a = ...) is exactly equivalent to: (let ((a ...)) (loop ...)) and so you can e.g. capture with-bindings in closures: (funcall (loop with a = 123 return (lambda () a))) ==> 123 > Additionally the LOOP sees the WITH definition in standard Common Lisp. It does not see any outside LET bindings, unless the Common Lisp would provide a feature to ask the current environment. This allows the LOOP to for example see a type declaration and provide a default value for the common cases. I think it is highly questionable whether providing implicit typed default values is actually a useful feature, but assuming for the sake of argument that it is, why not just make a general binding construct that does this? If it's useful, it should be useful (and usable) everywhere, not just inside a loop. > you could argue that this is just a named BLOCK around the LOOP construct Indeed you could. :-) > the name inside the LOOP makes it clear that the this is the name of this LOOP construct. Yes, but why is that useful? How is (loop named foo ...) any better than (block foo (loop ...)) ? And if it is better, then why not (progn named foo ...) ? (tagbody named foo ...) ?
- rjsw 4y agoLanguages that start out without printf or format end up adding it later.
- mughinn 4y agoThe argument of the article seems to be (AFAIK) to make a printf function but in a more "lispy" fashion, not to outright ban the concept
- numeromancer 4y agoEdi Weitz gave Common Lisp F-strings before F-strings were cool. https://edicl.github.io/cl-interpol/ https://edicl.github.io/cl-interpol/
- nerdponx 4y agoI mean, Bourne shell did it first. And Perl, and Ruby, and...
- agumonkey 4y agoGood point, I'm curious how it started
- nerdponx 4y agoThe oral history of string interpolation might make for an interesting Computerphile video!
- agumonkey 4y agoWhat surprises me is that bash is not syntax comfy but on this part they made a very ergonomic addition
- nerdponx 4y agoYou might really enjoy Zsh. It's a lot like the best of Ksh and Bash but better, because it has sensible word splitting behavior (disabled by default!) as well as both sequential and associative arrays. I use it for a lot of scripts.
- rst 4y agoFortran I/O statements specifying a FORMAT are the first example I'm aware of (released 1957), so the basic idea is at least that old.
- 4y ago
- JohnDeHope 4y ago"Lisp is a syntactically extensible language, meaning that it is quite easy, using macros, to create arbitrary language extensions, so long as they obey two basic rules: (1) A new statement must look like (op ...), where "..." has balanced parentheses; (2) the lexical conventions inside the new statement must be Lisp's (e.g., more characters (including '*', '+', and such) are ordinary symbol constituents, in contrast to their role in other languages, so adjacent symbols must be separated by whitespace; double quote starts a string; single quote, sharpsign, and a few other characters have special meanings). If you're used to Lisp, these rules are barely noticeable, so that Lisp hackers come to think of it as having the most flexible syntax in the world." I'm tired of reading about lisp, but I always do it. This gem is the sort of thing I'm after: an explanation of why (specifically) some folks just can't seem to give it up. I admit I still find the language incomprehensible. But at least I understand why others don't. It's a start.
- d3ckard 4y agoI would kill for the opportunity to work on Lisp full time (well, maybe not kill, but you get the level of motivation). The reason for it is in your quote - extremely regular syntax. No operators, no precedence, no special syntax forms - just a bunch of lists with symbols. I didn't realise how important it is until I started writing macros in Elixir and realised they are not quite "native", even if really powerful. Lisp will be back, I feel certain of it.
- ludston 4y agoYou can use reader macros to add any operators or special forms you want to Common Lisp. Things can get especially crazy if you use a reader-macro to override '(
- agumonkey 4y agoWhat surprises me is how lisp is such an obvious delight for some at first sight, while it's pure hell for most. There was no need for an explanation about beneficial syntactic reduction, extension and reuse it just massages a part of the brain that screams "Yes please".
- timonoko 4y agoAll kinds of ugly incomprehensible squiggles should be banned. And reader-macros too, they facilitates this shit.
- nerdponx 4y agoMeanwhile Scheme has at least two SRFIs for format strings! As I suggested elsewhere in this thread, IMO the ideal scenario would be to have both the string and sexpr "interfaces". Since we are stuck with the string version, it would be nice to have a macro that expands to a valid control string. Not unlike how you'd write a macro to generate SQL or HTML.
- lapsed_lisper 4y agoRichard P. Gabriel (one of the co-creators of Common Lisp, later a founder of the Lisp vendor, Lucid) has a few interesting things to say about CL's FORMAT in his Patterns Of Software, starting on page 101: https://www.dreamsongs.com/Files/PatternsOfSoftware.pdf https://www.dreamsongs.com/Files/PatternsOfSoftware.pdf > What are the trade-offs? Format strings don’t look like Lisp, and they constitute a non-Lispy language embedded in Lisp. This isn’t elegant. But, the benefit of this is compact encoding in such a way that the structure of the fill-in-the-blank text is apparent and not the control structure. IMO McDermott's OUT macro has precisely the drawbacks Gabriel predicts. While McDermott seems to think it's an advantage that > we no longer have to squeeze the output data into a form intelligible to format, because we can use any Lisp control structure we like his example PRINT-XAPPING basically duplicates the logic for extracting data from the xapping structure as Steele's FORMAT call, but buries the data extraction into control structure alongside constant strings that go into the output. And that's where I think FORMAT really a win: a FORMAT control deliberately separates the control flow and constant text from the data extraction logic. Presumably you could write functions that do the same thing using McDermott's OUT macro, but they'll be more verbose and no more enlightening; what's the point?
- interroboink 4y ago> what's the point? I think there is some advantage to allowing comments to be nested alongside the relevant bits of format text. It reminds me a bit of Perl's /x modifier[1], which arguably achieves some of the best of both worlds for regex expressions. [1] https://perldoc.perl.org/perlre#/x-and-/xx https://perldoc.perl.org/perlre#/x-and-/xx
- lapsed_lisper 4y agoDoes it really make that much difference if comments exist "inside" the embedded sublanguage vs. "outside"? After all, you can always construct a format specification by string concatenation, and in CL, you can trick the parser into doing this for you: (format stream t #.(concatenate 'string ;; comment about the first piece <piece 1> ;; comment about the second piece <piece 2>) ...) Additionally, for the record, CL's FORMAT is sufficiently hairy that you can achieve the effect of in-band comments if you wanted to: (format t "~ ~0{ Because the previous line ended with a tilde, the following newline is ignored. All of this text occurs inside an iteration construct that loops zero times, so will not be output. However, it will consume one element from the list of arguments, so after this loop, we'll use tilde-colon-asterisk to back up one element, and let what comes next decide how to format it. So this is more or less a comment inside a CL FORMAT string. ~}~:*~ ~S" 'Foo)
- aidenn0 4y agoThere is always a question of format-strings (or format-string like) and native syntax with the manipulation of parameters to be printed in native code. This is the same as C++ iostream vs printf.
- jgalt212 4y agoTo my feeble mind, the FORMAT DSL = Brainfuck + a few more letters.
- kazinator 4y agoTXR Lisp offers some nice choices for printing: 1> (let ((words '#"how now brown")) (put-line `@(+ 2 2) --- @{words ","}, she said`)) 4 --- how,now,brown, she said t 2> (put-line (pic "0,###,###,###.## <<<<<<< >>>>>>" 1234567.93 "xx" "yy")) 0,001,234,567.93 xx yy t 3> (format t "~0,8x\n" 12345) 3039 t
- noobermin 4y agoI feel like having all the options you'll want available is the best practice. I feel like python people went from liking long concatenations with arguments being infix with python2 print statements to str.format in early python3 and friends to liking long concatenations again with fstrings all again. And now we get the joy of having long conditional statements in the middle of a string, rather than it being assigned to a variable before the string, as god intended. Really, the right answer is having options for string interpolation (as long as they are safe (that is not perl)) and let programmers choose because string interpolation is just another one of those bikeshedding things that developers change their opinions on as the seasons change.
- reikonomusha 4y agoBecause Common Lisp allows libraries to extend the character syntax, interpolated strings are just a library away with CL-INTERPOL [1]. So (let ((x 5)) #?"there are ${x} hackers") works as expected. [1]: http://edicl.github.io/cl-interpol/ http://edicl.github.io/cl-interpol/
- bjoli 4y agoI really think the scheme SRFI 166 is the nices formatting utility I have worked with: https://srfi.schemers.org/srfi-166/srfi-166.html https://srfi.schemers.org/srfi-166/srfi-166.html Probably slow compared to format, but more easily extendable. The slowness could be fixed for the included formatters with something like CLs compiler macros