Comments (16)
Thanks for Credo from me too. Another example from my code is:
Stream.resource(&stream_start/0, stream_data(api_call_fn), &stream_end/1)
|> Stream.each(fn(data) -> send(caller, {message, data}) end)
|> Stream.run
Where I have to introduce a variable to prevent the warning
stream = Stream.resource(&stream_start/0, stream_data(api_call_fn), &stream_end/1)
stream
|> Stream.each(fn(data) -> send(caller, {message, data}) end)
|> Stream.run
Is there any plan to introduce something like # credo:disable Refactor.PipeChain
?
from credo.
@hickscorp the fact that that looks better is very much a matter of opinion. Personally, I think it's extremely confusing because of all the indentation and all the things going on. I'd much rather have things named:
a =
try do
...
end
{reply, state} =
case a do
...
end
{:reply, reply, state}
from credo.
@chrismcg There is, although I am still undecided on the format, since I personally dislike "magic comments". I would like to see a more "Elixiry" solution and I might just have had an idea 👍
P.S. While I know this does solve the actual problem we are discussing, wouldn't this be the "Credo compatible solution"?
&stream_start/0
|> Stream.resource(stream_data(api_call_fn), &stream_end/1)
|> Stream.each(fn(data) -> send(caller, {message, data}) end)
|> Stream.run
from credo.
I don't disagree. Your examples show that we need exceptions from the rule.
The value of this check is when you do things like:
String.downcase(params[:user_name])
|> valid_username?()
I would argue that
params[:user_name]
|> String.downcase
|> valid_username?()
is so much clearer.
So what would you propose as "exceptional cases"? Ranges seem like a good candidate, like in your first example.
from credo.
I agree with your example, it can seem clearer. In fact, I'm habitually used to writing the pipe style as I code along, in anticipation that the output may be used in further transformation. Perhaps, I'm so used to looking at functions being called via pipe, that function calls with 1 less argument within the parentheses looks less cluttered/nicer to read.
E.g. I like
values = input_str |> String.split(~r/some_long_complex_ugly_looking_regex/, trim: true)
instead of
values = String.split(input_str, ~r/some_long_complex_ugly_looking_regex/, trim: true)
However, the convention inside Elixir source code suggests otherwise. The pipe operator seems avoided when the output isn't further piped into another function.
In short, what I'm saying is this might be an area which boils down to personal taste. And it might be generating more noise than signal to suggest this refactoring.
from credo.
There is not much difference between our positions, I think.
We both agree that the "thing" going into the pipe should be clear. I see that in your initial examples, so I think rather than removing the check, I would like to search for sane defaults like "blocks and ranges can go into a pipe" with a possibility for configuration.
Will try to incooperate something into the 0.3.0
release 👍
from credo.
Great to hear you have plans. In ruby I have rubocop as part of CI and it fails the build if it isn't happy and so being able to disable it for a particular section of code is necessary sometimes. At the moment I've Credo turned off on CI for my Elixir projects because I can't tell it "I know, I'll fix it later but it's not the most important thing to be doing right now"
I hadn't thought of your approach above. I don't think it's something I'd do though.
foo
|> bar
|> baz
The code above tends to imply, in my mind at least, that foo
here is being passed through each function, like conn
in Phoenix or the stream in the example we've been talking about. I'm not passing the stream_start function through each call to stream so having it bare at the top of the pipe might be misleading compared to normal usage. Have to think about it more though.
from credo.
Yeah, I know what you mean with the conn
example. I have had the same problem with actual production code that I thought to myself "is it really okay that the type changes constantly between pipes"? Is that maintainable code?
Contrived example:
input_str # string with usernames
|> String.split(",") # list of usernames
|> Enum.map(&find_user_by_name/1) # list of %User{}
|> Enum.filter(&(&1.email_enabled?)) # list of %User{}
|> Enum.each(&send_reminder/1) # :ok
I am confident that we, as a community, will work things like these out over time. There's more open source Elixir code every day and more production code as well. Good times ahad 😁
from credo.
Agree there are good times ahead. I'm hoping later in the year I'll have more to contribute to Credo than examples on issues! In the meantime I still love chatting about this stuff though...
With the input_str
example although you don't pass the string all the way through the rest of the pipe is concerned with transforming it. &stream_start/0
is only used once in the Stream.resource
call so I don't think it fits the metaphor of pipes transforming an input to an output.
from credo.
You're right, "taking and transforming data" is the purpose of pipes and the &stream_start/0
example does not do that.
I will work on a method to exclude individual parts of code from certain checks 👍
from credo.
@chrismcg Credo v0.3.0
now includes support for a @lint
attribute. See CHANGELOG for further details.
What do you think?
from credo.
Looks great, thanks! Module attributes are a perfect solution for this.
from credo.
Thanks for @lint! And together with .credo.exs gives us full ability for customization.
from credo.
I would really like this to be extended to case
statements too... For example:
try do
f.(data)
rescue
e -> {:error, {:ex, e, System.stacktrace()}}
end
|> case do
{:ok, new_data} -> {:ok, {name, new_data}}
err -> {err, state}
end
|> Tuple.insert_at(0, :reply)
looks really better than the non-pipe version. Am I missing something?
from credo.
@hickscorp I just answered the same proposal of yours here. As @whatyouhide suggests, this is very much a matter of personal taste.
Also, I would be open to the possibility that this check is not "good" after all. Maybe it had the right intentions, but the current implementation is not the right way to accomplish them.
IMHO both refactorings Andrea and I suggested are what Credo should suggest. As I replied to the same proposal in the other issue, calling Tuple.insert_at/3
might make your function less comprehendable and unnecessarily confusing.
from credo.
@rrrene As I stated in the other issue, I agree to your comment, that it's a matter of taste. Therefore, a way of configuring that behaviour globally would be great (Eg disallow impure pipping unless the target function is within a list of arrity, much like how mix format
allows to exception-list the use of parentheses) using a configuration file.
@whatyouhide I agree that my example wasn't the best from a clarity perspective. There are other examples that are much clearer, with just a list of things being pipped through lots of list operations, and in the end to a case
statement, for example.
What do you think?
from credo.
Related Issues (20)
- Predicate callbacks from behavior are not ignored HOT 2
- Issue with Unused variables confused about the strategy HOT 1
- missing spec on unquoted function raise error HOT 3
- Credo.Check.Warning.WrongTestFileExtension displayed in non-test module in v1.7.4 HOT 8
- NoAmbiguousAliases fails on function param with name alias HOT 3
- "Most of the time you are using the multi-alias/require/import/use syntax..." with compile_env HOT 4
- Gracefully handle output of bitstring `:message` in `%Credo.Issue{}` HOT 8
- False positive: spec in quoted HOT 3
- False Positive: Macro sigils with uppercase words HOT 4
- `UnusedKeywordOperation` check should ignore `Keyword.validate!/2` HOT 4
- Arguable advice from `Credo.Check.Refactor.AppendSingleItem` HOT 1
- Predicate function names should not start with 'is', and should end in a question mark flagged for :ets.whereis HOT 2
- Not ready for Elixir 1.16: a lot of 'warning: negative steps are not supported in Enum.slice/2 HOT 1
- Elixir 1.17 issues HOT 7
- Dependency divergence on compile HOT 1
- Unicode characters (arrows) that indicate priority of found issues not printed correctly HOT 2
- Credo.Check.Warning.Dbg: doesn't warn when using `&dbg/1`
- Error `mix credo` on Elixir 1.17 and Erlang/OTP 27 HOT 4
- Credo.Check.Design.SkipTestWithoutComment and many others, fail to run (Elixir 1.17 issue?) HOT 1
- Error upgrading project to Elixir 1.17 HOT 3
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from credo.