Queries should start by the `FROM` clause, that way which entities are involved can be quickly resolved and a smart editor can aid you in writing a sensible query faster.
The order should be FROM -> SELECT -> WHERE, since SELECT commonly gives names to columns, which WHERE will reference.
You could even avoid crap like `SELECT * FROM table`, and just write `FROM table` and have the select clause implied.
Never mind me, I'm just an old man with a grudge, I'll go back to my cave...
Check out the DuckDB community extensions:
[0]: https://duckdb.org/community_extensions/extensions/psql.html
[1]: https://duckdb.org/community_extensions/extensions/prql.html
But I haven't found a good editor plugin that is actually able to use that information to do completions :/ If anyone knows I'd be happy to hear it
Now it is supported by DuckDB, BigQuery, Spark SQL, Firebolt, and probably others. I use it more and more (with BigQuery).
>The order should be FROM -> SELECT -> WHERE, since SELECT commonly gives names to columns, which WHERE will reference.
Per the SQL standard, you can't use column aliases in WHERE clauses, because the selection (again, relational algebra) occurs before the projection.
> You could even avoid crap like `SELECT * FROM table`, and just write `FROM table` and have the select clause implied.
Tbf, in MySQL 8 you can use `TABLE <table>`, which is an alias for `SELECT * FROM <table>`.
It's inspired by a mish-mash of both relational algebra and relational calculus, but the reason why SELECT comes first is because authors wanted it to read like English (it was originally called Structured English Query Language).
You can write the relational algebra operators in any order you want to get the result you want.
A common misconception (that SQL is a realization of RA instead of barely based on it).
In RA, is in fact `Relation > Operator`
More likely because this order is closer to typical English sentence structure. SQL was designed to look like English, not relational algebra.
Except this works in most major vendor SQL implementations. And they all support relation aliases in SELECT... Seems the standards have long fell behind actual implementations.
Don't blame the math for sloppy implementation. Nothing in the math suggest where aliases should be specified. Don't get me wrong it is a coinvent, logical place to put them, but as you say it rather limits their use, and could have been done better.
Okay, so what?
We're not obligated to emulate the notational norms of our source material, and it is often bad to do so when context changes.
https://learn.microsoft.com/en-us/kusto/query/?view=microsof...
Also the LINQ approach in .NET.
I do agree, that is about time that SQL could have a variant starting with FROM, and it shouldn't be that hard to support that, it feels like unwillingness to improve the experience.
As a self-taught developer, I didn't know what I was missing, but now the mechanics seem clear, and if somebody really needs to handle SELECT with given names, then he should probably use CTE:
WITH src AS (SELECT * FROM sales), proj AS (SELECT customer_id, total_price AS total FROM src), filt AS (SELECT * FROM proj WHERE total > 100) SELECT * FROM filt;
It would be equally declarative if FROM came first.
What do you mean? Both ALPHA (the godfather declarative database language created by Codd himself) and QUEL, both of which inspired SQL, put "FROM" first. Fun fact: SQL was originally known as SEQUEL, which was intended to be a wordplay on being a followup to QUEL.
Another commenter makes a good case that SQL ended up that way because it was trying to capture relational algebra notation (i.e. π₁(R), where π~select, ₁~column, R~table). Yet relational algebra is procedural, not declarative. Relational calculus is the declarative branch.
Although the most likely explanation remains simply that SEQUEL, in addition to being fun wordplay, also stood for Structured English Query Language. In English, we're more likely to say "select the bottle from the fridge", rather than "from the fridge, select the bottle". Neither form precludes declarative use.
C++ has this issue too due to the split between header declarations and implementations. Change a function name? You're updating it in the implementation file, and the header file, and then you can start wondering if there are callers that need to be updated also. Then you add in templates and the situation becomes even more fun (does this code live in a .cc file? An .h file? Oh, is your firm one of the ones that does .hh files and/or .hpp files also? Have fun with that).
> The order should be FROM -> SELECT -> WHERE, since SELECT commonly gives names to columns, which WHERE will reference.
Internally, most SQL engines actually process the clauses in the order FROM -> WHERE -> SELECT. This is why column aliases (defined in SELECT) work in the GROUP BY, HAVING and ORDER BY clauses, but not in the WHERE clause.FROM table -- equivalent to today's select * from table
SELECT a, 1 as b, c, d -- equivalent to select ... from table
WHERE a in (1, 2, 3) -- the above with the where
GROUP BY c -- the above with the group by
WHERE sum(d) > 100 -- the above with having sum(d) > 100
SELECT count(a distinct) qt_a, sum(b) as count, sum(d) total_d -- the above being a sub-query this selects from
WHERE sum(d) > 100 -- the above with having sum(d) > 100
0 - https://www.w3schools.com/sql/sql_having.aspYou want the DB to first run SELECT * FROM <table>, and then start operating on that?
I usually start with: ``` select * from <table> as <alias> limit 5 ```
BigQuery SQL and Spark SQL (and probably some others) have adopted pipelined syntax, DuckDB SQL simply allows you to write the query FROM-first.
Now we need to get the ANSI SQL committee to standardize it ANSI SQL 2027 or some such.
I will also hazard a guess that the total number of columns most people would need autocomplete for are rather limited? Such that you can almost certainly just tab complete for all columns, if that is what you really want/need. The few of us that are working with large databases probably have a set of views that should encompass most of what we would reasonably be able to get from the database in a query.
from(l in Blinq.Reservations.OrderCycleLinks)
|> join(:left, [l], r in Blinq.Reservations.Reservation, on: l.reservation_id == r.id)
|> select([l, r], %{
order_cycle_id: l.order_cycle_id,
customer_id: r.customer_id
})
|> where([l, r], l.order_cycle_id in ^order_cycle_ids and r.assignment_type == :TABLE)
|> Repo.all()FWIW that is the approach used by LINQ.
FROM ... SELECT ... WHERE ...: Q S L
Python's list /dict/set comprehensions are equivalent to typed for loops: where everyone complains about Python being lax with types, it's weird that one statement that guarantees a return type is now the target.
Yet most other languages don't have the "properly ordered" for loop, Rust included (it's not "from iter as var" there either).
It's even funnier when function calling in one language is compared to syntax in another (you can do function calling for everything in most languages, a la Lisp). Esp in the given example for Python: there is functools.map, after all.
As for comprehensions themselves, ignoring that problem I find them a powerful and concise way to express a collection of computed values when that's possible. And I'm particularly fond of generator expressions (which you didn't mention) ... they often avoid unnecessary auxiliary memory, and cannot be replaced with an inline for loop--only with a generator function with a yield statement.
BTW, I don't understand your comment about types. What's the type of (x for x in foo()) ?
The relative order of the variable being iterated on and the loop variable name is not relevant to OP's complaint. OP only requires that the expression which uses the loop variable comes after both, which is the case in Rust.
Reasons, sure, but whether those reasons correlate with things that matter is a different question altogether.
Python has a really strong on-ramp and, these days, lots of network effects that make it a common default choice, like Java but for individual projects. The rub is that those same properties—ones that make a language or codebase friendly to beginners—also add friction to expert usage and work.
> Failure to understand something is not a virtue.
This is what I want to say to all the Bash-haters that knee-jerk a "you should use a real language like Python" in the comments to every article that shows any Bash at all. It's a pet peeve of mine.
Comparisons won't tell us anything. If Python were the only programming language in existence, that doesn't imply that it would be loved. Or, if we could establish that Python is the technically worst programming language to ever be created, that doesn't imply that wouldn't be loved. Look at how many people in the world love other people who are by all reasonable measures bad for them (e.g. abusive). Love isn't rational. It is unlikely that it is possible for us to truly understand.
Python with strict type checking and its huge stdlib is my favourite scripting language now.
That's quite a lot of ifs though. Tbh I haven't found anything significantly better for scripting though. Deno is pretty nice but Typescript really has just as many warts as Python. At least it isn't so dog slow.
It's time to try Scala 3 with Java libs' inbound interop: https://docs.scala-lang.org/scala3/book/scala-features.html
For a language where there is supposed to be only one way to do things, there are an awful lot of ways to do things.
Don’t get me wrong, writing a list comprehension can be very satisfying and golf-y But if there should be one way to do things, they do not belong.
I would say unless you have a good reason to do so, features such as meta classes or monkey patching would be top of list to avoid in shared codebases.
That's not what the Zen says, it says that there should be one -- and preferably only one -- obvious way to do it.
That is, for any given task, it is most important that there is at least one obvious way, but also desirable that there should only be one obvious way, to do it. But there are necessarily going to be multiple ways to do most things, because if there was only one way, most of them for non-trivial tasks would be non-obvious.
The goal of Python was never to be the smallest Turing-complete language, and have no redundancy.
There are real concerns about tying the language's future to a VC-backed project, but at the same time, it's just such an improvement on the state of things that I find it hard not to use.
I'm working on a python codebase for 15 years in a row that's nearing 1 million lines of code. Each year with it is better than the last, to the extent that it's painful to write code in a fresh project without all the libraries and dev tools.
Your experience with Python is valid and I've heard it echoed enough times, and I'd believe it in any language, but my experience encourages me to recommend it. The advice I'd give is to care a lot, review code, and keep investing in improvements and dev tools. Git pre commit hooks (just on changed modules) with ruff, pylint, pyright, isort, unit test execution help a lot for keeping quality up and saving time in code review.
On the other hand if you are going to be building something that is going to be long lived, with multiple different teams supporting it over time, and\or larger programs where it all doesn't fit in (human) memory, well then python is going to bite you in the ass.
There isn't a one size fits all programming language, you need at least two. A "soft" language that stays out of your way and lets you figure things out, and a "hard" language that forces the details to be right for long term stability and support.
I've had the displeasure of working in codebases using the style of programming op says is great. It's pretty neat. Until you get a chain 40 deep and you have to debug it. You either have to use language features, like show in pyspark, which don't scale when you need to trace a dozen transformations, or you get back to imperative style loops so you can log what's happening where.
Lists comprehensions were added to the language after it was already established and popular and imho was the first sign that the emperor might be naked.
Python 3 was the death of it, imho, since it showed that improving the language was just too difficult.
People complain that we can’t have nice things. But even when we do, enough developers will be lazy enough not to learn them anyway.
That would be nice if devs always wrote code sequentially, i.e. left to right, one character at a time, one line at a time. But the reality is that we often jump around, filling in some things while leaving other things unfinished until we get back to them. Sometimes I'll write code that operates on a variable, then a minute later go back and declare that variable (perhaps assigning it a test value).
If i decide to add a new field to some class, i won't necessarily go to the class definition first, I'll probably write the code using that field because that's where the IDE was when i got the idea.
If I want to enhance some condition checking, i'll go through a phase where the piece of code isn't valid while I'm rearranging ifs and elses.
Often, not even then.
But some languages just won't let you do that, because they put in errors for missing returns or unused variables.
Reading through the article, the author makes the argument for the philosophy of progressive disclosure. The last paragraph brings it together and it's a reasonable take:
> When you’ve typed text, the program is valid. When you’ve typed text.split(" "), the program is valid. When you’ve typed text.split(" ").map(word => word.length), the program is valid. Since the program is valid as you build it up, your editor is able to help you out. If you had a REPL, you could even see the result as you type your program out.
In the age of CoPilot and agent coders I'm not so sure how important the ergonomics still are, though I dare say coding an LSP would certainly make one happy with the argument.
{3} for {2} in {1}
which would give you code completion for {3} based on the {1} and {2} that would be filled in first.There is generally a trade-off between syntax that is nice to read vs. nice to type, and I’m a fan of having nice-to-read syntax out of the box (i.e. not requiring tool support) at the cost of having to use tooling to also make it nice to type.
This is not meant as an argument for the above for-in syntax, but as an argument that left-to-right typing isn’t a strict necessity.
I know that Python is used for many more things than just data science, so I'd love to hear if in these other contexts, a pipe would also make sense. Just trying to understand why the pipe hasn't made it into Python already.
I find myself increasingly frustrated at seeing code like 'let foo = many lines of code'. Let me write something like 'many lines of code =: foo'.
Interesting idea! However, I'm not sure I would prefer
"Mix water, flour [...] and finally you'll get a pie"
to
"To make a pie: mix water, flour [...]"
It's use is discourages in most style guides. I do not use it in scripts, but I use it heavily in console/terminal workflows where I'm experimenting.
df |> filter() |> summarise() -> x
x |> mutate() -> y
plot(y)
bake(divide(add(knead(mix(flour, water, sugar, butter)),eggs),12),450,12)
versus
mix(flour, water, sugar, butter) %>% knead() %>% add(eggs) %>% divide(12) %>% bake(temp=450, minutes=12)
So much easier!
dough = mix(flour, water, sugar, butter)
dough.knead()
dough = dough.add(eggs)
cookies = dough.divide(12)
cookies = bake(temp=450, minutes=12)
Might be more verbose, but definitely readable. result = (df
.pipe(fun1, arg1=1)
.pipe(fun2, arg2=2)
)
is much less readable than result <- df |>
fun1(., arg1=1) |>
fun2(., arg2=2)
but I guess the R thing also works beyond dataframes which is pretty cool result <- df
|> fun1(arg1=1)
|> fun2(arg2=2)
Python doesn't have a pipe operator, but if it did it would have similar syntax: result = df
|> fun1(arg1=1)
|> fun2(arg2=2)
In existing Python, this might look something like: result = pipe(df, [
(fun1, 1),
(fun2, 2)
])
(Implementing `pipe` would be fun, but I'll leave it as an exercise for the reader.)Edit: Realized my last example won't work with named arguments like you've given. You'd need a function for that, which start looking awful similar to what you've written:
result = pipe(df, [
step(fun1, arg1=1),
step(fun2, arg2=2)
])If you design something to "read like English", you'll likely get verb-first structure - as embodied in Lisp/Scheme. Other languages like German, Tamil use verbs at the end, which aligns well with OOP-like "noun first" syntax. (It is "water drink" word for word in Tamil but "drink water" in English.) So Forth reads better than Scheme if you tend to verbalize in Tamil. Perhaps why I feel comfy using vim than emacs.
Neither is particularly better or worse than the other and tools can be built appropriately. More so with language models these days.
If its imperative, sure. If its declarative and designed to read like English it will be subject first.
Doesn't Kakoune reverses that, and it makes so much more sense?
Doesn't German have the main verb on the second position? (For a simple example, "I drink water" would be "Ich trinke Wasser")
import * as someLibrary from "some-library"
someLibrary.someFunction()
Which works pretty well with IDE autocomplete in my experience.[1] https://developer.mozilla.org/en-US/docs/Web/JavaScript/Refe...
Just do
import SomeLibrary {
asYetUnknownModule
}Essentially, what this combinator does is allow expressing a nested invocation such as:
f(g(h(x)))
To be instead: h(x) |> g |> f
For languages which support defining infix operators.EDIT:
For languages which do not support defining infix operators, there is often a functor method named `andThen` which serves the same purpose. For example:
h(x).andThen(g).andThen(f)
0 - https://leanpub.com/combinators/read#leanpub-auto-the-thrush map : (a -> b) -> a list -> b list
instead of map : a list -> (a -> b) -> b list
The main argument (data to be operated on) is positioned last, after the others which are more like parameters that tune the function.
It's to allow chaining these things up left to right like Unix pipes: map f l |> filter g |> ...The technique of currying method parameters such that the last one is the input to the operation also makes chaining Kleislis[0] quite nice, albeit using a bind operator (such as `>>=`) instead of the Thrush combinator operator (`|>`).
0 - https://bartoszmilewski.com/2014/12/23/kleisli-categories/
https://github.com/tc39/proposal-pipeline-operator
It would make it possible to have far more code written in the way you’d want to write it
I've seen some SQL-derived things that let you switch it. They should all let you switch it.
https://en.wikipedia.org/wiki/Non-English-based_programming_...
I prefer list/set/dict comprehensions any day. It's more general, doesn't require to know a myriad of different methods (which could not exists for all collections, PHP and JS are especially bad with this) and easily extendable to nested loops.
Yes it could be `[for line in text.splitlines() if line: for word in line.split(): word.upper()]`. But it is what it is. BTW I bet rust variant would be quite elaborate.
And these are flexible tools that you can take with you across projects.
It's the opposite, your knowledge of the standard set of folding algorithms (maps, filters, folds, traversals) is transferable almost verbatim across a wide range of languages: https://hoogletranslate.com/?q=map&type=by-algo
It's not. The author gives objective reasons why Python's syntax is inferior – namely, that it makes IDE support in the form of discoverability and auto-completion more difficult.
Not possible. There are more keystrokes that result in invalid programs (you are still writing the code!!) than keystrokes that result in a valid program.
More seriously, I do think that one consideration is that code is read more often that written, so fluidity in reading and comprehension seem more important to me than “a program should be valid after each keystroke.
This bit is an aside in the article but I agree so much! List comprehensions in python are great for the simple and awful for the complex. I love map/reduce/filter because they can scale up in complexity without becoming an unreadable mess!
This line, though, seems like it's using the wrong tools for the job:
len(list(filter(lambda line: all([abs(x) >= 1 and abs(x) <= 3 for x in line]) and (all([x > 0 for x in line]) or all([x < 0 for x in line])), diffs)))
To me it's crying out for the lines to be NumPy arrays: sum(1 for line in diffs
if ((np.abs(line) >= 1) & (np.abs(line) <= 3)).all()
and ((line > 0).all() or (line < 0).all()))
There's no need to construct the list in memory if you're just counting, and dealing with whole lines at once is much nicer than going element by element. On top of that, this version is much more left-to-right. diffs.countIf { line ->
line.all { abs(it) in 1..3 } and (
line.all { it > 0} or
line.all { it < 0}
)
}It's true that Python didn't cater to static analysis at all.
max(map(sum, input_list.split(None)))
To decipher this the eye has to jump to the middle of the line, move rightwards, then to the left to see the "map" then move right again to see what we are mapping and then all the way to the beginning to find the "max".The author would probably suggest rust's syntax* of
values.iter().split(None).map(Iterator::sum).max().unwrap_or(0)
but I was learning q at the time so came up with the much clearer /s, right to left max((0^+)\)l
*: Though neither python nor rust have such a nice `.split(None)` built in.Sorry, I'm not sure I understand what `.split(None)` would do? My initial instinct is that would would return each character. i.e. `.chars()` in Rust or `list(s)` in Python.
Reading the docs [0] it seems `.split(None)` returns an array of the indivual characters without whitespace - so something like [c in list(s) if not whitespace(c)]
[0] https://docs.python.org/3.3/library/stdtypes.html?highlight=...
Say what you will about clarity, but my mind sort of glossed over the intention in the python and rust code, focusing instead on the syntax, while the q code made me consider what was actually happening.
This may eventually be upstreamed: https://github.com/rust-itertools/itertools/issues/1026
input_groups = input_list.split(None)
group_sums = map(sum, input_groups)
max_sum = max(group_sums)
This also gives you a slightly higher-level view on how the algorithm proceeds just by reading the variable names on the LHS.It is sometimes still a problem when you define a function without reference to it (because the types are unnkown ofcourse). You will have to add types to it or call the function before implementing it.
There was also a great blogpost about it: https://www.javierchavarri.com/data-first-and-data-last-a-co...
[for b in c
let d = f(b)
for e in g if h else m
where p(d, e): b, d, e]
as alternative syntax to Python's ((b, d, e)
for b in c
for d in [f(b)]
for e in (g if h else m)
if p(d, e))
This solves five problems in Python listcomp syntax:1. The one this article is about, which is also a problem in SQL, as juancn points out.
2. The discontinuous scope problem: in Python
[Ξ for x in Γ for y in Λ]
x is in scope in Ξ and Λ but obviously not Γ. This is confusing and inconsistent.3. The ambiguity between conditional-expression ifs and listcomp-filtering trailing ifs, which Python solves by outlawing the former (unless you add extra parens). This is confusing when you get a syntax error on the else, but there is no non-confusing solution except using non-conflicting syntax.
4. let. In Python you can write `for d in [f(b)]` but this is inefficient and borders on obfuscated code.
5. Tuple parenthesization. If the elements generated by your iteration are tuples, as they very often are, Python needs parentheses: [(i, c) for i, c in enumerate(s) if c in s]. That's because `[i, c` looks like the beginning of a list whose first two items are i and c. Again, you could resolve these conflicting partial parses in different ways, but all of them are confusing.
I'd much rather optimize for understanding code. Give me the freedom to order such that the most important ideas are up front, whatever the important details are. I'd much rather spend 3x the time writing code of it means I spend half the time understanding it every time I return to it in the future.
Your editor can’t help you out as you write it.
You shouldn't need handholding when you're writing code. It seems like the whole premise of the author's argument is that you shouldn't learn anything about the language and programming should be reduced to choosing from an autocomplete menu and never thinking more than that. I've seen developers who (try to) work like this, and the quality of their work left much to be desired, to put it lightly.
From there you can eventually find fread, but you have no confidence that it was the best choice.
In C, you have to know ahead of time that fclose is a function that you’ll need to call once you’re done with the file.
It's called knowledge. With that sort of attitude, you're practically begging for AI to replace you.
No wonder people claim typing speed doesn't matter - they can barely think ahead one token, nevermind a statement or function, much less the whole design! Ideally your typing speed should become the bottleneck and you should be able to code "blind", without looking at the screen but merely outputting the code in your mind into the machine as fast as humanly possible. Instead we have barely-"developers" constantly chasing that next tiny dopamine hit of picking from an autocomplete menu. WTF!?
When this descent into mediocrity gets applauded, it's no surprise that so much "modern" software is the way it is.
Jokes apart, I think you're being too drastic. A good auto-complete is a nice feature, just like auto-indent, tab-complete, etc. Can it be abused? Sure. So what? Should we stop making it better for fear of abuse?
The reference to AI is far-fetched, too. We're talking about tools to help you with the syntax, not the semantic. I may forget if the function is called read, fread, or file_read, but I know what its effect is.
And finally, consider that if something is easier to parse for an editor, it most probably is for a human too. Not a rule, not working in 100% of cases, but usually exposing the user to the local context before the concept itself helps understanding.
If writing code is an automated process for you, you are also begging for AI to be replace you. Just a more advanced one than the code-monkey in the OP.
The pipe "|" as an analog to the cascade ";", but sending to the result, like a normal pipe would. This avoids having to go back and add parentheses when you have a longer expression involving keyword sends.
For example navigating a nested set of dictionaries
self classDefs at:className.
(self classDefs at:className) at:which.
((self classDefs at:className) at:which) at:methodName.
vs. self classDefs at:className.
self classDefs at:className | at:which.
self classDefs at:className | at:which | at:methodName.
[1] https://objecttive.stNo, you are bad because you use an editor with autocomplete.
And it's not even debatable, it's like playing bowling with rails, or riding a bycicle with training wheels. Sure you can argue that you are more efficient at bowling and riding a bike with those, but you are going to be arguing alone, and it's much better to realize that python is one of the best languages at the moment and therefore one of the best languages ever, instead of being a nobody and complaining about a lanugage because you are too encumbered by your own ego to realize that you are not as good a programmer as you thought.
Nothing wrong with being an amateur programmer or vibecoding or whatever, but if you come for the king you best not miss
Similarly, Python list/dict/set comprehensions are a form of for-loop syntax sugar to easily create particular structure. One can use functools.maps to get exactly the same behavior of Rust example.
If this was an all important text input microoptimization, we'd all be doing everything with pure functions like Lisp: yet somehow, functional languages are not the most popular even if they provide the highest syntax consistency.
It doesn't look anything like Lisp, though.
import { MyClass } from './lib.ts'
First you need to type what to import and then from where leaving. There is no linear way of discovering import options from the source of imports without extra jumping in the code.
Alternatively linear completion, or (TIL https://en.wikipedia.org/wiki/Progressive_disclosure) would be possible for imports of shape like:
from './lib.ts' import { MyClass }
If course authors of the import syntax had good reasons (which I don't know) to build stuff that way they've built it.
Although the subtitle was “programs should be valid as they are typed”, it’s weakened to “somewhat valid” at this point. And yes, it is valid enough that tooling can help, a lot of the time (but not all) at full capability. But there’s also interesting discussion to be had about environments where programs are valid as they are typed. Syntactically, especially, which requires (necessary but not sufficient) either eschewing delimition, or only inserting opening and closing delimiters together.
len(list(filter(lambda line: all([abs(x) >= 1 and abs(x) <= 3 for x in line]) and (all([x > 0 for x in line]) or all([x < 0 for x in line])), diffs)))
Well, point taken about the ordering, but there are many more legible ways to have written that code. Not everything has to be a one liner.even:
def f(diffs):
cond = lambda line: all([1 <= abs(x) <= 3 for x in line]) and (
all([x > 0 for x in line]) or all([x < 0 for x in line])
)
items = filter(cond, diffs)
return len(list(items))> Instead, you must know that functions releated to FILE tend to start with f, and when you type f the best your editor can do is show you all functions ever written that start with an f
Why do you think that this is a problem of C? no one is stopping your tools from searching `fclose` by first parameter type when you wrote `file.`. Moreover, I know that CLion already do this.
To make a long story short, we added features for "incomplete" programs in the language and tools, so that your program was always valid and could not be invalid. It was a reasonable concept, and I think could have been a game changer if AI didn't first change the game.
from line in text.Split('\n') select line.Split(null)For example, using "rm" on the command line, or an SQL "delete". I would very much like those short programs to be invalid, until someone provides more detail about what should be destroyed in a way that is accident-resistant.
If I had my 'druthers, the left-to-right prefix of "delete from table" would be invalid, and it would require "where true" as a safety mechanism.
Python offers an "extended" form of list comprehensions that lets you combine iteration over nested data structures.
The irony is that the extensions have a left-to-right order again, but because you have to awkwardly combine them with the rest of the clause that is still right-to-left, those comprehensions become completely unreadable unless you know exactly how they work.
E.g., consider a list of objects that themselves contain lists:
toolboxes = [
Box(tools=["hammer"]),
Box(tools=["wrench", "screwdriver"])
]
To get a list of lists of tools, you can use the normal comprehension: toolsets = [b.tools for b in toolboxes]
But to get a single flattened list, you'd have to do: tools = [t for b in toolboxes for t in b.tools]
Where the "t for b" looks utterly mystifying until you realize the "for" clauses are parsed left-to-right as [t (for b in toolboxes) (for t in b.tools)]
while the "t" at the beginning is parsed right-to-left and is evaluated last.for a in b:
for c in a:
use(a,b,c)
This would be[use(a,b,c) for a in b for c in a]
Everything stays the same except the "use" part that goes in the front (the rule also includes the filters - if).
len(list(filter(lambda line: all([abs(x) >= 1 and abs(x) <= 3 for x in line]) and (all([x > 0 for x in line]) or all([x < 0 for x in line])), diffs)))
This really isn’t fair on Python. Python isn’t very much not designed for this style of functional programming. Plus you haven’t broken lines where you could. Rewrite it as a list comprehension and add line breaks, and turn the inner list comprehensions into generator expressions (`all([…])` → `all(…)`), and change `abs(x) >= 1 and abs(x) <= 3` to `1 <= abs(x) <= 3` (thanks, Jtsummers), and it’s much better, though it still has the jumping around noted, and I do prefer the functional programming approach. I’m just saying the presentation isn’t fair on Python. len([line for line in diffs
if all(1 <= abs(x) <= 3 for x in line)
and (all(x > 0 for x in line) or all(x < 0 for x in line))])
(Aside: change the first line to `sum(1 for line in diffs` and drop the final `]`, and it will probably perform better.)I also want to note, in the JS… Math.abs(x) instead of x.abs() (as seen in Rust).
And, because nerd sniping, two Rust implementations, one a direct port of the JS:
diffs.iter().filter(|line| {
line.iter().all(|x| x.abs() >= 1 && x.abs() <= 3) &&
(line.iter().all(|x| x > 0) || line.iter().all(|x| x < 0))
}).count()
(`x.abs() >= 1 && x.abs() <= 3` would be better as `(1..=3).contains(x.abs())` or `matches!(x.abs(), 1..=3)`.)And one optimised to only do a single pass:
diffs.iter().filter(|line| {
let mut iter = line.iter();
let range = match iter.next() {
Some(-3..=-1) => -3..=-1,
Some(1..=3) => 1..=3,
Some(_) => return false,
None => return true,
};
iter.all(|x| range.contains(x))
}).count() abs(x) >= 1 and abs(x) <= 3
Is also unidiomatic in Python. 1 <= abs(x) <= 3
Means the same thing and tightens it up a bit, and reads better since it's indicating that you're testing if something is in a range more clearly.EDIT: To add:
The filter, list construction, and len aren't needed either. It's just:
sum(map(predicate, diffs)) # this counts the number of elements in diffs which satisfy predicate, map is lazy so no big memory overhead
Or alternatively: sum(predicate(diff) for diff in diffs)
The predicate is complex enough and used twice, so it warrants extraction to its own named function (or lambda assigned to a variable), but even if it were still embedded this form would be slightly clearer (along with adding the line breaks and removing the extra list generations): sum(map(lambda line: all(1 <= abs(x) <= 3 for x in line)
and (all(x > 0 for x in line) or all(x < 0 for x in line)),
diffs))Yes, I agree with the author, list comprehensions are readible, and I'd add, practical.
> it gets worse as the complexity of the logic increases
len(list(filter(lambda line: all([abs(x) >= 1 and abs(x) <= 3 for x in line]) and (all([x > 0 for x in line]) or all([x < 0 for x in line])), diffs)))
Ok, well this is something that someone would be unlikely to write... unless they wanted to make a contrived example to prove a point.It would be written more like:
result = sum(my_contrived_condition(x) for line in diffs)
See also the Google Python style guide, which says not to do the kind of thing in the contrived example above: https://google.github.io/styleguide/pyguide.html(Surely in any language it's possible to write very bad confusing code, using some feature of the language...)
And note:
x = [line.split() for line in text.splitlines()]
^- list comprehension is just a convenient shorthand for a `for loop`, i.e.: x = []
for line in text.splitlines():
x.append(line.split())
Just moving the `line.split()` to the front and removing the empty list creation and append.But you still have to define line without autocomplete.
If/else if fails the relocation principal across many languages, since the first must be if, and middles else if. Switch tends to pass.
Languages that don't allow trailing commas also fail
Sometimes it is called a fluent-interface in other languages.
Where've you heard it called that? I've normally heard tacit programming
Could you elaborate? AFAIK tacit programming tend to be scrambling around composition, paren, and args which makes left-to-right reading significantly harder for function with arity greater than 2.
I find Java's method reference or Rust's namespace resolution + function as an argument much better than Haskell tacit-style for left-to-right reading.
That would suggest the file object needs to be refactored and split.
> What's next, a fuzzy finding search box for all possible functions? Contextually relevant ones based on the code you've already written?
IDEs already provide both those options.
In Clojure I love the threading macro which accomplishes the same: (-> (h) (g) (f))
uint16_t (MyStruct* s) some_func() { .. }
uint16_t MyStruct_some_func(MyStruct* s) { .. }
(I guess I should just use C++... but C++ is overwhelmed in many ways)
"Count length of string s" -> LLM -> correct syntax for string-count for any programming language. This is the perfect context-length for an LLM. But note that you don't "complete the line", you tell the LLM what you want to have done in full (very isolated) context, instead of having it guessing.
If you already know how to compute `len` on some arbitrary syntax soup then the difference is just a minor annoyance where you have to jump back in your editor, add a function call and some punctuation, and jump back to where you were to add some closing punctuation. It's so fast you'd never bother with an LLM, so despite real and meaningful differences existing the LLM discussion point isn't relevant.
If you don't know how to compute `len` on some arbitrary syntax soup, I don't see how crafting an ideal prompt in a "full (very isolated) context" is ever faster than tab-completing things which look like "count" or "len."
> Here, your program is constructed left to right. The first time you type line is the declaration of the variable. As soon as you type line., your editor is able to suggest available methods.
Yeah, having LSP autocomplete here does feel nice.
But it also makes the code harder to scan than Python. Quick readability at a glance seems like the bigger win than just better autocomplete.
It depends a lot on what you’re accustomed to. You get used to whichever style. Just like different languages use different sentence order: subject, object and verb appear in all possible orders in different languages, and their speakers get along just fine. There are some situations where one is clearly superior to the other, and vice versa.
`text.lines().map(|line| line.split_whitespace())` can be read loosely as “take text; take its lines; map each line, split it on whitespace”. Straightforward and matching execution flow.
`[line.split() for line in text.splitlines()]` doesn’t read so elegantly left-to-right, but so long as it’s small enough you spot the `for` token, realise you’re dealing with a list comprehension, and read it loosely from left to right as “we have a list made up of splitting each line, where lines come from text, split”. Execution-wise, you execute `text.splitlines()`, then `for line in`, then `line.split()`. It’s a bunch of left-to-rights embedded in a right-to-left. This has long been noted as a hazard of list comprehensions, especially the confusion you end up with with nested ones. Now you could quibble over my division of `for line in text.splitlines()` into two runs; but I think it’s fair. Consider how in Rust you get both `for line in text.split_lines() { … }` and `text.split_lines().for_each(|line| { … })`. Sometimes the for block reads better, sometimes .for_each() or .map() or whatever does. (But map(lambda …: …, …) never really does.)
Python was my preferred language from 2009–2013 and I still use it not infrequently, but Rust has been my preferred language ever since. I can say: I find the Rust version significantly easier to read, in this particular case. I think the fact there are two levels of split contributes to this.
Here's an example I found https://github.com/gleam-lang/example-todomvc/blob/main/src/...
Must not be a vim user.
sometimes you want to think inside out
its the difference between imperative thinking and declarative
both are good, use each when applicable
“Shut up old man” yes yes okay.
Stair steps could be 450 mm high and work, but building codes make them 200 mm for a reason. And you are not "better" by saying that "I am fit enough to climb 450 mm steps, and you are all lazy for wanting stairs built to ergonomic standards".
This way, as soon as you type "1", it is truly one, you add a "2" and you know that's adding twenty...
IIRC Arabic gets this right!
/s
The idea is that we write `foo.bar`, where `foo` is in scope, exactly because `foo` is in scope. We don't write `bar from foo` or whatever, because it would be hard to reverse search the things that have `bar` in them.
But which is more likely: will `foo` be Liskov-substitutable for `bar`, or will other things that contain a `bar` have a `bar` of a Liskov-substitutable type?
Which is to say, the author is depending on a particularly idiosyncratic meaning of "valid".
That said, the problem the author describes at the beginning can be easily worked around, if you like this kind of workflow:
words_on_lines = [st # 'str' is a plausible autocompletion
words_on_lines = [str.sp # must be either 'split' or 'splitlines', unless 'str' is shadowed
words_on_lines = [str.split(line) for # 'line in' can be suggested
words_on_lines = [str.split(line) for line in text.splitlines()] # IDE can automatically check type and refactor the idiom
And it also isn't at all true that IDEs work left to right. They're constantly auto-typing the balancing close parentheses/braces/brackets for me and it's never clear to me what the intended flow is for moving past what was automatically typed, or whether manually typing that bracket explicitly (since I'm in my own "automatic typing") will double it up or not. There's nothing preventing the IDE from expecting you to type the clauses of the comprehension in a different order and I wouldn't at all be surprised to hear that someone has already implemented this.I believe there are some strongly typed stack based languages where you really always do have something very close to a syntactically correct program as you type. But now that LLMs exist to paper over our awful intuitions, we're stuck with bad syntax like python forever.
I've had some minor success with claude, but enabling the AI plugin in intellij has literally made my experience worse, even without using any AI interactions.