8

This is a homework, so I would prefer only tips or a link to where I can learn rather than a full answer. This is what I am given:

allEqual :: Eq a => a -> a -> a -> Bool

What I understand from this is that I am supposed to compare 3 values (in this case a, a, a?) and return whether or not they all equal one another. This is what I tried:

allEqual :: Eq a => a -> a -> a -> Bool
allEqual x y z do
  Bool check <- x == y
  Bool nextC <- y == z
  if check == nextC
    then True
    else False

I honestly feel completely lost with Haskell, so any tips on how to read functions or declare them would help immensely.

Will Ness
  • 70,110
  • 9
  • 98
  • 181
  • What exactly confuses you about the declaration? – Alejandro Alcalde Apr 30 '20 at 09:31
  • `a`, `a`, `a` are not the values, but the *types* of the values (i.e., all values have the same type). – mkrieger1 Apr 30 '20 at 09:32
  • 8
    The type signature is correct. And then.. perhaps it's a good idea if you read first few chapters of [LYAH](http://learnyouahaskell.com/chapters) like [Baby's first functions](http://learnyouahaskell.com/starting-out#babys-first-functions) for sure... – Redu Apr 30 '20 at 09:34
  • 1
    Concerning the logic you were trying to implement (I don't know that much Haskell to be able to tell if it actually compiles), consider: What would your function currently return if the values were `x=1`, `y=2`, `z=3`? – mkrieger1 Apr 30 '20 at 09:35
  • 3
    You do not need do-notation or `<-` for this. There's a very short translation of "all three being equal is equivalent to `x` being equal to `y` *and* `y` being equal to `z`" in most programming languages, including Haskell. – molbdnilo Apr 30 '20 at 09:47
  • 5
    On an unrelated note, `if fact then True else False` is equivalent to `fact`, and `if fact then False else True` is equivalent to `not fact`. – molbdnilo Apr 30 '20 at 09:52

5 Answers5

6

This question already has several other perfectly good answers explaining how to solve your problem. I don’t want to do that; instead, I will go through each line of your code, progressively correct the problems, and hopefully help you understand Haskell a bit better.

First, I’ll copy your code for convenience:

allEqual :: Eq a => a -> a -> a -> Bool
allEqual x y z do
  Bool check <- x == y
  Bool nextC <- y == z
  if check == nextC
    then True
    else False

The first line is the type signature; this is already explained well in other answers, so I’ll skip this and go on to the next line.

The second line is where you are defining your function. The first thing you’ve missed is that you need an equals sign to define a function: function definition syntax is functionName arg1 arg2 arg3 … = functionBody, and you can’t remove the =. So let’s correct that:

allEqual :: Eq a => a -> a -> a -> Bool
allEqual x y z = do
  Bool check <- x == y
  Bool nextC <- y == z
  if check == nextC
    then True
    else False

The next error is using do notation. do notation is notorious for confusing beginners, so don’t feel bad about misusing it. In Haskell, do notation is only used in specific situations where it is necessary to execute a sequence of statements line by line, and especially when you have some side-effect (like, say, printing to the console) which is executed with each line. Clearly, this doesn’t fit here — all you’re doing is comparing some values and returning a result, which is hardly something which requires line-by-line execution. So let’s get rid of that do:

allEqual :: Eq a => a -> a -> a -> Bool
allEqual x y z =
  let Bool check = x == y
      Bool nextC = y == z
  in
    if check == nextC
      then True
      else False

(I’ve also replaced the <- binding with let … in …, since <- can only be used within a do block.)

Next, another problem: Bool check is not valid Haskell! You may be familiar with this syntax from other languages, but in Haskell, a type is always specified using ::, and often with a type signature. So I’ll remove Bool before the names and add type signatures instead:

allEqual :: Eq a => a -> a -> a -> Bool
allEqual x y z =
  let check :: Bool
      check = x == y
      nextC :: Bool
      nextC = y == z
  in
    if check == nextC
      then True
      else False

Now, at this point, your program is perfectly valid Haskell — you’ll be able to compile it, and it will work. But there’s still a few improvements you can make.

For a start, you don’t need to include types — Haskell has type inference, and in most cases it’s fine to leave types out (although it’s traditional to include them for functions). So let’s get rid of the types in the let:

allEqual :: Eq a => a -> a -> a -> Bool
allEqual x y z =
  let check = x == y
      nextC = y == z
  in
    if check == nextC
      then True
      else False

Now, check and nextC are only used in one place — giving them names doesn’t do anything, and only serves to make the code less readable. So I’ll inline the definitions of check and nextC into their usages:

allEqual :: Eq a => a -> a -> a -> Bool
allEqual x y z =
  if (x == y) == (y == z)
    then True
    else False

Finally, I see you have an expression of the form if <condition> then True else False. This is redundant — you can simply return the <condition> with the same meaning. So let’s do that:

allEqual :: Eq a => a -> a -> a -> Bool
allEqual x y z = (x == y) == (y == z)

This is much, much better than the code you started with!

(There is actually one more improvement that you can make to this code. At this point, it should be obvious that your code has a bug. Can you find it? And if so, can you fix it? Hint: you can use the && operator to ‘and’ two booleans together.)

bradrn
  • 8,337
  • 2
  • 22
  • 51
  • [tag:do-notation] doesn't have to be hard for newbies; it could (*should?!*) be introduced [axiomatically](https://stackoverflow.com/search?q=user%3A849891+axiomatically), even [with pictures in vivid colors](https://stackoverflow.com/a/11326549/849891)! :) esp. when we consider how self-explanatory List Comprehensions are, and how similar the Monad Comprehensions are which are nearly equivalent to `do`. – Will Ness Apr 30 '20 at 11:21
  • @WillNess Good point that it’s not hard. (I learnt it fairly easily myself.) I suppose I should have said that it’s notorious for ‘confusing newcomers’ rather than for being ‘tricky to understand’ — I’ll edit that in now. – bradrn Apr 30 '20 at 11:26
  • yes, confusing, by the confusing presentations always speaking of monads and syntax desugarings... – Will Ness Apr 30 '20 at 11:27
  • @WillNess I’m of the opinion that those presentations are really the only way to learn `do`-notation properly — be careful to avoid the [monad tutorial fallacy](https://byorgey.wordpress.com/2009/01/12/abstraction-intuition-and-the-monad-tutorial-fallacy/)! But, if you’re not happy with my wording, what do you recommend saying? (I just wanted to acknowledge the fact that newcomers to Haskell often seem to misunderstand when to use `do`-notation.) – bradrn Apr 30 '20 at 11:29
  • Glad you like it. Thanks for the correction @WillNess! – bradrn Apr 30 '20 at 11:32
  • actually it was all right even before the edit. :) --- personally, for me the most confusing part about do always was the omission of explicit separators (and even parens around lambdas). if it's a programmable semicolon, let there be semicolons, for Pete's sake! :) – Will Ness Apr 30 '20 at 11:40
  • Thank you so much for your help! – Nico Arevalo May 01 '20 at 17:33
3

Lets start with a function that takes a single Int:

allEqual1Int :: Int -> Bool
allEqual1Int x = True

allEqual1Int' :: Int -> Bool
allEqual1Int' x = 
  if x == x
  then True
  else False

If we compare that to your line

allEqual x y z do

we notice that you miss a = and that you don't need a do.

A version for String could look like

allEqual1String' :: String -> Bool
allEqual1String' x = 
  if x == x
  then True
  else False

and we observe that the same implementation works for multiple types (Int and String) provided they support ==.

Now a is a type variable, think of it as a variable such that its value is a type. And the requirement that the given type supports == is encoded in the Eq a constraint (Think of it as an interface). Therefore

allEqual :: Eq a => a -> a -> a -> Bool

works for any such type that supports ==.

Will Ness
  • 70,110
  • 9
  • 98
  • 181
Micha Wiedenmann
  • 19,979
  • 21
  • 92
  • 137
3

Haskell is a bit strange language for those who programmed in different languages before. Let's first have a look at this function:

allEqual :: Int -> Int -> Int -> Bool

You can look at this like that: the last "thing" after "->" is a return type. Previews "things" are parameters. From that, we know the function accepts three parameters that are Int and returns the Bool.

Now have a look at your function.

allEqual :: Eq a => a -> a -> a -> Bool

There is an extra syntax "Eq a =>". What it basically does is all the following "a" in declaration must implement Eq. So it is a more generalized version of the first function. It accepts three parameters that implement "Eq" and returns Bool. What the function should probably do is check if all values are equal.

Now let's have a look at your implementation. You are using a do syntax. I feel like it is not the best approach in the beginning. Let's implement a very similar function which checks if all parameters are equal to 3.

allEq3 :: Int -> Int -> Int -> Bool
allEq3 x y z = isEq3 x && isEq3 y && isEq3 z
  where
    isEq3 :: Int -> Bool
    isEq3 x = x == 3

Like in your example, we have three parameters, and we return Bool. In the first line, we call function isEq3 on all the parameters. If all these calls return true allEq3 will also return true. Otherwise, the function will return false. Notice that the function isEq3 is defined below after the keyword "where". This is a very common thing in Haskell.

So what we did here is taking a big problem of checking if all the parameters are equal to 3 and divide it into smaller pieces which check whether a value is equal to 3.

You can improve this implementation a lot but I think this is the best way to take the first steps in Haskell. If you really want to learn this language you should have a look at this.

Will Ness
  • 70,110
  • 9
  • 98
  • 181
2
allEqual :: Eq a => a -> a -> a -> Bool

The signature says: allEqual consumes 3 values of type a; it produces a result of type Bool. The Eq a => part limits the possible operations a can have; it says whatever type a is, it needs to satisfy the requirements defined in Eq. You can find those requirements here: http://hackage.haskell.org/package/base-4.12.0.0/docs/Prelude.html#t:Eq You now know what operations a can do, you can then complete your function by following the type signature.

ƛƛƛ
  • 892
  • 7
  • 11
  • 1
    "limits" is the wrong choice of words. it's exactly the opposite: the constraint *prescribes* the operations that `a` *must* have. – Will Ness Apr 30 '20 at 23:51
1

Here's how you do it:

allEqual :: Eq a => a -> a -> a -> Bool
allEqual x y z = x == y && y == z

What does this mean?

The first line defines the function's type signature.

In human words, it would say something like:

There is a function called allEqual for any type a. It requires an instance of Eq a* and takes three parameters, all of type a, and returns a Bool

The second line says:

The function allEqual, for any parameters x, y, and z, should evaluate x == y && y == z, which simply compares that x equals y and y equals z.

* Instances or type classes are a language feature that not many other programming languages have, so if you're confused to what they mean I'd suggest learning about them first.

Markus Appel
  • 3,138
  • 1
  • 17
  • 46
  • 4
    OP asked for "I would prefer only tips or a link to where I can learn rather than a full answer". I think you should not provide the full answer. – Micha Wiedenmann Apr 30 '20 at 09:47
  • @MichaWiedenmann Allthough you are right, this would mean asking for recommendations, which is discouraged on StackOverflow. Questions should have definitive, clear answers. – Markus Appel Apr 30 '20 at 09:50
  • 2
    Note that a) I did not downvote b) this entirely your call, I just gave my opinion. Thank you for contributing. – Micha Wiedenmann Apr 30 '20 at 18:12