2011-06-26 22 views
11

Czy można emulować funkcję z własnym typem danych z pewnym rozszerzeniem GHC? Chcę zrobić np.Czy można emulować funkcję przy użyciu własnego typu danych?

(urojony składni)

data MyFunc = MyFunc String (Int->Int) 

instance (Int->Int) MyFunc where 
    ($) (MyFunc _ f) i = f i 

inc = MyFunc "increment" (1+) 

test = inc 1

Tj dane, które zawierają pewne meta-informacje i mogą być dopasowane do wzorca, ale które wciąż można nazwać zwykłą funkcją. Teraz wiem, że mógłbym zdefiniować własnego operatora infiksów, takiego jak $$ i zadzwonić pod numer inc $$ 1, ale możliwość używania zwykłej składni wywołania funkcji byłaby bardzo przydatna w przypadku osadzonych DSL.

Odpowiedz

18

Tak, można to zrobić w ograniczonym zakresie.

Ale najpierw musimy

{-# LANGUAGE Rank2Types #-} 

Zdefiniujmy

data M a b = M { name :: Int -> String -> String, eval :: a -> b } 

Dodaję więcej strukturę swoimi nazwami, więc mogę uzyskać ładniejszy wsparcie show. ;)

Następnie pozwala określić klasę:

class Magic m where 
    magic :: M a b -> m a b 

instance Magic M where 
    magic = id 

instance Magic (->) where 
    magic (M _ f) = f 

Teraz pod uwagę rodzaj:

type MyFunc a b = forall m. Magic m => m a b 

Typ wynikiem magic jest albo (a -> b) lub M a b.

Dzięki temu może być używany jako członek MyFunc. Teraz, ten typ jest nieco niezadowalająca, ponieważ nie można zrobić instancje wysyłką na nim, ale to nie znaczy, że

inc :: MyFunc Int Int 
inc = magic (M (const (showString "inc")) (+1)) 

test :: Int 
test = inc 1 

działa dobrze.

Możemy nawet zrobić raczej fajny sposób na pokazanie ich. Mimo że nie możemy użyć programu na MyFunc, możemy go zdefiniować dla M.

instance Show (M a b) where 
    showsPrec d (M s _) = s d 

Wtedy możemy utworzyć funkcję możemy zastosować do M a b (i co za tym idzie każdy MyFunc), aby wydostać się M a b.

m :: M a b -> M a b 
m = id 

i możemy zdefiniować specjalny COMBINATOR pokazać MyFunc s:

showM :: MyFunc a b -> String 
showM f = show (m f) 

Wtedy możemy grać. Możemy zdefiniować kompozycje MyFunc s.

infixr 9 .# 
(.#) :: MyFunc b c -> MyFunc a b -> MyFunc a c 
f .# g = magic (M 
    (\d -> showParen (d > 9) $ showsPrec 10 (m f) . 
           showString " . " . 
           showsPrec 9 (m g)) 
    (f . g)) 

inc2 :: MyFunc Int Int 
inc2 = inc .# inc 

test2 :: Int 
test2 = inc2 1 

bar, baz :: String 
bar = showM inc 
baz = showM inc2 

I dlatego dałem tyle strukturę nazw, możemy nawet uzyskać prawidłową parenthesization dla bardziej skomplikowanych kompozycjach, bez zbędnych nawiasów.

*Main> showM $ inc2 .# inc 
"(inc . inc) . inc" 

*Main> showM $ inc .# inc2 
"inc . inc . inc" 

Należy jednak pamiętać, że nie będzie w stanie określić żadnych wystąpień dla MyFunc, ponieważ może to być tylko type, a nie newtype. Aby zdefiniować instancje, musisz zdefiniować je na M, a następnie użyć m, aby przekonwertować na ten typ, tak aby niejawne wywoływanie miało typ, który można przechwycić.

Ze względu na typ 2 kategorii, jeśli używasz ich mocno w lokalnych kontekstach, możesz również włączyć NoMonoLocalBinds i/lub NoMonomorphismRestriction.

+9

To jest trochę przerażające. Kocham to. –

5

Nie, składnia f e nie może być przeciążona. Model f musi mieć typ S -> T.

Ale nadal można dużo zrobić z EDSL, jeśli wykonujesz głębokie osadzanie, tzn. Pozwalasz swoim funkcjom budować drzewa składniowe zamiast obliczeń.

3

Nie można bezpośrednio przeciążać składni wywołania funkcji, nie.

Możesz można zrobić własny niestandardowy typ strzałki --- patrz Control.Arrow. Następnie możesz wywołać swoje "funkcje", używając arrow notation (musisz umieścić {-# LANGUAGE Arrows #-} u góry pliku źródłowego). To, czy jest to wystarczająco dobre, zależy od potrzeb Twojego DSL.

+0

++ dla strzał. – fuz

+0

Nie zapomnij o aplikacjach! Mają też operatora aplikacji. – luqui

Powiązane problemy