axelson

axelson

Scenic Core Team

You’re second example pretty much works as is, I occasionally use a similar pattern.

I just added an extra paren for the “Space Cake” clause:

    inventory = [%{id: 1, name: "Space Cake", count: 11}]

    cond do
      moon_cake = Enum.find(inventory, &(&1.name == "Moon Cake")) ->
        {:eat, moon_cake.id}

      ((space_cake = Enum.find(inventory, &(&1.name == "Space Cake")))[:count] || 0) > 10 ->
        {:eat, space_cake.id}

      true ->
        nil
    end

Although if you can structure it as a single reduce it will be more performant (although you’ll need quite a long list before that will begin to matter much), but turning this into a reduce might make it more difficult to follow since it doesn’t seem like the operation you are doing is easily thought of as a transformation. Also if the clause is as complex as the “Space Cakes” clause I would probably think about restructuring the logic away from a cond, or maybe extracting a helper function.

1
Post #11
vans163

vans163 OP

Wow cool. How did I miss that.

Another thing missing thought is being able to treat it kinda like a fusion with a with statement, in that earlier assignments can be accessed later.

    cond do
      moon_cake = Enum.find(inventory, &(&1.name == "Moon Cake")) ->
        {:eat, moon_cake.id}

      ((space_cake = Enum.find(inventory, &(&1.name == "Space Cake")))[:count] || 0) > 10 ->
        {:eat, space_cake.id}

      !!space_cake and space_cake.count > 5 ->
        {:cook, space_cake.id}

      true ->
        nil
    end

Probably not so clean but just using for example purposes.

0
Post #12
lud

lud

Often I use what I call the “reverse with” form:

with :error <- try_this(),
     :error <- try_that(),
     :error <- try_other_stuff() do
  :error
else
  {:ok, _} = ok -> ok
end

In your case, that could be:

with nil <- Enum.find(inventory, &(&1.name == "Moon Cake")),
     nil <- Enum.find(inventory, &(&1.name == "Space Cake" && (&1[:count] || 0) > 10)) do
  nil
else
  item -> {:eat, item.id}
end
0
Post #13
bottlenecked

bottlenecked

Hi, I believe you’re trying to find a language feature that will exactly solve your particular business problem. I think that there’s an inherent mismatch with this approach as languages provide low-level tools that can be composed together to solve bigger business problems, they do not provide off-the-shelf solutions for every different business problem imaginable.

I believe that you could use most of the solutions already mentioned as most of them are quite extendable and can be made to fit the current problem as well as future changing requirements (like the Blackhole Cake curve balls that business people are so adept at throwing when you least expect them).

For what it’s worth, I believe you could break your problem down into 2 separate phases, the discovery and the action phases. You can Enum.reduce/3 over you inventory taking notes of which items you discovered (so all items, not a quick exit) and then decide on the importance/precedence of those findings with a simple if/else (or similar) decision making process

0
Post #14
vans163

vans163 OP

Yea this can work to an extent but its hard to read. I tried this pattern before but did not like that it reads very poorly and its hard to figure out what is going on since all the implications of the with (else clause and silently returning the match failure by default; I feel the last part causes chaos in a loosely typed language)

its also possible to assign then check in the with as well

inventory = [%{id: 1, name: "Space Cake", count: 11}]
with moon_cake <- Enum.find(inventory, & &1.name == "Moon Cake"),
     true <- is_nil(moon_cake) || {:eat, moon_cake.id},
     space_cake <- Enum.find(inventory, & &1.name == "Space Cake"),
     true <- is_nil(space_cake) || space_cake.count < 10 || {:eat, space_cake.id} do 
end

@bottlenecked

Yea definitely, but the with statement was created for exactly that. So if there is a nice way to solve a pattern it seems plausible to consider it at least as a feature. The reason why creating multiple guard-like functions does not work is that there are just so many variations and combinations that you end up with small functions that get passed 5+ arguments, then you start thinking maybe I should just pass a state object to each, which then leads to nightmare refactors if that state object structure changes. (Same nightmare refactor problem I see with ecto multis, tho the new 1.11 map key precompile checks might remedy this abit)

You can Enum.reduce/3.. yes this is a fine pattern and occasionally we use it but its still not ideal from a readability perspective IMO. Readability and maintainability is always subject to debate tho. On this line maybe to take this a step further, we should redesign our thinking and perhaps always use this type of pattern with a weight assigned to each action result. Then sort by weight and take the first. Kind of a GOAP like pattern.

0
Post #15
— All posts loaded —