C
Code for Bro
@codeforbro9 подп.
30просмотров
23 июня 2024 г.
storyScore: 33
Я сегодня понял, что такое HKT (high kind types). Вот представь, у нас есть Functor (математическая абстракция) - это что-то вроде интерфейса, который описывает, как конкретному типу нужно реализовать функцию fmap (часть этого функтора), что бы всё работало: instance Functor f where fmap :: (a -> b) -> f a -> f b Тут нужно понять, что есть некий тип f, который реализует Functor и у него надо реализовать функцию fmap, которая принимает на вход 2 аргумента: 1. функцию из a в b (a -> b). 2. собственно сам тип f с параметром a. А в качестве результата работы этой функции будет снова тип f, но уже с другим параметром b. Получается, у нас есть некий контейнер f, который содержит какие-то значения, в качестве примера можно привести: - Option<i32> - перечисление Option является f, т.к. содержит значения типа i32; - Vec<String> - вектор Vec является f, т.к. содержит значения типа String; - Result<User, Error> - перечисление Result является f, т.к. содержит значения User. Про второе значение Error - поговорим позже. Все эти типы внутри f можно обобщить до T: - Option<T>; - Vec<T>; - Result<T, E> (ошибку то же обобщим). Можно заметить, что они все содержат внутри себя T, который как раз и есть a из Functor. Теперь поговорим про kind (вид) типа. У обычного типа, например i32, String или bool у них всех kind - звёздочка , это значит, что тип является конечно определённым, и больше ничего не надо для его представления. А вот у типа Vec<T>, Option<T> kind - это звёздочка в звёздочку -> , т.е. нужно получить некий тип T (первая звёздочка), что бы получить последню, например нужно указать i32 для Vec<T>, что бы получить Vec<i32> - конечный тип. С Result kind ещё более интересный - -> -> , это связано с тем, что ему надо предать 2 типа - тип корректного результата T и тип ошибки, которая может возникнуть в процессе работы E. Просто так взять Vec и опирировать им в системе типов Rust нельзя, но определение Functor нас буквально об этом и просит, укажи некий контейнер и уже в сигнатуре fmap будет конкретизация. На Rust это можно обойти, сделав целый уровень абстракцции, который позволит скрыть kind с помощью трейтов Family и FamilyMember<T> и недавней фичи GAT (ступенька к HKT): trait Family { type Member<T>; } trait FamilyMember<T> { type Of: Family<Member<T> = Self>; } struct VecFamily; impl Family for VecFamily { type Member<T> = Vec<T>; } impl<T> FamilyMember<T> for Vec<T> { type Of = VecFamily; } trait Functor<A>: FamilyMember<A> { fn fmap<B>(self, f: impl FnMut(A) -> B) -> <Self::Of as Family>::Member<B>; } impl<A> Functor<A> for Vec<A> { fn fmap<B>(self, f: impl FnMut(A) -> B) -> Vec<B> { self.into_iter().map(f).collect() } } fn main() { let a = vec![1, 2, 3, 4, 5]; let b = a.fmap(|x| Some(x + 2)); println!("{b:?}"); } // [Some(3), Some(4), Some(5), Some(6), Some(7)] В результате HKT позволяют определять интерфейсы на конструкторах типов, у которых kind -> в примере с Functor (в примерах с другими интерфейсами, kind может быть другой).
30
просмотров
3059
символов
Нет
эмодзи
Нет
медиа

Другие посты @codeforbro

Все посты канала →