2012-12-31 19 views
6

Mam następującą strukturę danych:Haskell Powrót Rodzaj Polimorfizm

data TempUnit = Kelvin Float 
       | Celcius Float 
       | Fahrenheit Float 

Chcę zaimplementować funkcję, która przekształca temperaturę od Kelvina do innej jednostki. Jak mogę przekazać jednostkę typu zwrotu do funkcji?

Odpowiedz

14

Jednym ze sposobów jest użycie 3 oddzielnych typów dla różnych jednostek temperatury, a następnie użycie klasy typu, aby "zjednoczyć" je jako temperatury, np.

newtype Kelvin = Kelvin Float 
newtype Celcius = Celcius Float 
newtype Fahrenheit = Fahrenheit Float 

class TempUnit a where 
    fromKelvin :: Kelvin -> a 
    toKelvin :: a -> Kelvin 

instance TempUnit Kelvin where 
    fromKelvin = id 
    toKelvin = id 

instance TempUnit Celcius where 
    fromKelvin (Kelvin k) = Celcius (k - 273.15) 
    toKelvin (Celcius c) = Kelvin (c + 273.15) 

instance TempUnit Fahrenheit where 
    fromKelvin (Kelvin k) = Fahrenheit ((k-273.15)*1.8 + 32) 
    toKelvin (Fahrenheit f) = Kelvin ((f - 32)/1.8 + 273.15 

Teraz możesz po prostu użyć toKelvin/fromKelvin i odpowiednia realizacja zostanie wybrany na podstawie (wywnioskować) Zwraca typ, np

absoluteZeroInF :: Fahrenheit 
absoluteZeroInF = fromKelvin (Kelvin 0) 

(Uwaga wykorzystania newtype zamiast data ta jest taka sama jak data ale bez kosztów wykonawczego z dodatkowym konstruktora.)

Ta metoda zapewnia dowolną funkcję konwersji convert :: (TempUnit a, TempUnit b) => a -> b automatycznie: convert = fromKelvin . toKelvin. W tym przypadku wymaga to pisania podpisów funkcji, które obsługują arbitralne temperatury z ograniczeniami TempUnit a => ... a, a nie zwykłym TempUnit.


Można również użyć wartości "wartownika", która jest zignorowana, np.

fromKelvin :: TempUnit -> TempUnit -> TempUnit 
fromKelvin (Kelvin _) (Kelvin k) = Kelvin k 
fromKelvin (Celcius _) (Kelvin k) = Celcius (k - 273.15) 
fromKelvin (Fahrenheit _) (Kelvin k) = Fahrenheit (...) 

(to chyba lepiej zrobić metodą @seliopou sugeruje. Wyrwanie się oddzielny Unit typ)

ten może być stosowany tak:

-- aliases for convenience 
toC = Celcius 0 
toK = Kelvin 0 
toF = Fahrenheit 0 

fromKelvin toC (Kelvin 10) 
fromKelvin toF (Kelvin 10000) 

Zauważ, że ta metoda to jest , a nie typ-safe: co się stanie, gdy spróbujesz przekonwertować Celcius 100 z fromKelvin? (to znaczy.co jest wartością fromKelvin toF (Celcius 100)?)


Wszystko to powiedział, że najlepiej byłoby, aby wewnętrznie standaryzację na jednym urządzeniu i konwertować tylko do innych na wejściu i wyjściu, czyli tylko te funkcje, które czytają lub zapisują temperatury potrzebują martwić się o konwersje, wszystko inne działa z (np.) Kelvin.

+1

@SvenK Jest to tak dobre rozwiązanie, jak się najprawdopodobniej dostaniesz. Jeśli możesz zmienić własny kod, aby pasował do tego wzorca, powinieneś to zrobić. – seliopou

+1

Zgadzam się co do zasady, ale muszę stanowczo sprzeciwić się niezagrożonym przywilejom skali Kelvina. Powinieneś był oprzeć konwersję tylko na rozsądnej skali [Rankine scale] (http://en.wikipedia.org/wiki/Rankine_scale). –

+0

Dziękuję wszystkim za wspaniałe odpowiedzi. @dbaupp To nie jest kod produkcyjny, pracuję nad kilkoma ćwiczeniami na temat programowania funkcjonalnego moich studiów na uniwersytecie, aby nauczyć mnie własnego haskell. Szczęśliwego Nowego Roku. – SvenK

4

Pozwól mi zaproponować refaktoryzacji, które mogą pomóc Ci w drodze:

data Unit = Kelvin | Celcius | Fahrenheit 
data Temp = Temp Unit Float 

Następnie można łatwo zrobić, co chcesz zrobić:

convert :: Temp -> Unit -> Temp 

EDIT:

Jeśli nie możesz wykonać tego refaktoryzacji, możesz nadal robić to, co chcesz, to jest trochę mniej czyste:

convert :: Temp -> Temp -> Temp 

Powiedzmy, że chcesz przekonwertować temperatury w Kelvin (wartości związanej z identyfikatorem t) do Celcius. Można by zrobić coś takiego:

convert t (Celcius 0) 

Twój realizacja convert będzie mecz na wzór drugiego argumentu w celu określenia jednostek do konwersji.

3

W kodzie jest tylko jeden typ, a jest to TempUnit. Kelvin, Celcius i Fahrenheit nie są typami, są konstruktorami danych. Więc nie można używać polimorfizmu do wybierania między nimi.

Jeśli chcesz użyć polimorfizmu typu powrotu, musisz zdefiniować 3 różne typy i uczynić je instancjami tego samego typu. Że coś może wyglądać następująco:

newtype Kelvin = Kelvin Float 
newtype Celsius = Celsius Float 
newtype Fahrenheit = Fahrenheit Float 

class Temperature t where 
    fromKelvin :: Kelvin -> t 
    toKelvin :: t -> Kelvin 

instance Temperature Kelvin where 
    fromKelvin = id 
    toKelvin = id 

instance Temperature Celsius where 
    fromKelvin (Kelvin k) = Celsius $ -- insert formula here 
    toKelvin (Celsius c) = Kelvin $ -- insert formula here 

instance Temperature Fahrenheit where 
    -- same as for Celsius 

Następnie można wybrać, które konwersja chcesz dostarczając typu adnotacji (lub używając wynik w kontekście, w którym wymagany jest specyficzny typ):

myTemp :: Celsius 
myTemp = fromKelvin $ Kelvin 42 

jednak to nie wydaje się być dobrym zastosowaniem polimorfizmu. Podejście, w którym masz typ danych algebraicznych TemperatureUnit, a następnie przedstawia temperaturę jako liczby w połączeniu z jednostką, wydaje się dużo bardziej uzasadnione. W ten sposób funkcje konwersji po prostu przyjmą jednostkę docelową jako argument - nie ma w niej polimorfizmu.