Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add select keyword #3130

Merged
merged 1 commit into from
Aug 10, 2016
Merged

Add select keyword #3130

merged 1 commit into from
Aug 10, 2016

Conversation

asterite
Copy link
Member

@asterite asterite commented Aug 10, 2016

Fixes #688

This adds the select keyword, which is syntax sugar to perform
a Channel.select more easily.

For example, our current channel_select sample is:

def generator(n : T)
  channel = Channel(T).new
  spawn do
    loop do
      sleep n
      channel.send n
    end
  end
  channel
end

ch1 = generator(1)
ch2 = generator(1.5)
ch3 = generator(5)

loop do
  index, value = Channel.select(ch1.receive_select_action, ch2.receive_select_action, ch3.receive_select_action)
  case index
  when 0
    int = value.as(typeof(ch1.receive))
    puts "Int: #{int}"
  when 1
    float = value.as(typeof(ch2.receive))
    puts "Float: #{float}"
  when 2
    break
  else
    raise "BUG: Channel.select returned invalid index #{index}"
  end
end

With the new syntax it can be written as:

def generator(n : T)
  channel = Channel(T).new
  spawn do
    loop do
      sleep n
      channel.send n
    end
  end
  channel
end

ch1 = generator(1)
ch2 = generator(1.5)
ch3 = generator(5)

loop do
  select
  when int = ch1.receive
    puts "Int: #{int}"
  when float = ch2.receive
    puts "Float: #{float}"
  when ch3.receive
    break
  end
end

A select when expression can either be a call or an assignment whose right-hand side is a call. The call name is added "_select_action" (taking bang and question marks into account) and that is passed to the Channel.select method.

The nice thing about this is that this is extensible: we could define a timeout_select_action(n) method, so one could do:

select
when x = ch.receive
  puts x
when timeout(5.seconds)
  puts "Timeout!"
end

In fact we were able to implement this but this will be pushed in a later commit (the interface and docs about how to implement a "select action" are current missing).

This is a breaking change, because now select can't be used as a method call, just like case can't be used for that. One has to use an explicit receiver, like foo.select or self.select, or ::select.

@asterite
Copy link
Member Author

/cc @waj

@asterite
Copy link
Member Author

EDIT: added missing specs where that show how select is expanded in all cases

describe "Normalize: case" do
it "normalizes select with call" do
assert_expand "select; when foo; body; when bar; baz; end", <<-CODE
__temp_1, __temp_2 = ::Channel.select({foo_select_action, bar_select_action})
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

_for_select or _action, which is it? :)

Not too sure a about either name yet, have to sleep over it.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, good catch. _for_select was our original choice, but _select_action matches the SelectAction "interface". We aren't sure about the name either, but since it's not going to be written manually...

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just conceptually, not referring to the existing Future, can't SelectAction be thought of as a promise/future? Channel.select receives a list of promises and returns the first that could compute a value. Following that line of thought, SelectActon becomes Promise and the suffix becomes _promise, and channel.receive_promise suddenly reads pretty well to me. Going further the whole concept could be decoupled from Channel and Channel.select could become Promise.select.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I must admit it sounds good, but on second thought this is not really a promise. It's one possible action that can be executed in a select branch, but it's not guaranteed that it will be executed.

Plus, adding more functionality to it is pretty hard, you have to know the runtime internals. I'd say it's extensible so we can add more cases in the standard library, but I wouldn't expect 3rd party libraries to extend this, at least not now. So I prefer it to keep it under Channel and with a name that explicitly states it's for a select expression, and only through a select expression (so the underlying names don't matter much)

Copy link
Member

@jhass jhass Aug 10, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I asked a bit around whether there are existing terms or concepts to this problem.

One interesting note was that it could be a modled as a priority queue with dynamic weights. The weight would denote the readiness state, the queue would hold the operations and pop once. We would need then need to implement a constrained pop that blocks until an element with a high enough weight is available. But all of this doesn't help much with the name for the containers in the queue.

The other note I received was that OCaml calls this "alternatives", or its formal parent CSP calls it "choice". https://en.wikipedia.org/wiki/Communicating_sequential_processes#Algebraic_operators
I think that could actually work here as a generalized construct, Choice.select, Channel#receive_choice, Channel#send_choice, Choice#ready?, Choice#execute, Choice#wait, Choice#unwait.

I'm not sure I follow you on the runtime internals, Choice would define the the interface with how the operations have to behave, things like "wait should ensure the current fiber is rescheduled when value of ready? changes" and "unwait should remove any reschedule setup by wait". We then just have to document Scheduler.enqueue and that's it, the rest is up to the implementer.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds good, but I prefer to wait until we have true parallelism. I don't think any action could be put inside a select, there will be mutexes and locks involved and it will be highly coupled with how the runtime works. I'm not sure there will be many extensions to this, we just did it this way so we could more easily implement all the operations.

In any case, this will probably evolve and change in the future until we stabilize everything in 1.0. But I'm convinced that using these things outside a select expression makes little sense, so the real names aren't very important, and if they are called SelectAction then it's pretty clear it's for them to be used in a select.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants