Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Operator as an argument in Haskell

I'm quite new to Haskell, may be it's a stupid question. What I want is to give to my function as an argument any operator. For example:

myFunc :: a -> Int -> Int -> Boolean
myFunc operator a b = a operator b

*Project> myFunc (>) 5 2
True
*Project> myFunc (<=) 5 2
False

Help me in advice how to do that please!

like image 581
Vitali Kuzmin Avatar asked Oct 14 '16 00:10

Vitali Kuzmin


2 Answers

You can do that with haskell function arguments. In your function above, you want myFunc to take a function that takes two Ints and returns a Bool (not a Boolean, you must have typed that wrong). The declaration for that function would be (Int -> Int -> Bool). Therefore, you can write:

myFunc :: (Int -> Int -> Bool) -> Int -> Int -> Bool
myFunc op a b = a `op` b

This defines a higher-order function that takes a function with two Int parameters that returns a Bool (and two Ints). You can now use it like any other function parameter!

Note that this is exactly the same as doing:

myFunc (#) a b = a # b

Or:

myFunc (%) a b = a % b

Because using infix operaters like * or /, or any operator composed only of special characters, without backticks is just shorthand for using them with (typing `/` every time you want to divide something would get annoying!).

like image 200
Majora320 Avatar answered Nov 10 '22 01:11

Majora320


Under the hood, functions "exist" without names. Any function you define (or that is already defined in libraries), such as myFunc just is a function value, and the name just gives us a way to refer to it in other code that wants to use it. This is exactly the same as if you write x = 3: the value 3 "exists" independently of the name x, that name just gives us a way to refer to it.

Why is this relevant to your question about passing operators?

Well, as far as Haskell is concerned, operators like > and <= are also just nameless functions that happen to be bound to the names > and <=. The special treatment of them as operators (that you can write them infix between the arguments you're calling them on) is only about the names, and changes if you refer to them with different names.

There are two types of names in Haskell. Alphanumeric names (consisting only of letters, numbers, and underscores), and symbolic names (consisting only of symbol characters). If you have an expression {1} {2} {3}, then if {2} is a symbolic name (and {1} and {3} aren't symbolic names; otherwise you have a syntax error), then the expression is interpreted as meaning "call {2} on the arguments {1} and {3}". But if none of them are symbolic names, then it's instead interpreted as "call {1} on the arguments {2} and {3}".1

But all of this happens only with reference to the name, not to the functions actually referred to by those names. So if you write your myFunc like so:

myFunc operator a b = operator a b

Then it doesn't actually matter whether myFunc was called like myFunc (+) 1 2 or like myFunc plus 1 2; inside the definition of myFunc the "operator" is referred to by the name operator, which is an alphanumeric name. So you put it first when you want to call it, with its arguments following.

Alternatively you could use a symbolic name inside myFunc, like so:

myFunc ($&^*) a b = a $&^* b

Again, this also works even when myFunc was called with a non-operator function like myFunc plus 1 2.

And of course, there are ways to convert either kind of name to work like the other; you can put an alphanumeric name in backticks to use it infix like an operator:

myFunc operator a b = a `operator` b

And you can put a symbolic name in parentheses to simply use it as reference to the function it's bound to (and this is in fact the only way to use an operator without providing arguments for it):

myFunc ($^&*) a b = ($&^*) a b

So basically, the only special thing you needed to know to pass an operator to your function is what you already knew: put the operator in parentheses when you call the function. Inside the definition of the function, you can write it exactly the same as any other function; the style of name you choose in that function definition will determine whether you call it like an operator or like an ordinary function. You don't need to know (and in fact cannot find out) whether it was an operator "outside" the function.


1 Of course, when you have more complex expressions involving more than 3 things and multiple operators, then the rules of precedence and associativity come into play to determine exactly what's going on.

like image 22
Ben Avatar answered Nov 09 '22 23:11

Ben