8

In fortran können wir Standardargumente definieren. Wenn ein optionales Argument nicht vorhanden ist, kann es auch nicht festgelegt werden. Wenn Argumente als Keyword-Argumente mit Standardwerten, führt dies zu umständlich Konstrukte wieFortran 2003/2008: Elegante Standardargumente?

PROGRAM PDEFAULT 

    CALL SUB 
    CALL SUB(3) 

CONTAINS 
    SUBROUTINE SUB(VAL) 
    INTEGER, OPTIONAL :: VAL 
    INTEGER :: AVAL ! short for "actual val" 

    IF(PRESENT(VAL)) THEN 
     AVAL = VAL 
    ELSE 
     AVAL = -1 ! default value 
    END IF 

    WRITE(*,'("AVAL is ", I0)') AVAL 
    END SUBROUTINE SUB 

END PROGRAM PDEFAULT 

Ich persönlich lief oft über das Problem der accidentially VAL statt AVAL eingeben, dh die Trennung zwischen dem Variablennamen in der Schnittstelle, und der initialisierte Wert, der im Code verwendet wird, kann Laufzeitfehler verursachen - geschweige denn, dass diese Art der Initialisierung ziemlich ausführlich ist.

Gibt es eine elegantere Möglichkeit, optionale Argumente mit einem Standardwert zu verwenden?

Beispiel es natürlicher anfühlen würde so etwas wie

IF(NOT(PRESENT(VAL))) VAL = -1 

zu schreiben, weil es die VAL vs AVAL Verwirrung vermeidet. Aber es ist nicht gültig, vermutlich, weil Fortran Argumente als Referenz übergibt und somit VAL in der CALL-Anweisung nicht vorhanden ist, kein Speicher mit VAL assoziiert ist und VAL = -1 einen Segfault verursachen würde.

Antwort

4

Sie haben die Situation ziemlich gut beschrieben. Es gibt keine andere Möglichkeit, die mir bewusst ist (stellt sich heraus, dass ich wirklich nicht bewusst war, siehe Gabe's Antwort). Das Muster mit einer ähnlichen lokalen Variablen wird häufig verwendet. Die andere Möglichkeit ist, einfach if (present()) else überall hinzusetzen, aber das ist peinlich.

Der Punkt ist, dass sie optional sind Argumente, nicht Standard Argumente. Fortran hat keine Standardargumente. Das war vielleicht besser, aber das ist nicht das, was die Ausschussmitglieder in den 80ern bei der Vorbereitung von Fortran 90 gewählt haben.

1

Während ich sicherlich würde nicht befürworten dies in den meisten Situationen zu tun (und in der Tat kann man nicht in einigen Situationen), kann man manchmal eine Schnittstelle verwenden, um einen einzelnen Einstiegspunkt für mehrere Routinen mit unterschiedlichen erforderlichen Argumenten bereitzustellen, anstatt ein optionales Argument zu verwenden. Zum Beispiel könnten Sie den Code wie

MODULE subs 
    implicit none 
    public :: sub 

    interface sub 
    module procedure sub_default 
    module procedure sub_arg 
    end interface 
contains 
    SUBROUTINE SUB_arg(VAL) 
    INTEGER :: VAL 
    WRITE(*,'("VAL is ", I0)') VAL 
    END SUBROUTINE SUB_arg 

    SUBROUTINE SUB_default 
    integer, parameter :: default = 3 
    CALL SUB_arg(default) 
    END SUBROUTINE SUB_default 
END MODULE SUBS 

PROGRAM test 
    use subs, only: sub 
    call sub 
    call sub(5) 
END PROGRAM TEST 

wieder geschrieben werden, ich diesen Ansatz nicht empfehlen, aber ich dachte, dass ich es trotzdem als Alternative Möglichkeit, etwas zu schaffen, das wie ein Standard sieht enthalten soll.

1

Ich hoffe, Fortran eine populäre Syntax wie

subroutine mysub(x, val = -1) 
integer, optional :: val 

oder in einem Fortran-Stil

subroutine mysub(x, val) 
integer, optional :: val = -1  !! not SAVE attribute intended 

aber dies scheint nicht unterstützt (Stand 2016) zu unterstützen. Also müssen einige Workarounds von Seiten der Benutzer durchgeführt werden ...

In meinem Fall nach Versuch und Fehler, ließ ich mich auf die optionale Dummy-Argument Anbringen eines Strich nach unten, so etwas wie das Tun (*)

subroutine mysub(x, val_) 
integer, optional :: val_ 
integer val 

Andere Leute scheinen das Gegenteil Muster zu mögen (dh Dummy-Variable =>sep, lokale Variable =>sep_, siehe beispielsweise split() in StringiFor). Wie in this line gesehen, ist der kürzeste Weg, um den Standardwert zu setzen

val = -1 ; if (present(val_)) val = val_ 

Aber weil auch diese Linie etwas ausführlich ist, definiere ich in der Regel einen Makro wie

#define optval(x,opt,val) x = val; if (present(opt)) x = opt 

in einer gemeinsamen Header-Datei und die Nutzung als

subroutine mysub(x, val_, eps_) 
    integer :: x 
    integer, optional :: val_ 
    real, optional :: eps_ 
    integer val 
    real  eps 
    optval(val, val_, -1) 
    optval(eps, eps_, 1.0e-5)  

    print *, "x=", x, "val=", val, "eps=", eps 
endsubroutine 

... 
call mysub(100) 
call mysub(100, val_= 3) 
call mysub(100, val_= 3, eps_= 1.0e-8) 

aber ich glaube, das von elegant noch weit ist und nicht mehr als eine Anstrengung zu machen, etwas weniger fehleranfällig (indem sie die gewünschten Variablennamen in der Körper des Unterprogramms).


Eine andere Lösung für eine sehr „großen“ Unterprogramm könnte sein, einen abgeleiteten Typ übergeben, die alle Argumente verbleibenden Schlüsselwort enthält. Zum Beispiel

#define getkey(T) type(T), optional :: key_; type(T) key; if (present(key_)) key = key_ 

module mymod 
    implicit none 

    type mysub_k 
     integer :: val = -1 
     real  :: eps = 1.0e-3 
    endtype 
contains 

subroutine mysub(x, seed_, key_) 
    integer :: x 
    integer, optional :: seed_ 
    integer :: seed 
    getkey(mysub_k) !! for all the remaining keyword arguments 
    optval(seed, seed_, 100)  

    print *, x, seed, key% val, key% eps 
endsubroutine 

endmodule 

program main 
    use mymod, key => mysub_k 

    call mysub(10) 
    call mysub(20, key_= key(val = 3)) 
    call mysub(30, seed_=200, key_= key(eps = 1.0e-8)) ! ugly... 
endprogram 

Dies könnte ein bisschen nahe sein, was von einigen dynamischen Sprachen unter der Haube getan wird, aber das ist wieder weit von elegant in der obigen Form ...


(*) Ich weiß, dass es oft als hässlich angesehen wird, CPP-Makros zu verwenden, aber IMO hängt davon ab, wie sie verwendet werden; wenn sie auf begrenzte Fortran-Syntax beschränkt sind, halte ich das für sinnvoll (weil es in Fortran keine Metaprogrammiermöglichkeit gibt); andererseits sollte die Definition programmabhängiger Konstanten oder Verzweigungen wahrscheinlich vermieden werden. Auch ich denke, es wäre mächtiger sein Python zu verwenden, etc zu flexibleren Preprozessoren zu machen (zB PreForM.py und fypp und so weiter), beispielsweise eine Syntax wie subroutine sub(val = -1)

+0

Die erste Lösung ist, was ich in der ursprünglichen Frage veröffentlicht - Ändern der Namenskonvention macht es nicht wirklich ein neues Muster. Die zweite Lösung ist leider ungültiger Code, wenn es mehr als ein optionales Argument gibt, da fortran keine Mischdeklaration und Ausführungscode zulässt - während für ein einzelnes optionales Argument die Verwendung eines Makros wahrscheinlich nicht ausreicht, um die Dunkelheit zu rechtfertigen . – kdb

+0

Hallo, weil die aktuellen Fortran-Standards keine echte "Lösung" erlauben (wie von Vladimir unten vorgeschlagen), gibt es keinen anderen Weg, als eine lokale Variable mit ähnlichem Namen zu verwenden, und ich dachte, du suchst nach systematischen (weniger fehleranfällige Workarounds. RE der zweite Punkt, der obige Code ist gültig (Sie können es versuchen) und Sie können auch andere übliche optionale Variablen vor key_ hinzufügen. Der Punkt ist, dass setkey() nach allen anderen Deklarationen kommen sollte, aber das ist natürlich, weil diese Art von Schlüsselwortargumenten normalerweise hinter allen optionalen Argumenten steht. – roygvib

+0

Mein Zweck ist es auch, meine Praxis zu präsentieren, niemals um ihre Verwendung zu befürworten. Was noch wichtiger ist, meine Absicht ist es, hervorzuheben, wie die aktuelle Fortran-Syntax in einigen Teilen begrenzt ist und den Benutzer dazu zwingt, einen ziemlich ausführlichen/langwierigen Code zu schreiben. – roygvib

6

Während auch in dieser Suche zu ermöglichen, fand ich heraus, dass Sie tatsächlich etwas wie das vorgeschlagene Beispiel mit den Attributen OPTIONAL und VALUE tun können (zumindest mit gfortran, nicht sicher, wie verschiedene Compiler damit umgehen könnten). Zum Beispiel:

PROGRAM PDEFAULT 

    CALL SUB 
    CALL SUB(3) 

CONTAINS 
    SUBROUTINE SUB(VAL) 
    INTEGER, OPTIONAL,VALUE :: VAL 

    IF(.NOT. PRESENT(VAL)) VAL = -1 ! default value 

    WRITE(*,'("VAL is ", I0)') VAL 
    END SUBROUTINE SUB 

END PROGRAM PDEFAULT 

Dies wurde in Version 4.9 von gfortran implementiert. Und hier ist die entsprechende Erklärung in den documentation for argument passing conventions:

für optionale Dummy-Argumente, ein abwesendes Argument wird durch einen NULL Zeiger bezeichnet, mit Ausnahme von skalaren Dummy-Argumenten vom Typ INTEGER, LOGISCH, REAL und COMPLEX, die den Wert haben Attribut. Für diese wird ein verstecktes Boolesches Argument (logisch (Art = C_bool), Wert) verwendet, um anzugeben, ob das Argument vorhanden ist.

Ich fand auch this discussion interessant als historischen Kontext.

Vielleicht jemand kenntnisreicher könnte Bemerkungen darüber haben, ob dies eine schlechte Idee ist (abgesehen davon, Compiler abhängig zu sein), aber zumindest am Nennwert scheint es wie eine nette Problemumgehung.

Beachten Sie, dass dieses Verhalten nicht Teil des Fortran-Standards ist und von der Implementierung eines bestimmten Compilers abhängt. Der Beispielcode segmentiert beispielsweise bei Verwendung von ifort (Version 16.0.2).

+0

Wow, gut Finde, ich hatte keine Ahnung davon! –

+0

I _believe_ das ist nicht gültig Fortran (was nicht zu sagen ist, wird nicht tun, was man in der Praxis mit einigen Compilern/Laufzeiten will). Wenn "val" nicht vorhanden ist, ist es nicht erlaubt, sie zu referenzieren/zu definieren. Dies ist die 'if' Aussage. Die Einschränkungen, die der Standard für die Verwendung von nicht vorhandenen Dummy-Argumenten gibt, erwähnen das Attribut "value" nicht. Die "definierbare anonyme Kopie" gilt nur für ein vorhandenes Dummy-Argument mit dem Attribut "value". [Hoffentlich kann jemand zeigen, dass ich falsch liege.] – francescalus

+0

Hm, ich denke du hast Recht. Am Morgen machte ich mir für kurze Zeit Sorgen, aber dann hat mich der Bugzilla-Thread überzeugt. Aber nirgendwo auf dieser Seite schlagen sie vor, dass die Bezugnahme auf das Argument zulässig ist, wenn es nicht vorhanden ist. –