2016-12-25 1 views
1

Ich habe bereits eine verwandte Frage here gestellt und die Antwort hat mein Problem behoben, aber ich habe ein allgemeineres Missverständnis, wie Kristall prüft Typen, da ich in ähnliches Problem laufe, wenn jemand mir helfen könnte, herauszufinden, dass ich wäre gut. Ich habe viele Dinge ausprobiert, die gut mit Ruby funktionieren würden, aber absolut nicht mit Crystal funktionieren (ich weiß, dass sie viele Unterschiede haben, aber ich bin vertrauter mit Ruby).Typüberprüfung in Crystal

Hier ist eine Klasse:

class Narray(T) 
    getter shape 
    getter values 

    @shape : Tuple(Int32, Int32) | Nil 

    def initialize(values : T) 
    @values = values 
    @type = T 
    @shape = set_shape 
    end 

    def set_shape 
    if (@type == Array(Array(Int32))) || (@type == Array(Array(Float64))) 

     return {values.size, values[0].size}  # Line causing the error 

    elsif (@type == Array(Int32)) || (@type == Array(Float64)) 
     return {1, @values.size} 
    end 
    end 

    def is_matrix? 
    if @values[0].is_a?(Array(Int32)) || @values[0].is_a?(Array(Float64)) 
     return true 
    else 
     return false 
    end 
    end 
end 

ein Array zu definieren, sollte ich tun:

arr1 = Narray.new([1,2,3]) 

die durch set_shape abzurufen, die Form des Feldes (ein Tupel, die die Anzahl laufen würde von Reihen und Anzahl von Spalten). Bei dieser Methode checke ich die if-Anweisung ein, wenn es sich um ein 2D-Array handelt oder nicht. Wenn jedoch die Zeile oben ausgeführt wird, bekomme ich diesen Fehler:

in script.cr:16: undefined method 'size' for Int32 

     return {values.size, values[0].size} 

das ist genau das, was das erste if-Anweisung vermeiden sollte. Da ich ein 1D-Array initialisiere, sollte es nicht durch den .size Teil gehen.

Ich bin mir ziemlich sicher, dass es eine triviale Erklärung ist, und dass es etwas ziemlich Grundlegendes gibt, das ich nicht bekommen habe, aber ich möchte es bekommen, anstatt ständig über dieses Thema zu stolpern.

Wie kann ich in Crystal nach Typ suchen, da die Art und Weise, wie ich benutze, nicht der richtige Weg ist? Ich habe versucht mit is_a?, == Type, mit der is_matrix? Methode (die funktioniert gut und macht die Aufgabe zu bestimmen, ob es 2D oder 1D ist, aber immer noch durch den falschen Teil läuft).

EDIT: nach der ersten Antwort, ich habe die set_shape Methode geändert:

def set_shape 
    if (@values.is_a? Array(Array(Int32))) || (@values.is_a? Array(Array(Float64))) 
     return {values.size, values[0].size} 
    elsif (@values.is_a? Array(Int32)) || (@values.is_a? Array(Float64)) 
     return {1, @values.size} 
    end 
    end 

Aber ich habe noch genau die gleichen Fehler

Antwort

3

Das Problem, das Sie haben, ist, dass Sie indirekt Test für @values Geben Sie eine Instanzvariable ein, die zufällig die generische T ist. Der Compiler ist nicht so intelligent, und es besteht die Möglichkeit, dass sich @type zur Laufzeit ändert und nicht mehr @values entspricht. Auch wenn Sie es nicht wissen, möchte Crystal sicher sein (sonst das Programm segfaults). Crystal beschwert sich, weil Sie einen Typ annehmen, den er nicht erkennt.

Drop @type. Es hilft nicht und macht die Dinge komplizierter als nötig. Sie verwenden bereits ein Generikum, jetzt sollten Sie fragen @values für was es ist, dann handeln Sie entsprechend. Crystal wird das mögen, weil du feststellst, dass dieser Typ das ist und nichts anderes.

Das heißt, vielleicht sollten Sie 2 verschiedene Typen haben, um Array vs Array(Array) zu bezeichnen. Vielleicht wäre Ihr Code leichter zu handhaben?

+0

Vielen Dank für Ihre Antwort. Ich habe versucht, was Sie empfohlen haben, was @values ​​Typ direkt bewertet, aber nicht funktioniert (siehe meine Bearbeitung, ich habe hinzugefügt, was ich versucht habe).Wenn du zwei verschiedene Arten hast, meinst du 2 Unterklassen? Idealerweise versuche ich, einen Typ zu erstellen, der die gleichen Fähigkeiten wie numpy ndarrays hat, die sehr praktisch sind. Und es scheint mir, dass ein Teil davon, warum es bequem ist, ist, weil es ein Typ für 1D/2D/Int/Float ist. Aber ich bin immer offen für Vorschläge und wenn Sie irgendwelche "Design" -Ratschläge für diese Klasse haben, wäre es großartig. –

+0

Das Problem ist, dass Sie sich immer auf '@ values' beziehen und keine lokale Kopie davon machen 'Werte = @ Werte'. Der Typ wird für eine lokale Variable korrekt eingeschränkt, da sein Typ sich nicht ändert (er ist lokal für die Funktion), aber die Instanzvariable kann jederzeit von einem anderen Thread oder einer anderen Funktion geändert werden (es handelt sich um einen gemeinsamen Wert) Der Typ wird nicht durch den Aufruf "is_a?" eingeschränkt und bleibt die Union. –

1

Der folgende Code funktioniert:

class Narray(T) 
    getter shape 
    getter values 

    @shape : Tuple(Int32, Int32) | Nil 

    def initialize(@values : T) 
    @shape = set_shape 
    end 

    def set_shape 
    values = @values # => asign to local var 
    if values.is_a?(Array(Array(Int32))) || values.is_a?(Array(Array(Float64))) 
     return {values.size, values[0].size} 
    elsif (T == Array(Int32)) || (T == Array(Float64)) 
     return {1, values.size} 
    end 
    end 

    def is_matrix? 
    if @values[0].is_a?(Array(Int32)) || @values[0].is_a?(Array(Float64)) 
     return true 
    else 
     return false 
    end 
    end 
end 

arr1 = Narray.new([[1, 2], [3, 4]]) 
pp arr1.set_shape # => {2,2} 
pp arr1.is_matrix? # => true