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

Refine Take-A-Number #1511

Merged
merged 3 commits into from
Aug 29, 2024
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion concepts/processes/introduction.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ In Elixir, all code runs inside processes.
By default, a function will execute in the same process from which it was called. When you need to explicitly run a certain function in a new process, use `spawn/1`:

```elixir
spawn(&my_function/0)
spawn(fn -> 2 + 2 end)
# => #PID<0.125.0>
```

Expand Down
11 changes: 6 additions & 5 deletions exercises/concept/take-a-number/.docs/hints.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,17 @@
## 1. Start the machine

- The machine should run in a new process. There is [a built-in function that starts a new process][kernel-spawn-1].
- You will need another function that the new process will execute. You can name it, for example, `loop`.
- Use the [capture operator][special-forms-capture] to pass a named function as an argument.
- Use a 0-arity anonymous function when starting a new process. This function doesn't need to do anything yet.

## 2. Report the machine state

- The machine's process needs to respond to messages.
- You will need a new named function. You can name it, for example, `loop`.
- This new function should accept one argument, the state.
- The 0-arity anonymous function from the previous step can call the new named function, passing the initial state.
Comment on lines +15 to +17
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 hope those instructions are explicit enough to lead people to a solution similar to the exemplar

Copy link
Contributor

Choose a reason for hiding this comment

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

I think they should be

- The new named function needs to respond to messages.
- There is [a built-in function that waits for a message to arrive in the process's mailbox][kernel-receive].
- There is [a built-in function that sends a message to another process][kernel-send].
- Use recursion to wait for more than one message.
- Pass the machine's state as an argument to the recursive function.

## 3. Give out numbers

Expand All @@ -27,6 +28,7 @@

- This step doesn't require sending any messages as a response.
- A process will exit if it has no more code to execute.
- When you need to create an "empty" code block in Elixir, you can use `nil` as the only expression in that code block.
- This is a base case of the recursive function.

## 5. Ignore unexpected messages
Expand All @@ -39,4 +41,3 @@
[kernel-spawn-1]: https://hexdocs.pm/elixir/Kernel.html#spawn/1
[kernel-receive]: https://hexdocs.pm/elixir/Kernel.SpecialForms.html#receive/1
[kernel-send]: https://hexdocs.pm/elixir/Kernel.html#send/2
[special-forms-capture]: https://hexdocs.pm/elixir/Kernel.SpecialForms.html#&/1
10 changes: 8 additions & 2 deletions exercises/concept/take-a-number/.docs/instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ You are writing an embedded system for a Take-A-Number machine. It is a very sim

## 1. Start the machine

Implement the `start/0` function. It should spawn a new process that has an initial state of `0` and is ready to receive messages. It should return the process's PID.
Implement the `start/0` function. It should spawn a new process and return the process's PID. The new process doesn't need to do anything yet.

```elixir
TakeANumber.start()
Expand All @@ -15,12 +15,15 @@ Note that each time you run this code, the PID may be different.

## 2. Report the machine state

Modify the machine so that it can receive `{:report_state, sender_pid}` messages. It should send its current state (the last given out ticket number) to `sender_pid` and then wait for more messages.
Modify the machine so that the newly spawned process is ready to receive messages (start a _receive loop_) with an initial state of `0`. It should be able to receive `{:report_state, sender_pid}` messages. As a response to those messages, it should send its current state (the last given out ticket number) to `sender_pid` and then wait for more messages.
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 hope that the phrase "receive loop" will remind people of the paragraph with the same title from the introductions

Copy link
Contributor

Choose a reason for hiding this comment

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

Have you considered adding some example code in the receive loop section? I think code will make it a lot more memorable. Something like:

def loop(ping_count) do
  receive do
    {:ping, sender_pid} ->
      send(sender_pid, :pong)
      loop(ping_count + 1)
  end
end

It's pretty close to the solution though.

Copy link
Member Author

Choose a reason for hiding this comment

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

Yes, many times, but it is clearly against the recommendation:

Code examples should only be used to introduce new syntax (students should not need to search the web for examples of syntax). In other cases provide descriptions or links instead of code.

Defining a recursive function is not new syntax...


```elixir
machine_pid = TakeANumber.start()

# a client sending a message to the machine
send(machine_pid, {:report_state, self()})

# a client receiving a message from the machine
receive do
msg -> msg
end
Comment on lines +22 to 29
Copy link
Member Author

Choose a reason for hiding this comment

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

Are those comments helpful? I was staring at this snippet and I was afraid that people might not understand that this code is an example code of how to test the implementation of the machine, and not the code that should go into the implementation of the machine 🤔

Copy link
Contributor

Choose a reason for hiding this comment

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

I see your concern, but that's usually what those code snippets do.
Maybe if you wanted to be even more explicit, you could frame those as tests?

  test "reports its own state" do
    pid = TakeANumber.start()
    send(pid, {:report_state, self()})
    assert_receive 0
  end

but that might not help so much.
I think adding the comments is fine for now.

Expand All @@ -34,8 +37,11 @@ Modify the machine so that it can receive `{:take_a_number, sender_pid}` message

```elixir
machine_pid = TakeANumber.start()

# a client sending a message to the machine
send(machine_pid, {:take_a_number, self()})

# a client receiving a message from the machine
receive do
msg -> msg
end
Expand Down
2 changes: 1 addition & 1 deletion exercises/concept/take-a-number/.docs/introduction.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ In Elixir, all code runs inside processes.
By default, a function will execute in the same process from which it was called. When you need to explicitly run a certain function in a new process, use `spawn/1`:

```elixir
spawn(&my_function/0)
spawn(fn -> 2 + 2 end)
# => #PID<0.125.0>
```

Expand Down
Loading