Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Haskell function definition convention

I am beginner in Haskell .

The convention used in function definition as per my school material is actually as follows

function_name arguments_separated_by_spaces = code_to_do

ex :

f a b c = a * b +c

As a mathematics student I am habituated to use the functions like as follows

function_name(arguments_separated_by_commas) = code_to_do

ex :

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

Its working in Haskell .

My doubt is whether it works in all cases ?

I mean can i use traditional mathematical convention in Haskell function definition also ?

If wrong , in which specific cases the convention goes wrong ?

Thanks in advance :)

like image 457
hanugm Avatar asked Mar 31 '14 06:03

hanugm


People also ask

How do you define a function in Haskell?

The most basic way of defining a function in Haskell is to ``declare'' what it does. For example, we can write: double :: Int -> Int double n = 2*n. Here, the first line specifies the type of the function and the second line tells us how the output of double depends on its input.

What is <> called in Haskell?

It's an alias for mappend , from the Data. Monoid module.

What does foo mean in Haskell?

Foo (pronounced FOO) is a term used by programmers as a placeholder for a value that can change, depending on conditions or on information passed to the program. Foo and other words like it are formally known as metasyntactic variables.


1 Answers

Let's say you want to define a function that computes the square of the hypoteneuse of a right-triangle. Either of the following definitions are valid

hyp1 a b = 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 => part for now) the type tells you that the function takes a pair (a, a) and returns another a (e.g it might take a pair of integers and return another integer, or a pair of real numbers and return another real number). You use it like this

>> hyp2 (3,4)
25

Notice that the parentheses aren't optional here! They ensure that the argument is of the correct type, a pair of as. If you don't include them, you will get an error (which will probably look really confusing to you now, but rest assured that it will make sense when you've learned about type classes).

Now looking at hyp1 one way to read the type a -> a -> a is it takes 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 do include parentheses!

So the first thing to notice is that the way you use the function has to match the way you defined it. If you define the function with parens, you have to use parens every time you call it. If you don't use parens when you define the function, you can't use them when you call it.

So it seems like there's no reason to prefer one over the other - it's just a matter of taste. But actually I think there is a good reason to prefer one over the other, and you should prefer the style without parentheses. There are three good reasons:

  1. It looks cleaner and makes your code easier to read if you don't have parens cluttering up the page.

  2. You will take a performance hit if you use parens everywhere, because you need to construct and deconstruct a pair every time you use the function (although the compiler may optimize this away - I'm not sure).

  3. You want to get the benefits of currying, aka partially applied functions*.

The last point is a little subtle. Recall that I said that one way to understand a function of type a -> a -> a is that it takes two things of type a, and returns another a. But there's another way to read that type, which is a -> (a -> a). That means exactly the same thing, since the -> operator is right-associative in Haskell. The interpretation is that the function takes a single a, and returns a function of type a -> a. This allows you to just 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 wide variety of situations. For example, the map functions lets you apply some function to every element of a list -

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

Say you have the function (++ "!") which adds a bang to any String. But you have lists of Strings and you'd like them all to end with a bang. No problem! You just partially apply the map function

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

Now bang is a function of type**

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

and you can use it like this

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

Pretty useful!

I hope I've convinced you that the convention used in your school's educational material has some pretty solid reasons for being used. As someone with a math background myself, I can see the appeal of using the more 'traditional' syntax but I hope that as you advance in your programming journey, you'll be able to see the advantages in changing to something that's initially a bit unfamiliar to you.


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

** Actually GHCI will tell you the type is bang :: [[Char]] -> [[Char]] but since String is a synonym for [Char] these mean the same thing.

like image 89
Chris Taylor Avatar answered Nov 15 '22 08:11

Chris Taylor