Розглянемо Functor
клас типу в Haskell, де f
є вища змінна типу:
class Functor f where
fmap :: (a -> b) -> f a -> f b
Підпис цього типу говорить про те, що fmap змінює параметр типу від f
від a
до b
, але залишає f
як було. Отже, якщо ви використовуєте fmap
над списком, ви отримуєте список, якщо ви використовуєте його над синтаксичним аналізатором, ви отримуєте синтаксичний аналізатор тощо. І це статичні гарантії під час компіляції.
Я не знаю F #, але давайте розглянемо, що трапиться, якщо ми спробуємо висловити Functor
абстракцію на такій мові, як Java або C #, з успадкуванням та узагальненнями, але жодних вищих типів. Перша спроба:
interface Functor<A> {
Functor<B> map(Function<A, B> f);
}
Проблема цієї першої спроби полягає в тому, що реалізації інтерфейсу дозволено повертати будь-який клас, який реалізує Functor
. Хтось міг написати, FunnyList<A> implements Functor<A>
чий map
метод повертає інший тип колекції, або навіть щось інше, що взагалі не є колекцією, але все ще є Functor
. Крім того, коли ви використовуєте map
метод, ви не можете викликати будь-які специфічні для підтипу методи в результаті, якщо не знизити його до типу, який ви насправді очікуєте. Отже, у нас дві проблеми:
- Система типів не дозволяє висловити інваріант, що
map
метод завжди повертає те самеFunctor
підклас, що і одержувач.
- Отже, не існує статично безпечного способу викликати неметод
Functor
на результат map
.
Є й інші, більш складні способи, які ви можете спробувати, але жоден з них насправді не працює. Наприклад, ви можете спробувати збільшити першу спробу, визначивши підтипи, Functor
які обмежують тип результату:
interface Collection<A> extends Functor<A> {
Collection<B> map(Function<A, B> f);
}
interface List<A> extends Collection<A> {
List<B> map(Function<A, B> f);
}
interface Set<A> extends Collection<A> {
Set<B> map(Function<A, B> f);
}
interface Parser<A> extends Functor<A> {
Parser<B> map(Function<A, B> f);
}
Це допомагає заборонити реалізаторам тих вужчих інтерфейсів повертати неправильний тип Functor
із map
методу, але оскільки обмеження кількостіFunctor
реалізацій ви можете мати, немає межі того , скільки вже , інтерфейсів вам потрібно.
( EDIT: І зауважте, що це працює лише тому, що Functor<B>
відображається як тип результату, і тому дочірні інтерфейси можуть це звузити. Тож AFAIK ми не можемо звузити обидва способи використання Monad<B>
в наступному інтерфейсі:
interface Monad<A> {
<B> Monad<B> flatMap(Function<? super A, ? extends Monad<? extends B>> f);
}
У Haskell із змінними типу вищого рангу це (>>=) :: Monad m => m a -> (a -> m b) -> m b
.)
Ще одна спроба полягає у використанні рекурсивних дженериків, щоб спробувати інтерфейс обмежити тип результату підтипу самим підтипом. Приклад іграшки:
interface Semigroup<T extends Semigroup<T>> {
T append(T arg);
}
class Foo implements Semigroup<Foo> {
Foo append(Foo arg);
}
class Bar implements Semigroup<Bar> {
Semigroup<Bar> append(Semigroup<Bar> arg);
Semigroup<Foo> append(Bar arg);
Semigroup append(Bar arg);
Foo append(Bar arg);
}
Але такий метод (який є досить загадковим для вашого розробника OOP, а також для вашого функціонального розробника) ще не може виразити бажане Functor
обмеження:
interface Functor<FA extends Functor<FA, A>, A> {
<FB extends Functor<FB, B>, B> FB map(Function<A, B> f);
}
Проблема тут полягає в тому, що це не обмежує FB
мати те саме, що F
і FA
- тому, коли ви оголошуєте тип List<A> implements Functor<List<A>, A>
, map
метод все одно може повернути a NotAList<B> implements Functor<NotAList<B>, B>
.
Останній спробу на Java, використовуючи необроблені типи (непараметризовані контейнери):
interface FunctorStrategy<F> {
F map(Function f, F arg);
}
Тут F
буде отримати примірник для unparametrized типів , як тільки List
або Map
. Це гарантує, що a FunctorStrategy<List>
може повернути лише aList
- але ви відмовились від використання змінних типу для відстеження типів елементів у списках.
Суть проблеми полягає в тому, що такі мови, як Java та C #, не дозволяють параметрам типу мати параметри. У Java, якщо T
є змінною типу, ви можете писати T
і List<T>
, але ні T<String>
. Типи вищого типу знімають це обмеження, щоб у вас могло бути щось подібне (не до кінця продумане):
interface Functor<F, A> {
<B> F<B> map(Function<A, B> f);
}
class List<A> implements Functor<List, A> {
<B> List<B> map(Function<A, B> f) {
}
}
І зокрема, звертаючись до цього біта:
(Я думаю) Я розумію, що замість myList |> List.map f
або myList |> Seq.map f |> Seq.toList
вище введені типи дозволяють вам просто писати, myList |> map f
і це поверне a List
. Це чудово (якщо припустити, що це правильно), але здається якийсь дріб’язковий? (І чи не можна це зробити, просто дозволивши перевантаження функції?) Я зазвичай перетворюю на Seq
все одно, а потім згодом можу перетворити на все, що хочу.
Є багато мов, які узагальнюють ідею map
функції таким чином, моделюючи її так, ніби в основі суть відображення - це послідовності. Це ваше зауваження в цьому дусі: якщо у вас є тип, який підтримує перетворення в і з Seq
, ви отримуєте операцію з картою "безкоштовно", повторно використовуючи Seq.map
.
Однак у Хаскелі Functor
клас є загальнішим за цей; це не пов'язано з поняттям послідовностей. Ви можете реалізувати fmap
для типів, які не мають належного відображення послідовностей, таких як IO
дії, комбінатори синтаксичного аналізатора, функції тощо:
instance Functor IO where
fmap f action =
do x <- action
return (f x)
newtype Function a b = Function (a -> b)
instance Functor (Function a) where
fmap f (Function g) = Function (f . g)
Поняття "відображення" насправді не пов'язане з послідовностями. Найкраще зрозуміти закони функторів:
(1) fmap id xs == xs
(2) fmap f (fmap g xs) = fmap (f . g) xs
Дуже неофіційно:
- Перший закон говорить, що відображення за допомогою функції ідентичності / noop - це те саме, що нічого не робити.
- Другий закон говорить, що будь-який результат, який ви можете отримати шляхом картографування двічі, ви також можете отримати, виконавши картографування один раз.
Ось чому ви хочете fmap
зберегти тип - адже як тільки ви отримуєте map
операції, які дають інший тип результату, стає набагато важче зробити такі гарантії.