48

I'd like to write the code like this:

def boundary do
  :crypto.rand_bytes(8)
  |> Base.encode16
  |> &("--------FormDataBoundary" <> &1)
end

But it doesn't work.

6 Answers 6

80

It will look bit weird but must work:

def boundary do
  :crypto.rand_bytes(8)
  |> Base.encode16
  |> (&("--------FormDataBoundary" <> &1)).()
end
Sign up to request clarification or add additional context in comments.

4 Comments

is there any reason for it to be so weird? When I pass a function to a pipe I treat it more like a variable (yes, I'm from Javascript), but doing (fn).() looks like I'm immediately invoking it which I am not.
(&("--------FormDataBoundary" <> &1)).() It looks like you define an anonymous function that takes 1 argument, then call it with zero arguments, and then it's part of the pipeline? I don't understand why you need the .() at the end. Isn't this calling the anonymous function? I don't want to call it myself, I want it to be part of the pipeline.
I think I get it now. The fact that the function is part of a pipeline means that the pipeline supplies the first argument. All that is left is for the function to be called, which you do with the .().
I struggled with this at first. Then I realized when you use pipelines, every function in the pipeline is a function call, not a function reference. This is obvious if the functions in your pipeline have arity 2+, since then you have to use parentheses for the named functions too. Making the function call with parentheses for the anonymous function isn't weird once you realize all of the named functions are explicitly called also. If their arity is 1, you can omit the parentheses (makings it look like a function reference rather than a function call, but it's a function call nonetheless.)
17

Related: if the "anonymous" function has been assigned to a variable, you can pipe to it like this:

def boundary do
  add_marker = fn (s) ->
    "--------FormDataBoundary" <> s
  end

  :crypto.rand_bytes(8)
  |> Base.encode16
  |> add_marker.()
end

Comments

6

Elixir 1.12 introduced macro then(value, fun). Citing the docs:

then(value, fun)

Pipes value into the given fun.

In other words, it invokes fun with value as argument. This is most commonly used in pipelines, allowing you to pipe a value to a function outside of its first argument.

Your code becomes:

:crypto.strong_rand_bytes(8)
|> Base.encode16
|> then(&("--------FormDataBoundary" <> &1))

I'm using :crypto.strong_rand_bytes/1 above because :crypto.rand_bytes/1 was deprecated in Erlang/OTP 18.

Comments

4

The accepted answer works, but you can do this a bit more elegantly by using

(&"--------FormDataBoundary#{&1}").()

instead of

(&("--------FormDataBoundary" <> &1)).()

Here is the full function:

def boundary do
  :crypto.strong_rand_bytes(8)
  |> Base.encode16()
  |> (&"--------FormDataBoundary#{&1}").()
end

Bonus: I've also replaced :crypto.rand_bytes/1 (which doesn't exist in elixir 1.6+) with :crypto.strong_rand_bytes/1.

Comments

2

You can also use something like this:

def boundary do
  :crypto.rand_bytes(8)
  |> Base.encode16
  |> (fn chars -> "--------FormDataBoundary" <> chars end).()
end

One advantage of this form over others is that you can easily write simple 'case' statements:

def do_some_stuff do
  something
  |> a_named_function()
  |> (
    fn
      {:ok, something} -> something
      {:error, something_else} ->
        Logger.error "Error"
        # ...
    end
  ).()
end

From:


Using fn as above is a tiny bit clearer than couchemar's answer:

def boundary do
  :crypto.rand_bytes(8)
  |> Base.encode16
  |> (&("--------FormDataBoundary" <> &1)).()
end

... but, for your particular example, the above form using & is probably best. If the pipeline expression was more complicated, naming the anonymous function parameters might be more useful.

My answer is also a little more concise than Nathan Long's answer:

def boundary do
  add_marker = fn (s) ->
    "--------FormDataBoundary" <> s
  end

  :crypto.rand_bytes(8)
  |> Base.encode16
  |> add_marker.()
end

... tho his answer would be nicer if, for some reason, you needed to call that function more than once in the pipeline.

1 Comment

One can also skip the parenthesis: |> fn x -> x end.()
1

can't you literally just go?

thing
|> func_one()
|> fn input -> do_stuff_here() end)

You can do stuff like piping things directly into case like

thing
|> func_one()
|> case do

so, I would think you can just pipe into an anonymous function.

2 Comments

The last line would need to be like |> (fn input -> do_stuff_here() end).() but otherwise you're correct.
I didn't know one could pipe into case tho – that's handy!

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.