andregps
andregps

Reputation: 341

Haskell: Is it possible to identify which function was passed as parameter to a high order function?

I want to idenfity what function was passed as parameter to a high-order function. How can i do that? Using pattern matching? I want to do something like the following code:

add x y = x+y
sub x y = x-y

myFunc :: (a->a->a) -> a -> a -> IO a
myFunc add x y = do print "add was performed"
                    add x y 
myFunc sub x y = do print "sub was performed"
                    sum x y
myFunc f x y = do print "another function was performed"
                  f x y

If this is not possible, does anyone has other idea to do that?

Upvotes: 3

Views: 171

Answers (3)

dfeuer
dfeuer

Reputation: 48611

It's not possible to do exactly what you requested. I would recommend that you instead make an embedded domain-specific language (EDSL) and write one or more interpreters for it. The most common approach is to represent the EDSL using an algebraic datatype or (in more complicated situations) a generalized algebraic datatype. Here you might have something like

data Expr a = Lit a
            | BinOp (Op a) (Expr a) (Expr a)
            deriving (Show)

data Op a = Add
          | Sub
          | Other (a -> a -> a)

instance Show (Op a) where
  show Add = "Add"
  show Sub = "Sub"
  show Other{} = "Other"

Now you can write an evaluator that takes an Expr a and performs the requested operations:

evalExpr :: Num a => Expr a -> a
evalExpr (Lit x) = x
evalExpr (BinOp op e1 e2) = runOp op (evalExpr e1) (evalExpr e2)

runOp :: Num a => Op a -> a -> a -> a
runOp Add a b = a + b
runOp Sub a b = a - b
runOp (Other f) a b = f a b

You can add tracing too:

evalExpr' :: (Num a, MonadWriter [(Expr a, a)] m) => Expr a -> m a
evalExpr' e = do
    result <- case e of
                Lit a -> return a
                BinOp op e1 e2 -> runOp op <$> evalExpr' e1 <*> evalExpr' e2
    tell [(e, result)]
    return result

Sample use:

*Write> runWriter $ evalExpr' (BinOp Add (Lit 3) (BinOp Sub (Lit 4) (Lit 5)))
(2,[(Lit 3,3),(Lit 4,4),(Lit 5,5),(BinOp Sub (Lit 4) (Lit 5),-1),(BinOp Add (Lit 3) (BinOp Sub (Lit 4) (Lit 5)),2)])

For convenience, you can write

instance Num a => Num (Expr a) where
  fromInteger = Lit . fromInteger
  (+) = BinOp Add
  (-) = BinOp Sub

Then the above can be abbreviated

*Write Control.Monad.Writer> runWriter $ evalExpr' (3 + (4-5))
(2,[(Lit 3,3),(Lit 4,4),(Lit 5,5),(BinOp Sub (Lit 4) (Lit 5),-1),(BinOp Add (Lit 3) (BinOp Sub (Lit 4) (Lit 5)),2)])

Upvotes: 3

sharp_c-tudent
sharp_c-tudent

Reputation: 463

Maybe to simplify and not to change a lot the overall look of your code, if it's already a long project and that's a concern, you could do something like:

add  x y = x+y
sub  x y = x-y

myFunc :: (Eq a, Num a) => (a->a->a) -> a -> a -> IO a
myFunc f x y =  if (add x y) == (f x y) then 
                   do print "add was performed"
                      return (add x y) 
                else if (sub x y) == (f x y) then 
                        do print "sub was performed"
                        return (sub x y)
                else
                    do print "another function was performed"
                       return (f x y)

It works, the only problem is that you wont be able to diferentiate for example an add 2 1 from a multiplication 2 1, so if thats a possibility you can throw new cases in there to cover all important grounds, like instead of only comparing add x y = f x y, also compare add y x with f y x. With some thought it will work 100% of the time.

Upvotes: 1

Frerich Raabe
Frerich Raabe

Reputation: 94429

No, this is not possible.

You could achieve something to that effect by having a data type which represents the operation, maybe

data Operation
    = Add (a -> a -> a)
    | Sub (a -> a -> a)
    | Other (a -> a -> a)

myFunc :: Operation -> a -> a -> IO a
myFunc (Add f) x y = do print "add was performed"
                     return (f x y)
myFunc (Sub f) x y = do print "sub was performed"
                     return (f x y)
myFunc (Other f) x y = do print "another function was performed"
                     return (f x y)

Upvotes: 10

Related Questions