zip function requires also a second list, how can it work with only one argument list

六眼飞鱼酱① 提交于 2019-12-24 02:13:45

问题


I started learning Haskell and found a nice exercise. It's the following:

grouping: Int -> [Student]->[(Team, Student)]
grouping teamNumber = zip ys
                      where ...

So, the exercise wants that i try to fill the rest. The function should do the following: Example : grouping 2 ['Mark','Hanna','Robert','Mike','Jimmy'] = [(1,'Mark'),(2,'Hanna'),(1,'Robert'),(2,'Mike'),(1,'Jimmy')].

So, we are building teams which consists of two Students, and the last Student 'Jimmy' has no teammates.

Then, I also look up what the predefined function zip does. It gets two list arguments and connects each element of the lists to a tuple to build a list of tuples.

My idea: 1) I try to build two functions "grab" and "infinite". They are looking as following:

grap :: Int -> [a] -> [a]
grab _ [] = []
grab n (x:xs) = if n <= 0 then [] else x : grab (n-1) xs  

infinite :: Num a => a -> [a]
infinite x = x : infinite(x+1)

So, what they do is: With infinite I want to create an infinite list. And grap should take n elements of that. Example grap 2 (infinite 1) = [1,2].

I use these two in the first line of my where-declaration to fulfill the given function from above. So, I have:

grouping: Int -> [Student]->[(Team, Student)]
grouping teamNumber = zip ys
                      where 
                      xs = grap teamNumber (infinite 1)

So, xs is now my first list of zip, especially the integer-list.

But now my question: zip as predefined function requires also a second list, especially the list of the names of the students, but in the given function they give zip only one argument, namely the ys as a list. How can I understand that?


回答1:


Type of `grouping teamNumber`

Look carefully at the type of grouping :: Int -> [Student]->[(Team, Student)], and the arguments that are being declared for its declaration

grouping :: Int        -> [Student]->[(Team, Student)]
grouping    teamNumber =  ...

What is the return type (the type on the right-hand side of the equals sign) if grouping is provided with all the arguments listed on the left-hand side of the equals sign?

Answer

The type on the right-hand side of the equals sign is [Student]->[(Team, Student)]. In Haskell, a function that takes two arguments and returns a result can be equivalently seen or defined as a function that takes the first argument and returns a (function that takes the second argument and returns the result). So we could say, for example, that the expression

grouping 3 :: [Student]->[(Team, Student)]

(grouping 3) is a function that takes a list of students and returns a list of those students, labeled in 3 groups. Presumably, if (grouping 3) were applied to the list of students from your example, we would have

(grouping 3) [   'Mark' ,   'Hanna' ,   'Robert' ,   'Mike' ,   'Jimmy' ] =
             [(1,'Mark'),(2,'Hanna'),(3,'Robert'),(1,'Mike'),(2,'Jimmy')]

Type of `zip ys`

What does currying have to do with the following type and expression?

zip :: [a] -> [b] -> [(a, b)]
zip    ys

What would the type of zip ys be if, for example, ys :: [Bool]?

What does this have to do with your question?

When you consider this together with the type of grouping teamNumber, how does this tell you what the type of ys needs to be in your exercise?

Putting it all together

From the exercise code (ignoring the types and the where clause) we have:

grouping teamNumber = zip ys

Two things can only be = in Haskell if their types will unify. In this case, the type of grouping teamNumber must unify with the type of zip ys.

From the first part, we know that the type of grouping teamNumber is [Student]->[(Team,Student)].

From the second part, we know that zip ys has the type [b] -> [(a, b)], where a is a type such that ys has the type [a].

Therefore, we know that (~ is type equality in Haskell)

[Student]->[(Team,Student)] ~ [b] -> [(a, b)]

These will unify if we substitute the following for the type variables b and a

b ~ Student
a ~ Team

Now, we know the type of ys is [a], which, if we make the same substitution, is [Team].

Therefore, the types will be correct if ys :: [Team].

Conclusion

If you can provide a ys :: [Team], you can produce a function from students to students tagged with their team ([Student]->[(Team,Student)]) by passing the ys as the first argument to zip. Such a function is exactly what grouping needs to return when it has been applied to the single argument, teamNumber :: Int.




回答2:


Currying can be a bit confusing when you first encounter it. Here's the deal, pretty much (there are some technicalities I'm going to ignore).

The basic concept is this: in Haskell, every function takes just one argument. If you want to simulate a function that takes two arguments, there are two ways to do this:

Tuples

You can write a function that takes a tuple. This is the conventional approach in Standard ML, but is typically only used in Haskell in cases where it is very particularly the most sensible thing to do:

distanceFromOrigin :: (Double, Double) -> Double
distanceFromOrigin (x, y) = sqrt (x^2 + y^2)

Currying

You can write a curried function. The concept behind currying is that when you apply a function to an argument you can get another function back that takes a second argument. I'll write this out very explicitly to begin with, using lambda notation:

product :: Double -> (Double -> Double)
product x = \y -> x * y

Suppose I start with (product 3) 4. I can reduce (product 3) first to get

(\y -> 3 * y) 4

Then I can finish off the job, getting 12.

Haskell offers a bit of syntax to help with this sort of thing. First off, it lets me write

product x y = x * y

to mean

product x = \y -> x * y

Secondly, it makes function application left-associative, so I can write product 3 4 to mean (product 3) 4.

Finally, it makes the -> type constructor right-associative, so I can write product :: Double -> Double -> Double instead of product :: Double -> (Double -> Double).




回答3:


In Haskell, the following is equivalent:

f = (\x      y -> ..x..y..  )
f = (\x -> (\y -> ..x..y.. ))  -- this equivalence is known as "currying"
f     x =  (\y -> ..x..y.. )   -- partially applying f with x gives (\y->...)
f     x      y =  ..x..y..

((\x -> ...) is of course Haskell's notation for anonymous, so called "lambda" functions (\ being a reminder for the Greek letter λ.)

In Haskell, functions are just like other values, so there's no special syntax for function calls, or "function pointers" etc.. As for the types, the above naturally entails

f ::  a ->   b ->   t
f     x ::   b ->   t  -- the result of calling f w/ x (of type a) has type b->t
f     x      y ::   t  -- when f :: a->b->t, x :: a, y :: b, then f x y :: t

Stare at it for a moment.

So that's that about currying. Function calls are denoted just by juxtaposition in Haskell, and so it associates to the left (f x y is really ((f x) y)). And because Haskell definitions are automatically curried, the arrows in types associate to the right (a->b->c is really a->(b->c)).



来源:https://stackoverflow.com/questions/23614986/zip-function-requires-also-a-second-list-how-can-it-work-with-only-one-argument

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!