2015-02-19 20 views
13

Załóżmy, że mam struct z wielu dziedzin:Czy istnieje skrótowy sposób aktualizowania określonego pola struktury w przekrętach?

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

Jeśli mam wrócić nowy struct z f2 zaktualizowane, muszę rephrase co inne dziedziny:

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

co jest zbędne i że być źródłem błędów, jeśli zaktualizuję liczbę pól lub zmieniam ich zamówienia.

Naprawdę zastanawiam się, czy istnieje taki sposób mogę zaktualizować określonego pola dla struct jak:

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

Albo można po prostu ustawić je do nowej wartości, jak:

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

zawiadomieniu to nie mutuje tutaj s; my-struct-f2-update i my-struct-f2-set powinny zwracać kopię zaktualizowanego pola s z f2.

W Haskell znam bibliotekę "soczewki", która wykonuje to zadanie. Zastanawiam się tylko, czy są jakieś podobne sposoby, które mogę zaadoptować na rakietę. Dzięki.

Odpowiedz

11

Wiesz co? To jest naprawdę dobry pomysł. W rzeczywistości było kilka przypadków, w których chciałem tę funkcjonalność, ale jej nie miałem. Zła wiadomość jest taka, że ​​nic takiego nie zapewnia Rakieta. Dobra wiadomość jest taka, że ​​Racket ma makra!

Przedstawiam Wam 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) ...)) 
     ...)])) 

Jeśli nie jesteś zaznajomiony z makr, może to wyglądać trochę przytłaczający, ale to nie jest rzeczywiście skomplikowana makro. Na szczęście nie musisz rozumieć, jak to działa, aby z niego korzystać. Oto, jak to zrobić:

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

Teraz możesz korzystać ze wszystkich odpowiednich seterów i aktualizacji funkcji, jak chcesz.

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

Sądzę, że istniały pewne teoretyczne plany przeprojektowania systemu konstrukcyjnego Racket i myślę, że byłby to cenny dodatek. Do tego czasu nie krępuj się korzystać z tego rozwiązania.

Z powodu jego użyteczności, mogę spróbować umieścić to w paczce, aby było łatwiej używać, w takim przypadku będę edytować tę odpowiedź za pomocą tych informacji.

+0

Należy zauważyć, że to makro, podobnie jak struktura-kopia, utraci pola podstruktury. –

+0

@ SamTobin-Hochstadt True. Byłoby miło mieć sposób na uzyskanie informacji o instancji struktury w czasie wykonywania, choć myślę, że może to naruszyć koncepcję nieprzejrzystych struktur. –

+1

Należy zaktualizować tę odpowiedź, aby dodać łącze do [dokumentacji tutaj] (http://docs.racket-lang.org/alexis-util/Untyped_Utilities.html#%28part._.Purely_.Functional_.Struct_.Updaters% 29) do tego teraz, gdy jest w paczce? –

8

Lubię makro Alexis! Ma więcej pożądanego "soczewki".

Chcę również zwrócić uwagę na struct-copy.Biorąc pod uwagę:

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

Można użyć struct-copy aby ustawić wartość:

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

lub zaktualizować wartość:

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

Aktualizacja: myśleć o tym więcej, Oto kilka innych pomysłów.

Można również aktualizować za pomocą match „s struct* Wzór:

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

oczywiście, że jest bardzo gadatliwy. Z drugiej strony struct* wzór sprawia, że ​​łatwo zdefiniować makro używając prostsze define-syntax-rule:

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

Oczywiście ustawienie jest szczególny przypadek, gdy dajesz aktualizuje const funkcję:

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

Wreszcie, jest to podobne do struct-update, ale zwraca funkcję aktualizacji, w duchu makro Alexisa:

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

Nie twierdzę, że to wszystko jest idiomatyczne lub efektywne. Ale to możliwe. :) makro

7

Alexis jest fantastyczna, a Greg słusznie wskazał struct-copy i match+struct*, ale skoro wyraźnie wymienione soczewki w przykładzie będę podkreślić, że there is now a lens package for Racket (Zastrzeżenie: Napisałem go dużo). Zapewnia struct/lens i define-struct-lenses makr w Twoim przypadku zastosowanie:

> (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 pozwala zdefiniować soczewki osobno z kodowanym:

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

powyższe jest równoważne (struct/lens foo (a b c)). Jeśli działasz tylko na strukturach w oderwaniu od innych rodzajów struktur, użycie opcji define-struct-updaters jest prostsze. Ale jeśli masz wiele zagnieżdżonych struktur danych o różnych smakach, umiejętność komponowania soczewek czyni z nich potężne narzędzie do pracy.