2014-09-21 11 views
6

Aby spróbować uprościć ten problem mam z definicją tych funkcji strzałka:Czy są jakieś użyteczne abstrakcje dla składni rekordu Haskell?

splitA :: (Arrow arr) => arr a b -> arr a (b,a) 
splitA ar = ar &&& (arr (\a -> id a)) 

recordArrow 
    :: (Arrow arr) 
    => (d -> r) 
    -> (d -> r -> d) 
    -> (r -> r) 
    -> arr d d 
recordArrow g s f = splitA (arr g >>^ f) >>^ \(r,d) -> s d r 

Które następnie niech mi zrobić coś takiego:

unarrow :: ((->) b c) -> (b -> c) -- unneeded as pointed out to me in the comments 
unarrow g = g 


data Testdata = Testdata { record1::Int,record2::Int,record3::Int } 

testRecord = unarrow $ 
     recordArrow record1 (\d r -> d { record1 = r }) id 
    >>> recordArrow record2 (\d r -> d { record2 = r }) id 
    >>> recordArrow record3 (\d r -> d { record3 = r }) id 

Jak widać nie ma to bardzo dobre wykorzystanie DRY.

Mam nadzieję, że może istnieć rozszerzenie języka, które może uprościć ten proces. Tak że powyższe może po prostu być ponownie zapisać jako:

testRecord' = unarrow $ 
     recordArrow' record1 id 
    >>> recordArrow' record2 id 
    >>> recordArrow' record3 id 

Aktualizacja dla jasności: Aby wyjaśnić trochę. Jestem świadomy, że mógłbym zrobić coś takiego:

foo d = d { record1 = id (record1 d), record2 = id (record2 d) } 

Ale to ignoruje wszelkie zlecenia wykonania i dowolny stan. Załóżmy, że funkcja aktualizacji dla record2 opiera się na zaktualizowanej wartości dla record1. Lub alternatywnie, mogę chcieć utworzyć inną strzałkę, która wygląda tak: arr d (d,x), a następnie chcę zbudować listę [x], której kolejność zależy od kolejności oceny zapisów.

Co znalazłem to, że często chcę wykonać niektóre funkcje, a następnie zaktualizować rekord. Można to zrobić przez gwintowania stan jak ten

g :: d -> r -> d 
foo d = let d' = d { record1 = (g d) (record1 d) } in d' { record2 = (g d') (record2 d') } 

Ale myślę, że strzałka jest notacja neater i mogłem mieć [arr d d] i łańcucha je razem w sekwencji. Plus jeśli r lub są Monadami, to tworzy to lepszy kod. Albo jeśli obie są Monadami, to pozwalam mi wykonywać warstwowe wiązania bez konieczności używania Monad Transformatora. W przypadku ST s x, pozwól mi łączyć w uporządkowany sposób stan s.

Nie próbuję rozwiązać jednego konkretnego problemu. Po prostu próbuję znaleźć abstrakcyjną metodę aktualizowania składni rekordów bez konieczności jednoznacznego definiowania jakiegoś "gettera" i "setera".


Poniżej odpowiedział w comments-- Sidenote: miałem do definiowania funkcji unarrow do konwersji funkcję (->) strzałkę z powrotem do funkcji. W przeciwnym razie, jeśli mam someArrow b dla strzałki arr b c, nie mogę uzyskać wartości c. Dzięki funkcji unarrow mogę napisać unarrow someArrow b i działa dobrze. Czuję, że muszę zrobić coś złego tutaj, ponieważ moja definicja dla unarrow jest po prostu unarrow g = g.

+0

Funkcja 'unarrow' może zostać usunięta, jeśli podasz' testRecord' jawny typ podpisu. Wystąpił problem z wnioskiem typu, nie można stwierdzić, że 'testRecord' powinien być typem funkcji. – bmaderbacher

+0

Byłoby miło, gdybyś mógł trochę wyjaśnić, czego nie możesz osiągnąć. Czy chcesz zastosować funkcje (id w tym przypadku) do pól rekordu, jak zakładałem dla mojej odpowiedzi? – bmaderbacher

+0

Dzięki za wskazówkę na temat 'unarrow' @bmaderbacher. Rozgryzałem się w głowie, dlaczego nie mogłem zastosować strzałki w niektórych przypadkach - jednoznacznie definiując, że jest to funkcja w sygnaturze typu, wydaje się teraz oczywista. – TheCriticalImperitive

Odpowiedz

7

Abstrakcja, której szukasz, nazywa się obiektywem, a pakiet lens w Hackage jest prawdopodobnie najbardziej rozpowszechnioną implementacją obecnie używanego pomysłu. Korzystanie z pakietu lens można zdefiniować recordArrow' jak

{-# LANGUAGE RankNTypes #-} 

import Control.Arrow 
import Control.Lens 

recordArrow' :: Arrow arr => Lens' d r -> (r -> r) -> arr d d 
recordArrow' field f = arr $ field %~ f 

%~ jest operatorem aktualizacja, która aktualizuje do wartości wewnątrz większego struktur danych przy użyciu funkcji przez danego obiektywu.

Problem polega na tym, że nie otrzymujesz automatycznie soczewek dla pól rekordów, ale możesz je ręcznie zdefiniować lub wygenerować automatycznie za pomocą szablonu Haskell. Na przykład:

{-# LANGUAGE TemplateHaskell #-} 

import Control.Lens 

data Testdata = Testdata { _record1::Int, _record2::Int, _record3::Int } 

makeLenses ''Testdata 

Należy pamiętać, że oryginalne akcesory rekordów są poprzedzone podkreśleniami, abyśmy mogli używać oryginalnych nazw dla soczewek.

testRecord :: Testdata -> Testdata 
testRecord = 
     recordArrow' record1 id 
    >>> recordArrow' record2 id 
    >>> recordArrow' record3 id 

Funkcja unarrow nie jest potrzebna. Lepiej jest wymusić typ rodzajowy na konkretny typ przez proste podanie podpisu typu.

Należy pamiętać, że jeśli poszukujesz ładniejszej składni do operacji łańcuchowego zapisu i nie masz żadnego innego zastosowania dla strzałek, możesz po prostu użyć monady State z soczewkami. Na przykład:

import Control.Monad.State (execState) 

testRecord' :: Testdata -> Testdata 
testRecord' = execState $ do 
    record1 .= 3 
    record2 %= (+5) 
    record3 += 2 
+0

Dzięki! Właśnie tego szukałem. W moich poszukiwaniach natknąłem się na listy heterogeniczne i CTRex (http://www.haskell.org/haskellwiki/CTRex). Ale było dużo napięć, bo to, co miałem nadzieję, byłoby super proste. – TheCriticalImperitive

+0

Właśnie zobaczyłem twoje zaktualizowane pytanie i dodałem przykład używania soczewek w monadzie państwowej. To może być bliższe temu, czego naprawdę szukasz. – shang

Powiązane problemy