Mało znanym faktem jest to, że jeśli włączysz wystarczającą liczbę rozszerzeń języka (ghc), Haskell stanie się dynamicznie pisanym językiem interpretowanym! Na przykład poniższy program implementuje dodawanie.
{-# Language MultiParamTypeClasses, FunctionalDependencies, FlexibleInstances, UndecidableInstances #-}
data Zero
data Succ a
class Add a b c | a b -> c
instance Add Zero a a
instance (Add a b c) => Add (Succ a) b (Succ c)
To już tak naprawdę nie wygląda jak Haskell. Dla jednego zamiast działania na obiektach, działamy na typach. Każda liczba ma własny typ. Zamiast funkcji mamy klasy typów. Zależności funkcjonalne pozwalają nam wykorzystywać je jako funkcje między typami.
Jak więc wywołać nasz kod? Używamy innej klasy
class Test a | -> a
where test :: a
instance (Add (Succ (Succ (Succ (Succ Zero)))) (Succ (Succ (Succ Zero))) a)
=> Test a
To ustawia typ test
na typ 4 + 3. Jeśli otworzymy to w ghci, okaże się, że test
rzeczywiście jest to typ 7:
Ok, one module loaded.
*Main> :t test
test :: Succ (Succ (Succ (Succ (Succ (Succ (Succ Zero))))))
Zadanie
Chcę, abyś zaimplementował klasę, która zwielokrotnia dwie liczby Peano (nieujemne liczby całkowite). Liczby Peano zostaną zbudowane przy użyciu tych samych typów danych w powyższym przykładzie:
data Zero
data Succ a
Twoja klasa będzie również oceniana w taki sam sposób jak powyżej. Możesz nazwać swoją klasę, jak chcesz.
Możesz używać dowolnych rozszerzeń języka GHC bez bajtów.
Przypadki testowe
Te przypadki testowe zakładają, że twoja klasa ma nazwę M
, możesz nazwać ją inną nazwą, jeśli chcesz.
class Test1 a| ->a where test1::a
instance (M (Succ (Succ (Succ (Succ Zero)))) (Succ (Succ (Succ Zero))) a)=>Test1 a
class Test2 a| ->a where test2::a
instance (M Zero (Succ (Succ Zero)) a)=>Test2 a
class Test3 a| ->a where test3::a
instance (M (Succ (Succ (Succ (Succ Zero)))) (Succ Zero) a)=>Test3 a
class Test4 a| ->a where test4::a
instance (M (Succ (Succ (Succ (Succ (Succ (Succ Zero)))))) (Succ (Succ (Succ Zero))) a)=>Test4 a
Wyniki
*Main> :t test1
test1
:: Succ
(Succ
(Succ
(Succ
(Succ (Succ (Succ (Succ (Succ (Succ (Succ (Succ Zero)))))))))))
*Main> :t test2
test2 :: Zero
*Main> :t test3
test3 :: Succ (Succ (Succ (Succ Zero)))
*Main> :t test4
test4
:: Succ
(Succ
(Succ
(Succ
(Succ
(Succ
(Succ
(Succ
(Succ
(Succ
(Succ
(Succ (Succ (Succ (Succ (Succ (Succ (Succ Zero)))))))))))))))))
Inspiracje czerpie z pisania wywiadu technicznego
źródło
Odpowiedzi:
130121 bajtów-9 bajtów dzięki Ørjan Johansen
Wypróbuj online!
Definiuje to rodziny zamkniętych typów dla dodawania
(+)
i mnożenia(*)
. Następnie(#)
definiowana jest klasa typów, która wykorzystuje(*)
rodzinę typów wraz z ograniczeniem równości do konwersji ze świata famili typów na świat prologów klasowych.źródło
Zero
przezz
.+
jest przydatna?139 bajtów
Wypróbuj online!
Definiuje operator typu
*
. Odpowiednik programu Prolog:Potato44 i Hat Wizard zapisały po 9 bajtów. Dzięki!
źródło
f
zamiastSucc
.Wersja rodzinna, 115 bajtów
Wypróbuj online!
Jest to rodzina zamknięta, taka jak potato44 . Z wyjątkiem, w przeciwieństwie do innych odpowiedzi, używam tylko rodziny 1 typu.
To definiuje operatora dla trzech typów. Zasadniczo implementuje
(a*b)+c
. Ilekroć chcemy dodać nasz argument prawej ręki do sumy, zamiast tego umieszczamy go w akumulatorze.To uniemożliwia nam
(+)
w ogóle zdefiniowanie . Technicznie możesz użyć tej rodziny do implementacji dodawania poprzez działanieWersja klasy, 137 bajtów
Wypróbuj online!
Ta wersja klasowa traci trochę podstaw do wersji rodzinnej, jednak wciąż jest krótsza niż najkrótsza wersja klasowa tutaj. Wykorzystuje to samo podejście, co moja wersja rodzinna.
źródło
Constraint
. Powinieneś więc zaktualizować specyfikację lub powrócić do formularza, który używa klasy zamiast synonimu typu. Gdybym użył synonimu typu, mógłbym uzyskać odpowiedź do 96 bajtów, więc oszczędza mi to jeszcze jeden bajt