| |
| ▲ | eru 11 hours ago | parent | next [-] | | > This is the beauty of ocaml (and strongly typed functional languages more broadly) I don't think that's anything specific to strongly typed functional languages. In eg Rust even the standard library relies on third party crates. Though it is still somewhat amusing to me that loops in Haskell are delivered via a third party library, if you ever actually want them. See https://hackage.haskell.org/package/monad-loops-0.4.3/docs/C... I do agree that it's good language design, if you can deliver what would be core functionality via a library. Whether you want to integrate that library into the standard library or not is an independent question of culture and convenience. (Eg Python does quite well with its batteries-included approach, but if they had better dependency management, using third party libraries wouldn't be so bad. It works well in eg Rust and Haskell.) | |
| ▲ | koito17 11 hours ago | parent | prev [-] | | As the other commenter pointed out, this isn't restricted to strongly-typed functional languages. Clojure has core.async, which implements "goroutines" without any special support from the language. In fact, the `go` macro[1] is a compiler in disguise: transforming code into SSA form then constructing a state machine to deal with the "yield" points of async code. [2] core.async runs on both Clojure and ClojureScript (i.e. both JVM and JavaScript). So in some sense, ClojureScript had something like Golang's concurrency well before ES6 was published. [1] https://github.com/clojure/core.async/blob/master/src/main/c... [2] https://github.com/clojure/core.async/blob/master/src/main/c... | | |
| ▲ | Blackthorn 10 hours ago | parent [-] | | > something like Golang's concurrency That's wildly overselling it. Closure core async was completely incapable of doing the one extremely important innovation that made goroutines powerful: blocking. | | |
| ▲ | koito17 7 hours ago | parent | next [-] | | Assuming "blocking" refers to parking goroutines, then blocking is possible. (let [c (chan)]
;; creates channel that is parked forever
(go
(<! c)))
The Go translation is as follows. c := make(chan interface{})
// creates goroutine that is parked forever
go func() {
<-c
}()
| | |
| ▲ | Blackthorn 15 minutes ago | parent [-] | | No, blocking refers to calling a function that blocks. Core.async can't handle that because macros are actually not capable of handling that, you need support from the runtime. Call a function that blocks in go, the routine will park. Do that in Clojure and the whole thing stalls. |
| |
| ▲ | conjurernix 9 hours ago | parent | prev [-] | | Can you elaborate? As far as I'm aware if you pull from an empty nchannel it wikl be blocking ubtik it gets a value. |
|
|
|
| |
| ▲ | shortrounddev2 6 days ago | parent [-] | | I dislike that there's a kind of sub-syntax specifically for async. I like how C# converts `await` into the necessary calls. In this code I think it would look better to simply have: let async printAfter (s: float<second>) =
let time = TimeSpan.FromSeconds (float s)
await Task.Delay time
printfn $"Hello from F# after {s} seconds"
and then printAfter is called with `await` as well. I'm sure there's some FP kind of philosophy which prohibits this (code with potential side effects not being properly quarantined), but to me it just results in yet more purpose-specific syntax to have to learn for F#, which is already very heavy on the number of keywords and operators | | |
| ▲ | neonsunset 6 days ago | parent [-] | | It's the other way around. 'async'-annotated methods in C# enable 'await'ing on task-shaped types. It is bespoke and async-specific. There is nothing wrong with it but it's necessary to acknowledge this limitation. let!, and!, return!, etc. keywords in F# are generic - you can build your own state machines/coroutines with resumable code, you can author completely custom logic with CEs. I'm not sure what led you to believe the opposite. `await Task.Delay` in C# is `do! Task.Delay` in F#. `let! response = http.SendAsync` is for asynchronous calls that return a value rather than unit. In the same vein, seq is another CE that is more capable than iterator methods with yield return: let values = seq {
// yield individual values
for i in 1..10 -> i
// yield a range, merged into the sequence
yield! [11..20] // note the exclamation mark
}
Adding support for this in C# would require explicit compiler changes. CEs are generic and very powerful at building execution blocks with fine control over the behavior, DSLs and more.Reference: https://learn.microsoft.com/en-us/dotnet/fsharp/language-ref... | | |
| ▲ | shakna 12 hours ago | parent | next [-] | | > It is bespoke and async-specific. There is nothing wrong with it but it's necessary to acknowledge this limitation. I would disagree. If you need to have a bespoke set of syntax, then something is not integrated where it should be. The language design should not be such that you are writing things differently, depending on the paradigm that you're handling. That's not something that occurs in every language, so it isn't essential that it exists. We can acknowledge the differences in a way that alerts the programmer, without forcing the programmer to switch syntaxes back and forth when moving between the paradigms. async/await is one method, Promises another, etc. A different syntax is a much, much higher cognitive load. | | |
| ▲ | Vetch 9 hours ago | parent | next [-] | | F#'s computation expressions are closely related to Haskell's monads + do-notation combo, CEs are both more limited than Haskell's approach to monads (from a type expressibility perspective) and more expressive than pure monads (from a modeling perspective, can model a general class of computational structures beyond monads; CE's also share F#'s syntax, with extensible semantics). This notation can be advantageous and clarifying when used in the right places. It has advantages over C#'s async from a flexibility/extensibility perspective and also provides more options in orchestrating more complex control flow across async computations. C#'s approach is more streamlined if you only care about using async according to how Tasks are designed (which still enable a quite broad scope) and don't need the flexibility for other computational patterns. Simple things like the maybe and either monad are often clearer in this notation. Complex things like alternatives to async (such as CSP derived message passing concurrency), continuations, parser combinators, non-determinism, algebraic effects and dependency tracked incremental computations are naturally modeled with this same machinery, with CE notation being a kind of super helpful DSL builder that makes certain complex computations easier to express in a sequenced manner. If the custom syntax was only for async you'd have a point, but the general power of the framework make it the more preferable approach by far, in my opinion. | | |
| ▲ | shakna 8 hours ago | parent [-] | | However, most of the industry has moved away from DSLs. Whilst having a unique language can make certain things more expressive, having something standard makes mistakes happen less, and increases the effectiveness of a programmer. Lisp doesn't rule our day to day. We shoehorn things that feel like, but are structurally different, to DSLs into config files and the like, using JSON/YAML/etc in rough ways, because DSLs introduce a cognitive overhead that doesn't need to be there. That the shoehorn happens, does mean that DSLs are something natural to reach for. You're right there. But that we have moved away, as an industry, indicates that using any kind of DSL is a smell. That there probably is a better way to do it. Having a core language feature using a DSL, is a smell. It could be done better. |
| |
| ▲ | neonsunset 10 hours ago | parent | prev [-] | | I cannot make sense of this reply. Different languages have different syntax. Support of asynchronous code and of its composition is central to C#, which is why it does it via async/await and Task<T> (and other Task-shaped types). Many other languages considered this important enough to adopt a similar structure to their own rendition of concurrency primitives, inspired by C# either directly or indirectly. Feel free to take issue with the designers of these languages if you have to. F#, where async originates from, just happens to be more "powerful" as befits an FP language, where resumable code and CEs enable expressing async in a more generalized fashion. I'm not sold on idea that C# needs CEs. It already has sufficient complexity and good balance of expressiveness. | | |
| ▲ | shakna 8 hours ago | parent [-] | | Different languages have different syntax, but most do not have a separate syntax inside themselves. A function is generally a function. They do adopt various structures - but those are structures, not syntax. I'm not sure you've understood that was my point. | | |
| ▲ | neonsunset 7 hours ago | parent [-] | | [edited out the swipe] Do-notation-like 'await' is not for calling functions, it is for acting on their return values - to suspend the execution flow until the task completes. | | |
| ▲ | shakna 7 hours ago | parent [-] | | I've written patches for F#. I do know what the hell I'm talking about. However, the compiler does not, has never, required that it does things via a different syntax. In fact, in the early branches before that was adopted, it didn't! The same behaviour was seen in those branches. This behaviour you expect, was never something that had to be. It was something chosen to simplify the needs of the optimiser, and in fact cut the size of code required in half. It was to reduce the amount of code needed to be maintained by the core team. And so 1087 [1] was accepted. So perhaps you might need to read more into the process of the why and how async was introduced into C# and F#. It was a maintenance team problem. It was a pragmatic approach for them - not something that was the only way that this became a possibility. As said, in the original branch for using tasks... > Having two different but similar ways of creating asynchronous computations would add some cognitive overhead (even now there are times when I am indecisive between using async or mailbox for certain parallelism/concurrency scenarios). [0] [0] https://github.com/fsharp/fslang-suggestions/issues/581 [1] https://github.com/fsharp/fslang-design/blob/main/FSharp-6.0... | | |
| ▲ | neonsunset 6 hours ago | parent [-] | | Alright, I stand corrected. However, this is where our opinions differ. I like task CE (and taskSeq for that matter too). It serves as a good and performant default. It's great to be able to choose the exact behavior of asynchronous code when task CE does not fit. |
|
|
|
|
| |
| ▲ | shortrounddev2 5 days ago | parent | prev [-] | | I'm somewhat already aware of these considerations, it's just that when you're working in web development, a huge amount of your code is async and this means that a large part of the code is wrapped up in these computation expressions that I think are just plain ugly | | |
| ▲ | miffy900 10 hours ago | parent [-] | | What's so ugly about them? I don't code in F#, but I do in C# and after reading about them I wish C# had something similar. |
|
|
|
|