Yes, it can be done to a limited extent.
But first we'll need
{-# LANGUAGE Rank2Types #-}
Let's define
data M a b = M { name :: Int -> String -> String, eval :: a -> b }
I'm adding more structure to your names so I can get nicer show support. ;)
Then lets define a class:
class Magic m where
magic :: M a b -> m a b
instance Magic M where
magic = id
instance Magic (->) where
magic (M _ f) = f
Now, consider the type:
type MyFunc a b = forall m. Magic m => m a b
The result type of magic
is either (a -> b)
or a M a b
.
So it can be used as a member of MyFunc
. Now, this type is somewhat unsatisfying, because you can't make instances dispatch on it, but it does mean that
inc :: MyFunc Int Int
inc = magic (M (const (showString "inc")) (+1))
test :: Int
test = inc 1
works just fine.
We can even make a rather nice way to show them. Even though we can't use show on MyFunc
, we can define it for M
.
instance Show (M a b) where
showsPrec d (M s _) = s d
Then we can make a function we can apply to M a b
(and by extension any MyFunc
) to get out an M a b
.
m :: M a b -> M a b
m = id
and we can define a special combinator to show MyFunc
s:
showM :: MyFunc a b -> String
showM f = show (m f)
Then we can play. We can define compositions of 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
And because I gave enough structure to the names, we even get correct parenthesization for more complicated compositions, without needless parentheses.
*Main> showM $ inc2 .# inc
"(inc . inc) . inc"
*Main> showM $ inc .# inc2
"inc . inc . inc"
But remember, you won't be able to define any instances for MyFunc
, since it can only be a type
, and not a newtype
. In order to define instances you'll have to define them on M
, and then use m
to convert to that type so that implicit dispatch has a type to grab onto.
Because of the rank 2 type, if you use these heavily in local contexts, you may also want to turn on NoMonoLocalBinds
and/or NoMonomorphismRestriction
.