Wie überprüfen Sie den Variablentyp in Elixir?

138

Wie können Sie in Elixir nach Typ suchen, z. B. in Python:

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

Ich habe in Elixir gelesen, dass es Typprüfer wie 'is_bitstring', 'is_float', 'is_list', 'is_map' usw. gibt, aber was ist, wenn Sie keine Ahnung haben, welcher Typ sein könnte?

Low Kian Seong
quelle

Antworten:

104

Es gibt keine direkte Möglichkeit, den Typ einer Variablen in Elixir / Erlang abzurufen.

Normalerweise möchten Sie den Typ einer Variablen kennen, um entsprechend zu handeln. Sie können die is_*Funktionen verwenden, um basierend auf dem Typ einer Variablen zu handeln.

Learn You Some Erlang hat ein schönes Kapitel über das Tippen in Erlang (und damit in Elixir).

Die idiomatischste Art, die is_*Funktionsfamilie zu verwenden, besteht wahrscheinlich darin, sie in Musterübereinstimmungen zu verwenden:

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
was du versteckst
quelle
3
Hat Erlang / Elixir wirklich keine gespeicherten Typinformationen? Muss ich wirklich einen neuen Wrapper über vorhandenen Typen erstellen, damit die Sprache verwendet werden kann? Oo
Dmitry
2
@Dmitry was meinst du mit verwendbar? Kann ich ein konkretes Beispiel sehen, in dem Sie das Ergebnis von so etwas verwenden würden typeof(variable)?
Whatyouhide
1
Wenn ein Programm die Kompilierungszeit verlässt und zur Laufzeit wechselt, gehen alle Informationen darüber verloren, was für ein Objekt verloren geht. Wenn ich Informationen eines laufenden Programms überprüfen möchte, kann ich nur wissen, was gerade passiert, indem ich Dinge überprüfe, die über ein Netzwerk von Karten angezeigt werden. Wenn die Typinformationen nicht verfügbar sind und ich den Typ untersuchen möchte, kostet es viel mehr, das Objekt zu analysieren, um seinen Typ zu erhalten, als wenn der Typ bereits verfügbar war. Mit typeof können wir das laufende System analysieren und zur Laufzeit so erweitern, dass Typchecking und Polymorphismus möglich sind.
Dmitry
2
Um genauer zu sein; Die nützlichste Verwendung von typeof ist die Möglichkeit, eine Hash-Tabelle von [type string, function] direkt auf eine Liste von Unbekannten abzubilden. Beispielsweise; IO.puts können nicht foo = [1, "hello", [1, 2, 3]]mit Code zugeordnet werden, Enum.map(foo, fn(x) -> IO.puts x end)da [1,2, 3] als Zeichen gelesen werden (warum erlang !!?) Und Ihnen ein paar lächelnde Gesichter zeigen (probieren Sie es aus!). Daher sind wir gezwungen, inspect zu verwenden, obwohl inspect nur benötigt wird, wenn es sich um eine Liste handelt. Andernfalls benötigen wir sie meistens nicht. Mit typeof können wir if-Anweisungen (O (n)) in Wörterbuchsuchen (O (1)) umwandeln.
Dmitry
1
@Dmitry für diese Art der Verwendung Elixir-Protokolle wären nützlich. elixir-lang.org/getting-started/protocols.html Sie können ein eigenes PrintableProtokoll implementieren , das das Druckverhalten umschließt und ändert, z. B. Listen von Ganzzahlen. Stellen Sie nur sicher, dass Sie es nicht mit Erlang-Code verwenden - oder Sie kratzen sich am Kopf und fragen sich, warum anstelle von Nachrichten Listen mit ganzen Zahlen angezeigt werden.
Matt Jadczak
168

Ab Elixir 1.2 gibt es iin iex einen Befehl, der den Typ und mehr aller Elixir-Variablen auflistet.

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

Wenn Sie im Code nach dem iBefehl suchen, werden Sie feststellen, dass dies über ein Protokoll implementiert wird.

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

Wenn Sie eine Funktion für einen beliebigen Datentyp in Elixir implementieren möchten, müssen Sie dazu ein Protokoll definieren und das Protokoll für alle Datentypen implementieren, an denen die Funktion arbeiten soll. Leider können Sie in Guards keine Protokollfunktion verwenden. Ein einfaches "Typ" -Protokoll wäre jedoch sehr einfach zu implementieren.

Fred der magische Wunderhund
quelle
1
im Jahr 2019 gibt dies einen Fehler zurück undefined function i/1- das gleiche gilt für info / 1
krivar
1
Dies funktioniert immer noch in Elixir 1.8.1. Sie müssen eine sehr alte Version von Elixir installiert haben.
Fred der magische
2
@krivar @ Fred-the-Magic-Wonder-Dog ihr seid beide richtig :). &i/1ist eine Funktion auf IEx.Helpers. Wenn Sie es &IEx.Helpers.i/1in Ihr Vanille-Elixier geben, generieren Sie ein, es CompileErrorsei denn, Sie haben es :iexals Anwendung in Ihr Vanix-Elixier aufgenommen mix.exs.
Popedotninja
39

Wenn Sie sich nicht in iex befinden, können Sie es auch zu Debugging-Zwecken direkt aufrufen:

IEx.Info.info(5)
=> ["Data type": "Integer", "Reference modules": "Integer"]
Atomkirk
quelle
1
Wenn Sie es in Ihrem Protokoll sehen möchten, fügen Sie IO.inspect (IEx.Info.info (5))
Guillaume
24

Ein anderer Ansatz ist die Verwendung des Mustervergleichs. Angenommen, Sie verwenden Timex, das eine %DateTime{}Struktur verwendet, und Sie möchten sehen, ob ein Element eines ist. Sie können eine Übereinstimmung mithilfe des Mustervergleichs in der Methode finden.

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

def is_a_datetime?(_) do
  false
end
popedotninja
quelle
1
oder, wie die akzeptierte Antwort bemerkte, aber nicht betonte: »Normalerweise möchten Sie den Typ einer Variablen kennen, um entsprechend zu handeln«. In Elixir handeln Sie entsprechend durch Pattern Matching, nicht durch switch/ case.
Mariotomo
18

Ich lasse das hier, um jemanden zu finden, der hoffentlich eine wirklich vernünftige Version herausfindet. Im Moment gibt es keine guten Antworten auf diese Frage auf 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

Der Vollständigkeit halber Testfälle:

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

Hier ist eine Lösung mit Protokollen; Ich bin nicht sicher, ob sie schneller sind (ich hoffe, sie machen nicht eine Schleife über alle Typen), aber es ist ziemlich hässlich (und zerbrechlich; wenn sie einen Basistyp hinzufügen oder entfernen oder umbenennen, wird es ihn brechen).

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
quelle
Wenn Sie wirklich einen "Typprüfer" wollen, können Sie einfach einen mit den Werkzeugen in der Steinorganisation des Philosophen erstellen. github.com/philosophers-stone . Phenetic ist noch in den Anfängen, aber es kann dies und noch viel mehr.
Fred der magische
mich leicht an eine externe Abhängigkeit binden? Wie wird das meine Fähigkeit verbessern, Code mit Freunden zu teilen? Dies ist ein Weg zu 2 Problemen.
Dmitry
Danke für die Bearbeitung @aks; Ich kann jetzt tatsächlich zu 4 Feldern zurückkehren ^ _ ^
Dmitry
15

Ich füge einfach den Code von https://elixirforum.com/t/just-created-a-typeof-module/2583/5 ein :)

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
quelle
Clevere Verwendung des Zitats! Je mehr ich Elixir-Code sehe, desto mehr erinnert er mich an Perl. Das ~ w-Konstrukt sieht qw // sehr ähnlich. Ich frage mich, ob Perl einen cleveren Mechanismus hat, um Lisplike-Zitate zu simulieren.
Dmitry
Ich frage mich, wie Zitat funktioniert. Kann es mit einem Präprozessor für reguläre Ausdrücke emuliert werden oder muss ein Parser den gesamten Code durchlaufen, um eine Makroerweiterung durchzuführen?
Dmitry
1

Ich bin auf eine Situation gestoßen, in der überprüft werden muss, ob der Parameter ein bestimmter Typ sein muss. Vielleicht kann man besser aktiv werden.

So was:

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

Verwendung:

Enum.map(@required, &(match_desire?/1))
Bingoabs
quelle
1

Nur weil es niemand erwähnt hat

IO.inspect/1

Ausgaben, um das Objekt zu trösten ... es entspricht fast JSON.stringify

Sehr hilfreich, wenn Sie für Ihr ganzes Leben nicht herausfinden können, wie ein Objekt in einem Test aussieht.

John Nicholas
quelle
4
Keine Antwort auf die Frage, nicht einmal in der Nähe
LowFieldTheory