2011-10-22 13 views
5

Aby keep it simple, będę korzystać z tej contrived przykład klasę (chodzi o to, że mamy kilka drogich dane pochodzące z metod):memoization i typeclasses

class HasNumber a where 
    getNumber :: a -> Integer 
    getFactors :: a -> [Integer] 
    getFactors a = factor . getNumber 

oczywiście możemy dokonać implementacji memoizing z tej klasy, takie jak:

data Foo = Foo { 
    fooName :: String, 
    fooNumber :: Integer, 
    fooFactors :: [Integer] 
} 

foo :: String -> Integer -> Foo 
foo a n = Foo a n (factor n) 

instance HasNumber Foo where 
    getNumber = fooNumber 
    getFactors = fooFactors 

Ale wydaje się nieco brzydki być wymagane, aby ręcznie dodać pole „czynniki” do każdego rekordu, który będzie instancją HasNumber. Następny pomysł:

data WithFactorMemo a = WithFactorMemo { 
    unWfm :: a, 
    wfmFactors :: [Integer] 
} 

withFactorMemo :: HasNumber a => a -> WithFactorMemo a 
withFactorMemo a = WithFactorMemo a (getFactors a) 

instance HasNumber a => HasNumber (WithFactorMemo a) where 
    getNumber = getNumber . unWfm 
    getFactors = wfmFactors 

Będzie to wymagało dużo boilerplate do podnoszenia wszelkie inne operacje oryginalnego a do WithFactorMemo a, choć.

Czy są jakieś eleganckie rozwiązania?

+0

Innym rozwiązaniem, o którym właśnie pomyślałem, jest uczynienie funkcji * factor * zapamiętaniem, choć byłoby to mniej praktyczne, gdyby wynikiem 'getNumber' była jakaś większa struktura danych, a (AFAIK) wpisy nigdy nie zostałyby zebrane jako śmieci (w przeciwieństwie do dwóch rozwiązań w moim pytaniu). – FunctorSalad

Odpowiedz

7

Oto rozwiązanie: stracić typeklass. Rozmawiałem o tym here i here. Każda czcionka numeryczna TC a, dla której każdy z jej elementów przyjmuje pojedynczy argument a, jest izomorficzna z typem danych. Oznacza to, że każda instancja klasy HasNumber mogą być reprezentowane w tego typu danych:

data Number = Number { 
    getNumber' :: Integer, 
    getFactors' :: [Integer] 
} 

Mianowicie, według tej transformacji:

toNumber :: (HasNumber a) => a -> Number 
toNumber x = Number (getNumber x) (getFactors x) 

I Number jest oczywiście instancją HasNumber również.

instance HasNumber Number where 
    getNumber = getNumber' 
    getFactors = getFactors' 

Ten izomorfizm pokazuje nam, że ta klasa jest w przebraniu typem danych i powinna umrzeć. Po prostu użyj zamiast tego Number. Może początkowo nie jest oczywiste, jak to zrobić, ale z niewielkim doświadczeniem powinno nadejść szybko. Np., Twój typ Foo staje:

data Foo = Foo { 
    fooName :: String, 
    fooNumber :: Number 
} 

Twój memoization wtedy przyjść za darmo, ponieważ czynniki są przechowywane w strukturze Number danych.

+0

Właściwie to właśnie postanowiłem wypróbować także tuż przed wysłaniem :) Zgadzam się na umieszczenie operacji w jednym typie ("Numer" tutaj), ale może nadal dobrze jest mieć 'Class HasNumber a where numberDict :: a -> Number' wraz z wrappers 'getNumber = getNumber '. numberDict' i tak dalej. Ale faktycznie należy przechowywać "Numer" w rekordach, które mają być "HasNumber", zamiast tworzyć "Numer" od liczby całkowitej w implementacji 'numerDict' (co oczywiście zostawiłoby nas bez ponownej zapamiętywania) . – FunctorSalad

+0

Gorąco polecam przeciwko typeclass w takich przypadkach, to tylko wejdzie ci w drogę. Po prostu modeluj to konkretnie, zestaw narzędzi FP jest lepiej dostosowany do tego rodzaju programowania, język jest lepszy w abstrahowaniu od typów danych niż typografia, i nie pozwala ci się oszukać, że robisz modelowanie OO (którym jesteś nie - i jeśli myślisz w ten sposób, nawet nie zdając sobie z tego sprawy, język skończy się ograniczaniem cię w dół drogi). – luqui

Powiązane problemy