Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Does Haskell have implicit pattern matching?

Take this example:

module Main where

main = print (reverseWords "lol")
  
reverseWords :: String -> [String]  
reverseWords = words

reverseWords function is not pattern matching against any argument here yet the function runs and outputs "[lol]".

I have two questions here:

  1. How does Haskell know whether or not I am invoking the words function against the input to reverseWords? In this syntax it looks like I am simply returning the function words.

  2. Why is this able to run successfully even though I have not provided any input arguments in the pattern to reverseWords?

like image 561
Andrew Tang Avatar asked Apr 25 '26 04:04

Andrew Tang


2 Answers

You're just saying that reverseWords is the function words. Anywhere that reverseWords appears, it can be replaced by the function words. So print (reverseWords "lol") is exactly equivalent to print (words "lol"). Basically, you have this new function reverseWords that takes a String argument just like words does and simply passes that argument to words, returning whatever words returns for that argument. Your definition of reverseWords is equivalent to:

reverseWords :: String -> [String]
reverseWords s = words s

Given all of that, reverseWords is a misleading name because it's not doing anything differently than words. So, you're not really doing anything useful at all, just renaming something. A better example would be this:

reverseWords :: String -> [String]
reverseWords = reverse . words

where reverse is some other function you compose with words using the composition operator (.) to make a new function that does something useful. That's called point-free style, where you define new functions by combining other functions without referring to any arguments. That definition is equivalent to:

reverseWords :: String -> [String]
reverseWords s = reverse (words s)
like image 58
gdejohn Avatar answered Apr 26 '26 22:04

gdejohn


You're declaring a new function reverseWords. First you declare it's type:

reverseWords :: String -> [String]

So it's going to be a function that takes a string and returns a list of strings.

Now there are two ways we can approach this. The first is to write a rule that says that when reverseWords receives some argument, the result is an expression (probably involving calling other functions and using the argument) for a list of strings. Like this:

reverseWords s = words s

This says "expressions of the form reverseWords s are defined to be equal to words s". So then the compiler knows that reverseWords "lol" is equal to words "lol". The function reverseWords is implicitly defined by the rules we write for it1.

But there's another way we can think about this. I assume you're perfectly comfortable with how this works:

myFavouriteNumber :: Integer
myFavouriteNumber = 28

We first declare that myFavouriteNumber will be of type Integer, and then define it by just writing down an integer.

Well, functions are first class values in Haskell, which means we don't only have to define them using dedicated special-purpose syntax. If we can make definitions of type Integer by just writing down an integer, we should be able to make definitions of type String -> [String] by just writing down something with that type, rather than writing down rules. That is what is going on in this form:

reverseWords = words

Rather than writing a rule for what the result of reverseWords will be when it's applied to something, we just write down what reverseWords is. In this case, we tell the compiler that reverseWords is defined to be equal to words. That still lets the compiler know that reverseWords "lol" is equal to words "lol", but it does it just by looking at reverseWords part; it could work that out even without looking at the "lol".

Furthermore, we can also write definitions like this:

two :: Integer
two = 1 + 1

Here instead of defining two as equal to some pre-existing thing, we calculate its value (from other pre-existing things: 1 and +). And because functions are first class, we can do the same thing:

reversedWords :: String -> [String]
reversedWords = reverse . words

Here we don't say reversedWords is equal to an existing function, instead we calculate the function that reverseWords is what you get by calling the composition operator . on the pre-existing functions reverse and words. But we're still calculating the function (of type String -> [String]), not the function's result (of type [String]).


So to answer your questions:

How does haskell know whether or not I am invoking the "words" function against the input to reverseWords? In this syntax it looks like I am simply returning the function "words"

Yes, you are just returning the function words. But you're "returning" it as the function reversedWords itself (before it's applied to anything), not as the result of reversedWords when it's applied. And that's how Haskell knows that the words function is to receive the input to reverseWords; reverseWords is equal to words, so any time you pass some input to reverseWords you're really passing it to words.

Why is this able to run successfully even though I have not provided any input arguments in the pattern to reverseWords?

Because you defined the function reverseWords. You defined it by declaring it equal to some other existing function, so it does whatever that function does. Writing rules for function results (based on arguments) is not the only way to define a function.

The fact that you didn't provide a name for the argument of reverseWords in your definition is exactly how Haskell knows that's what you're doing. If you're defining a function of type A -> B and you give a name to the argument, then the right hand side must be something of type B. If you don't, then the right hand side must be something of type A -> B.2

But for your tile question:

Does haskell have implicit pattern matching?

I'm not sure how to answer that, because none of this discussion has involved pattern matching at all. You can use pattern matching to define functions in Haskell, but that's not what is going on here.


1 Okay, in this case reverseWords is pretty explicitly defined by the rule, but in general functions can be defined using multiple rules using pattern matching and guards, and with auxilliary where definitions; the actual value of the function is sort of an emergent property of all the rules (and knowledge of how they'll be tried in order top-down) and where clauses.

2 This logic works regardless of what A and B are. In particular, B could be something with more arrows in it! That is exactly how functions with multiple arguments work in Haskell. A function like:

foo :: Int -> String -> (Int, String)

could be defined by either:

  1. Writing a rule taking two arguments (an Int and a String), with a right hand side of type (Int, String)
  2. Writing a rule taking one argument (an Int), with a right hand side of type String -> (Int, String)
  3. Writing a direct definition with no arguments, with a right hand side of type Int -> String -> (Int, String)

The pattern is clear; each time you add an argument to your rule, the RHS has a type that strips off one more arrow (starting from the left).

All 3 options produce a function foo with the same type that you can call the same way. The internal definition of the function doesn't matter to the outside world.

like image 26
Ben Avatar answered Apr 27 '26 00:04

Ben



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!