Size: a a a

2019 December 05

DA

Denis Afonin in pro.elixir
А есть ли какие-нибудь паттерны, чтобы покрасивее написать что-то типа:

v = if (t = something1()), do: do_something1(v, t), else: v
v = if (t = something2()), do: do_something2(v, t), else: v
v = if (t = something3()), do: do_something3(v, t), else: v
?
источник

DA

Denis Afonin in pro.elixir
Например, конструирование Ecto-запроса получается не очень благовидным:

   q = from v in Something, …
   q = if (limit = opts[:limit] || @default_limit), do: from(q, limit: ^limit), else: q
   q = if (begin_at = opts[:begin_at]), do: from(t in q, where: t.at >= ^begin_at), else: q
   q = if (end_at = opts[:end_at]), do: from(t in q, where: t.at < ^end_at), else: q
   …
источник

AA

Adilet Abylov in pro.elixir
with, case, паттерн матчинг функциями
источник

IB

Ilya Borovitinov in pro.elixir
Denis Afonin
А есть ли какие-нибудь паттерны, чтобы покрасивее написать что-то типа:

v = if (t = something1()), do: do_something1(v, t), else: v
v = if (t = something2()), do: do_something2(v, t), else: v
v = if (t = something3()), do: do_something3(v, t), else: v
?
Можно написать функцию и делать pipe

def maybe_apply(value, nil, _applier), do: value
def maybe_apply(value, fn, applier) when function(fn, 0), do: maybe_apply (value, fn.(), applier)

def maybe_apply(value, check, applier) when not is_function(check) and is_function(applier, 2), do: applier.(value, check)



q = (from v in Something, …)
|> maybe_apply(opts[:limit] || @default_limit), &from(&1, limit: &2))
|> maybe_apply(opts[:begin_at], &from(t in &1, where: t.at >= ^&2))
|> maybe_apply(opts[:end_at], &from(t in &1, where: t.at < ^&2))
источник

IB

Ilya Borovitinov in pro.elixir
Я с телефона и не уверен, что с амперсандом и from так можно, но идея ясна, думаю
источник

DA

Denis Afonin in pro.elixir
Ilya Borovitinov
Можно написать функцию и делать pipe

def maybe_apply(value, nil, _applier), do: value
def maybe_apply(value, fn, applier) when function(fn, 0), do: maybe_apply (value, fn.(), applier)

def maybe_apply(value, check, applier) when not is_function(check) and is_function(applier, 2), do: applier.(value, check)



q = (from v in Something, …)
|> maybe_apply(opts[:limit] || @default_limit), &from(&1, limit: &2))
|> maybe_apply(opts[:begin_at], &from(t in &1, where: t.at >= ^&2))
|> maybe_apply(opts[:end_at], &from(t in &1, where: t.at < ^&2))
Да, я тоже думаю в этом направлении (только вместо функции макрос, сейчас разбираюсь, как value забиндить в стиле Ecto.Query.where/3).

Просто я подумал, что многие с подобным паттерном сталкивались и, может быть, давно уже придумали изящное решение без кастомных функций/макросов.
источник

IB

Ilya Borovitinov in pro.elixir
Denis Afonin
Да, я тоже думаю в этом направлении (только вместо функции макрос, сейчас разбираюсь, как value забиндить в стиле Ecto.Query.where/3).

Просто я подумал, что многие с подобным паттерном сталкивались и, может быть, давно уже придумали изящное решение без кастомных функций/макросов.
Я не встречал других решений, но на то там и дана система макросов :)
Если допигешь макрос - покажи, мне лично будет интересно посмотреть, чем он от функции в применении будет отличаться, и что в итоге будет чище
источник

IB

Ilya Borovitinov in pro.elixir
А то самому пока не было кейсов сильно макросы кроме using писать
источник

DA

Denis Afonin in pro.elixir
Ilya Borovitinov
Я не встречал других решений, но на то там и дана система макросов :)
Если допигешь макрос - покажи, мне лично будет интересно посмотреть, чем он от функции в применении будет отличаться, и что в итоге будет чище
Ну, кое-что сналёту получилось:

  defmacro cond_pipe(value, condition, do: do_clause) do
   quote do
     if unquote(condition) do
       unquote(value) |> unquote(do_clause)
     else
       unquote(value)
     end
   end
 end

 defmacro cond_do(value, name \\ :value, condition, do: do_clause) do
   quote do
     with unquote(Macro.var name, nil) <- unquote(value) do
       if unquote(condition) do
         unquote(do_clause)
       else
         unquote(value)
       end
     end
   end
 end

   from(t in Something,
     |> (cond_pipe lim = opts[:limit], do: limit ^lim)
     |> (cond_pipe begin_at = opts[:begin_at] do where [t], t.at >= ^begin_at end)
     |> (cond_pipe end_at = opts[:end_at] do where [t], t.at < ^end_at end)
     |> (repo.all timeout: @query_timeout)

iex(abillix@raznix.vnix)3> 3                                  
3
iex(abillix@raznix.vnix)4> v |> cond_do(value > 0, do: value-1)
2
iex(abillix@raznix.vnix)5> value
** (CompileError) iex:5: undefined function value/0

iex(abillix@raznix.vnix)5> v |> cond_do(:vv, vv > 0, do: vv - 1)
1
iex(abillix@raznix.vnix)6> vv
** (CompileError) iex:6: undefined function vv/0

iex(abillix@raznix.vnix)6> v |> cond_do(:vv, vv > 0, do: vv - 1)
0
iex(abillix@raznix.vnix)7> v |> cond_do(:vv, vv > 0, do: vv - 1)
0
источник

D

Dmitry in pro.elixir
Denis Afonin
Ну, кое-что сналёту получилось:

  defmacro cond_pipe(value, condition, do: do_clause) do
   quote do
     if unquote(condition) do
       unquote(value) |> unquote(do_clause)
     else
       unquote(value)
     end
   end
 end

 defmacro cond_do(value, name \\ :value, condition, do: do_clause) do
   quote do
     with unquote(Macro.var name, nil) <- unquote(value) do
       if unquote(condition) do
         unquote(do_clause)
       else
         unquote(value)
       end
     end
   end
 end

   from(t in Something,
     |> (cond_pipe lim = opts[:limit], do: limit ^lim)
     |> (cond_pipe begin_at = opts[:begin_at] do where [t], t.at >= ^begin_at end)
     |> (cond_pipe end_at = opts[:end_at] do where [t], t.at < ^end_at end)
     |> (repo.all timeout: @query_timeout)

iex(abillix@raznix.vnix)3> 3                                  
3
iex(abillix@raznix.vnix)4> v |> cond_do(value > 0, do: value-1)
2
iex(abillix@raznix.vnix)5> value
** (CompileError) iex:5: undefined function value/0

iex(abillix@raznix.vnix)5> v |> cond_do(:vv, vv > 0, do: vv - 1)
1
iex(abillix@raznix.vnix)6> vv
** (CompileError) iex:6: undefined function vv/0

iex(abillix@raznix.vnix)6> v |> cond_do(:vv, vv > 0, do: vv - 1)
0
iex(abillix@raznix.vnix)7> v |> cond_do(:vv, vv > 0, do: vv - 1)
0
имхо, макросами лучше не увлекаться, оставь их для конфигов) а if, cond, with - они и так макросы
источник

D

Dmitry in pro.elixir
всё украдено до нас, как говорится
источник

DA

Denis Afonin in pro.elixir
Хорошо. Но как тогда уменьшить бойлерплейт в в вышеописанном случае?

И можно поконкретнее, какие могут быть подводные камни в моём варианте?
источник

D

Dmitry in pro.elixir
я бы не назвал это бойлерплейтом, если всё по ф-циям разбить и по DRY. Я думаю, макросы усложняют понимание кода. Реальная выгода от них в DSL и конфигах(модуль, который на этапе компиляции «превращается» в клиент и стаб в зависимости от environment)
источник

DA

Denis Afonin in pro.elixir
Да, такой подход мне понятен, и для сложной командной разработки он, конечно, оправдан.

Я же пока "играюсь" с новым языком, пробуя те или иные подходы; в общем-то, для меня сейчас любая программа на Elixir-е - это, пожалуй, DSL 😊
источник

DA

Denis Afonin in pro.elixir
И если каждый очередной раз, встречая паттерн вида v = if cond, do: …, else v, я "спотыкаюсь" на нём, то, по сути, выхода тут только три: либо продолжать раз за разом "спотыкаться" (искренне матерясь), либо устранить причину вышеописанным способом, либо перестроить своё сознание, устранив из него причину "спотыкания".
источник

D

Dmitry in pro.elixir
Denis Afonin
Да, такой подход мне понятен, и для сложной командной разработки он, конечно, оправдан.

Я же пока "играюсь" с новым языком, пробуя те или иные подходы; в общем-то, для меня сейчас любая программа на Elixir-е - это, пожалуй, DSL 😊
если так - согласен)
источник

D

Dmitry in pro.elixir
Denis Afonin
И если каждый очередной раз, встречая паттерн вида v = if cond, do: …, else v, я "спотыкаюсь" на нём, то, по сути, выхода тут только три: либо продолжать раз за разом "спотыкаться" (искренне матерясь), либо устранить причину вышеописанным способом, либо перестроить своё сознание, устранив из него причину "спотыкания".
я ифы чаще как тернарники использую - а так: with, case и pattern matching функциями
источник

S

Shieldy in pro.elixir
Леона, пожалуйста, нажмите на кнопку ниже в течение указанного времени, иначе вы будете кикнуты. Спасибо! (60 сек)
источник

D

Dmitry in pro.elixir
Denis Afonin
Например, конструирование Ecto-запроса получается не очень благовидным:

   q = from v in Something, …
   q = if (limit = opts[:limit] || @default_limit), do: from(q, limit: ^limit), else: q
   q = if (begin_at = opts[:begin_at]), do: from(t in q, where: t.at >= ^begin_at), else: q
   q = if (end_at = opts[:end_at]), do: from(t in q, where: t.at < ^end_at), else: q
   …
тут можно dynamic использовать
источник

IB

Ilya Borovitinov in pro.elixir
Denis Afonin
Например, конструирование Ecto-запроса получается не очень благовидным:

   q = from v in Something, …
   q = if (limit = opts[:limit] || @default_limit), do: from(q, limit: ^limit), else: q
   q = if (begin_at = opts[:begin_at]), do: from(t in q, where: t.at >= ^begin_at), else: q
   q = if (end_at = opts[:end_at]), do: from(t in q, where: t.at < ^end_at), else: q
   …
И правда, ровно твой кейс описан в dynamic
https://hexdocs.pm/ecto/dynamic-queries.html#dynamic-fragments
источник