Skip to content

Latest commit

 

History

History
194 lines (143 loc) · 6.64 KB

File metadata and controls

194 lines (143 loc) · 6.64 KB

Полиморфични функции




Георги Наков, nakov.gl at gmail com
Марин Маринов, marinov.ms+tues at gmail com

Технологично училище "Електронни Системи"
16 Ноември 2016г.


Полиморфични функции

Проблем (голям):

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

lengthInt :: [Int] -> Int
lengthInt [] = 0
lengthInt (_:xs) = 1 + lengthInt xs

lengthString :: String -> Int
lengthString [] = 0
lengthString (_:xs) = 1 + lengthString xs

Това е ужасно!


Полиморфични функции

Haskell предоставя решение:

Когато не ни интересува конкретния тип може вместо него да използваме т. нар типова променлива.

length :: [a] -> Int
length [] = 0
length (_:xs) = 1 + length xs

Тук a значи "да е който и да е тип". Конкретните типове започват с главна буква, типовите променливи започват с малка буква (и по конвенция често са с по една буква).
Функциите с поне една типова променлива се наричат полиморфични.


Полиморфични функции - примери

Вградените функции за манипулация на списъци в Haskell работят с списъци от какъвто и да е тип! Да проверим сигнатурите:

> head [1, 2, 3]
1

> head "Hi"
'H'
ghci> :t head
head :: [a] -> a

ghci> :t reverse
reverse :: [a] -> [a]

ghci> :t (++)
(++) :: [a] -> [a] -> [a]

Полиморфични функции - начин на работа

Не може да предполагаме нищо за аргументите или променливите, които са от тип a. Не може да извършваме операции с тях.

bad :: a -> Int
bad x = x + 1
-- грешка
-- Couldn't match expected type ‘Int’ 
-- with actual type ‘a’

Полиморфични функции - начин на работа

В рамките на едно извикване на полиморфичните функции, a заема конкретен тип, който не се сменя по време на изпълнението на функцията. a има само един конкретен тип в рамките на извикването (тоест не може при първия аргумент да е Char, вторият - Int и тн).

ghci> :t (++)
(++) :: [a] -> [a] -> [a]

ghci> :t ("Hi" ++ " all")
"Hi" ++ " all" :: [Char]

Ограничения

В някои случаи искаме да използваме полиморфична функция, но и някакво свойство на a.

elem :: a -> [a] -> Bool
elem _ [] = False
elem e (x:xs)
  | x == e    = True
  | otherwise = elem e xs

За съжаление това е невалидна функция в Haskell, защото сме допуснали, че можем да сравним e с елемент от списъка x.

x == e - Невалидно, защото извършваме операции с променливи от тип a.


Oграничения

Решение: Може да кажем, че a трябва да поддържа сравнение за равенство с (Eq a) => пред сигнатурата.

elem :: (Eq a) => a -> [a] -> Bool

(Eq a) => наричаме класови ограничение (class constraint).


Oграничения

Haskell ни дава много готови класови ограничения.

  • Eq a - може да сравняваме с == и /=
equals :: (Eq a) => a -> a -> Bool
equals x y = x == y

> equals 3 3 
True

> equals "Hi" "hi"
False

Oграничения

  • Show a - типът може да се конвертира към низ с show
toString :: (Show a) => a -> String
toString x = show x

> toString 4
"4"

> toString [42,0]
"[42,0]"

Вградените стандартни типове (числа, низ, симвoли) поддържат и Eq a и Show a.


Полиморфични функции - алгоритъм

Как да "полиморфизираме" дадена функция, ако сметнем, че е подходящо?

  1. Заменете конкретния тип на удачен аргумент с а (b/c/d).
  2. Компилирайте. Ако няма грешка, готово!
  3. Ако има грешка, добавете (Eq a) => и компилирайте пак.
  4. Ако има грешка отново, върнете конкретния тип.
  5. Преминете на следващия удачен аргумент.

Полиморфични функции - абстракции

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

Какво може да кажем за func :: Int -> String -> Int? По колко начина може да я имплементираме? Не знаем нищо - може да връщаме дължината на низа, може дължината на низа на квардрат, може да връщаме 42, може да взимаме първите n символа и да умножаваме ASCII кодовете


Полиморфични функции - абстракции

Какво може да кажем за func :: a -> b -> а? По колко начина може да я имплементираме? Досещаме се за очевидна имплементация : func x y = y - функция, която винаги връща втория си аргумент.