Haskell:是否有可能确定哪个函数作为参数传递给高阶函数?

时间:2016-01-12 14:24:08

标签: haskell

我想识别哪个函数作为参数传递给高阶函数。 我怎样才能做到这一点?使用模式匹配? 我想做类似下面的代码:

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

如果无法做到这一点,有没有人有其他想法呢?

3 个答案:

答案 0 :(得分:10)

不,这是不可能的。

你可以通过拥有代表操作的数据类型来实现这种效果,也许

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)

答案 1 :(得分:3)

不可能完全按照您的要求行事。我建议您改为使用嵌入式域特定语言(EDSL)并为其编写一个或多个解释器。最常见的方法是使用代数数据类型或(在更复杂的情况下)使用广义代数数据类型来表示EDSL。在这里你可能会有像

这样的东西
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"

现在,您可以编写一个带Expr a并执行请求操作的求值程序:

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

您也可以添加跟踪:

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

样品使用:

*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)])

为方便起见,您可以写

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

然后上面可以缩写

*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)])

答案 2 :(得分:1)

也许是为了简化并且不要改变代码的整体外观,如果它已经是一个很长的项目并且这是一个问题,你可以做类似的事情:

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)

它的工作原理,唯一的问题是你不能区分例如乘法2 1中的加2 1,所以如果有可能你可以在那里抛出新的案例来覆盖所有重要的理由,比如只比较添加xy = fxy,也比较添加yx和fy x。有些人认为它会100%有效。