Haskell Function Definition Agreement

I am new to Haskell.

The convention used in defining a function according to my training material is as follows

file_name arguments_separated_by_spaces = code_to_do

ex:

fabc = a * b +c 

As a student of mathematics, Iโ€™m used to using features like the following

function_name (arguments_separated_by_commas) = โ€‹โ€‹code_to_do

ex:

 f(a,b,c) = a * b + c 

Works at Haskell.

My doubt is, does it work in all cases?

I mean, can I use the traditional mathematical convention in defining a Haskell function?

If it is wrong, in what specific cases does the agreement go wrong?

Thank you in advance:)

+6
source share
3 answers

Suppose you want to define a function that calculates the square of the hypotenuse of a right triangle. Any of the following definitions are valid

 hyp1 ab = a * a + b * b hyp2(a,b) = a * a + b * b 

However, they are not the same function! You can tell by looking at their types in GHCI

 >> :type hyp1 hyp1 :: Num a => a -> a -> a >> :type hyp2 hyp2 :: Num a => (a, a) -> a 

Taking hyp2 first (and ignoring the Num a => ), the type tells you that the function takes a pair (a, a) and returns another a (for example, it can take a pair of integers and return another integer or a pair of real numbers and return other real number). You use it like this:

 >> hyp2 (3,4) 25 

Please note that parentheses are not optional here! They guarantee that the argument is of the correct type, a pair a s. If you do not enable them, you will receive an error message (which will probably look very confusing to you now, but be sure that it will make sense when you learn about class types).

Now, looking at hyp1 , one way to read type a -> a -> a is two things of type a and returns something else of type a . You use it like this:

 >> hyp1 3 4 25 

Now you will get an error if you include parentheses!

So, the first thing to notice is that the way you use the function should match the way you defined it. If you define a function using parens, you must use parens every time you call it. If you do not use parens when defining a function, you cannot use them when you call it.

Thus, it seems that there is no reason to prefer one over the other - it is just a matter of taste. But in fact, I think there is a good reason to prefer one over the other, and you should prefer a style without parentheses. There are three good reasons:

  • It looks cleaner and makes it easier to read code if you don't have partners cluttering the page.

  • If you use parsers everywhere, you will get a performance hit because you need to create and deconstruct a pair every time you use this function (although the compiler can optimize this - I'm not sure).

  • You want the benefits of currying, aka partially applied functions *.

The last point is a little thin. Recall that I said that one way to understand a function like a -> a -> a is that it takes two things of type a and returns another a . But there is another way to read this type, which is a -> (a -> a) . This means exactly the same thing, since the -> operator is right-associative in Haskell. The interpretation is that a function takes one a and returns a function of type a -> a . This allows you to simply provide the first argument to the function and apply the second argument later, for example

 >> let f = hyp1 3 >> f 4 25 

This is practically useful in a variety of situations. For example, map functions allow you to apply some function to each element of the list -

 >> :type map map :: (a -> b) -> [a] -> [b] 

Say you have a function (++ "!") That adds a hit to any String . But you have Strings lists, and you want them all to end in a bang. No problems! You just partially use the map function

 >> let bang = map (++ "!") 

Now bang is a function of type **

 >> :type bang bang :: [String] -> [String] 

and you can use it as follows

 >> bang ["Ready", "Set", "Go"] ["Ready!", "Set!", "Go!"] 

Pretty helpful!

I hope I convinced you that the convention used in your training material has some pretty good reasons to use. As someone with a mathematical background myself, I see the appeal of using a more โ€œtraditionalโ€ syntax, but I hope that as you progress along your programming journey, you can see the benefits of changing what is initially unfamiliar to you.


* Note for pedants - I know that currying and partial application are not exactly the same thing.

** In fact, GHCI will tell you that the type is bang :: [[Char]] -> [[Char]] , but since String is synonymous with [Char] , it means the same thing.

+13
source
 f(a,b,c) = a * b + c 

The key difference is that the above function takes a triple and gives a result. What you are actually doing is pattern matching in the top three. The type of function above is something like this:

 (a, a, a) -> a 

If you write functions like this:

 fabc = a * b + c 

You get automatic curry in function. You can write things like let b = f 3 2 and it will look like typecheck, but the same will not work with your initial version. In addition, things like currying can help with composing various functions with (.) , Which cannot yet be achieved using the old style unless you are trying to make triples.

+2
source
  • Mathematical notation is incompatible. If all functions were given arguments using (,) , you would have to write (+)((*)(a,b),c) to pass a*b and c for function + - of course, a*b obtained by passing a and b for function * .

  • You can write everything in the form, but it is more difficult to determine the composition. While now you can specify the type a->b to cover functions of any arity (so you can define composition as a function of type (b->c)->(a->b)->(a->c) ) , it is much more difficult to define functions of arbitrary arity using sets (now a->b will only mean a function of one argument, you can no longer make up a function of many arguments with a function of many arguments). Thus, it is technically possible, but this will require a language function to make it simple and convenient.

+2
source

All Articles