Пытаясь композировать некомпозируемое: поднимаем всё

    Рекомендуется прочитать первую статью, если вы еще этого не сделали. Эта статья будет покороче, меньше сконцентрирована на деталях и больше — на возможностях.

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

    Будущее не за горами, поэтому приступать нужно уже сейчас.

    Поднимаем эффекты до уровня других эффектов


    class Liftable eff schema where
    	lift :: eff ~> schema
    

    Что означает «поднятие» по сути? lift — это тот же самый pure/return, только мы погружаем не значение в эффект, а эффект в какой-нибудь трансформер (в нашем случае — в трансформерную схему):

    pure :: a -> t a
    lift :: u a -> t u a
    

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

    Таким образом, мы можем спокойно скомпозировать поднятые эффекты:

    let f = lift get :: Configured _ t => t _
    let g = lift Nothing :: Optional t => t _
    let h = lift (failure _) :: Failable _ t => t _
    let x = f *> g *> h :: (Applicative t, Configured _ t, Optional t, Failable _ t) => t _
    

    И представить их в любом удобном для нас порядке:

    let y = pure _ :: Reader _ :> State _ :> Either _ :> Maybe := Int
    let z = pure _ :: State _ :> Either _ :> Maybe _ :> Reader := _
    
    let x = f *> g *> h :: (Applicative t, Configured _ t, Optional t, Failable _ t) => t _
    
    let xy = x *> y :: Reader _ :> State _ :> Either _ :> Maybe := _
    let xz = x *> z :: State _ :> Either _ :> Maybe _ :> Reader := _
    

    Адаптируем одни эффекты к другим


    class Adaptable subeff eff | subeff -> eff where
    	adapt :: subeff ~> eff
    

    Адаптация означает, что некоторые эффекты могут быть заменены более мощными эффектами. Например, эффекты Reader и Writer могут быть использованы в State, потому что State может читать и писать и таким образом изменять хранимое значение:

    lift put :: Accumulated _ t => t _
    lift get :: Configured _ t => t _
    (lift . adapt $ put) :: Stateful _ t => t _
    (lift . adapt $ get) :: Stateful _ t => t _ 
    

    Как такое возможно? В предыдущей статье мы поделили State на два эффекта:

    State s = (->) s :. (,) s
    

    В случае с Reader, мы просто поднимаем функтор стрелки до уровня State, а в случае с Writer — функтор кортежа:

    Reader s = (->) s
    Writer s = (,) s
    

    Мы можем адаптировать Failable к Optional, но мы потеряем информацию об ошибке:

    (lift $ Just _) :: Optional t => t _
    (lift $ failure _) :: Failable _ t => t _
    (lift . adapt $ failure _) :: Optional t => t _
    

    Запускаем эффекты в трансформерах


    Чтобы проинтерпретировать какой-нибудь эффект в трансформере, достаточно одного метода run:

    let xy = x *> y :: Reader _ :> State _ :> Either _ :> Maybe := _
    let xy' = run xy _ :: State _ :> Either _ :> Maybe := _
    let xy'' = run xy' _ :: Either _ :> Maybe := _
    let xy''' = run xy'' :: Maybe (Either _) _
    

    Заключение и примеры


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

    Еще есть презентация с доклада по этой теме на локальном митапе в Ростове-на-Дону, которую можно полистать в браузере.

    Лучшие примеры — которые максимально приближены к реальности. Я люблю музыку, поэтому вы можете посмотреть, как используется эта система эффектов в программе, скачивающей альбомы с Bandcamp.
    AdBlock похитил этот баннер, но баннеры не зубы — отрастут

    Подробнее
    Реклама

    Комментарии 0

    Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

    Самое читаемое