Можно ли эмулировать функцию, используя свой собственный тип данных?

Можно ли эмулировать функцию с вашим собственным типом данных с некоторым расширением 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.

Ответ 1

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

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

{-# 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), чтобы вывести a 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 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

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

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

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

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

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

Ответ 2

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

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

Ответ 3

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

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