是否有Elixir相当于Rails`try`功能?

时间:2016-03-14 03:41:19

标签: ruby elixir

在Rails中,您可以执行以下操作:

@user.try(:contact_info).try(:phone_number)

如果phone_number@user都不为零,则会返回contact_info。如果其中一个为零,则表达式将返回nil。

我想知道在Elixir中执行此操作的最惯用方法是什么,因为@usercontact_info结构

7 个答案:

答案 0 :(得分:6)

我认为一种方法是使用模式匹配,这样就可以了:

case user do
  %{contact_info: %{phone_number: phone_number}} when phone_number != nil -> 
    #do something when the phone number is present
  _ -> 
    #do something when the phone number is absent
end

答案 1 :(得分:2)

修改:测试我的建议以使用with,我意识到它不会因为匹配错误发生短路而无法正常工作,但是你会在{0}上遇到UndefinedFunctionError。所以我删除了那部分。我正在进行模式匹配,正如@JustMichael建议的那样。

如果您控制结构的定义,结构中的默认值可能会有所帮助。

defmodule ContactInfo do
  defstruct phone_number: nil
end

defmodule User do
  defstruct contact_info: %ContactInfo{}
end

iex> user = %User{}
%User{contact_info: %ContactInfo{phone_number: nil}}
iex> user.contact_info.phone_number
nil

有人仍然故意将:contact_info设为零,但考虑让它崩溃......:)

答案 2 :(得分:1)

您可以使用内核中的get_in

iex(2)> user = %{contact_info: %{name: "Bob"}}
%{contact_info: %{name: "Bob"}}
iex(3)> get_in(user, [:contact_info, :name])
"Bob"
iex(4)> user2 = %{}
%{}
iex(5)> get_in(user2, [:contact_info, :name])
nil
iex(6)> user3 = %{contact_info: %{}}
%{contact_info: %{}}
iex(7)> get_in(user3, [:contact_info, :name])
nil

请注意,这不适用于Ecto模型/结构,但是普通地图很好。

答案 3 :(得分:0)

可能不,Elixir不是面向对象的,所以你在已定义的模块上调用已知函数(如果未定义函数/模块,则代码甚至不编译)。

对于使用管道运算符连续调用函数,在任何步骤结果都可以是nil,我认为单个大try块就足够了。或者,您可以尝试使用此库https://github.com/batate/elixir-pipes

中的pipe_while个宏

答案 4 :(得分:0)

defmodule ContactInfo do
  defstruct phone_number: nil
end

defmodule User do
  defstruct contact_info: nil
end

defmodule Test do
  def extract_phone_number(%User{contact_info: contact_info}) do
    case contact_info do
      %ContactInfo{phone_number: phone_number} -> phone_number
      _ -> nil
    end
  end
end

iex(1)> user = %User{}
%User{contact_info: nil}
iex(2)> Test.extract_phone_number user
nil
iex(3)> user = %User{contact_info: nil}
%User{contact_info: nil}
iex(4)> Test.extract_phone_number user
nil
iex(5)> user = %User{contact_info: %ContactInfo{phone_number: nil}}
%User{contact_info: %ContactInfo{phone_number: nil}}
iex(6)> Test.extract_phone_number user
nil
iex(7)> user = %User{contact_info: %ContactInfo{phone_number: 1234567}}
%User{contact_info: %ContactInfo{phone_number: 1234567}}
iex(8)> Test.extract_phone_number user
1234567

它不像Maybe monad那样工作,但它是一种在处理nil时使用模式匹配从嵌套结构中提取值的方法。从我到目前为止所阅读的内容(我对Elixir本人来说还是比较新的)使用这样的模式匹配似乎是更惯用的Elixir。

答案 5 :(得分:0)

我到目前为止最接近的是使用ok_jose应用程序:

use OkJose

defmodule Person do
  defstruct [:name, :address]
end

defmodule Address do
  defstruct [:locale, :state]
end

defmodule Locale do
  defstruct [:number, :street, :city]
end

defmodule Main do
  def main do
    person = %Person{
      name: "Homer",
      address: %Address{
        locale: %Locale{
          number: 742,
          street: "Evergreen Terrace",
          city: "Springfield",
        },
        state: "???"
      }
    }

    person
    |> case do %{address: %{locale: %{city: city}}} -> city; _ -> nil end
    |> String.upcase
    |> String.reverse
    |> Pipe.if(&(!is_nil(&1)))
    |> IO.inspect
  end
end

Main.main

答案 6 :(得分:-1)

Elixir似乎没有烘焙解决方案。所以我卷起了自己的。

defmodule Maybe do
  def and_then(nil, _), do: nil
  def and_then(val,fnc), do: fnc.(val)
  def and_get(struct, key), do: struct |> and_then(&(Map.get(&1, key)))
end

no_user = nil
without_contact_info = %{contact_info: nil}
with_contact_info = %{contact_info: %{address: "here"}}

no_user |> Maybe.and_then(&(Map.get(&1, :contact_info))) |> Maybe.and_then(&(Map.get(&1, :address)))
#> nil
without_contact_info |> Maybe.and_then(&(Map.get(&1, :contact_info))) |> Maybe.and_then(&(Map.get(&1, :address)))
#> nil
with_contact_info |> Maybe.and_then(&(Map.get(&1, :contact_info))) |> Maybe.and_then(&(Map.get(&1, :address)))
#> "here"

# or with the less generic `and_get`

with_contact_info |> Maybe.and_get(:contact_info) |> Maybe.and_get(:address)
#> "here"

它可能不是惯用的Elixir,但它会清理我的代码。