Hello:
[this is going to be a little long-winded, but I'm hoping the format will assist other Elixir newbies]
I'm just getting started with Elixir and trying to understand the "Elixir way" of doing things. The immediate task at hand is a text file transformer. Ideally, I want the final code to look conceptually like this, the focus being on readability:
File.stream!(inFile)
|> Enum.map( transform1 )
|> Enum.map( transform2 )
|> Enum.map( transform3 )
|> IO.write
I started with this, the code failing as noted in the comment:
inFile = "./sample.tsv"
File.stream!(inFile) # iterates by :line, by default
|> Enum.map( String.replace("\t", ",") ) # fails with - wrong arity
|> IO.write
After some experimentation, I found that using the capture syntax fixed the arity problem, though I'm not at all clear on why [in IEx the same String.replace/3, getting the 1st arg from the pipeline works fine...]:
File.stream!(inFile)
# |> Enum.map( String.replace("\t", ",") ) # fails with - wrong arity
|> Enum.map( &(String.replace(&1, "\t", ",")) ) # works!
|> IO.write
Although, one has to be careful about where that 1st paren goes:
File.stream!(inFile)
# |> Enum.map( &(String.replace(&1, "\t", ",")) ) # works!
|> Enum.map (&(String.replace(&1, "\t", ",")) ) # fails with - protocol String.Chars not impl for #Function<0.33035400...
|> IO.write
Then, hoping to avoid the capture syntax, I created some helper functions [attached] that have an arity of /1 and tried calling those instead of the String.replace/3, but that failed, the same way my first attempt had:
import StringUtils
File.stream!(inFile)
# |> Enum.map( &(String.replace(&1, "\t", ",")) ) # works!
|> Enum.map( replaceTabWithComma ) # fails with - wrong arity
|> IO.write
Once again, capture syntax to the rescue:
File.stream!(inFile)
# |> Enum.map( replaceTabWithComma ) # fails with - wrong arity
|> Enum.map &(replaceTabWithComma(&1)) # works!
|> IO.write
But as soon as you add a 2nd transform, you get a different failure:
File.stream!(inFile)
|> Enum.map &(replaceTabWithComma(&1)) # works!
|> Enum.map &(trimInstr(&1)) # fails with - nested captures via & are not allowed: &(trimInstr(&1) |> IO.write())
|> IO.write
It doesn't look nested *to me*, and this failure does *not* occur if using multiple calls to the original String.replace/3 function. Hmmm. So, I tried capturing the helper functions, invoking them as above:
tabsToCommas = &replaceTabWithComma/1
shortenInstr = &trimInstr/1
updateYear = &chgYear/1
File.stream!(inFile)
|> Enum.map( &(tabsToCommas.(&1)) )
|> Enum.map( &(shortenInstr.(&1)) )
|> Enum.map( &(updateYear.(&1)) )
|> IO.write
Ok...that all works...still unclear why the capture syntax is there...what if I remove it?
File.stream!(inFile)
|> Enum.map( tabsToCommas )
|> Enum.map( shortenInstr )
|> Enum.map( updateYear )
|> IO.write
Woo-hoo! It works, and looks just how I wanted it to. Now, I clearly need to read the Elixir sources and see if I can figure out why! The final code is attached: transform.ex
So, finally, the question: Although presumably without the circuitous route, would the final version be considered "idiomatic Elixir"? Is there a better way to accomplish this?
thanks,
ak