Гмуркане в гъвкавите функции
Нека да разгледаме лесен пример:
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")]
В обобщение, различните букви означават, че нашата функция е по-гъвкава и има потенциала да работи с различни типове, въпреки че не винаги е необходимо.