Подтвердить что ты не робот

Как Haskell справляется с перегрузкой полиморфизма?

У меня вопрос о полиморфизме Хаскелла.

Как я узнал, существует два типа полиморфизма:

  • Параметрический: где вы не указываете тип ввода.

    Пример:

    functionName :: [a] -> a
    
  • Перегрузка: как обязательное программирование, т.е. передача различных аргументов одной и той же функции.

Моя проблема: как Haskell обрабатывает перегрузку?

4b9b3361

Ответ 1

Перегрузка в Haskell выполняется с использованием классов типов. Например, допустим, вы хотите перегрузить функцию foo, которая возвращает Int:

class Fooable a where
    foo :: a -> Int

instance Fooable Int where
    foo = id

instance Fooable Bool where
    foo _ = 42

Однако они более мощные, чем механизмы перегрузки, обнаруженные на большинстве языков. Например, вы можете перегрузить возвращаемый тип:

class Barable a where
    bar :: Int -> a

instance Barable Int where
    bar x = x + 3

instance Barable Bool where
    bar x = x < 10

Для получения дополнительных примеров ознакомьтесь с предопределенными типами классов в Haskell.

Ответ 3

В некоторых языках перегрузка означает использование одного и того же имени для нескольких функций, которые предоставляют аналогичные, но разные функции, поэтому вы можете попробовать

split :: String -> [String]                      -- splits on whitespace
split :: Char -> String -> [String]              -- splits on the given character
split :: [Char] -> String -> [String]            -- splits on any of the given characters
split :: (Char -> Bool) -> String -> [String]    -- splits using a function that tells you when

который даст вам ошибку Duplicate type signature, которую вы получаете.

Haskell не делает такого типа перегрузки, и программист Haskell дал бы эти разные имена:

words :: String -> [String]                        -- splits on whitespace
splitOn :: Char -> String -> [String]              -- splits on the given character
splitsOn :: [Char] -> String -> [String]           -- splits on any of the given characters
splitWith :: (Char -> Bool) -> String -> [String]  -- splits using a function that tells you when

Причина, по которой Haskell не допускает такого перегруза, о котором я думаю, о чем вы спрашиваете, заключается в том, что он действительно не позволяет вам делать что-либо, что вы не могли бы сделать без него, и позволяя ему сделать это почти невозможно выполнить более сложные виды перегрузки. Перегрузка Haskell - действительно очень мощный инструмент; узнать о типах классов и классах конструкторов для начала.

Собственно, поскольку String= [Char], а программисты Haskell любят повторное использование кода, они гораздо чаще будут писать:

words :: String -> [String]                -- splits on whitespace
splitOn :: Eq a => a -> [a] -> [[a]]       -- splits on the given item
splitsOn :: Eq a => [a] -> [a] -> [[a]]    -- splits on any of the given items
splitWith :: (a -> Bool) -> [a] -> [[a]]   -- splits using a function that tells you when

Здесь Eq a является примером своего рода перегрузки Haskell позволяет, где splitOn позволит вам разделить любой список, пока элементы могут сравниваться для равенства (т.е. Haskell позволяет вам определить свое собственное понятие равенства). Вы можете использовать это, чтобы разделить String или, например, список строк, но вы не можете разбить список функций, потому что вы не можете проверить две функции, чтобы убедиться, что они равны. splitWith - пример Haskell, позволяющий вам рассматривать функцию так же, как и большинство других данных - вы можете передать ее как аргумент!

[Примечание 1: words - стандартная функция, splitWith находится в библиотеке со слегка отличающейся типоразмерностью.]
[Примечание 2: если вы действительно хотите написать эти функции, вот как:

splitWith isSplitter list =  case dropWhile isSplitter list of
  [] -> []
  thisbit -> firstchunk : splitWith isSplitter therest
    where (firstchunk, therest) = break isSplitter thisbit

-- words = splitWith isSpace           -- not needed, standard function from the Prelude
splitOn c = splitWith (== c)           -- notice I passed == in an argument! 
splitsOn chars = splitWith (`elem` chars)

]

Ответ 4

Вы указываете сигнатуру типа вашей функции в исходном классе типа, тогда вы создаете несколько экземпляров этой функции, для которой вы написали подпись. Итак, в приведенном выше примере hammar вы можете считать a полиморфным и тип, указанный в каждом экземпляре (например, экземпляр Fooable Bool) как тип a (в этом случае a является Bool). Поэтому, когда вы вызываете функцию foo с помощью значения Bool, вызывается экземпляр Fooable для значения Bool.

BTW, вы можете поместить несколько функций в класс типа, и вы можете определять функции в терминах других определенных функций.

например.

class  Eq a  where
    (==), (/=)  ::  a -> a -> Bool

    x /= y  = not (x == y)
    x == y  = not (x /= y)

Это может быть не очевидно здесь, но если вы определяете экземпляр Eq, вам нужно только определить == или /=, а не оба, так как они определены в терминах друг друга.

Ответ 5

В Haskell основное отличие в Haskell совершенно иное, хотя вы можете сделать что-то похожее.

я. В качестве выбранного ответа используйте классы.

class YesNo a where  
    yesno :: a -> Bool  

Вам нужно реализовать его с помощью экземпляра, а затем вы можете использовать его следующим образом:

> yesno $ length []  
False  
> yesno "haha"  
True  
> yesno ""  
False  
> yesno $ Just 0  
True  
> yesno True  
True  
ghci> yesno EmptyTree  
False  
> yesno []  
False  
> yesno [0,0,0]  
True  

http://learnyouahaskell.com/making-our-own-types-and-typeclasses

II. Используйте сопоставление шаблонов конструктора типов, например:

data Shape = Circle Float Float Float | Rectangle Float Float Float Float 
surface :: Shape -> Float  
surface (Circle _ _ r) = pi * r ^ 2  
surface (Rectangle x1 y1 x2 y2) = (abs $ x2 - x1) * (abs $ y2 - y1)

а затем вы можете использовать их следующим образом:

> surface $ Circle 10 20 10  
314.15927  
> surface $ Rectangle 0 0 100 100  
10000.0

http://learnyouahaskell.com/making-our-own-types-and-typeclasses

III. Имейте функцию "oveloaded" в модуле sepatate, но вам нужно будет префикс имени импорта или имени импортируемого импорта

http://learnyouahaskell.com/modules

Ответ 6

Здесь у вас есть простой пример объединения

  • ad-hoc полиморфизм (перегрузка): одна и та же функция с различным поведением для разных типов (с помощью классов типа Haskell)

  • параметрический полиморфизм: одна и та же функция с одинаковым поведением для разных типов (с помощью параметризованной функции типа. принцип, тип не имеет значения, но мы использовали классы типов для ограничения приемлемые типы).

код:

import Data.Char

class MyTypeFamily t where
      f1 :: t -> Int
      f2 :: t -> Int

instance MyTypeFamily Int where
         f1 x = x*x
         f2 x = x+x

instance MyTypeFamily Char where
         f1 x = (ord x) * (ord x)
         f2 x = (ord x) + (ord x)

instance MyTypeFamily Bool where
         f1 x 
            | x = 10
            | otherwise = 10 
         f2 x 
            | x = 100
            | otherwise = -100

-- ...............................................................
-- using f1, f2 as "overloaded" functions ("ad-hoc polymorphism)
--  (the algorithm for f1, f2 is chosen depending on their type)
--
-- using  fun as polymorphic (parametric polymorphic function)
-- the algorithm of fun is always the same but it works on
-- different types
fun :: (MyTypeFamily t) => t -> Int
fun x = (f1 x) + (f2 x) 

-- ...............................................................
-- ...............................................................
main =  do
        print $  fun 'J'
        print $  fun True
        print $  fun False
        print $  fun (8 :: Int)