Closed magnus-madsen closed 1 year ago
I agree that the <-
syntax feels strange. I think the reason is that it looks very much like an infix operator.
In my experience, arrows typically either:
case a => b
, do b <- a
, Map(a -> b)
, for a -> b
), saying a
leads to or goes into b
case a => // noop
)
So the <- i
tells me "putting i
in something", but doesn't tell me what, which feels confusing.That all said, I think the syntax is get-used-to-able.
Proposal 1:
let c1 = Channel.bounded(5);
let c2 = Channel.unbounded();
Channel.get(c1);
Channel.put(c2, value);
let n = Nursery.new();
spawn(n, 1 + 2);
select {
case x = Channel.get(c1) => ...
case Channel.put(c2, value) => ...
}
Not really a fan. But its an idea.
Proposal 2 using more keywords:
let c1 = Channel.bounded(5);
let c2 = Channel.unbounded();
recv c1;
send c2 v;
let n = Nursery.new();
spawn n (1 + 2);
select {
case x = recv c => ...
case c2 send v => ...
}
Also not a fan.
I have a stylistic preference against multi-parameter keywords outside of infixes.
I have a stylistic preference against multi-parameter keywords outside of infixes.
Agreed. I don't like any of the proposals so far.
@paulbutcher Pinging you so you can have a quick laugh and maybe offer some suggestions for alternative channel syntax.
(Or perhaps you think the current syntax is OK)
I think definitely the chan <type> <size>
should be changed. It feels foreign to Flix and its weird to have the type in the expression.
I think definitely the
chan <type> <size>
should be changed. It feels foreign to Flix and its weird to have the type in the expression.
It is very ugly!
For my part, I have no problem with the current syntax with the single exception of channel creation as mentioned by @JonathanStarup above.
What would we think about:
chan[Int32](100)
Are there other options?
Here is my new proposal with some example code:
let c1 = Channel.bounded(5);
let c2 = Channel.unbounded();
Channel.send(c1, 123) // Note channel is first argument.
Channel.send(c2, 456)
Channel.recv(c1)
Channel.recv(c2) |> Channel.send(c1) // Works because channel is first argument to send.
select {
case x <- recv(c1) => ... // strange because recv is not imported, but okay...
case y <- recv(c2) => ... // ... do we also allow Channel.recv(c2) ?
}
Why use <-
in case? I think to highlight something "fishy" is going on; like with for-each and for-yield. Also I find it pleasing.
(Sidenote: Later I want to split channels into sender and receiver).
Rust style:
select {
case recv(r1) -> x => ...
case recv(r2) -> y => ...
}
without case:
select {
recv(r1) -> x => ...
recv(r2) -> y => ...
}
Note channel is first argument.
This doesn't allow c !> send(12) |> send(13)
. But Channel.recv(c2) |> Channel.send(c1)
is nice
Note channel is first argument.
This doesn't allow
c !> send(12) |> send(13)
Ah right, so you are saying the channel argument should be last and we should use !>
instead?
Note channel is first argument.
This doesn't allow
c !> send(12) |> send(13)
Ah right, so you are saying the channel argument should be last and we should use
!>
instead?
Maybe, its just what we discussed previously. It wouldn't allow element first in the pipe element |> ...
How would these functions be implemented? They have to return the fake Channel[a]
type?
let c1 = Channel.bounded(5);
How do we know what type the channel is?
How do we know what type the channel is?
From type inference, like everywhere else. Its a legacy thing that channels required types.
Ah cool. Even better 👍
Maybe, its just what we discussed previously. It wouldn't allow element first in the pipe
element |> ...
How would these functions be implemented? They have to return the fake
Channel[a]
type?
I don't understand at all :) Are you saying that |>
solves our problems or no?
A bit more detail on the proposed API:
type alias Sender[t] = Channel[t] // later Sender and Receiver will become actual types in the Flix compiler.
type alias Receiver[t] = Channel[t] // also later they will be equipped with regions.
namespace Channel {
def buffered(n: Int32): (Sender[t], Receiver[t])
def unbuffered(): (Sender[t], Receiver[t])
def send(m: a, s: Sender[a]): Unit
def recv(r: Receiver[a]): a
def tryRecv(r: Receiver[a]): Option[a] // internally uses select with default case.
// trySend does not exist because we do not support send in select (yet).
def timeout(d: Duration): Receiver[Unit] // returns a channel that sends unit after `d`.
}
I wonder if we can support a drain
operation, but that probably requires us to know when the channel is closed.
EDIT: timeout
is like Timer
, and I don't think we should have Ticker
.
I don't understand at all :) Are you saying that |> solves our problems or no?
c !> send(1) !> send(2) |> send(3)
complexComputation |> send(c)
1 is most consistent with the rest of flix
I don't think we should have Ticker
I like ticker, I feel many would have to implement it themselves otherwise. It seems common to do something every X time interval.
I wonder if we can support a drain operation
maybe def drainUntill(c: Channel[a], f: a -> Bool)
or it could drain until no further elements are present (with select)
A bit more detail on the proposed API:
Is this a library based on the current one or a replacement? I guess we could remove the inbuilt channel type now right? Channels are really just a library, not an inbuilt thing (except expression shorhands)
What's the perceived (or actual) value in separating a channel into sender and receiver?
Its easier to reason about deadlocks I think. If a create an channel that's meant to signal you, I will now block if you spam the channel with things. If I can restrict you to receiving, I only have to make sure I don't block myself
Continued in https://github.com/flix/flix/issues/4755
A gentleman on Reddit humbly suggested that our syntax is sometimes confusing:
I generally disagree and as someone pointed out:
::
for cons is well-established.<-
for channel operations is well-established (at least in the research literature and in Go).Yet, on the last point, it could be argued that channel operations are much much less frequently used than all of the other constructs. Hence, why not have a keyword for them? (I am definitely not changing anything else).
What do you think?