8 ms·
Their Hello, Dada! example: print("...").await I'm coming from Python, and I can't help but ask: If my goal as a programmer is to simply print to the console,
by yamrzou 3y ago
Their Hello, Dada! example:
print("...").await
I'm coming from Python, and I can't help but ask: If my goal as a programmer is to simply print to the console, why should I care about the await? This already starts with a non zero complexity and some cognitive load, like the `public static void main` from Java.
- baq 3y agoI'd say you want something like 'debug_msg()' for this. 'print()' should be async because it does IO. In the real world most likely you'd see the output once you yield.
- DinaCoder99 3y agoHuh, typically print is the debug message function vs explicitly writing to stdout
- dartos 3y agoI don’t think so. Normally print isn’t a debug message function, people just use it like that. (it normally works on non debug builds)
- couchand 3y agoPrinting directly to the console, even in a console app, is for debug purposes only. If your console app is writing output to any device, it must, for instance, handle errors gracefully. That means, at least in Rust, write! rather than print!.
- lkirkwood 3y agoWhat makes you say that? I almost always use println! over write!. From the docs: "Use println! only for the primary output of your program. Use eprintln! instead to print error and progress messages."
- couchand 3y agoWhat makes me say that a well-built program properly handles errors?
- myrmidon 3y agoPanic is a perfectly proper way for a well-built program to stop execution. There is no point in juggling around Result types if a failure means that you can not recover/continue execution. That is in fact exactly what panic! is intended for [1]. [1]: https://doc.rust-lang.org/book/ch09-03-to-panic-or-not-to-panic.html#to-panic-or-not-to-panic https://doc.rust-lang.org/book/ch09-03-to-panic-or-not-to-pa...
- couchand 3y agoPanic is perfectly fine in certain cases, but it's absolutely not a general error-handling mechanism for Good Programs (TM). (Some contexts excluded, horses for courses and all that) You can and should recover from bog standard IO failures in production code, and in any case you'd better not be panicking in library code without making it really clear that it's justified in the docs. If your app crashes in flames on predictable issues it's not a good sign that it handles the unpredictable ones very well.
- int_19h 3y agoA failure to write to stdout should not be unexpected given that stdout is routinely redirected to files or to pipes, both of which can be suddenly closed or otherwise fail from the other direction. Yes, you can't recover in this case, but you should at least properly report the error to stderr before exiting, in a way that lets the end user (rather than app dev) properly diagnose the error. Now if you fail to write to stderr, yeah, that's a good reason for a console app to panic. The onus is on the user to provide something that is "good enough" in that case. IMO the real problem is that print() etc defaults to stdout historically, but is used mostly for diagnostic information rather than actual output in practice, so it should really go to stderr instead. This would also take care of various issues with buffering etc.
- nine_k 3y agoProduction builds should retain all debug prints, only hide them behind a flag. This helps you preserve sanity when troubleshooting something.
- jeroenhd 3y agoPrint is a debug function in some languages, but it's usually just stdout. You can add all kinds of logging libraries that wrap around print() with prefixes and control sequences to add colours, but I generally don't bother with those myself. In those circumstances, I would use something like logger.debug() instead of plain print(), though. I personally find myself using print debugging as a last resort when the debugger doesn't suffice.
- DinaCoder99 3y agoThere seems to be a conflation in two subtly different types of debugging here—one is simply regurgitating state while tracking down a specific bug on a local machine and should not be committed and the other is intended to be checked into possibly production code with the assumption being sent to a log aggregator. I think both are valid techniques, but one clearly benefits from the use of the `log` crate more than the other.
- wongarsu 3y agoIn rust, the common debug message function would be log::info! or log::debug!, with two lines of setup to make logs print to stderr. Or for something more ad-hock, there's dbg! (which adds context what you are printing, and doesn't care about your logging config). Not that people don't use print for the purpose, but it's basically never the best choice. I assume Dada is conceived with the same mindset.
- DinaCoder99 3y agoI disagree—println! is far more common for every day printf debugging than the log crate is. Do i have any evidence of this? No, but it takes less to type and is harder to mess up with log levels while working just as effectively.
- est 3y agogevent handles async fine without the explicit async/await .NET core will introduce something similar
- nine_k 3y agoThe cost of it is that when you need to make something explicitly async, you have to wrap it into a greenlet in a much more involved way. JavaScript lets you do it much more ergonomically.
- gpderetta 3y agoDoesn't seem very involved; for example (cilk inspired syntax): let f = spawn { print() } // fork ... wait f // join. f is a linear type You only pay the complexity cost if you need it.
- yamrzou 3y ago“The most effective debugging tool is still careful thought, coupled with judiciously placed print statements.” — Brian Kernighan, co-creator of Unix
- rhaps0dy 3y agoIn my experience the best debugging tool in Python is judiciously placed asserts, breakpoints and PDB. Not print.
- MatthiasPortzel 3y ago> 'print()' should be async because it does IO What if I want to do synchronous IO?
- n2d4 3y agoJust from glancing over the docs, that doesn't seem supported: > Dada, like JavaScript, is based exclusively on async-await. This means that operations that perform I/O, like print, don't execute immediately. Instead, they return a thunk, which is basically "code waiting to run" (but not running yet). The thunk doesn't execute until you await it by using the .await operation. Good riddance, IMO — never been a fan of blocking IO. Dada does have threads though, so I wonder how that works out. (Forcing async/await makes a lot more sense in JavaScript because it's single-threaded.)
- dahart 3y agoThen use .await?
- bastawhiz 3y agoNode lets you cheat with "synchronous" APIs: it stops the whole event loop. If you start making exceptions for "little" bits of IO like printing to the console or reading files, the async parts of your code are "async when someone hasn't done something synchronous". Doing a `readFileSync` even on a small file in a hot code path means you're meaningfully tanking the performance of your code. What you're asking for is "stop running my code until I've finished printing to the console". That's what the `.await` does. Synchronous IO on `print()` would mean _everything in the whole application that logs_ suddenly blocks the whole application from doing _anything_ while the console is being written to, not just the currently running code. If you want synchronous stop-the-world IO (like Python, where async/await is bolted on), you shouldn't choose a language based around async/await concurrency.
- Alifatisk 3y agoThe reasoning with await is valid, it's an I/O call, but the await should maybe be hidden inside the print then?
- yamrzou 3y agoIt's a leaky abstraction (https://www.joelonsoftware.com/2002/11/11/the-law-of-leaky-abstractions/ https://www.joelonsoftware.com/2002/11/11/the-law-of-leaky-a...), but maybe there is no helping it and for some reason it is a necessary tradeoff for performance?
- mtsr 3y agoMaybe it’s actually a non-leaky abstraction because it makes the async-nature explicit. The alternative is hiding it, but it’s still going to affect your code, making that effectively a leaky abstraction.
- luke-stanley 3y agoMaybe there could be something like a aprint() wrapper, if the authors wanted to make the async nature explicit? Or something else, probably not this for one of the most common things a programmer must do.
- steveklabnik 3y agoWhy is aprint “non leaky” but print.await “leaky”?
- luke-stanley 3y agoHey Steve, I wouldn't say that "print.await" is a leak abstraction. I think "print.await" is explicit and that's good, it communicates it's abstraction fairly clearly, presumably following a pattern used commonly in this, imagined language. I suppose that a wrapper like "aprint" (a convenience function labelled async, like with an "a" prefix), would be a bit better than having people continually try using print, not await it, and not getting the expected output in stdout (or whatever stream it's sent to), while they are in the middle of trying to test something or otherwise get something working because I'm of the opinion that common things should be easy. Maybe "people would generally expect a print function to just work and not return a promise or something" is an abstraction? "aprint" might actually be the wrong name I'm not sure I've really thought about it right.
- secondcoming 3y agoSurely `public static void main` has less congnitive load than if __name__ == "__main__": main()
- abdusco 3y agoYou don't have to do this, though. You can have an entrypoint.py that simply calls `main()` without that if. You don't even need to have modules if you want to, so you can write your functions and call them right after.
- wredue 3y agoSo in python, you need to understand not 1, but at least 3 different versions of “an entry point”, and to you, this is “less cognitive load”? I had the same issue with Swift. There’s 30 ways to write the exact same line of code, all created by various levels of syntax sugar. Very annoying to read, and even more annoying because engaging different levels of sugar can engage different rulesets.
- elevatedastalt 3y agoYou don't "need" any of the entry points when you are beginning Python. print("Hello World") is a perfectly valid and runnable Python code. And when you are working on a small part of a large code base, you usually don't care about __main__ either. So yes, it's complexity but it's complexity that you don't need to encounter right away. Python is intuitive off the bat. public static void main(String[] args) is not.
- wtetzner 3y ago> If my goal as a programmer is to simply print to the console, why should I add care about the await? Because that isn't ever anyone's actual goal? Optimizing a language design for "Hello World" doesn't seem like a particularly useful decision.
- pas 3y agosure, but it seems useful to be able to opt-in/opt-out of async easily. ie. if I want a short/simple program it would be cool to put a stanza on the top of the file to auto-await all futures.
- wtetzner 3y agoI'm not a big fan of async/await in general, I just didn't think this specific complaint was particularly compelling.
- MatthiasPortzel 3y agoIt’s not an end goal, maybe, but if I’m writing a complex program and I want to print to the console for logging or debugging or status, I shouldn’t have to think about the design of that print-call. I would like to be able to focus on the main complexity of the program, rather than worry about boiler-plate complexity every time I want to print.
- avianlyric 3y agoYou seem to be making the assumption that in other languages calling print is a blocking function that guarantees the printing of a string. Which it isn’t. In python print adds your string to the stdout buffer, which eventually gets written out to the console. But it not guaranteed, if you want that guarantee you need to call flush on the stdout IO handler. Dada has taken the approach of making blocking IO operations explicit, rather than purely implicit. The result is that if you want to perform an IO operation, you need to explicitly say when you want to block, rather than allowing an elaborate stack of runtime buffers dictate what happens immediately, what happens later, and what going to block further code execution. In short this completely exists in other languages like Python, you’ve simply not be aware of it, or aware of the nuanced was in which it fails. But if your someone whose wasted hours wrestling with Python IO system, then you’ll appreciate the explicit nature of Dada’s IO system.
- sanderjd 3y agoAlso it immediately makes me wonder what `await` is... Is it a reference to a field of whatever the `print()` method is returning? Is it calling a method? If it's a method call without parentheses, how do I get a reference to a method without calling it? (These kinds of questions are just unavoidable though; everyone will have these little pet things that they subjectively prefer or dislike.)
- n2d4 3y agoThey borrowed it from Rust: `.await` is special syntax, roughly equivalent to `await print(...)` in other languages. https://rust-lang.github.io/async-book/01_getting_started/04_async_await_primer.html https://rust-lang.github.io/async-book/01_getting_started/04...
- sanderjd 3y agoYeah but for a new language I haven't seen before, I immediately wonder!
- spankalee 3y agoI wonder why not do `await print()` though? It reads more naturally as "wait for this" and is more clearly not a property access.
- tubthumper8 3y agoSome more details on `await` as a postfix operator rather than prefix https://blog.ceejbot.com/posts/postfix-await/ https://blog.ceejbot.com/posts/postfix-await/
- int_19h 3y agoPostfix operators are much more readable when chaining or composing. I used to write a lot of async C#, and it quickly gets tiresome to constantly have to write stuff like (await (await (...) ...), and reading such code requires jumping back and forth to unravel. Amusingly, this is history repeating itself. These days we consider the X.Y syntax for object members quite natural, but historically if you look at the earliest examples, it was actually prefix. The first ALGOL-60 dialects to add records used functional notation, so you had to do Y(X). In ALGOL-68, they made it an operator instead (which allowed for proper namespacing), but it was still prefix: Y OF X; very straightforward and natural. But then people pretty quickly found out that (Y OF (X OF (...)) does not make for readable code in practice. What I think they did wrong was require a period there - that is the part that makes it look like a property access. It would have been better as `print() await`, making it clear that it is just a postfix operator.
- jlouis 3y agoIt is a fundamental question in your language design. Some languages make side-effects explicit one way or the other. Other languages handles side-effects in a more implicit fashion. There's a tradeoff to be made here.
- pmontra 3y agoI'm coming from several languages (C, Perl, Java, JavaScript, Ruby, Python) and I strongly dislike the async/await thing. At least let people change the default. For example await { // all the code here // runs synchronously async { // except this part where // async methods will return early print("but not me!").await() } } However the remark I make to people advocating for static typed Ruby holds for this language too: there are already languages like that (in this case await by default,) we can use them and let Dada do its own thing.
- wredue 3y agoJust going to be honest here: “Zero complexity print to the screen” Is, quite possibly, the dumbest argument people make in favour of one language over another. For experienced people, a cursory glance at the definitions should be enough. For new programmers, ignoring that part “for now” is perfectly fine. So to is “most programming languages, even low level ones, have a runtime that you need to provide an entry point to your program. In Java, that is public static void main. We will go over the individual aspect of this later. ”. This really is not that difficult, even for beginners. Personally, I find more “cognitive load” in there not being an explicit entry point. I find learning things difficult when you’re just telling me extremely high level *isms.
- lamontcg 3y agoThis does surface the fact that its another await/async red/green function language though. If they're already making it gradually typed and not low-level, I don't understand why they don't throw away the C ABI-ness of it and make it more like Ruby with fibers/coroutines that don't need async/await. I'd like parametric polymorphism and dynamic dispatch and more reflection as well if we're going to be making a non-low-level rust that doesn't have to be as fast as humanly possible. (And honestly I'd probably like to keep it statically typed with those escape hatches given first-class citizen status instead of the bolted on hacks they often wind up being) [Ed: also rather than go back to object oriented, I'd rather see really easy composition, delegation and dependency injection without boilerplate code and with strongly typed interfaces]
- raverbashing 3y agoYeah I'm not so sold, but mainly, I don't understand the logic here If I'm declaring an async function, why do I need to await inside it? like, if the return of an async function is a promise (called a thunk), why can't I do async async_foo() { return other_async_foo(); } and it will just pass the promise? Then you await on the final async promise. Makes sense?
- deleted 3y ago[deleted]
- spankalee 3y agoThe other problem I see here is that starting and awaiting the task are too coupled. In JavaScript calling the function would start the task, and awaiting the result would wait for it. This lets you do several things concurrently. How would you do this in Dada: const doThings = async () => { const [one, two, three] = await Promise.all([ doThingOne(), doThingTwo(), doThingThree(), ]); }; And if you wanted to return a thunk to delay starting the work, you would just do that yourself.
- janderland 3y agoI assumed dada is using promises under the hood, just as JS is. If this is the case it could provide a static method for waiting on multiple promises, just as JS does.
- spankalee 3y agoThis seems to say otherwise (specifically the "but not running" part): Dada, like JavaScript, is based exclusively on async-await. This means that operations that perform I/O, like print, don't execute immediately. Instead, they return a thunk, which is basically "code waiting to run" (but not running yet). The thunk doesn't execute until you await it by using the .await operation. From https://dada-lang.org/docs/dyn_tutorial https://dada-lang.org/docs/dyn_tutorial
- pama 3y agoIn Python if you carelessly print within a multiprocess part of an application you may end up getting a nonreproducible mess on stdout with multiple streams merged at random points. So the cognitive load in this example is that this new language is meant for multithreaded coding and can make multithreading easy compared to other languages.
- bmitc 3y agoThat's a great example of the "simplicity" of Python being anything but.
- int_19h 3y agoThis is not at all unique to Python, and a footgun present in any language that allows multiple threads. But if you're spawning multiple threads - in Python or any other language - you're already past any semblance of "simplicity", threads or no threads.
- bmitc 3y agoThat's a good point regarding print, however several other languages make multithreading easy. F#'s async is easy and just works as does Erlang and Elixir of course. Python's asyncio is barely even an async library, much less one that is simple.
- int_19h 3y agoF# async will not prevent you from causing data races. Erlang does by basically not having shared mutable data.
- bmitc 3y ago> F# async will not prevent you from causing data races. That's true regarding side-effects and mutable data, but I wasn't saying that. It's still a much more sane and actually concurrent and asynchronous library than Python's asyncio, which is not actually concurrent, single threaded, and very difficult to work with. For example, there's nonway to laumch an asynchronous process and then later await it from synchronous code, whereas it's easy in F#.