简体   繁体   中英

How do you pass list in a function parameter in haskell?

I know this question has been asked previously many times, and I've carefully read them, but it doesn't help me answer my type of question. I'm very new to Haskell,

Lets suppose we have the following:

filter p [] = []
filter p (h:l) = if (p h) then (h:(filter p l)) else (filter p l)

I have two questions

  1. How can I call filter? All I know is that you pass p which is a list

  2. I honestly don't know what is polymorphic type in general, and I cant figure out the polymorphic type of filter function.

I dont event understand what the function filter does in the if statement.

I would really appreciate if you can assist me in these two question.

There's plenty of resources to explain polymorphism, but I don't understand them.

p is not a list. p is short for predicate - usual terminology for a function taking a value and returning Bool . A list is the second argument to filter .

How do you call filter? You need to be reading one of the many haskell books out there. Right now. Some examples:

filter (> 5) [1, 6, 2, 8, 9, 3] -- [6, 8, 9]
filter (== 'z') "bazzz" -- "zzz" (String === [Char])

Here (> 5) :: Int -> Bool and (== 'z') :: Char -> Bool are predicates.

Polymorphic very loosely means it has the same form for different types:

filter :: (a -> Bool) -> [a] -> [a]

filter must work for any type a . The particular a is thus unknown to the implementer and the function cannot assume anything about a . a is chosen by the function user at the call site.

Btw. it's a fun little exercise to figure out what the following function is allowed to do:

:: a -> a

(Hint: there's only one thing it can do, and the name gives it away so I left it out)

You could also think of filter as a family of functions that are implemented exactly the same and only differ in a . Some of those could be:

:: (Int -> Bool) -> [Int] -> [Int]
:: (Char -> Bool) -> [Char] -> [Char]
:: (Foo -> Bool) -> [Foo] -> [Foo]

SO is not really a great place to start when learning new concepts. You should really grab a good book.

Before getting into any details about the implementation, we should settle on what the type of filter should be. Indeed you should generally design the type signature of a function without ever writing any actual code... but the damage is done here already. (As chi remarks, you could at this point actually ask GHCi what the type of your implementation is... but again, that's backwards so I won't delve into it.)

So what do you want filter to accomplish? Indeed it should take a list. You want to extract certain elements of that list depending on some property each of them might have; but filter shouldn't have any hard-baked assumptions what criteria to use, ie it should be the type of a list of any type of element . In Haskell, we write this [a] (which is actually shorthand for ∀ a . [a] , read this as “for all element-types you might consider – say, A – it's a list of type [A] ”).

What the actual criterion is should then be determined by an extra argument: the filtering predicate . For instance, you might want to filter all numbers smaller than 5 from a list of integers – you'd use the predicate (<5) :: Int -> Bool . In general, for your [a] list you need a predicate with the type a -> Bool . The end result would have the same list-elements as you passes in, so then filter would have the signature

filter :: [a] -> (a -> Bool) -> [a]

...except by convention, we put the predicate first, ie

filter :: (a -> Bool) -> [a] -> [a]

Let's check this makes sense... we'd want, for instance,

> filter ((<5) :: Int -> Bool) ([4,9,3] :: [Int])

in which case a ~ Int so

filter :: (Int -> Bool) -> [Int] -> [Int]

...yup, that makes sense.

Now you start actually worrying about the implementation. There are two general approaches:

  • Use some pre-existing combinators from a library to define the function. This is very of preferrable to start dabbling with manual recursion etc., but for now let's do everything by hand.
  • Deconstruct the list. Basically, there are only two ways a list can look: it can either contain something, or be empty. Empty is easy, because in this case you can't possibly return anything but an empty list again. You can't even use the predicate because there is no element you could check with it, hence just discard it by matching to _ :

     filter _ [] = [] 

    (Alternatively as you had it, you can also match the predicate as p , but then people will wonder: what happened to the mouse p ?)

    If the list is not empty, we can straight pop one element from it:

     filter p (h:l) = … 

    here, h is the head element and l is the rest of the list. So great, we now have an element of type a , let's see what the predicate tells us about it!

     filter p (h:l) = if ph then … else … 

    So if the predicate is fulfilled we want to see h again in the final result, won't we? In fact the final result should start with h , thus

     filter p (h:l) = if ph then h : … else … 

    The rest of the final result should have something to do with the rest of the input list. We could pass is as is then h : l else … , but that would mean we would only ever control the condition for the head element. No, we still have to filter the rest of the list as well:

     filter p (h:l) = if ph then h : filter pl else … 

    Indeed we also want to do that even if the predicate is not fulfilled for h , except then we don't prepend it:

     filter p (h:l) = if ph then h : filter pl else filter pl 

And there you go:

filter _ [] = []
filter p (h:l) = if p h then h : filter p l else filter p l

This if looks a bit clunky, the preferred syntax are actually guards (which do the same thing)

filter _ [] = []
filter p (h:l)
 | p h        = h : filter p l
 | otherwise  = filter p l

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM