我怎样才能 return Elixir 中的第一个真值?
How can I return the first truthy value in Elixir?
我正在阅读教科书使用 Elixir 学习函数式编程,我对示例问题的一些给定答案不满意。所述问题的中心是检查井字游戏的获胜者,但我会尝试将其归结为本质。假设我们要检查一些值元组的有效性和 return 返回的内容,例如
iex(1)> some_function({:some, :list, :of, :values})
{:ok}
iex(2)> some_function({:some, :other, :list, :of, :values})
{:error}
教科书解决方案是完全匹配允许的值:
def some_function({_, _, :of, _}), do: {:ok}
def some_function(_everything_else), do: {:error}
但是,我不喜欢这个解决方案,因为在大量情况下它会变得非常重复。我试图更直接地对逻辑进行编码,但我想不出一种惯用的方法来做到这一点。我试过了:
def some_function(input) do
test1(input) or
test2(input) or
{:error}
end
def test1({:some, :case}), do: false
def test1({:some, :other, :case}), do: {:ok}
def test2({:some, :case}), do: false
def test2({:some, :other, :case}), do: {:ok}
这在所有测试都失败时有效,但是当其中任何一个通过时,您会收到无法将 {:ok} 作为布尔值进行比较的错误。尽管我意识到这很可怕,但我最终做的是
def some_function(input) do
cond do
test1(input) -> test1(input)
test2(input) -> test2(input)
fail(input) -> {:error}
end
end
def test1({:some, :case}), do: false
def test1({:some, :other, :case}), do: {:ok}
def test2({:some, :case}), do: false
def test2({:some, :other, :case}), do: {:ok}
def fail(_input), do: true
从黑盒的角度来看,这实际上表现得很完美,但是代码中有重复(特别是每个测试必须评估两次,以确定真实性并捕获价值)。是否有一种惯用的方式 return 给定一系列要评估的测试用例的第一个真值?我精通 python 和 C,但我试图避免使用 if
并以“功能”风格执行此操作。
or/2
, unlike ||/2
,需要 boolean 参数,而后者会接受任何值,将 nil
和 false
视为 falsey
和其他所有内容作为 truthy
值。
- 单元素元组在绝大多数情况下都有点意义,使用原子本身 (
{:ok}
→ :ok
.)
- 有
Enum.find/3
that expects an enumerable which tuple is not, but we have Tuple.to_list/1
并且可以将输入转换为列表并调用 Enum.find/3
。
- 正如 Adam 在评论中所说,模式匹配解决方案简洁明了,在这种情况下效果很好。
模式匹配是惯用的,但是,正如您所指出的,当应用于大量案例时它可能会很笨重,如果您直到运行时才知道需要匹配的内容,它可能无法工作。给定的解决方案要求元组在特定索引 处具有特定成员 ,而您似乎更倾向于开放式解决方案。
作为其他人建议的变体,您可以尝试使用 Enum.member?/2
,例如测试元组是否包含您正在寻找的东西的东西:
iex> {:some, :list, :of, :values}
|> Tuple.to_list()
|> Enum.member?(:of)
true
或者,如果您想检查元组是否包含任何 list 可接受值,您可以使用 Enum.any?/2
,例如通过在保护子句中定义可接受值的列表
iex> {:some, :list, :of, :values}
|> Tuple.to_list()
|> Enum.any?(fn
x when x in [:of] -> true
_ -> false
end)
true
出于好奇,这是一个带有一些元编程的高性能解决方案,它为所有可能的元组生成所有子句(最大元组大小作为参数给出)
defmodule Checker do
defmacro __using__(opts) do
total = Keyword.get(opts, :count, 8)
for count <- 1..total, place <- 1..count do
pre = for _ <- 1..place - 1//1, do: Macro.var(:_, nil)
post = for _ <- place + 1..count//1, do: Macro.var(:_, nil)
quote do
def member?(what,
{unquote_splicing(pre), what, unquote_splicing(post)}
), do: true
end
end ++ [quote do: (def member?(_, _), do: false)]
end
end
defmodule Test do
use Checker, count: 4
end
测试为:
Test.member? :ok, {:ok}
#⇒ true
Test.member? :ok, {42, true, :ok, :foo}
#⇒ true
Test.member? :ok, {42, true, :foo}
#⇒ false
我正在阅读教科书使用 Elixir 学习函数式编程,我对示例问题的一些给定答案不满意。所述问题的中心是检查井字游戏的获胜者,但我会尝试将其归结为本质。假设我们要检查一些值元组的有效性和 return 返回的内容,例如
iex(1)> some_function({:some, :list, :of, :values})
{:ok}
iex(2)> some_function({:some, :other, :list, :of, :values})
{:error}
教科书解决方案是完全匹配允许的值:
def some_function({_, _, :of, _}), do: {:ok}
def some_function(_everything_else), do: {:error}
但是,我不喜欢这个解决方案,因为在大量情况下它会变得非常重复。我试图更直接地对逻辑进行编码,但我想不出一种惯用的方法来做到这一点。我试过了:
def some_function(input) do
test1(input) or
test2(input) or
{:error}
end
def test1({:some, :case}), do: false
def test1({:some, :other, :case}), do: {:ok}
def test2({:some, :case}), do: false
def test2({:some, :other, :case}), do: {:ok}
这在所有测试都失败时有效,但是当其中任何一个通过时,您会收到无法将 {:ok} 作为布尔值进行比较的错误。尽管我意识到这很可怕,但我最终做的是
def some_function(input) do
cond do
test1(input) -> test1(input)
test2(input) -> test2(input)
fail(input) -> {:error}
end
end
def test1({:some, :case}), do: false
def test1({:some, :other, :case}), do: {:ok}
def test2({:some, :case}), do: false
def test2({:some, :other, :case}), do: {:ok}
def fail(_input), do: true
从黑盒的角度来看,这实际上表现得很完美,但是代码中有重复(特别是每个测试必须评估两次,以确定真实性并捕获价值)。是否有一种惯用的方式 return 给定一系列要评估的测试用例的第一个真值?我精通 python 和 C,但我试图避免使用 if
并以“功能”风格执行此操作。
or/2
, unlike||/2
,需要 boolean 参数,而后者会接受任何值,将nil
和false
视为falsey
和其他所有内容作为truthy
值。- 单元素元组在绝大多数情况下都有点意义,使用原子本身 (
{:ok}
→:ok
.) - 有
Enum.find/3
that expects an enumerable which tuple is not, but we haveTuple.to_list/1
并且可以将输入转换为列表并调用Enum.find/3
。 - 正如 Adam 在评论中所说,模式匹配解决方案简洁明了,在这种情况下效果很好。
模式匹配是惯用的,但是,正如您所指出的,当应用于大量案例时它可能会很笨重,如果您直到运行时才知道需要匹配的内容,它可能无法工作。给定的解决方案要求元组在特定索引 处具有特定成员 ,而您似乎更倾向于开放式解决方案。
作为其他人建议的变体,您可以尝试使用 Enum.member?/2
,例如测试元组是否包含您正在寻找的东西的东西:
iex> {:some, :list, :of, :values}
|> Tuple.to_list()
|> Enum.member?(:of)
true
或者,如果您想检查元组是否包含任何 list 可接受值,您可以使用 Enum.any?/2
,例如通过在保护子句中定义可接受值的列表
iex> {:some, :list, :of, :values}
|> Tuple.to_list()
|> Enum.any?(fn
x when x in [:of] -> true
_ -> false
end)
true
出于好奇,这是一个带有一些元编程的高性能解决方案,它为所有可能的元组生成所有子句(最大元组大小作为参数给出)
defmodule Checker do
defmacro __using__(opts) do
total = Keyword.get(opts, :count, 8)
for count <- 1..total, place <- 1..count do
pre = for _ <- 1..place - 1//1, do: Macro.var(:_, nil)
post = for _ <- place + 1..count//1, do: Macro.var(:_, nil)
quote do
def member?(what,
{unquote_splicing(pre), what, unquote_splicing(post)}
), do: true
end
end ++ [quote do: (def member?(_, _), do: false)]
end
end
defmodule Test do
use Checker, count: 4
end
测试为:
Test.member? :ok, {:ok}
#⇒ true
Test.member? :ok, {42, true, :ok, :foo}
#⇒ true
Test.member? :ok, {42, true, :foo}
#⇒ false