2015-02-19 4 views
13

Angenommen, ich habe eine Struktur mit vielen Feldern:Gibt es eine Kurzform, um ein bestimmtes Strukturfeld im Racket zu aktualisieren?

(struct my-struct (f1 f2 f3 f4)) 

Wenn ich eine neue Struktur mit f2 aktualisiert zurückzukehren, muss ich alle anderen Felder neu formulieren:

(define s (my-struct 1 2 3 4)) 
(my-struct (my-struct-f1 s) 
      (do-something-on (my-struct-f2 s)) 
      (my-struct-f3 s) 
      (my-struct-f4 s)) 

Welche überflüssig und würde eine Fehlerquelle sein, wenn ich die Anzahl der Felder aktualisiere oder ihre Bestellungen ändere.

Ich frage mich wirklich, ob es eine solche Art und Weise, das ich wie ein bestimmtes Feld für eine Struktur aktualisieren:

(my-struct-f2-update (my-struct 1 2 3 4) 
        (lambda (f2) (* f2 2))) 
;; => (my-struct 1 4 3 4) 

Oder ich kann sie nur auf einen neuen Wert gesetzt, wie:

(define s (my-struct 1 2 3 4) 
(my-struct-f2-set s (* (my-struct-f2 s) 2)) 
;; => (my-struct 1 4 3 4) 

Hinweis , das mutiert hier nicht s; my-struct-f2-update und my-struct-f2-set sollten nur eine Kopie von s mit f2 Feld aktualisiert werden.

In Haskell kenne ich die "Objektiv" -Bibliothek, die diese Aufgabe erledigt. Ich frage mich nur, ob es einige ähnliche Möglichkeiten gibt, die ich für den Schläger übernehmen kann. Vielen Dank.

Antwort

11

Sie wissen was? Dies ist eine wirklich gute Idee. In der Tat gab es ein paar Fälle, in denen ich diese Funktionalität wollte, aber ich hatte sie nicht. Die schlechte Nachricht ist, dass nichts von dieser Art von Racket zur Verfügung gestellt wird. Die gute Nachricht ist, dass Racket Makros hat!

Ich präsentiere Ihnen define-struct-updaters!

(require (for-syntax racket/list 
        racket/struct-info 
        racket/syntax 
        syntax/parse)) 

(define-syntax (define-struct-updaters stx) 
    (syntax-parse stx 
    [(_ name:id) 
    ; this gets compile-time information about the struct 
    (define struct-info (extract-struct-info (syntax-local-value #'name))) 
    ; we can use it to get the constructor, predicate, and accessor functions 
    (define/with-syntax make-name (second struct-info)) 
    (define/with-syntax name? (third struct-info)) 
    (define accessors (reverse (fourth struct-info))) 
    (define/with-syntax (name-field ...) accessors) 
    ; we need to generate setter and updater identifiers from the accessors 
    ; we also need to figure out where to actually put the new value in the argument list 
    (define/with-syntax ([name-field-set name-field-update 
          (name-field-pre ...) (name-field-post ...)] 
          ...) 
     (for/list ([accessor (in-list accessors)] 
        [index (in-naturals)]) 
     (define setter (format-id stx "~a-set" accessor #:source stx)) 
     (define updater (format-id stx "~a-update" accessor #:source stx)) 
     (define-values (pre current+post) (split-at accessors index)) 
     (list setter updater pre (rest current+post)))) 
    ; now we just need to generate the actual function code 
    #'(begin 
     (define/contract (name-field-set instance value) 
      (-> name? any/c name?) 
      (make-name (name-field-pre instance) ... 
         value 
         (name-field-post instance) ...)) 
     ... 
     (define/contract (name-field-update instance updater) 
      (-> name? (-> any/c any/c) name?) 
      (make-name (name-field-pre instance) ... 
         (updater (name-field instance)) 
         (name-field-post instance) ...)) 
     ...)])) 

Wenn Sie mit Makros nicht vertraut sind, kann es ein wenig einschüchternd aussehen, aber es ist eigentlich nicht ein kompliziertes Makro. Glücklicherweise müssen Sie nicht verstehen, wie es funktioniert, es zu verwenden. Hier ist, wie Sie das tun würden:

(struct point (x y) #:transparent) 
(define-struct-updaters point) 

Jetzt können Sie alle relevanten funktionalen Setter und Updater wie Sie möchten.

> (point-x-set (point 1 2) 5) 
(point 5 2) 
> (point-y-update (point 1 2) add1) 
(point 1 3) 

Ich glaube, es gab einige theoretische Pläne, das Racket struct System neu zu gestalten, und ich denke, das ist eine wertvolle Ergänzung sein würde. Bis dann, zögern Sie nicht, diese Lösung zu verwenden.

Aufgrund seiner Nützlichkeit, kann ich versuchen, dies in ein Paket aufzunehmen, um es einfacher zu verwenden, in diesem Fall werde ich diese Antwort mit diesen Informationen bearbeiten.

+0

Beachten Sie, dass dieses Makro, wie struct-copy, Unterstrukturfelder verliert. –

+0

@ SamTobin-Hochstadt Wahr. Es wäre schön, eine Möglichkeit zu haben, Informationen über eine Strukturinstanz zur Laufzeit zu erhalten, obwohl ich denke, dass dies das Konzept der undurchsichtigen Strukturen verletzen könnte. –

+1

Sollten Sie diese Antwort aktualisieren, um einen Link zur [Dokumentation hier] hinzuzufügen (http://docs.racket-lang.org/alexis-util/Untyped_Utilities.html#%28part._.Purely_.Functional_.Struct_.Updaters% 29) für das jetzt, dass es in einem Paket ist? –

8

Ich mag Alexis 'Makro! Es hat mehr von der "Linse" Geschmack, die Sie wollten.

Ich möchte auch darauf hinweisen, struct-copy.Gegeben:

#lang racket 
(struct my-struct (f1 f2 f3 f4) #:transparent) 
(define s (my-struct 1 2 3 4)) 

Sie können struct-copy verwenden, um einen Wert zu setzen:

(struct-copy my-struct s [f2 200]) 
;;=> (my-struct 1 200 3 4) 

oder einen Wert zu aktualisieren:

(struct-copy my-struct s [f2 (* 100 (my-struct-f2 s))]) 
;;=> (my-struct 1 200 3 4) 

Update: Nachdenken über dieses mehr, Hier sind ein paar mehr Ideen.

Sie könnten auch match ‚s aktualisieren mit struct* Muster:

(match s 
    [(struct* my-struct ([f2 f2])) 
    (struct-copy my-struct s [f2 (* 100 f2)])]) 

Natürlich, das sehr ausführlich ist. Auf der anderen Seite machen die struct* Muster es einfach, einen Makro mit dem einfacheren define-syntax-rule zu definieren:

;; Given a structure type and an instance of it, a field-id, and a 
;; function, return a new structure instance where the field is the 
;; value of applying the function to the original value. 
(define-syntax-rule (struct-update struct-type st field-id fn) 
    (match st 
    [(struct* struct-type ([field-id v])) 
    (struct-copy struct-type st [field-id (fn v)])])) 

(struct-update my-struct s f2 (curry * 100)) 
;;=> (my-struct 1 200 3 4) 

Natürlich Einstellung ist der Spezialfall, wo Sie eine const Funktion aktualisieren geben:

(struct-update my-struct s f2 (const 42)) 
;;=> (my-struct 1 42 3 4) 

Schließlich ist dies wie struct-update, aber gibt eine Updater-Funktion, im Geiste von Alexis 'Makro:

(define-syntax-rule (struct-updater struct-type field-id) 
    (λ (st fn) 
    (struct-update struct-type st field-id fn))) 

(define update-f2 (struct-updater my-struct f2)) 

(update-f2 s (curry * 100)) 
;;=> (my-struct 1 200 3 4) 

Ich sage nicht, dass das alles idiomatisch oder effizient ist. Aber es ist möglich. :)

7

Alexis Makro ist fantastisch, und Gregs Recht darauf hingewiesen, struct-copy und match+struct*, aber da Sie speziell Linsen in Ihrem Beispiel erwähnt werde ich, dass there is now a lens package for Racket hinweisen (Disclaimer: Ich habe eine Menge davon geschrieben). Es bietet struct/lens und define-struct-lenses Makros für Ihren Anwendungsfall:

> (struct/lens foo (a b c)) 
> (lens-view foo-a-lens (foo 1 2 3)) 
1 
> (lens-set foo-a-lens (foo 1 2 3) 'a) 
(foo 'a 2 3) 
> (lens-transform foo-a-lens (foo 1 2 3) number->string) 
(foo "1" 2 3) 

define-struct-lenses können Sie die Linsen separat von den Strukturen definieren:

> (struct foo (a b c)) 
> (define-struct-lenses foo) 

Die oben (struct/lens foo (a b c)) entspricht. Wenn Sie nur mit Strukturen arbeiten, die von anderen Arten von Strukturen isoliert sind, ist die Verwendung von define-struct-updaters einfacher. Aber wenn Sie viele verschachtelte Datenstrukturen verschiedener Geschmacksrichtungen haben, ist die Fähigkeit, Linsen zu komponieren, ein mächtiges Werkzeug für den Job.

Verwandte Themen