在Rails中,您可以执行以下操作:
@user.try(:contact_info).try(:phone_number)
如果phone_number
和@user
都不为零,则会返回contact_info
。如果其中一个为零,则表达式将返回nil。
我想知道在Elixir中执行此操作的最惯用方法是什么,因为@user
和contact_info
是结构。
答案 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,但它会清理我的代码。