Thread: [q-lang-users] RFC: Conditional syntax
Brought to you by:
agraef
From: Albert G. <Dr....@t-...> - 2006-05-30 23:11:57
|
Hi all, I've completed: memoized special arguments; call-by-need pattern matching; and a built-in lambda implementation -- along with the usual kind of syntactic sugar, i.e., "\X.Y". As a wanted side-effect, lambdas also work much faster now, and correctly deal with the anonymous variable and non-linear patterns. This is all in CVS now. For the next RC I still have one item on my TODO list: conditionals. For this, I'm seeking your advice and comments. First, I'd like to add sugar for the ifelse function ("if X then Y else Z"). That should be rather straightforward, but I still have to figure out how to add that to the syntax without making the grammar ambiguous. But do we really need/want this? Or is everyone happy with "ifelse X Y Z" as it is? Second, I'd like to replace the multiway and matching conditionals in the standard library. These look awfully C-stylish and verbose right now (the following examples are somewhat silly since you'd probably do them with conditions and patterns on the equation level instead, but they are for illustrative puposes only): (Ex. 0) abs X = switch (case (X<0) (-X), default X); empty Xs = match Xs (case [] true, default false); So this what we have right now. I'd like to replace this with something more tidy and Lisp-like. Maybe this: (Ex. 1) abs X = cond ((X<0, -X), (true, X)); empty Xs = case Xs (([], true), (_, false)); Or maybe the sequence of clauses should actually be a stream instead of a tuple? (Ex. 2) abs X = cond {(X<0, -X), (true, X)}; empty Xs = case Xs {([], true), (_, false)}; This has the advantage that the sequence of clauses could be a non-special argument (since the expressions inside of it are already protected by the stream constructor), and that we could even have infinite lists of clauses. I'm also considering to add some unobtrusive syntactic sugar to allow "grouping" inside tuples, lists and streams. E.g., [X1,Y1;X2,Y2;...] would be the same as [(X1,Y1),(X2,Y2),...]; this would also be useful, e.g., for writing lists of key-value pairs. With this notation, the above could be rewritten as: (Ex. 3) abs X = cond (X<0, -X; true, X); empty Xs = case Xs ([], true; _, false); Which, IMHO, is quite tidy and also looks ok when formatted in two columns, e.g.: empty Xs = case Xs ( [] , true; _ , false; ); Incidentally this also closely resembles the corresponding constructs in ML and Haskell (minus some of the sugar, of course). So what do you prefer? Multiple choices possible. :) 0. Keep the old definitions (Ex. 0). 1. Clauses as a tuple of (guard/pattern, value) pairs (Ex. 1). 2. Clauses as a stream of (guard/pattern, value) pairs (Ex. 2). 3. 1 or 2 with "grouping" sugar (Ex. 3). 4. Don't care. 5. Any other ideas? I'm currently leaning towards 2+3. Comments appreciated... Cheers, Albert -- Dr. Albert Gr"af Dept. of Music-Informatics, University of Mainz, Germany Email: Dr....@t-..., ag...@mu... WWW: http://www.musikinformatik.uni-mainz.de/ag |
From: Tim H. <q...@st...> - 2006-05-31 09:29:15
|
Albert Graef <Dr....@t-...> writes: [snip] > So what do you prefer? Multiple choices possible. :) > > 0. Keep the old definitions (Ex. 0). Backwards compatibility is always good ;) > 1. Clauses as a tuple of (guard/pattern, value) pairs (Ex. 1). > 2. Clauses as a stream of (guard/pattern, value) pairs (Ex. 2). Having thought `hmm, braces, but not in the perl/C sense' I think I like 2 more. Nested-parens are all very well in a lisp/scheme language but they're not what I see in Q. YMMV. :] > 3. 1 or 2 with "grouping" sugar (Ex. 3). It's certainly neat, watching other languages' switch/case layout come about as a result of a formatting change, but as with other sugar, I'm wondering what potential for tooth-rot it's letting in through the back door. Any? Am I paranoid? > 4. Don't care. I'd say I don't mind too much. I'm just a mortal user :) ~Tim -- <http://spodzone.org.uk/> |
From: Albert G. <Dr....@t-...> - 2006-05-31 13:44:20
|
Tim Haynes wrote: >>0. Keep the old definitions (Ex. 0). > > Backwards compatibility is always good ;) That's right, and normally I'd agree with you, but in this case I argue against it, for the following reasons: - Having two incompatible constructs for exactly the same thing will be confusing. - I really want to use "case" for the new matching conditional. That's the most obvious choice, and will be familiar to both Lisp and Haskell/ML users. Unfortunately, "case" is already declared as a constructor with the old constructs. So I'm afraid that it's either (0) or (1|2), but not both. - AFAICT, nobody really seems to use the current constructs right now. I scanned all Q sources I have lying around on my hard disk (which also includes all the add-on modules and the scripts contributed by you, Peter and Rob), and there's exactly one invokation of switch and one invokation of match. Both are in gqbuilder.q, which belongs to the core distribution, so it can be fixed pretty easily without anyone noticing. And if anyone still uses the old constructs occasionally, it should be an easy matter to parse those scripts with -w to identify all the undefined uses of "switch" and "match" and fix them. So it seems the right time now to make a clean cut. If there's anyone who has a significant amount of code using the old switch and match constructs, please speak up now! Talking about backward compatibility, I think that, even with all the other changes under the hood, Q 7.1 as it is in cvs now should still be 100% backward-compatible, with the following two exceptions: - The apply and compose functions have been removed from stdlib.q. These have been deprecated since the introduction of ($) and (.) in Q 6.1 and 5.3, so it should be safe to remove them now. - The lambda function used to return combinator terms which weren't nice to look at, but could be unparsed, written to a file and read back. With the new built-in lambda function, you'll get an external <<Function>> object instead. I don't think that the latter incompatibility will be much of a problem, except if you want to serialize a partially evaluated stream comprehension (which makes heavy use of embedded lambdas) and store it on disk. I could actually unparse the new "compiled" lambdas to something which can be parsed to reconstruct the function, but I'm afraid that it won't look very pretty, so the <<Function>> notation might actually be preferable, at least for interactive usage. And it's similar to what you'd get for anonymous closures with other FPs, too. >>1. Clauses as a tuple of (guard/pattern, value) pairs (Ex. 1). >>2. Clauses as a stream of (guard/pattern, value) pairs (Ex. 2). > > Having thought `hmm, braces, but not in the perl/C sense' I think I like 2 > more. Nested-parens are all very well in a lisp/scheme language but they're > not what I see in Q. YMMV. :] Note that the parens/braces there actually belong to the tuple/stream arguments of the cond/case functions. There's no syntactic sugar involved here, both cond and case will just be ordinary Q functions. >>3. 1 or 2 with "grouping" sugar (Ex. 3). > > It's certainly neat, watching other languages' switch/case layout come > about as a result of a formatting change, but as with other sugar, I'm > wondering what potential for tooth-rot it's letting in through the back > door. Any? Am I paranoid? Yeah, the "cancer of the semicolon"... That's the reason why I've always been reluctant to add more syntactic sugar to Q. I think that Haskell really goes a little overboard with that (in some cases out of necessity, since there are no user-definable special forms in Haskell). But I'd say that the syntax I've added in Q 7.1 is quite unobtrusive, really makes frequently-used stuff easier to read and write, was not introduced without a second and third thought (in fact I've been thinking about most of these features for years, but refrained from introducing them before everything else was in place), and fits well with the basic syntax. Here is a list of what's new in Q 7.1: - Stream syntax which looks like lists. I think that noone will argue that this is a vast improvement. - List and stream comprehension syntax. This uses a tried and proven set-like notation which comes from mathematics and has been around for centuries, I think. - Lambda syntax. Also uses a tried and proven notation from mathematical logic, which has been around since the 1930s. - If-then-else. Used in many FP textbooks, even if they otherwise use applicative syntax throughout. Note that all of these can still be specified using the basic applicative syntax (using nil_stream/cons_stream, listof/streamof, lambda, ifelse/when) if you prefer that. The proposed grouping notation is also quite common in mathematics, for the purpose of improving readability of sequences and argument lists, albeit with different meanings. I can think of three different reasonable implementations: - Visual cue only. Allow ";" instead of "," in sequences, but without any special meaning. - Nested sequence of same type. [X,Y; ...] will become [[X,Y],...], (X,Y; ...) will become ((X,Y),...) and {X,Y; ...} will become {{X,Y},...}. - Nested tuple. In this case the group always produces a nested tuple, i.e., [X,Y; ...] will become [(X,Y),...], (X,Y; ...) will become ((X,Y),...) and {X,Y; ...} will become {(X,Y),...}. I think that the latter interpretation would be the most useful for Q. Clearly, this kind of notation is not "standard" in the same way as the other items above, though. But, as I pointed out earlier, it would be fairly convenient in different contexts. Cheers, Albert -- Dr. Albert Gr"af Dept. of Music-Informatics, University of Mainz, Germany Email: Dr....@t-..., ag...@mu... WWW: http://www.musikinformatik.uni-mainz.de/ag |
From: John C. <co...@cc...> - 2006-05-31 14:11:05
|
Albert Graef scripsit: > (Ex. 1) > > abs X = cond ((X<0, -X), (true, X)); > empty Xs = case Xs (([], true), (_, false)); > > Or maybe the sequence of clauses should actually be a stream instead of > a tuple? > > (Ex. 2) > > abs X = cond {(X<0, -X), (true, X)}; > empty Xs = case Xs {([], true), (_, false)}; I actually prefer Ex. 1 to Ex. 2, because there is nothing lazy here. What we have is an immutable sequence (tuple) of possibilities to try. I grant that using a stream makes "cond" and "case" not special forms, but that is an implementation-level consideration, and unless special forms are far more expensive in the interpreter, ought not to be controlling. > This has the advantage that the sequence of clauses could be a > non-special argument (since the expressions inside of it are already > protected by the stream constructor), and that we could even have > infinite lists of clauses. Only if we are capable of making infinite decisions, which I at least am not. > I'm also considering to add some unobtrusive syntactic sugar to allow > "grouping" inside tuples, lists and streams. E.g., [X1,Y1;X2,Y2;...] > would be the same as [(X1,Y1),(X2,Y2),...]; this would also be useful, > e.g., for writing lists of key-value pairs. I agree with this, and I agree that the implied internal objects should be tuples in all cases; again, the situation is inherently immutable and fixed. -- A poetical purist named Cowan [that's me: co...@cc...] Once put the rest of us dowan. [on xml-dev] "Your verse would be sweeter http://www.ccil.org/~cowan If it only had metre And rhymes that didn't force me to frowan." [overpacked line!] --Michael Kay |
From: Albert G. <Dr....@t-...> - 2006-05-31 19:47:00
|
John Cowan wrote: > I actually prefer Ex. 1 to Ex. 2, because there is nothing lazy here. Do you mean "lazy" as in "deferred+memoizing" or just "deferred"? If it's the former, then you're right (but streams, by default, aren't memoizing in Q either). If it's the latter then you're wrong because the guards/patterns and the corresponding values need to be deferred until they are looked at. The tails of the clause sequence don't have to be deferred, with that I agree. Remember, in Q "special" == "deferred" == "call by name". In Q, there's neither a conceptual nor a technical difference between, say, the deferred arguments of "defined functions" like (or else) or ifelse on the one side and those of "constructors" like (') and cons_stream on the other side. That's because there's actually no distinction between defined functions and constructors in the first place. Unfortunately, I got into the habit of using "lazy" synonymous with just "special" or "call by name", whereas most of the FP community will reserve "lazy" for "deferred+memoizing" a.k.a. "call by need" (which I'd call "fully lazy"). Maybe that's the source of the confusion. Anyway, I'm perfectly happy with option (1), too. [infinite clause lists] > Only if we are capable of making infinite decisions, which I at least > am not. But you could write a program to generate a potentially infinite (or huge) sequence of conditional clauses and then start evaluating it until you find a clause that's true. Semi-decision procedures come to my mind. Anyway, such (rather esoteric) applications can already be handled using stream programming, so there doesn't seem to be a point in supporting that directly with cond. [grouping syntax] > I agree with this, and I agree that the implied internal objects should > be tuples in all cases; again, the situation is inherently immutable > and fixed. All right, I'm going to implement this and commit it to cvs tonight, along with cond/case as described in Ex. (1). Judging from the replies, everybody should be happy (or at least not unhappy) with that. Thanks for all the responses! :) Cheers, Albert -- Dr. Albert Gr"af Dept. of Music-Informatics, University of Mainz, Germany Email: Dr....@t-..., ag...@mu... WWW: http://www.musikinformatik.uni-mainz.de/ag |
From: John C. <co...@cc...> - 2006-05-31 21:27:38
|
Albert Graef scripsit: > Do you mean "lazy" as in "deferred+memoizing" or just "deferred"? If > it's the former, then you're right (but streams, by default, aren't > memoizing in Q either). If it's the latter then you're wrong because the > guards/patterns and the corresponding values need to be deferred until > they are looked at. The tails of the clause sequence don't have to be > deferred, with that I agree. It was the last point that I had in mind. > Unfortunately, I got into the habit of using "lazy" synonymous with just > "special" or "call by name", whereas most of the FP community will > reserve "lazy" for "deferred+memoizing" a.k.a. "call by need" (which I'd > call "fully lazy"). Maybe that's the source of the confusion. Probably yes. -- John Cowan co...@cc... http://ccil.org/~cowan The known is finite, the unknown infinite; intellectually we stand on an islet in the midst of an illimitable ocean of inexplicability. Our business in every generation is to reclaim a little more land, to add something to the extent and the solidity of our possessions. --Thomas Henry Huxley |
From: John C. <co...@cc...> - 2006-05-31 14:15:13
|
Albert Graef scripsit: > - The lambda function used to return combinator terms which weren't nice > to look at, but could be unparsed, written to a file and read back. With > the new built-in lambda function, you'll get an external <<Function>> > object instead. For this reason, and also for the sheer coolness of it, I'd like to see the old lambda stay in the standard library, but renamed "Lambda". It's a nice executable demonstration of exactly how lambdas can be expressed as combinators. (It would be all right if, like graphics.q, the modified lambda.q were not loaded by default.) -- John Cowan co...@cc... http://ccil.org/~cowan Original line from The Warrior's Apprentice by Lois McMaster Bujold: "Only on Barrayar would pulling a loaded needler start a stampede toward one." English-to-Russian-to-English mangling thereof: "Only on Barrayar you risk to lose support instead of finding it when you threat with the charged weapon." |
From: Albert G. <Dr....@t-...> - 2006-05-31 19:51:35
|
John Cowan wrote: > For this reason, and also for the sheer coolness of it, I'd like to > see the old lambda stay in the standard library, but renamed "Lambda". > It's a nice executable demonstration of exactly how lambdas can be > expressed as combinators. I'd rather move it to the examples folder. If someone really wants to use it, it can just be copied over. "Lambda" isn't possible, though, as it's a variable identifier. I could call it _lambda. -- Dr. Albert Gr"af Dept. of Music-Informatics, University of Mainz, Germany Email: Dr....@t-..., ag...@mu... WWW: http://www.musikinformatik.uni-mainz.de/ag |
From: John C. <co...@cc...> - 2006-05-31 20:28:34
|
Albert Graef scripsit: > I'd rather move it to the examples folder. If someone really wants to > use it, it can just be copied over. "Lambda" isn't possible, though, as > it's a variable identifier. I could call it _lambda. That sounds great. -- Only do what only you can do. John Cowan <co...@cc...> --Edsger W. Dijkstra's advice to a student in search of a thesis |
From: Albert G. <Dr....@t-...> - 2006-06-03 00:41:33
|
All right, the revised lambda.q is in cvs now, under examples. Realizing that Q has namespaces :), I didn't even bother to change the name of the lambda operation. In fact you can even replace the builtin lambda with the one from lambda.q by uncommenting a single line near the beginning of lambda.q. John Cowan wrote: > Albert Graef scripsit: > >>- The lambda function used to return combinator terms which weren't nice >>to look at, but could be unparsed, written to a file and read back. With >>the new built-in lambda function, you'll get an external <<Function>> >>object instead. > > For this reason, and also for the sheer coolness of it, I'd like to > see the old lambda stay in the standard library, but renamed "Lambda". > It's a nice executable demonstration of exactly how lambdas can be > expressed as combinators. -- Dr. Albert Gr"af Dept. of Music-Informatics, University of Mainz, Germany Email: Dr....@t-..., ag...@mu... WWW: http://www.musikinformatik.uni-mainz.de/ag |
From: Albert G. <Dr....@t-...> - 2006-05-31 22:53:52
|
Albert Graef wrote: > (Ex. 3) > > abs X = cond (X<0, -X; true, X); Arrgh, I just noticed that cond is already used in clib for pthread conditions. Too bad. :( Can anyone think of another nice name for the conditional operation? -- Dr. Albert Gr"af Dept. of Music-Informatics, University of Mainz, Germany Email: Dr....@t-..., ag...@mu... WWW: http://www.musikinformatik.uni-mainz.de/ag |
From: Patrick A. <rp...@al...> - 2006-06-01 04:31:50
|
On Thu, Jun 01, 2006 at 12:59:19AM +0200, Albert Graef wrote: > Albert Graef wrote: > >(Ex. 3) > > > >abs X = cond (X<0, -X; true, X); > > Arrgh, I just noticed that cond is already used in clib for pthread > conditions. Too bad. :( Can anyone think of another nice name for the > conditional operation? > Perhaps? abs X = choose (X<0, -X; true, X); Patrick. -- |
From: Albert G. <Dr....@t-...> - 2006-06-01 08:11:46
|
Patrick Albuquerque wrote: > abs X = choose (X<0, -X; true, X); Keith Trenton wrote: > abs X = branch (X<0, -X; true, X); I like those. "choose" seems to be a bit too generic, though, and thus it might collide with user definitions (I actually have a few programs where I use that for nondeterministic choice). That also rules out "test", I guess. But "branch" seems to be ok. Keith and Brian, thanks for your other suggestions, too. But those abbreviations sound an awful lot like assembler opcodes... ;-) Ok, here is what we got so far: 1. cop, coop, co_op 2. mbr 3. choose 4. branch Here are some more I "invented" myself: 5. pick 6. check 7. test 8. query 9. guard BTW, in the library of the Icon language, Dijkstra-style guarded statements are named if_fi, but this also looks a bit clumsy. Or how about: 10. ifcond This is sufficiently similar to cond, not likely to collide with user definitions, and we already have ifelse for the two-way conditional. Of course, the question is whether we actually need this construct at all, since one could always use an if-then-else if-... chain like in other programming languages: sign X = if X<0 then -X else if X>0 then X else X; Hmm ... actually, that looks quite messy when compared to: sign X = ifcond (X<0, -X; X>0, X; true, 0); I can understand now why Lisp programmers love their cond. ;-) Comments? Other suggestions? Albert -- Dr. Albert Gr"af Dept. of Music-Informatics, University of Mainz, Germany Email: Dr....@t-..., ag...@mu... WWW: http://www.musikinformatik.uni-mainz.de/ag |
From: John C. <co...@cc...> - 2006-06-01 11:45:29
|
Albert Graef scripsit: > Arrgh, I just noticed that cond is already used in clib for pthread > conditions. Too bad. :( Can anyone think of another nice name for the > conditional operation? Umm, is it out of the question to change "cond" in clib to "condition"? -- My confusion is rapidly waxing John Cowan For XML Schema's too taxing: co...@cc... I'd use DTDs http://www.ccil.org/~cowan If they had local trees -- I think I best switch to RELAX NG. |
From: Albert G. <Dr....@t-...> - 2006-06-01 13:29:26
|
John Cowan wrote: > Umm, is it out of the question to change "cond" in clib to "condition"? Well, if I change that, I'll have to change sem to semaphore, too, for consistency, and maybe some other pthread ops. Actually that's not a bad idea. But it will break most of the multimedia examples and maybe other stuff in the multimedia library, so all of this would have to be fixed. :( I'm willing to take the plunge, though. So does anyone else use those thread operations? -- Dr. Albert Gr"af Dept. of Music-Informatics, University of Mainz, Germany Email: Dr....@t-..., ag...@mu... WWW: http://www.musikinformatik.uni-mainz.de/ag |
From: Tim H. <q...@st...> - 2006-06-01 13:37:47
|
Albert Graef <Dr....@t-...> writes: > John Cowan wrote: > > Umm, is it out of the question to change "cond" in clib to "condition"? > > Well, if I change that, I'll have to change sem to semaphore, too, for > consistency, and maybe some other pthread ops. Actually that's not a bad > idea. But it will break most of the multimedia examples and maybe other > stuff in the multimedia library, so all of this would have to be fixed. > :( > > > I'm willing to take the plunge, though. So does anyone else use those > thread operations? AFAIK I make very limited use of threading only in my wittering/index.q: | //How to kill a thread | terminate T = cancel T if active T; | = result T otherwise; | | main = TP || insertarticle || sleep 3 || terminate TP || writes output | if [snip] | where TP=thread ping; This code is mostly inactive & bugged atm anyway and doesn't mention `sem' or anything either, so don't worry about me! :) ~Tim -- <http://spodzone.org.uk/> |
From: Albert G. <Dr....@t-...> - 2006-06-01 23:00:08
|
Tim Haynes wrote: > AFAIK I make very limited use of threading only in my wittering/index.q: Ok, since noone really complained, it's all done and in cvs now. I've renamed the "cond" and "sem" stuff in clib, and the multiway conditional in prelude.q is properly named "cond" now. :) Note that the new cond and case conditionals will now raise an exception when they "fall off" the list of clauses. I thought that was more appropriate than just returning (), as was the case with the old switch/match stuff. -- Dr. Albert Gr"af Dept. of Music-Informatics, University of Mainz, Germany Email: Dr....@t-..., ag...@mu... WWW: http://www.musikinformatik.uni-mainz.de/ag |