user2240431
user2240431

Reputation: 348

Idiomatic way to strip/check :ok and pipe second arg

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

Answers (3)

Aleksei Matiushkin
Aleksei Matiushkin

Reputation: 121010

There is no idiomatic way to pipe the second arg.

discourages using pipes in two cases:

  • there are only two links in the chain
  • the next function in the chain accepts the outcome of the previous function not as a first argument or the outcome provides something different from what the next function in the chain expects.

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 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

Brett Beatty
Brett Beatty

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

Milan Jaric
Milan Jaric

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

Related Questions