Is it possible to emulate a function using your own data type?

前端 未结 3 1710
南方客
南方客 2021-01-11 16:37

Is it possible to emulate a function with your own data type with some GHC extension? What I want to do is e.g.

(imaginary syntax)

data MyFunc = MyFu         


        
相关标签:
3条回答
  • 2021-01-11 17:18

    No, the syntax f e cannot be overloaded. The f has to have type S -> T.

    But you can still do a lot with EDSLs if you do a deep embedding, i.e., you let your functions build syntax trees instead of computing.

    0 讨论(0)
  • 2021-01-11 17:32

    You can't directly overload function call syntax, no.

    You can make your own custom arrow type --- see Control.Arrow. You can then invoke your "functions" using arrow notation (you will need to include {-# LANGUAGE Arrows #-} at the top of your source file). Whether this is good enough for you depends on the needs of your DSL.

    0 讨论(0)
  • 2021-01-11 17:33

    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 MyFuncs:

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

    Then we can play. We can define compositions of MyFuncs.

    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.

    0 讨论(0)
提交回复
热议问题