В Haskell такая перегрузка функций (специальный полиморфизм) достигается с помощью классов типов, а не путем привязки одного и того же имени к нескольким типам.
{-# LANGUAGE FlexibleInstances, TypeSynonymInstances #-}
class F a where f :: String -> a
instance F String where f = id
instance F Int where f = read
instance F Char where f = read
instance F Float where f = read
-- etc.
Теперь f
может работать с любым типом, для которого был объявлен экземпляр F
.
К сожалению, вы не можете сойти с рук:
instance Read a => F a where f = read
Возможно, неинтуитивно, это не объявляет экземпляр F
только для типов, которые имеют экземпляр Read
. Поскольку GHC разрешает экземпляры, используя только заголовок объявления экземпляра (часть справа от =>
), это фактически объявляет все типы a
как экземпляры F
, но делает ошибку типа для вызова f
на что-либо, что не является экземпляром Read
.
Он скомпилируется, если вы включите расширение UndecidableInstances
, но это только приводит к другим проблемам. Это кроличья нора, которую ты не хочешь рисковать.
Вместо этого вы должны объявить экземпляр F
для каждого отдельного типа, над которым вы собираетесь работать f
. Это не очень обременительно для простого класса, такого как этот, но если вы используете последнюю версию GHC, вы можете использовать следующее, чтобы сделать его немного легче:
{-# LANGUAGE DefaultSignatures #-}
class F a where f :: String -> a
default f :: Read a => String -> a
f = read
Теперь для любого типа, который является экземпляром Read
, вы можете объявить его экземпляр F
без необходимости явно указывать реализацию f
:
instance F Int
instance F Char
instance F Float
-- etc.
Для любых типов без экземпляров Read
вам все равно придется написать явную реализацию для f
.