JEG2
How do I read available characters?
While working on the ICFP Contest this weekend, I struggled to find a good way to periodically read content not delimited by newlines. Let me show some examples of what I mean.
Here’s a Ruby script that produces messages:
$stdout.sync = true
loop do
message = rand(1_000_000)
$stdout.write "<#{message}>"
sleep rand(3) + 1
end
I can think of multiple ways, using Ruby, to read these messages as they come in. For example, I can use non-blocking I/O:
loop do
begin
raw = $stdin.readpartial(1_024)
puts raw[/\d+/]
rescue EOFError
sleep 0.1
end
end
Or I can read what’s available:
require "io/wait"
loop do
$stdin.wait_readable
raw = $stdin.read($stdin.nread)
puts raw[/\d+/]
end
There are other options too, like using IO::select(). Here’s how the above examples work in practice:
$ ruby producer.rb | ruby read_nonblocking.rb
319187
122221
30420
…
$ ruby producer.rb | ruby read_ready.rb
640243
971582
366808
…
I haven’t found a good way to do similar work with Elixir. The best I’ve come up with for the same input is to read character by character:
defmodule MessageReader do
def read_message(device, buffer \\ "") do
new_buffer = buffer <> IO.read(device, 1)
if String.first(new_buffer) == "<" and String.last(new_buffer) == ">" do
String.slice(new_buffer, 1..-2)
else
read_message(device, new_buffer)
end
end
def read_messages(device, handler) do
read_message(device)
|> handler.()
read_messages(device, handler)
end
end
MessageReader.read_messages(:stdio, &IO.puts/1)
This does work:
$ ruby producer.rb | elixir read_chars.exs
963609
378034
387827
…
However, that would be pretty inefficient with long messages and I can’t find a way to read ahead. Am I missing a useful trick?
Thanks in advance!
First Post!
NobbZ
Have you took a look at IO.stream/2?
:stdin
|> IO.stream(1_024)
|> Enum.each(&IO.puts/1)
Should be roughly equivalent to your first ruby version.
Most Liked
josevalim
If you really want to play with what is under the hood, the IO message protocol in Erlang does provide a get_until functionality: The Erlang I/O Protocol — OTP 29.0.2 (stdlib 8.0.1)
The Erlang io:read/2 function uses it to parse terms out of the standard input:
read(Io, Prompt) ->
case request(Io, {get_until,unicode,Prompt,erl_scan,tokens,[1]}) of
{ok,Toks,_EndLine} ->
erl_parse:parse_term(Toks);
% {error, Reason} when atom(Reason) ->
% erlang:error(conv_reason(read, Reason), [Io, Prompt]);
{error,E,_EndLine} ->
{error,E};
{eof,_EndLine} ->
eof;
Other ->
Other
end.
Dave Thomas has recently asked for a similar feature and we would be glad to expose it in the IO module API.
josevalim
I would personally prefer to provide a low-level function first and then a higher level one. Otherwise folks will have to reach the low-level protocol every time they need something more complex than an expression.
I am also not sure we can support the until approach in binread/2 since it uses a different protocol iirc.
JEG2
Sorry, but I don’t believe it is. I think your code blocks until it can deliver 1,024 bytes, so you don’t receive messages as they come in. I tried to run it to verify my assumptions (after I fixed the :stdin to :stdio bug) and it did seem to be the case.
Last Post!
marciol
Seems that it was implemented 2 months ago right?
Popular in Questions
Other popular topics
Categories:
Sub Categories:
Forums
Popular Tags
- #ecto
- #liveview
- #troubleshooting
- #learning-elixir
- #deployment
- #library
- #erlang
- #testing
- #genserver
- #mix
- #absinthe
- #remote-other
- #otp
- #plug
- #how-to-question
- #macros
- #postgres
- #channels
- #elixirconf
- #exunit
- #discussion
- #code-sync
- #javascript
- #podcasts
- #onsite
- #dialyzer
- #docker
- #authentication
- #umbrella
- #full-time-contract
- #podcasts-by-brainlid
- #ecto-query
- #elixir-ls
- #phoenix_html
- #iex
- #blog-post
- #graphql
- #genstage
- #ai
- #websockets
- #supervisor
- #elixirconf-us
- #advent-of-code
- #distillery
- #processes
- #api
- #forms
- #metaprogramming
- #security
- #hex









