Jak sprawdzić typ zmiennej w Elixirze

138

W Elixirze jak sprawdzić typ, taki jak w Pythonie:

>>> a = "test"
>>> type(a)
<type 'str'>
>>> b =10
>>> type(b)
<type 'int'>

Czytałem w Elixirze, że istnieją narzędzia do sprawdzania typów, takie jak „is_bitstring”, „is_float”, „is_list”, „is_map” itd., Ale co jeśli nie masz pojęcia, jaki może to być typ?

Low Kian Seong
źródło

Odpowiedzi:

104

Nie ma bezpośredniego sposobu na uzyskanie typu zmiennej w Elixir / Erlang.

Zwykle chcesz znać typ zmiennej, aby odpowiednio postępować; możesz użyć is_*funkcji, aby działać w oparciu o typ zmiennej.

Learn You Some Erlang ma fajny rozdział o pisaniu w języku Erlang (a tym samym w Elixirze).

Najbardziej idiomatycznym sposobem wykorzystania is_*rodziny funkcji byłoby prawdopodobnie użycie ich w dopasowaniach wzorców:

def my_fun(arg) when is_map(arg), do: ...
def my_fun(arg) when is_list(arg), do: ...
def my_fun(arg) when is_integer(arg), do: ...
# ...and so on
co ukrywasz
źródło
3
Czy Erlang / Elixir naprawdę nie ma zapisanych informacji o typie? Czy naprawdę muszę tworzyć zupełnie nowe opakowanie na istniejące typy, aby język był użyteczny? Oo
Dmitry
2
@Dmitry, co masz na myśli, mówiąc o użytecznym? Czy mogę zobaczyć konkretny przykład, w którym użyłbyś wyniku czegoś takiego typeof(variable)?
ukrywasz
1
Gdy program opuszcza czas kompilacji i przechodzi do środowiska uruchomieniowego, wszystkie informacje o tym, jaki obiekt został utracony, są tracone. Kiedy chcę sprawdzić informacje o uruchomionym programie, jedynym sposobem, aby dowiedzieć się, co się dzieje, jest inspekcja rzeczy, które są ujawniane za pośrednictwem sieci map. jeśli informacje o typie nie są dostępne, a chcę sprawdzić typ, przeanalizowanie obiektu w celu uzyskania jego typu kosztuje znacznie więcej, niż gdyby typ był już ujawniony. typeof pozwala nam analizować działający system i rozszerzać go w czasie wykonywania w sposób umożliwiający sprawdzanie typów i polimorfizm.
Dmitry,
2
Być bardziej specyficznym; najbardziej użytecznym zastosowaniem typeof jest możliwość bezpośredniego mapowania tablicy hash typu [string, function] na listę niewiadomych. Na przykład; IO.puts nie mogą zostać zmapowane foo = [1, "hello", [1, 2, 3]], z kodem, Enum.map(foo, fn(x) -> IO.puts x end)ponieważ [1, 2, 3] zostanie odczytane jako znaki (dlaczego erlang !!?) I pokaże ci kilka uśmiechniętych twarzy (spróbuj!). więc jesteśmy zmuszeni do korzystania z inspekcji, mimo że inspekcja jest potrzebna tylko wtedy, gdy jest to lista, w przeciwnym razie przez większość czasu jej nie potrzebujemy. typeof pozwala nam zamienić instrukcje if (O (n)) na wyszukiwania słownikowe (O (1)).
Dmitry
1
@Dmitry do tego typu zastosowań przydałyby się protokoły Elixir. elixir-lang.org/getting-started/protocols.html Możesz zaimplementować swój własny Printableprotokół, który zawija i zmienia zachowanie drukowania, np. listy liczb całkowitych. Tylko upewnij się, że nie używasz go z kodem Erlang - w przeciwnym razie będziesz drapać się po głowie, zastanawiając się, dlaczego zamiast wiadomości widzisz listy liczb całkowitych.
Matt Jadczak,
168

Począwszy od ielixiru 1.2 jest polecenie w iex, które wyświetli typ i więcej dowolnej zmiennej Elixir.

iex> foo = "a string" 
iex> i foo 
Term
 "a string"
Data type
 BitString
Byte size
 8
Description
 This is a string: a UTF-8 encoded binary. It's printed surrounded by
 "double quotes" because all UTF-8 encoded codepoints in it are        printable.
Raw representation
  <<97, 32, 115, 116, 114, 105, 110, 103>>
Reference modules
  String, :binary

Jeśli zajrzysz do kodu ipolecenia, zobaczysz, że jest to realizowane za pośrednictwem protokołu.

https://github.com/elixir-lang/elixir/blob/master/lib/iex/lib/iex/info.ex

Jeśli chcesz zaimplementować funkcję dla dowolnego typu danych w Elixirze, sposobem na to jest zdefiniowanie protokołu i implementacja protokołu dla wszystkich typów danych, na których ma działać funkcja. Niestety nie możesz używać funkcji protokołu w strażnikach. Jednak prosty protokół „typu” byłby bardzo prosty w implementacji.

Fred the Magic Wonder Dog
źródło
1
w 2019 zwraca to błąd undefined function i/1- tak samo dla info / 1
krivar
1
To nadal działa w Elixir 1.8.1. Musisz mieć zainstalowaną bardzo starą wersję eliksiru.
Fred the Magic Wonder Dog
2
@krivar @ fred-the-magic-Wonder-dog oboje macie rację :). &i/1to funkcja włączona IEx.Helpers. Jeśli włożysz go &IEx.Helpers.i/1do swojego waniliowego eliksiru, wygenerujesz plik, CompileErrorchyba że umieściłeś go :iexjako aplikację w swoim mix.exs.
popedotninja
39

Również do celów debugowania, jeśli nie jesteś w iex, możesz wywołać to bezpośrednio:

IEx.Info.info(5)
=> ["Data type": "Integer", "Reference modules": "Integer"]
atomkirk
źródło
1
Jeśli chcesz zobaczyć to w swoim logu, dodaj IO.inspect (IEx.Info.info (5))
Guillaume
24

Innym podejściem jest użycie dopasowania wzorców. Załóżmy, że używasz Timex, który używa %DateTime{}struktury i chcesz sprawdzić, czy element jest jeden. Możesz znaleźć dopasowanie za pomocą dopasowania do wzorca w metodzie.

def is_a_datetime?(%DateTime{}) do
  true
end

def is_a_datetime?(_) do
  false
end
popedotninja
źródło
1
lub, jak zauważyła zaakceptowana odpowiedź, ale nie podkreślała: »Zwykle chcesz znać typ zmiennej, aby odpowiednio postępować«. w Elixirze działasz odpowiednio, dopasowując wzorce, a nie switch/ case.
mariotomo
18

Zostawię to tutaj ze względu na kogoś, kto ma nadzieję, że wymyśli naprawdę rozsądną wersję. W tej chwili nie ma dobrych odpowiedzi na to pojawiające się w Google ...

defmodule Util do
    def typeof(self) do
        cond do
            is_float(self)    -> "float"
            is_number(self)   -> "number"
            is_atom(self)     -> "atom"
            is_boolean(self)  -> "boolean"
            is_binary(self)   -> "binary"
            is_function(self) -> "function"
            is_list(self)     -> "list"
            is_tuple(self)    -> "tuple"
            true              -> "idunno"
        end    
    end
end

Ze względu na kompletność przypadki testowe:

cases = [
    1.337, 
    1337, 
    :'1337', 
    true, 
    <<1, 3, 3, 7>>, 
    (fn(x) -> x end), 
    {1, 3, 3, 7}
]

Enum.each cases, fn(case) -> 
    IO.puts (inspect case) <> " is a " <> (Util.typeof case)
end

Oto rozwiązanie z protokołami; Nie jestem pewien, czy są szybsze (mam nadzieję, że nie robią pętli na wszystkich typach), ale jest dość brzydki (i delikatny; jeśli dodadzą lub usuną podstawowy typ lub zmienną nazwę, to je zepsuje).

defprotocol Typeable, do: def typeof(self)
defimpl Typeable, for: Atom, do: def typeof(_), do: "Atom"
defimpl Typeable, for: BitString, do: def typeof(_), do: "BitString"
defimpl Typeable, for: Float, do: def typeof(_), do: "Float"
defimpl Typeable, for: Function, do: def typeof(_), do: "Function"
defimpl Typeable, for: Integer, do: def typeof(_), do: "Integer"
defimpl Typeable, for: List, do: def typeof(_), do: "List"
defimpl Typeable, for: Map, do: def typeof(_), do: "Map"
defimpl Typeable, for: PID, do: def typeof(_), do: "PID"
defimpl Typeable, for: Port, do: def typeof(_), do: "Port"
defimpl Typeable, for: Reference, do: def typeof(_), do: "Reference"
defimpl Typeable, for: Tuple, do: def typeof(_), do: "Tuple"

IO.puts Typeable.typeof "Hi"
IO.puts Typeable.typeof :ok
Dmitry
źródło
Jeśli naprawdę chcesz sprawdzać "typy", możesz je łatwo zbudować za pomocą narzędzi z kamiennej organizacji filozoficznej. github.com/philosophers-stone . Phenetic jest jeszcze w początkach, ale może to zrobić i wiele więcej.
Fred the Magic Wonder Dog
łatwo wiązać się z zewnętrzną zależnością? w jaki sposób poprawi to moją zdolność udostępniania kodu znajomym? To jest droga do dwóch problemów.
Dmitry
Dzięki za edycję @aks; Mogę teraz wrócić do 4 miejsc ^ _ ^
Dmitry
15

Po prostu wklejam kod z https://elixirforum.com/t/just-created-a-typeof-module/2583/5 :)

defmodule Util do
  types = ~w[function nil integer binary bitstring list map float atom tuple pid port reference]
  for type <- types do
    def typeof(x) when unquote(:"is_#{type}")(x), do: unquote(type)
  end
end
user3197999
źródło
Sprytne wykorzystanie cytatu! Im więcej widzę kod Elixir, tym bardziej przypomina mi on Perl; ta konstrukcja ~ w wygląda bardzo podobnie do qw //. Zastanawiam się, czy Perl ma jakiś sprytny mechanizm do symulacji cytatu Lisplike.
Dmitry
Zastanawiam się, jak działa cytat; czy może być emulowany przy użyciu preprocesora wyrażeń regularnych, czy też wymaga przejścia przez parser przez cały kod w celu rozwinięcia makr.
Dmitry
1

Natknąłem się na sytuację, w której trzeba sprawdzić parametr musi być określonego typu. Może można aktywować w lepszy sposób.

Lubię to:

@required [{"body", "binary"},{"fee", "integer"}, ...]
defp match_desire?({value, type}) do
  apply(Kernel, :"is_#{type}", [value])
end

Stosowanie:

Enum.map(@required, &(match_desire?/1))
Bingoabs
źródło
1

Tylko dlatego, że nikt o tym nie wspomniał

IO.inspect/1

Dane wyjściowe do konsoli obiektu ... jest prawie równoważne z JSON.stringify

Bardzo pomocne, gdy po prostu nie możesz przez całe życie dowiedzieć się, jak wygląda przedmiot w teście.

John Nicholas
źródło
4
Brak odpowiedzi na pytanie, nawet nie blisko
LowFieldTheory