Literate.vimrc
Сначала, у меня был один .vimrc to rule them all, но когда плагинов становится больше дюжины, становится сложно отслеживать настройки. Некоторые опции (deoplete) надо было настраивать до загрузки плагинов, некоторые — после. Что-то настраивалось через `let g:...`, что-то через `call plugin#function(...`. Потом я разбил на много файликов, отдельно для каждого плагина и source'ил их из .vimrc — стало легче читать, но сложнее редактировать. С Literate .vimrc стало возможным накидать markdown-якорей по конфигу, заголовков, нумерованных списков, e.t.c., а потом просматривать всё в браузере через markdow-preview.
Вообще, это не круто выкладывать решения с открытой площадки. Не знаю, на ком они зарабатывают, но проходить таски по гайду — фу.
Если совсем не идёт, можно попросить совета на форуме/ирке/написать в личку.
Я слышал, что в одной вселенной люди умеют решать задачи на логику, не притягивая свои иррациональные идеологические суждения. В этой вселенной люди читают этот абзац так:
«Докажем, что существует недвудольный граф, в котором нельзя выбрать стабильную комбинацию пар. Пусть его матрица весов выглядит так:
* 3 2 1
2 * 3 1
3 2 * 1
1 1 1 *
Тогда мы не можем выбрать пары, стабильные согласно определению выше.»
но по этой же логике вам надо писать тесты на фреймворк тестов.
Вообще-то надо. Тесты — код первого порядка и должен подчиняться всем требованиям релизного кода: стили, ревью, ограничения на выбор зависимостей (и расширений в случае Хаскеля) и наличие тестов.
Другое дело, что писать тесты на внешний код — ну такое.
Так это ж и получается Dyn ctx, который получился в самом конце, разве нет?
Разве что, можно явно Constraint kind не указывать, тайпчекер выведет.
Точно, просмотрел.
Хм, что-то у меня это не очень сработало, тайпчекер всё равно не видит a1, a2 в rhs, даже несмотря на ScopedTypeVariables. Там нет ли того, что можно упоминать только те переменные, которые были объявлены с forall в сигнатуре функции?
Неаккуратно, написал, сорян:
{-# LANGUAGE ConstraintKinds #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeOperators #-}
import Prelude
import Data.Typeable
data Wrapper cxt where
Wrap :: (Typeable a, cxt a) => a -> Wrapper cxt
withDyns :: (forall a. Ord a => a -> a -> b)
-> (TypeRep -> TypeRep -> b)
-> Wrapper Ord -> Wrapper Ord -> b
withDyns f def (Wrap (v1 :: a1)) (Wrap (v2 :: a2)) = case eqT :: Maybe (a1 :~: a2) of
Nothing -> def (typeOf v1) (typeOf v2)
Just Refl -> f v1 v2
main :: IO ()
main = do
let a = Wrap True
let b = Wrap False
let c = Wrap 'a'
print $ withDyns (\x y -> show $ x `compare` y) (\x y -> show x ++ show y) a b
print $ withDyns (\x y -> show $ x `compare` y) (\x y -> show x ++ show y) a c
$ runghc main.hs
"GT"
"BoolChar"
Добавил TypeOperators, Typeable constraint в Wrapper и поправил тип функции def.
Вообще, я лично не очень люблю интроспекцию типов, свой код в итоге перегрузил кучей вызовов cast, а потом долго ловил ошибку, вызванную cast'ом функции. Сейчас склоняюсь к мысли, что если пространство возможных типов не слишком велико, то проще работать с большим алгебраическим типом, a la, «data ArrayItem = C Char | I Int | B Bool...». Для работы с динамическими типами нужно крайне тщательно спланировать дизайн, потому что расширять его потом может быть очень мучительно, и нужно очень хорошо понимать, что сделать принципиально возможно, а что нет.
Можно немного проще, не добавляя TypeRep в обёртку, при помощи ScopedTypeVariables (не увидел объявления SomeTypeRep, кстати):
data SomeOrd where
MkSomeOrd :: Ord a => a -> SomeOrd
withDyns :: (forall a. Ord a => a -> a -> b) ->
(SomeTypeRep -> SomeTypeRep -> b) ->
SomeOrd -> SomeOrd -> b
withDyns f def (SomeOrd (v1 :: a1)) (SomeOrd (v2 :: a2)) = case eqT :: Maybe (a1 :~: a2) of
Nothing -> def (typeOf v1) (typeOf v2)
Just Refl -> f v1 v2
Ещё советую посмотреть статью про гетерогенные списки на wiki.haskell, там интересная техника с обёртыванием constraint'а, чтобы не заводить отдельный тип Some для каждого ограничения
data Wrapper (con :: Constraint) where
Wrap :: (con a) => a -> Wrapper con
withDyns :: (forall a. Ord a => a -> a -> b) ->
(Wrapper Ord -> Wrapper Ord -> b) ->
SomeOrd -> SomeOrd -> b
withDyns f def (Wrap (v1 :: a1)) (Wrap (v2 :: a2)) = case eqT :: Maybe (a1 :~: a2) of
Nothing -> def (typeOf v1) (typeOf v2)
Just Refl -> f v1 v2
Мне кажется, тут смешали две группы людей: будущие профессионалы-программисты и условные «домохозяйки», которые хотят себе себе сайт-визитку или малинку с чайником связать. Вот для первых тезис «пытаться изучать тестирование плюс что-либо ещё — ошибка» я бы развил в «следовательно они должны начинать с тестирования». Тестирование очень сильно способствует элегантности кода, а правило «тесты — код первого порядка и подчиняются тем же требованиям, что и основной код» способствует элегантности тестов.
Есть ещё известный эффект, что мотивация сильно падает, если долгое время не видно результата — если стек глубокий, а до hello world'а ещё пилить и пилить. Так вот, если параллельно делать тесты, то эффект сильно слабеет, во всяком случае для меня лично.
Спасибо.
У вас псевдокод во фреймах с подсветкой Питона, после первой кавычки всё зеленеет. Попробуйте поиграть с подсветкой, а лучше поменяйте на честный Питон, все всё поймут :)
Это то что я назвал вторым способом. При этом мы теряем информацию, в каком конкретно вызове произошло исключение и должны поддерживать несколько обработчиков, что затрудняет чтение и добавляет сложности.
def foo(a, b, dictionary, host):
j = divide(a / b)
l = http.get(host).parseSmth()
k = dictionary[l]
m = divide(k / j)
...
Здесь у нас могут быть разные исключения — арифметика, сеть, отсутствует ключ. Либо мы вешаем на весь блок «except Exception», либо делаем серию except'ов на каждый тип исключения, либо вешаем персональный try на каждый опасный вызов. Второй и третий способ раздувает код и делает его нелинейным, первый я не приемлю, потому что слишком общий. Монада Result добавляет один декоратор за пределами тела функции и по .unwrap() в конец каждой строки.
Вторая проблема — два опасных divide в одной функции. Допустим мы поставили общий «except ZeroDivisionError», но теперь мы не знаем, какой из них бросил исключение. Монада Result позволяет перед вызовом unwrap() сделать rescue() и добавить подробное описание.
Если я правильно понял — завернуть всё в pipeline и везде где мы получили эти переменные j, l, n, k, z получать их unwrap'ом. Тогда в самих переменных будут честные int'ы, но если хотя бы одна завалится, то pipeline тормознёт вычисления.
В Haskell есть какая-то похожая штука для неявной работы с контейнерами? inline-разворачивания, так сказать?
Конечно — трансформеры монад. Делаем всякие разные грязные действия, возвращающие Either (Result в статье), если хотя бы одно вернёт Left (Failure), игнорируем все последующие и возращаем этот Left. При этом локальные присваивания игнорируют Either (Result) и всегда думают, что им вернули Right (Success). Если не вернули — смотри выше.
Вроде бы и да, но в этой статье я вижу другой смысл. В RP коллбеки очень тонкие и не порождают новые коллбеки (они могут, но это не идиоматично). Их задача — получить сообщение и послать его в граф. В статье описывается функция которая занимается вычислениями, выводом и вводом, хотя в RP все три части разделены.
Как такой же фрагмент кода будет работать в реактивном стиле? Нить исполняет вычисления, посылает HTTP-запрос и вместо того чтобы заблокироваться и при получении результата синхронно обработать его, описывает код (оставляет callback) который должен быть исполнен в качестве реакции (отсюда слово реактивный) на результат. После этого нить продолжает работу, делая какие-то другие вычисления (может быть, как раз обрабатывая результаты других HTTP-запросов) без переключения контекста.
Это не реактивное, а асинхронное программирование на коллбеках. Хотя реактивные фреймворки зачастую имеют асинхронный реактор, но одной из фич реактивного программирования является как раз избегание коллбэков. Реактивное программирование — это прежде всего про потоки данных внутри системы и распространение изменений. Никаких отложенных вызовов не предполагается. За манифест спасибо, посмотрю.
Пришла мысль, что геймдев идёт по стопам музыкальной и писательской индустрии. Огромное количество авторов, магазины/лейблы/издательства. Всё те же плюсы и минусы: и снижение порога входа и жадные издатели и огромное количество мусора. Жаловаться бессмысленно, зато можно предугадать некоторые будущие шаги.
Увы, но из новых аминокислот получить улучшенные органы человека не более просто, чем из новых инструкций процессора получить искусственный интеллект. Слишком много запутанных логических уровней в промежутке.
Сначала, у меня был один .vimrc to rule them all, но когда плагинов становится больше дюжины, становится сложно отслеживать настройки. Некоторые опции (deoplete) надо было настраивать до загрузки плагинов, некоторые — после. Что-то настраивалось через `let g:...`, что-то через `call plugin#function(...`. Потом я разбил на много файликов, отдельно для каждого плагина и source'ил их из .vimrc — стало легче читать, но сложнее редактировать. С Literate .vimrc стало возможным накидать markdown-якорей по конфигу, заголовков, нумерованных списков, e.t.c., а потом просматривать всё в браузере через markdow-preview.
Если совсем не идёт, можно попросить совета на форуме/ирке/написать в личку.
«Докажем, что существует недвудольный граф, в котором нельзя выбрать стабильную комбинацию пар. Пусть его матрица весов выглядит так:
* 3 2 1
2 * 3 1
3 2 * 1
1 1 1 *
Тогда мы не можем выбрать пары, стабильные согласно определению выше.»
Вообще-то надо. Тесты — код первого порядка и должен подчиняться всем требованиям релизного кода: стили, ревью, ограничения на выбор зависимостей (и расширений в случае Хаскеля) и наличие тестов.
Другое дело, что писать тесты на внешний код — ну такое.
А вообще зачётно, в духе постов Aphyr.
Точно, просмотрел.
Неаккуратно, написал, сорян:
Добавил TypeOperators, Typeable constraint в Wrapper и поправил тип функции def.
Вообще, я лично не очень люблю интроспекцию типов, свой код в итоге перегрузил кучей вызовов cast, а потом долго ловил ошибку, вызванную cast'ом функции. Сейчас склоняюсь к мысли, что если пространство возможных типов не слишком велико, то проще работать с большим алгебраическим типом, a la, «data ArrayItem = C Char | I Int | B Bool...». Для работы с динамическими типами нужно крайне тщательно спланировать дизайн, потому что расширять его потом может быть очень мучительно, и нужно очень хорошо понимать, что сделать принципиально возможно, а что нет.
Ещё советую посмотреть статью про гетерогенные списки на wiki.haskell, там интересная техника с обёртыванием constraint'а, чтобы не заводить отдельный тип Some для каждого ограничения
Есть ещё известный эффект, что мотивация сильно падает, если долгое время не видно результата — если стек глубокий, а до hello world'а ещё пилить и пилить. Так вот, если параллельно делать тесты, то эффект сильно слабеет, во всяком случае для меня лично.
У вас псевдокод во фреймах с подсветкой Питона, после первой кавычки всё зеленеет. Попробуйте поиграть с подсветкой, а лучше поменяйте на честный Питон, все всё поймут :)
Здесь у нас могут быть разные исключения — арифметика, сеть, отсутствует ключ. Либо мы вешаем на весь блок «except Exception», либо делаем серию except'ов на каждый тип исключения, либо вешаем персональный try на каждый опасный вызов. Второй и третий способ раздувает код и делает его нелинейным, первый я не приемлю, потому что слишком общий. Монада Result добавляет один декоратор за пределами тела функции и по .unwrap() в конец каждой строки.
Вторая проблема — два опасных divide в одной функции. Допустим мы поставили общий «except ZeroDivisionError», но теперь мы не знаем, какой из них бросил исключение. Монада Result позволяет перед вызовом unwrap() сделать rescue() и добавить подробное описание.
Конечно — трансформеры монад. Делаем всякие разные грязные действия, возвращающие Either (Result в статье), если хотя бы одно вернёт Left (Failure), игнорируем все последующие и возращаем этот Left. При этом локальные присваивания игнорируют Either (Result) и всегда думают, что им вернули Right (Success). Если не вернули — смотри выше.
Это не реактивное, а асинхронное программирование на коллбеках. Хотя реактивные фреймворки зачастую имеют асинхронный реактор, но одной из фич реактивного программирования является как раз избегание коллбэков. Реактивное программирование — это прежде всего про потоки данных внутри системы и распространение изменений. Никаких отложенных вызовов не предполагается. За манифест спасибо, посмотрю.