Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Abstracting Function in Haskell

I am currently taking a class in Haskell and am having a bit of trouble understanding how functions are passed as parameters. For this assignment, we were tasked with creating a program that would evaluate expressions. To reduce boiler plating, I wanted to abstract the function by creating a helper function that would take in an operator as an input and return the result

Main Function:

eval :: EDict -> Expr -> Maybe Double
eval _ (Val x) = Just x
eval d (Var i) = find d i
eval d (Add x y) = evalOp d (+) x y
eval d (Mul x y) = evalOp d (*) x y
eval d (Sub x y) = evalOp d (-) x y

Helper Function:

evalOp:: EDict -> ((Num a) => a -> a -> a) -> Expr -> Expr -> Maybe Double
evalOp d op x y =
  let r = eval d x
      s = eval d y
  in case (r, s) of
    (Just m, Just n) -> Just (m `op` n)
    _                -> Nothing

Other definitions

data Expr
  = Val Double
  | Add Expr Expr
  | Mul Expr Expr
  | Sub Expr Expr
  | Dvd Expr Expr
  | Var Id
  | Def Id Expr Expr
  deriving (Eq, Show)

type Dict k d  =  [(k,d)]

define :: Dict k d -> k -> d -> Dict k d
define d s v = (s,v):d

find :: Eq k => Dict k d -> k -> Maybe d
find []             _                 =  Nothing
find ( (s,v) : ds ) name | name == s  =  Just v
                         | otherwise  =  find ds name

type EDict = Dict String Double

I looked into how +,-, and * are to be passed into other functions and found that these operators are defined by the following definition:

ghci> :t (*)
(*) :: (Num a) => a -> a -> a  

However, when I run my code I get the following compilation error:

Illegal polymorphic or qualified type: Num a => a -> a -> a
    Perhaps you intended to use RankNTypes or Rank2Types
    In the type signature for ‘evalOp’:
      evalOp :: EDict
                -> ((Num a) => a -> a -> a) -> Expr -> Expr -> Maybe Double

I am not really sure why this is happening as I gave my function the proper parameters as defined by Haskell. Any help would be greatly appreciated as I am still very new to the language.

like image 606
Jengels Avatar asked Jun 17 '26 00:06

Jengels


1 Answers

Right now, your Expr data type is constrained to Double-valued expressions, so there is no need to deal with polymorphism.

evalOp:: EDict -> (Double -> Double -> Double) -> Expr -> Expr -> Maybe Double
evalOp d op x y =
  let r = eval d x
      s = eval d y
  in case (r, s) of
    (Just m, Just n) -> Just (m `op` n)
    _                -> Nothing

(+) :: Num a => a -> a -> a is a valid argument for evalOp, because its type can be "restricted" to Double -> Double -> Double.

> let f :: Double -> Double -> Double; f = (+)
> f 3 5
8.0

If your expression type were parameterized, then you would put a Num a constraint on your functions (not just on the arguments that involve a, because you want the same a throughout the function).

data Expr a
  = Val a
  | Add (Expr a) (Expr a)
  | Mul (Expr a) (Expr a)
  | Sub (Expr a) (Expr a)
  | Dvd (Expr a) (Expr a)
  | Var Id
  | Def Id (Expr a) (Expr a)
  deriving (Eq, Show)

type EDict a = Dict String a

evalOp:: Num a => EDict a -> (a -> a -> a) -> Expr a -> Expr a -> Maybe a
evalOp d op x y =
  let r = eval d x
      s = eval d y
  in case (r, s) of
    (Just m, Just n) -> Just (m `op` n)
    _                -> Nothing


eval :: Num a => EDict a -> Expr a -> Maybe a
eval _ (Val x) = Just x
eval d (Var i) = find d i
eval d (Add x y) = evalOp d (+) x y
eval d (Mul x y) = evalOp d (*) x y
eval d (Sub x y) = evalOp d (-) x y
like image 103
chepner Avatar answered Jun 19 '26 14:06

chepner



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!