Reputation: 348
I like pipes and Elixir likes pipes, but it also seems to be common to return {:ok, actual_return_value}. Is there an idiom for handling this with pipes?
I am studying "Programming Elixir" right now, and the following solution to an exercise might be possible but incomplete.
File.cwd()
|> IO.puts()
I am not looking for this solution:
{:ok, ret} = File.cwd()
IO.puts(ret)
Upvotes: 1
Views: 2008
Reputation: 121010
There is no idiomatic way to pipe the second arg.
elixir discourages using pipes in two cases:
Whenever it makes sense, the core library does it’s best to provide all the sugar to make piping a charm. Consider tweaking strings. We have two replace/4
functions: String.replace/4
and Regex.replace/4
with nearly similar functionality, but the former takes a string as a first argument to make it easier to pipe into chains when the string comes, and the latter accepts regex as a first argument.
If you find yourself in a bewilderment of how to pipe — it’s a clear sign you should not pipe. That simple, the language itself hints the best way to go. Indeed, nobody returns {:ok, value}
from the function that always succeeds. That said, there might be some other outcome. In the case in the question, {:error, reason}
. Let me guess: you don’t want your code to blow up on it. And elixir kindly reminds you about that. Of course, you still can pipe with Kernel.elem/2
, but do you really want to? I bet no.
There are at least two different approaches, depending on what do you actually want to achieve.
One would be to use a monadic SpecialForms.with/1
, that either continues to execute clauses or returns the first non-matched result.
with {:ok, a} <- Mod.fun1(),
{:ok, b} <- Mod.fun2(a),
{:ok, c} <- Mod.fun3(b),
do: IO.puts(c)
That way you either output c
or return the first non-matched (read: errored) outcome.
Another way around would be to use Kernel.SpecialForms.case/2
.
File.cwd()
|> case do
{:ok, result} -> result
{:error, reason} -> LOG_OR_SOMETHING
end
Pick up any of your choices, but remember: pipe should be used where it belongs, not everywhere.
Upvotes: 6
Reputation: 5993
It all depends how you want to handle errors.
If you wanted to branch on the return value, you could use a case
or a with
or something like that. If you put it in a private function, it allows for piping (you can pipe in directly, but it gets ugly quick).
with {:ok, pwd} <- File.cwd() do
IO.puts(pwd)
end
If getting a response other than an ok tuple is exceptional, you could use the bang versions of functions.
File.cwd!()
|> IO.puts()
If your function does not have a bang version, you could make a helper function to do something similar.
File.cwd()
|> ok!()
|> IO.puts()
defp ok!({:ok, value}), do: value
If you're 100% confident a function will return an ok tuple, you could use elem/2
to get a value from the tuple. This isn't as explicit, so it's not my favorite.
File.cwd()
|> elem(1)
|> IO.puts()
That being said, control flow is not where piping exceeds. It does best if you're doing several operations to transform a data structure. For such a situation you're probably better off using logical constructs and breaking things up into smaller functions.
Upvotes: 3
Reputation: 5646
If you want to print value to stdout you have few options but here are two closest to your example
File.cwd()
|> inspect()
|> IO.puts()
or you can use shorter version
File.cwd()
|> IO.inspect()
difference is that IO.inspect/2
accepts any term and returns same term, while IO.puts() accept string and iolist and returns :ok
or {:error, ...}
so you need to cast value to string if you want to use IO.puts().
Upvotes: 0