2016-03-14 15 views
5

In Rails si può fare qualcosa di simile:Esiste un equivalente Elixir della funzione `try` di Rails?

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

che restituirà il phone_number se il @user e contact_info sono entrambi non nullo. Se uno di questi è zero, l'espressione restituirà zero.

voglio sapere qual è il modo più idiomatico per fare questo in Elixir, sapendo che @user e contact_info sono struct.

+2

Penso che si può semplicemente utilizzare il pattern matching per gestire le situazioni in cui non c'è e non c'è 'phone_number' – JustMichael

+0

@JustMichael È dovrebbe fare una risposta, in quanto è la soluzione migliore. –

risposta

0

Probabilmente no, l'elisir non è orientato agli oggetti, quindi si chiamano funzioni note su moduli definiti (se la funzione/modulo non è definita, il codice non viene nemmeno compilato).

Per chiamate successive su funzioni con l'operatore di condotte, in cui il risultato può essere nil in qualsiasi passaggio, penso che sia sufficiente un unico grande blocco try. Oppure si può provare le pipe_while macro da questa libreria https://github.com/batate/elixir-pipes

+0

'elixir-pipes' sembra fare un po 'quello che voglio, tranne che dobbiamo dare uno schema che corrisponda al successo. Mi piacerebbe il contrario, qualcosa che non corrisponde a zero. – Martinos

6

Penso che un modo per farlo è di andare con pattern matching quindi sarebbe qualcosa di simile:

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 
+0

Indipendentemente dal pattern-match con un'istruzione case o su un head function, questo è probabilmente il Best Way ™. –

-1

Sembra che non ci sono al forno in soluzione in elisir. Così ho arrotolato il mio.

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" 

Probabilmente non è un elisir idiomatico, ma pulisce il mio codice.

+0

In questo caso particolare stai facendo molta codifica difensiva. Questo non è assolutamente il modo idiomatico in Elixir. Il modo idiomatico in Elixir è "lascia che si schianti". Se il tuo utente o contact_info sono nulli, come recupererai? Qual è il percorso alternativo in questo caso? –

+0

Forse qualcosa come "N/A". 'with_contact_info |> Forse.e_get (: contact_info) |> Forse.and_get (: indirizzo) &&" N/A "'. In tal caso, voglio visualizzarlo in una vista, lasciandolo in crash non è quello che voglio. – Martinos

+0

Penso che tu fraintenda cosa intendo con "lascia che si blocchi". Non sto dicendo che l'intera applicazione andrà in crash, ma solo il processo che sta tentando di estrarre le informazioni. Fondamentalmente si desidera avviare il processo per estrarre i dati che si stanno cercando da un supervisore. Quindi se i dati non ci sono, lascia che il processo si blocchi. Vedi questo: http://c2.com/cgi/wiki?LetItCrash per ulteriori informazioni sull'argomento. –

1

È possibile utilizzare get_in da Kernel:

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 

Si noti che questo non funziona con i modelli Ecto/le strutture, ma le mappe semplici vanno bene.

+0

'get_in' non funziona con Structs. In questo caso funziona perché 'utente' è una mappa. – Martinos

+0

Grazie per la correzione, ho aggiornato la mia risposta – chrismcg

+0

Ho appena messo le strutture in grassetto nella mia domanda, quindi è più chiaro. Grazie – Martinos

2

Modifica: test del mio suggerimento per utilizzare with, mi rendo conto che non funzionerà come cortocircuiti sull'errore di corrispondenza, ma avrete UndefinedFunctionError su zero. Quindi ho rimosso quella parte. Andrei con la corrispondenza dei pattern, come suggerito da @JustMichael.

Se si controlla la definizione delle strutture, può essere utile un valore predefinito nella struttura.

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 

Qualcuno potrebbe ancora volutamente impostato :contact_info a zero, ma prendere in considerazione solo lasciando che il crash ... :)

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 

non funziona come una monade Forse, ma è un modo per utilizzare modello corrispondente per estrarre un valore dalle strutture nidificate durante la gestione di nil. Da quello che ho letto fino ad ora (sono abbastanza nuovo per Elisir in persona), l'uso di pattern matching come questo sembra essere un elisir più idiomatico.

0

Il più vicino che posso ottenere finora è con con ok_jose applicazione:

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 
Problemi correlati