Sometimes I remember the Access module and how woefully underutilized it is.

What if we added `Access.skip_if` and `Access.skip_where`?

Usage would look like this:

```elixir
map
|> put_in([Access.skip_if(false), :key], value)
|> put_in([Access.skip_where(fn map -> map.valid? end), :key], value)
```

> On Dec 6, 2024, at 5:27 PM, Christopher Keele <christheke...@gmail.com> wrote:
> 
> > One pattern I see repeated constantly in different apps developed by myself 
> > or others is adding values to a map conditionally or returning the map 
> > unchanged.
> 
> I agree this is a wart common with maps in particular (as the out-of-the-box 
> update-often data structure), but the problem is not specific to the Map API; 
> rather, conditional expressions in Elixir.
> 
> The intentional design decision for if-and-friends conditionals to honor 
> lexical scoping was not originally part of the language, but added for 
> consistency with other branching structures early on. So you in fact used to 
> be able to just do  map = %{}; if conditional, do: map = Map.put(map, :foo, 
> :bar). Changing this was controversial at the time partially because of this 
> knock-on effect of having to always exhaustively handle all branches of a 
> conditional if assigning results directly to a variable (or otherwise only 
> temporarily branching the control flow of the current scope).
> 
> TL;DR you have to do a lot more foo = if ..., else: foo to keep conditional 
> lexical scoping consistent, and I'm in agreement with José that it's that 
> slightly irritating else: foo that (if anything) should be solved 
> holistically at the core language level, rather than extending individual 
> data-structure's APIs.
> I don't think we can "solve" else: foo without discussing why it's a problem. 
> I can think of two rationales, but interested in other opinions:
> 
> 1. Accidentally omitting it can lead to unintentional nil assignments.
> 2. It is syntactically noisy for what it accomplishes (from the programmer's 
> perspective, literally "nothing"—as in, leaving the assignment in question 
> the same).
> 
> In my experience, 1. is not a huge issue, but others may have stronger 
> opinions. It's 2. that makes it a wart. The problem is that there is not much 
> more syntax to strip away from if: no else clause means nil and that cannot 
> reasonably change, and the rest of the macro does not understand that there 
> is a "subject" being assigned to for it to choose to return unchanged. I 
> would propose either introducing a new conditional assignment macro (as 
> discussed a little here already), or consider additional syntaxes for 
> conditionals that makes it a little easier visually to ignore the fallback 
> case.
> 
> In either case, as José points out, we need to consider 3 components: a 
> subject to or to not update, a condition, and an action.
> New Macro
> 
> I agree with the criticisms of then_if. I would rather see something 
> explicitly about updating the subject. Say, a hygine-modifying 
> update_when(subject, condition, fn/block) that required a variable reference 
> subject. Ex:
> 
> update_when(map, condition, &Map.put(&1, :key, value))
> 
> or
> 
> update_when(map, condition) do
>   Map.put(map, :key, value)
> end
> 
> The pipe-ability of this is limited by design, but this could still work with 
> then:
> changeset
> |> do_some_checking()
> |> then(fn changeset -> 
>   update_when(changeset, changeset.valid) do
>     do_more(changeset)
>   end
> end)
> |> do_something_else()
> 
> Honestly, not in love with this, but I'm slow to warm to these things. We 
> could get cuter with the syntax by overloading guards:
> 
> update map when condition do
>   Map.put(map, :key, value)
> end
> 
> Reads better, technically parses, but kind of inconsistent with other guard 
> constructs conceptually. Also, how would piping work? Is there a way for this 
> to make sense in a larger pipeline:
> 
> map
> |> update when condition do
>   Map.put(map, :key, value)
> end
> Changing if
> 
> Since if cannot be fundamentally aware of a subject, it would have to have a 
> place to specify the default fallback, which else already does in this 
> situation; it's as semantically dense as it can be. To alleviate the noise 
> the fallback block introduces, one option would be to have the if macro 
> accept optional keyword arguments before the block, merging them together, 
> allowing hoisting the trivial else case inline with the condition, 
> independent of the consequent, to create a denser syntax:
> 
> map = if condition, else: map do
>   Map.put(map, :key, value)
> end
> 
> This also cannot really be piped through without then, but otherwise reads 
> (slightly) nicer than the base case:
> 
> changeset
> |> do_some_checking()
> |> then(fn changeset -> 
>   if changset.valid, else: changeset do
>     do_more(changeset)
>   end
> end)
> |> do_something_else()
> 
> It's a really small change that I think pretty much fully addresses the 
> syntactic noise problem. It does lead to this rather odd formulation I'm not 
> sure about:
> 
> condition
> |> if(else: map) do
>   Map.put(map, :key, value)
> end
> Changing case/cond
> 
> Of course, we do already have a conditional expression with a semantic notion 
> of a subject, case. However, there's no specific syntax for referencing it, 
> outside clause heads, so the programmer would have to provide it again, 
> similar to the fallback _ -> subject construct today:
> 
> map = case map do
>   %{} -> Map.put(map, :key, value)
>   _ -> map
> end
> 
> I think this is orthogonal to the problem we are trying to solve, but if we 
> went the if(conditional, else: fallback) do route, we'd need to consider if 
> we should extend case/cond with similar semantics for consistency's sake, so:
> 
> case map, else: map do
>   %{key: old_value} -> Map.put(map, :key, old_value + 1)
>   %{} -> Map.put(map, :key, 0)
> end
> 
> Of course the problem here is that implies the existence of general else 
> clauses in those constructs:
> 
> case map do
>   %{key: old_value} -> Map.put(map, :key, old_value + 1)
>   %{} -> Map.put(map, :key, 0)
> else
>   map
> end
> 
> We could implement support this and have it compile down to the correct _ -> 
> map fallback case and warn/error if one was already provided (similarly with 
> true -> map for cond), but generally, not a fan of so many ways to do the 
> same thing.
> This is less an argument for adding else to these constructs, and more an 
> argument for calling the keyword argument to if something else less likely to 
> be confused with block semantics. So I'd say that I personally am warmest on 
> the if  proposal alone, and am open to calling the keyword something 
> different and merging it in with the block with the same else duplication 
> warnings/errors we'd need regardless of name, like:
> 
> map = if condition, fallback: map do
>   Map.put(map, :key, value)
> end
> 
> The update subject when condition do syntax sugar reads very nicely, but 
> feels like it would lead to confusion down the line.
> On Friday, December 6, 2024 at 11:01:28 AM UTC-6 jimf...@gmail.com wrote:
>> then_if has no meaning to me and breaks my brain. 
>> 
>> Seems not to flow with other pipeline commands. 
>> 
>> Dr. Jim Freeze, Ph.D.
>> ElixirConf®
>> ElixirConf.com <https://elixirconf.com/>
>> ElixirConf.eu <http://elixirconf.eu/>
>> (m) 512 949 9683 <tel:(512)%20949-9683>
>> 
>> 
>> On Fri, Dec 6, 2024 at 10:58 AM José Valim <jose....@gmail.com <>> wrote:
>>> Thank you Zach. When I wrote the proposal I felt it was missing something 
>>> still and I think you nailed it.
>>> 
>>> Passing two anonymous functions would help with the pipeline but it feels 
>>> it would be detrimental to other cases.
>>> 
>>> 
>>> 
>>> José Valim
>>> https://dashbit.co/
>>> 
>>> 
>>> On Fri, Dec 6, 2024 at 17:41 Zach Daniel <zachary....@gmail.com <>> wrote:
>>>> Despite typically being a "put it in the standard library" guy, I don't 
>>>> think that `then_if` actually composes as well as it looks like it does on 
>>>> the tin due to the fact that `then` is often used in pipelines, where some 
>>>> transformation has happened and you want to check a condition *on that 
>>>> result*. For example:
>>>> 
>>>> ```elixir
>>>> changeset
>>>> |> do_some_checking()
>>>> |> then_if(<is_valid>, &do_more/1)
>>>> ```
>>>> 
>>>> I think that `then` is kind of "already" the composition tool that we need 
>>>> for expressive pipes.
>>>> 
>>>> ```elixir
>>>> changeset
>>>> |> do_some_checking()
>>>> |> then(fn changeset -> 
>>>>   If changeset.valid do
>>>>       do_more(changeset)
>>>>   else
>>>>      changeset
>>>>   end
>>>> end)
>>>> ```
>>>> 
>>>> I can see an argument that it is very verbose, but its also about as 
>>>> flexible as it can get. My suggestion would be to, if added, have 
>>>> `then_if` take a function as its first argument.
>>>> 
>>>> ```elixir
>>>> changeset
>>>> |> do_some_checking()
>>>> |> then_if(&(&1.valid?), &do_more/1)
>>>> ```
>>>> 
>>>> 
>>>>> On Dec 6, 2024, at 10:30 AM, Ben Wilson <benwil...@gmail.com <>> wrote:
>>>>> 
>>>>> Exploring what that looks concretely in this case:
>>>>> 
>>>>> ```
>>>>> map
>>>>> |> other_stuff
>>>>> |> then_if(opts[:foo], &Map.put(&1, :key, value))
>>>>> ```
>>>>> 
>>>>> I like it! Conditional map insert helper functions are definitely 
>>>>> something we've written over and over again in our code bases and while 
>>>>> it's easy to do, I think in some cases this is cleaner looking than a 
>>>>> proliferation of `maybe_put_foo` functions.
>>>>> 
>>>>> - Ben
>>>>> 
>>>>> On Friday, December 6, 2024 at 9:59:40 AM UTC-5 José Valim wrote:
>>>>>> Hi Juan!
>>>>>> 
>>>>>> My initial gut feeling is that this approach does not scale. What if you 
>>>>>> want to delete a key conditionally? Should we have delete_if?
>>>>>> 
>>>>>> It feels a more general approach would be to introduce `then_if`:
>>>>>> 
>>>>>> then_if(subject, condition?, function)
>>>>>> 
>>>>>> Or similar. :)
>>>>>> 
>>>>>> José Valim
>>>>>> https://dashbit.co/
>>>>>> 
>>>>>> 
>>>>>> On Fri, Dec 6, 2024 at 3:27 PM Juan Manuel Azambuja <ju...@mimiquate.com 
>>>>>> <>> wrote:
>>>>>>> Hello,
>>>>>>> 
>>>>>>> After working with Elixir for some time I have found myself repeating 
>>>>>>> some patterns when dealing with maps.
>>>>>>> 
>>>>>>> One pattern I see repeated constantly in different apps developed by 
>>>>>>> myself or others is adding values to a map conditionally or returning 
>>>>>>> the map unchanged. This comes in different flavors:
>>>>>>> 
>>>>>>> 
>>>>>>> or
>>>>>>> 
>>>>>>> 
>>>>>>> When this pattern gets used enough in an app, it's normal to see it 
>>>>>>> abstracted in a MapUtils module that updates the map conditionally if a 
>>>>>>> condition is met or returns the map unchanged otherwise.
>>>>>>> 
>>>>>>> My proposal is to include Map.put_if/4 which would abstract the 
>>>>>>> condition check and return the map unchanged if the condition is not 
>>>>>>> met:
>>>>>>> 
>>>>>>> 
>>>>>>> 
>>>>>>> Enhancing the API by doing this will result in less code and more 
>>>>>>> readable solutions.
>>>>>>> 
>>>>>>> Thanks for reading!
>>>>>>> 
>>>>>> 
>>>>>>> -- 
>>>>>>> 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 elixir-lang-co...@googlegroups.com <>.
>>>>>>> To view this discussion visit 
>>>>>>> https://groups.google.com/d/msgid/elixir-lang-core/ed7da716-b9f5-4f64-a77d-d32696326b9en%40googlegroups.com
>>>>>>>  
>>>>>>> <https://groups.google.com/d/msgid/elixir-lang-core/ed7da716-b9f5-4f64-a77d-d32696326b9en%40googlegroups.com?utm_medium=email&utm_source=footer>.
>>>>> 
>>>>> 
>>>>> -- 
>>>>> 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 elixir-lang-co...@googlegroups.com <>.
>>>>> To view this discussion visit 
>>>>> https://groups.google.com/d/msgid/elixir-lang-core/e9e799a2-ad69-4791-bd9a-22bca327652fn%40googlegroups.com
>>>>>  
>>>>> <https://groups.google.com/d/msgid/elixir-lang-core/e9e799a2-ad69-4791-bd9a-22bca327652fn%40googlegroups.com?utm_medium=email&utm_source=footer>.
>>>> 
>>>> 
>>>> -- 
>>>> 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 elixir-lang-co...@googlegroups.com <>.
>>>> To view this discussion visit 
>>>> https://groups.google.com/d/msgid/elixir-lang-core/61753088-63E3-4DA0-8CEF-925149D789C6%40gmail.com
>>>>  
>>>> <https://groups.google.com/d/msgid/elixir-lang-core/61753088-63E3-4DA0-8CEF-925149D789C6%40gmail.com?utm_medium=email&utm_source=footer>.
>>> 
>>> 
>>> -- 
>>> 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 elixir-lang-co...@googlegroups.com <>.
>> 
>>> To view this discussion visit 
>>> https://groups.google.com/d/msgid/elixir-lang-core/CAGnRm4JSE7vhfHukf2EZ6bmi4%3DNrfX28q3%2BKpQGZMgFoCM%3D%2BWg%40mail.gmail.com
>>>  
>>> <https://groups.google.com/d/msgid/elixir-lang-core/CAGnRm4JSE7vhfHukf2EZ6bmi4%3DNrfX28q3%2BKpQGZMgFoCM%3D%2BWg%40mail.gmail.com?utm_medium=email&utm_source=footer>.
> 
> 
> -- 
> 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 elixir-lang-core+unsubscr...@googlegroups.com 
> <mailto:elixir-lang-core+unsubscr...@googlegroups.com>.
> To view this discussion visit 
> https://groups.google.com/d/msgid/elixir-lang-core/a2b730c9-67e8-44d0-be8f-22ba4761fea7n%40googlegroups.com
>  
> <https://groups.google.com/d/msgid/elixir-lang-core/a2b730c9-67e8-44d0-be8f-22ba4761fea7n%40googlegroups.com?utm_medium=email&utm_source=footer>.

-- 
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 elixir-lang-core+unsubscr...@googlegroups.com.
To view this discussion visit 
https://groups.google.com/d/msgid/elixir-lang-core/7E1B3911-34FA-4BA6-A0EF-452223B2546A%40gmail.com.

Reply via email to