Гмуркане в гъвкавите функции

Нека да разгледаме лесен пример:

multThree :: (Num a) => a -> a -> a -> a
multThree x y z = x * y * z

Тук multThree приема три параметъра. Всички параметри трябва да са от същия тип, както е отбелязано от a в декларацията на типа. Освен че са от един и същи тип, те имат допълнително ограничение, както е показано от класа тип Num, т.е. (Num a).

Какво се счита за част от класа тип Num? За да се счита даден тип за част от класа тип Num, той трябва да може да работи с функции като

  • (+): допълнение
  • (-): изваждане
  • (*): умножение

и няколко други.

И така, в обобщение, нашата multThree функция приема три аргумента x,y,z, които всички трябва да са от един и същи тип и да се считат за част от класа тип Num.

Нека да разгледаме нещо малко по-сложно:

zipWith' :: (a -> b -> c) -> [a] -> [b] -> [c]

Примерът по-горе е откраднат от Learn You A Haskell. Разбиват го в книгата, но това не ми беше достатъчно, за да разбера. Нека се потопим по-дълбоко.

С функцията zipWith' тя приема функция като първи аргумент, списък като втори аргумент и ще създаде списък като трети аргумент. Това, което ме обърка най-много, бяха различните букви, използвани тук в сравнение с нашите multThree примери по-горе. Забележете добавянето на буквите b и c. Това, което казва, е, че тази функция може да приема аргументи от различни типове!

Нека видим примерите, показани в Научаване на Haskell:

Най-лесният пример за разбиране е може би първият, при който предаваме функцията за добавяне в zipWith и след това предаваме двата списъка като аргументи, получавайки резултата от [6,7,8,9] . Е, всичко това е страхотно, но имаме работа само с един тип в примерите по-горе. По същество всички реализации на тези функции могат да бъдат декларирани с помощта на един и същи тип.

zipWith’ :: (a -> a -> a) -> [a] -> [a] -> [a]

Тааааа, нека внедрим функцията zipWith’, използвайки различни типове, за да разберем по-добре декларацията на типа, която споменахме по-рано:

zipWith' :: (a -> b -> c) -> [a] -> [b] -> [c]

Можем да направим следното:

zipWith' (\x y -> (x, y)) [1, 2, 3] ["one", "two", "three"]

В примера, показан по-горе, ние прилагаме анонимна функция, както е отбелязано от (\x y -> (x , y)). Това ще вземе отделните елементи в двата ни масива и ще направи кортеж от тях. Крайният резултат ще бъде следният:

[(1, "one"), (2, "two"), (3, "three")]

Така че в нашата декларация за тип

zipWith' :: (a -> b -> c) -> [a] -> [b] -> [c]

Виждаме, че първият аргумент

(a -> b -> c)

приема функция, която в нашия случай беше анонимната функция, която предадохме. Тази функция приема два аргумента, както е отбелязано тук с буквите a и b. Те гласят, че двата аргумента могат да бъдат от различни типове, въпреки че не е задължително да са през цялото време и да дадат резултат (както е отбелязано с буквата c), който също може да бъде от различен тип. В нашия случай функцията zipwith' ни даде кортеж.

Следващото писмо

[a]

представлява в нашия случай масива, който сме предали като втори аргумент

[1, 2, 3]

след това накрая преминаваме третия масив от напълно различен тип

["one", "two", "three"]

което в крайна сметка ни дава масив от напълно различен тип, който е представен от буквата c в нашия тип декларация.

[(1, "one"), (2, "two"), (3, "three")]

В обобщение, различните букви означават, че нашата функция е по-гъвкава и има потенциала да работи с различни типове, въпреки че не винаги е необходимо.