Можно ли эмулировать функцию, используя ваш собственный тип данных? - PullRequest
11 голосов
/ 26 июня 2011

Можно ли эмулировать функцию с вашим собственным типом данных с некоторым расширением GHC?Я хочу сделать, например,

(мнимый синтаксис)

data MyFunc = MyFunc String (Int->Int)

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

inc = MyFunc "increment" (1+)

test = inc 1

Т.е. данные, которые несут с собой некоторую мета-информацию и могут быть сопоставлены с шаблоном, но которые все еще могут называться какобычная функция.Теперь я знаю, что могу определить свой собственный инфиксный оператор, такой как $$ и вызов inc $$ 1, но возможность использовать синтаксис обычного вызова функции будет очень полезна во встроенных DSL.

Ответы [ 3 ]

18 голосов
/ 26 июня 2011

Да, это может быть сделано в ограниченной степени.

Но сначала нам понадобится

{-# LANGUAGE Rank2Types #-}

Давайте определим

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

Я добавляю больше структуры к вашим именам, чтобы получить лучшую поддержку шоу. ;)

Тогда давайте определим класс:

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

instance Magic M where
    magic = id

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

Теперь рассмотрим тип:

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

Тип результата magic: (a -> b) или M a b.

Так что его можно использовать как член MyFunc. Теперь этот тип несколько неудовлетворителен, потому что вы не можете отправлять экземпляры на него, но это означает, что

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

test :: Int
test = inc 1

работает просто отлично.

Мы можем даже сделать довольно хороший способ показать их. Хотя мы не можем использовать show для MyFunc, мы можем определить его для M.

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

Затем мы можем создать функцию, которую мы можем применить к M a b (и, соответственно, к любому MyFunc), чтобы получить M a b.

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

и мы можем определить специальный комбинатор, чтобы показать MyFunc s:

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

Тогда мы можем играть. Мы можем определить композиции MyFunc с.

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

И поскольку я дал достаточно структуры именам, мы даже получаем правильные скобки для более сложных композиций без лишних скобок.

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

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

Но помните, вы не сможете определить экземпляры для MyFunc, поскольку это может быть только type, а не newtype. Чтобы определить экземпляры, вам нужно определить их в M, а затем использовать m для преобразования в этот тип, чтобы неявная диспетчеризация имела тип для захвата.

Из-за типа ранга 2, если вы интенсивно используете их в локальном контексте, вы также можете включить NoMonoLocalBinds и / или NoMonomorphismRestriction.

5 голосов
/ 26 июня 2011

Нет, синтаксис f e не может быть перегружен. f должен иметь тип S -> T.

Но вы все равно можете многое сделать с EDSL, если вы делаете глубокое встраивание, то есть вы позволяете своим функциям строить синтаксические деревья вместо вычислений.

3 голосов
/ 26 июня 2011

Вы не можете напрямую перегружать синтаксис вызова функции, нет.

Вы можете создать свой собственный тип стрелки --- см. Control.Arrow .Затем вы можете вызывать свои «функции», используя обозначение стрелки (вам нужно будет указать {-# LANGUAGE Arrows #-} в верхней части исходного файла).Будет ли это достаточно хорошо для вас, зависит от потребностей вашего DSL.

...