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?

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

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.


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"]

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

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

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

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))

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.


4
Keine Antwort auf die Frage, nicht einmal in der Nähe
LowFieldTheory
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.