As a concrete example, here's the implementation I came up with for this
word count exercise <http://exercism.io/exercises/elixir/word-count/readme>:
defmodule Words do
def count("" <> string), do: count(parse(string), %{})
defp count([], map), do: map
defp count(["" | words], map), do: count(words, map)
defp count([word | words], map) do
word = word |> String.replace("_", "-")
count(words, put_in(map[word], (map[word] || 0) + 1))
end
# strip underscores, retain hyphens
defp parse(string), do: string |> String.downcase |> String.replace("-",
"&hyph;") |> String.replace("_", "-") |> String.replace("&hyph;", "_") |>
String.split(~r/\W/u)
end
To apply your advice to my final definition of count, I guess the cleanest
way to rewrite it would be along the lines of...
defp count([], map), do: map
defp count(["" | words], map), do: count(words, map)
defp count([word | words], map), do: count(words, increment(map, word))
defp increment(map, word)
word = word |> String.replace("_", "-")
put_in(map[word], (map[word] || 0) + 1)
end
But I'm ... not really seeing the upshot of pulling out the logic this way.
Besides having another place to look to find out what my code is doing, I
haven't actually simplified the final function body that much.
I'll keep practicing, and it'll probably click soon enough.
On Wednesday, May 3, 2017 at 11:45:00 AM UTC-7, Josh Bourgeois wrote:
>
> Thank you for the reply! We use Rails exclusively on my team, so all of my
> exposure to Elixir so far has come through HackerRank, Exercism, and poring
> over the online guide and core documentation.
>
> May I ask for clarification around what you mean when you say "create
> small named functions [when you can't pipe]"? From everything I've read
> and seen,
>
> thing |> a_function
>
> is simply converted to
>
> a_function(thing)
>
> at compile time, so... "when you can't pipe", literally translates to
> "when you would be unable to call a function"... Unless I've missed
> something? How would one apply the advice of creating a small named
> function to a practical example to a workflow point where you would be
> unable to call a function?
>
> Since my password function was the only semi-noncontrived example, I guess
> you're saying it would be best practice to rewrite it from
>
> def encrypt_password(user, password) do
> encrypted = password
> |> Salt.add
> |> BCrypt.hash
> put_in(user[:hashed_password], encrypted)
> |> EctoLike.update
> end
>
> to something like
>
> def encrypt_password(user, password) do
> put_in(user[:hashed_password], encrypt(password))
> |> EctoLike.update
> end
>
> defp encrypt(password) do
> password
> |> Salt.add
> |> BCrypt.hash
> end
>
> or something similar, right? I've learned to try to keep code as close to
> where it winds up getting run as I can (jumps are bad, m'kay?
> <https://www.joelonsoftware.com/2003/10/13/13/>), so my instinct is to
> keep processes inlined in their methods until I use the process in more
> than one place, or (in this new Elixir land) until I have a second clause I
> need to define.
>
>
>
> On Tuesday, May 2, 2017 at 10:12:59 PM UTC-7, José Valim wrote:
>>
>> As explained in the many others pipe proposals sent to this mailing list,
>> we don't plan to add more complexity to the pipe operator.
>>
>> Despite the suggestions of many, anonymous functions are not the solution
>> when you can't pipe. Well named and defined private functions are. Elixir
>> is a functional language. So create small named functions to improve code
>> readability.
>>
>> On Wed, May 3, 2017 at 01:48 OvermindDL1 <[email protected]> wrote:
>>
>>> Hmm, I rather like it, `|>` is pipe-start (still think it should be pipe
>>> into the end of the argument list) and `|^` could be pipe-pin, interesting
>>> approach, I like it.
>>>
>>>
>>> On Tuesday, May 2, 2017 at 5:09:57 PM UTC-6, Josh Bourgeois wrote:
>>>>
>>>> Many significant apologies for reopening this very-very-well-treaded
>>>> topic, but I do want to advocate this *one* more time.
>>>>
>>>> The pipe operator is used to illustrate the flow of data
>>>> transformation, but while functional programming is very expressive, there
>>>> are times when a signature requires the data in question to be used as a
>>>> different argument. In those cases, you must either break the pipe chain
>>>> (and thus the flow of data transformation) to assign the transformed data
>>>> to a variable, or send the data through an anonymous function to continue
>>>> the program flow.
>>>>
>>>> Take a process where data should be transformed before being saved to a
>>>> database:
>>>>
>>>> # intermediary value
>>>> def encrypt_password(user, password) do
>>>> encrypted = password
>>>> |> Salt.add
>>>> |> BCrypt.hash
>>>> put_in(user[:hashed_password], encrypted)
>>>> |> EctoOrSomething.UpdateIGuess.ImStillNewHere
>>>> end
>>>>
>>>> # anonymous function wrap
>>>>
>>>> def encrypt_password(user, password) do
>>>> password
>>>> |> Salt.add
>>>> |> BCrypt.hash
>>>> |> (fn(encrypted) -> put_in(user[:hashed_password], encrypted) end
>>>> ).()
>>>> # or |> (&(put_in(user[:hashed_password], &1)).()
>>>> # but considerably messy either way
>>>> |> ...
>>>> end
>>>>
>>>> The main issue in conceptualizing a more elegant workflow to represent
>>>> these transformations is that any construct implemented will make an
>>>> affordance in one direction or the other. A pipe-to-argument operator
>>>> would
>>>> offer strong reusability, but proposals in this group suggested a token
>>>> that poorly expresses its usage (I'm thinking of a thread where ~| had
>>>> been proposed as an anonymous variable binding with |>) . The other
>>>> proposals are for inflexible cases that would necessitate more extensions
>>>> for further implementations, and/or borrow symbols from other languages
>>>> that are ambiguous in their usage (such as proposals for <|, |<, and
>>>> |>> as tokens for pipe-to-last).
>>>>
>>>> I think Elixir is in a unique position to solve this problem, though.
>>>> If I could draw a correlation, the pin macro, Kernel.^/1 is used to
>>>> bind a variable in at a location for pattern-matching...
>>>>
>>>> good_status = "200"
>>>> {^good_status, response} = fetch("www.example.com")
>>>> # expands to {"200", response} = fetch("www.example.com")
>>>>
>>>> I believe there's syntactic justification for a new pipe macro,
>>>> Kernel.|^/2, that would interact with a Kernel.^/0 macro, to combine
>>>> the concepts of the pipe and the pin. Assuming the current pipe
>>>> operator symbolically translates | to "pipe to" and > to "the left", |^
>>>> would
>>>> represent "pipe to the pin". The pin would appear at least once in the
>>>> expression on the right in order to represent where the expression on the
>>>> left will appear, expanded, like such:
>>>>
>>>> def encrypt_password(user, password) do
>>>> password
>>>> |> Salt.add
>>>> |> BCrypt.hash
>>>> |^ put_in(user[:hashed_password], ^)
>>>> |> ...
>>>> # expands to ...(put_in(user[:hashed_password],
>>>> BCrypt.hash(Salt.add(password)))
>>>> end
>>>>
>>>> Benefits to this approach:
>>>>
>>>> - The current pipe operator behavior remains pristine
>>>> - ^/0 continues to act as a reference to expanding a variable
>>>> - No introduction of new symbolic concepts
>>>> - Syntax is flexible enough to expand to any (or multiple) argument
>>>> position
>>>>
>>>> # more contrived examples!!
>>>> > map = %{little_bunny: %{}}
>>>> > :foo
>>>> |^ put_in(map[:little_bunny][^], ^)
>>>> %{little_bunny: %{foo: :foo}}
>>>>
>>>> > "Ton" |^ (^<>"y! " <> ^<>"i! " <> ^<>"é!")
>>>> "Tony! Toni! Toné!"
>>>>
>>>> Again, I know there've been many discussions about the drawbacks to
>>>> implementing a new pipe mechanism, and the Elixir community may have
>>>> already come to a consensus around how to handle transformations like
>>>> this,
>>>> but I think the longevity of this topic points to a desire for some kind
>>>> of
>>>> better solution.
>>>>
>>>> If everyone is tired of talking about this, I'll happily let it end
>>>> here, but I thought one more perspective couldn't hurt.
>>>>
>>>> Thanks for reading! ^_^
>>>>
>>>> On Wednesday, July 9, 2014 at 5:10:19 AM UTC-7, José Valim wrote:
>>>>>
>>>>> at the risk of being painfully obvious, this works:
>>>>>>
>>>>>> :crypto.rand_bytes(8)
>>>>>> |> Base.encode16
>>>>>> |> (&("--------FormDataBoundary" <> &1)).()
>>>>>>
>>>>>
>>>>> Yup, this form works and is usually the solution proposed for such
>>>>> cases.
>>>>>
>>>>> I concur that allowing such forms would be a special case for pipe and
>>>>> possibly confusing in the long term. However, given that many were
>>>>> expecting it to work, I decided to gather everyone's feedback. And based
>>>>> on
>>>>> this thread, it doesn't seem we should change the pipe operator.
>>>>>
>>>>>
>>>>> *José Valim*
>>>>> www.plataformatec.com.br
>>>>> Skype: jv.ptec
>>>>> Founder and Lead Developer
>>>>>
>>>>>
>>>>> --
>>
>>
>> *José Valim*
>> www.plataformatec.com.br
>> Skype: jv.ptec
>> Founder and Director of R&D
>>
>
--
You received this message because you are subscribed to the Google Groups
"elixir-lang-core" group.
To unsubscribe from this group and stop receiving emails from it, send an email
to [email protected].
To view this discussion on the web visit
https://groups.google.com/d/msgid/elixir-lang-core/39274730-c7b8-4a38-b4ca-431538a621e7%40googlegroups.com.
For more options, visit https://groups.google.com/d/optout.