Pull to refresh
19
0
Viacheslav Lotsmanov @unclechu

Haskell enthusiast

Send message

Спасибо за наводку. Около месяца аптайма компьютера и браузера под кеш нарезервировалось почти все 32ГБ. После большого перерыва стал пересобирать систему, обновление большое, понадобилось оперативной памяти прилично. И вместо того, чтобы откусить от того, что нарезервировалось, система полезла в своп на диск, на 3,5ГиБ примерно. Разумеется всё начало адово фризиться (впрочем не так долго, т.к. SSD, но тем не менее). Отключил к чертям своп на диске и подключил ZRAM swap, чтобы такого больше не повторялось.

Живу в Финляндии, о*уел немного от прочитанного, надо признаться. А ведь я когда-то выбирал между Германией и Финляндией, ну где быстрее нарисуется хороший случай. Повезло же мне! Розовые очки конечно тоже поблекли в нюансах, но никаких серьёзных потрясений за полтора года не было. Окажись я в Вашей шкуре, плевался бы очень долго. Хотя из перечисленного о весьма и весьма специфичной культуре германского сервиса я был наслышан.

Это у Вас молниеносно по памяти не текло. Если там чего-то недохватило, то в фризы могут быть и незначительными. А вот если у вас что-то пошло не так, и нечто начало аллоцировать как не в себя, да ещё параллельно в несколько потоков/процессов (ну вот сидели Вы, погромировали, экспериментировали, допустили страшное недоразумение в творческом потоке), а Вы и не заметили сначала, то если есть своп, пиши «пропало», потому что как оно доест до свопа, всё повиснет, и проще ресетнуть компьютер, чем ждать пока оно мееееееедленно доест своп (а диск медленный) и встретит свою OOM-смерть. А вот если свопа нет совсем, то OOM такой франкенштайн встретит гораздо быстрее.

И вообще, даже если поверить на слово, что именно swap файл вот так прямо нужен. Давайте может я тогда:


  1. Создам tmpfs на несколько гигибайт
  2. Смонтирую в /tmplol
  3. sudo dd if=/dev/zero of=/tmplol/swaplol bs=1M count=4096
  4. sudo mkswap /tmplol/swaplol
  5. sudo chmod 600 /tmplol/swaplol
  6. sudo swapon /tmplol/swaplol

ОС будет мне благодарна? Ведь оперативной памяти у меня, ух, аж на 3 Electron-приложения хватит, положу swapfile прямо туда, в оперативную память. Зачем мне дёргать лишний раз не вечный и медленный диск?

Когда возникает необходимость в swap-файле? Самый частый ответ — «когда не хватает памяти». И, естественно, это ответ неправильный почти полностью. Правильный ответ — необходимость в использовании swap-файла возникает тогда, когда система не может удержать в памяти необходимый кэш и грязные страницы.

Это прекрасно, когда объем оперативной памяти достаточно велик, чтобы вместить и весь необходимый кэш, и данные. Но если у вас бюджетная система с 8 … 16ГБ оперативной памяти

Взаимоисключающие параграфы? «Памяти хватает, система не бюджетная (32Г думаю достаточно для аргумента), зачем мне своп, если я точно знаю, что если система дошла то того, что полезла в своп, то проще нажать Reset, т.к. фризы будут такие, что даже открыть терминал по хоткею и кильнуть вредный процесс может стать невыполнимой задачей.»


«когда не хватает памяти». И, естественно, это ответ неправильный почти полностью.

В каком месте он тут неправильный? Если:


Это прекрасно, когда объем оперативной памяти достаточно велик, чтобы вместить и весь необходимый кэш, и данные.

Ну я уже всех не помню, это было лет 8-9 назад. В качестве DAW Reaper, из VST плагинов GuitarRig, TRex, эквалайзеры, компрессоры, ревербераторы, набор плагинов Wave, Native Instruments, какой-то очень известный барабанный сэмплер, забыл название, я всех уже и не вспомню, и имена не помню все. Какие-то, куда всякого DRM и прочей «защиты от пользователя» напихали, могут и не заработать без плясок. Но те, что мне были нужны, все работали.

Ну для кого-то персонально и настал. Я в своё время, когда убедился, что нужный мне музыкальный софт (который я позже заменил open source решениями) отлично работает из под Wine, то избавился от дуал бута, и за все эти года (наверное уж с десятилетие) видел настоящий шиндовс только «на картинках», ну или где-нибудь на чужом компьютере.

В разделе «Лучшие дистрибутивы для опытных специалистов» нет самого главного — NixOS. Знакомство с Nix в 2019, а потом установка NixOS и последующая репликация системы на другие свои компьютеры (я мечтал о чём-то таком не один год в виде идеи) в 2020 — это было в некотором роде революционным шокирующим событием для меня за около 10 лет использования дистрибутивов GNU/Linux. Я до последнего не верил что это сработает, учитывая в частности особенности своего рабочего окружения. NixOS — это настоящая жемчужина, аналогов которой нет.

А ошибка может быть только any, по понятным причинам (мы не управляем источником ошибок).

Да нет же, она может иметь строковое представление, как в примере, может быть инстансом Error, может иметь и какую-то более сложную модель данных, и вообще состоять из sum-type-а. Если ошибка имеет тип any, то это плохо сделанная работа. А если считаете, что это ок, то мой совет: выкидывайте TypeScript и не тратьте на него время. Когда он используется таким образом от него больше вреда, как от time-spender-а, чем пользы.


Если же вас смущает, что она не string

Исходя из его был сделан такой вывод? Смущает. Точнее смущает если она не имеет конкретного описанного типа.

Вы это затем как-нибудь типизируете?

В первой части рассматривался пример без типизации. Если нужна типизация, хорошая, а не абы какая, часто приходится где-то жертвовать краткостью, не обязательно в данном конкретном случае. На мой взгляд строгая корректная модель типов гораздо важнее предельной краткости. По-этому я бы вообще не стал сравнивать самый короткий код и самый хорошо типизированный. Т.к. всякие хитрые helper-ы пересобирающие объекты из одного вида в другой порой просто невозможно корректно описать в рамках TypeScript.


Сюда бы immer, а то всё ещё очень страшно выглядит. Особенно если потребуется вложенные объекты менять.

Я бы immutable.js использовал, но для простоты примерра намеренно опустил любые доп. библиотеки.


А затем шаг номер 2: перестать хранить в Redux всякую ерунду вроде статуса загрузки, и перенести это в хуки

Если я к примеру не хочу повторно загружать данные при отмонтировании компонента, то это не подходит. Если я хочу иметь доступ к данным из разных компонентов, то тоже не подходит. И вообще, мне не нравится идея нагружать компоненты логикой загрузки данных откуда-то, предпочитаю делать это в стороне от компонентов (View). От компонентов должен только поступить сигнал «хочу данные».

Всё прекрасно типизируется

Поле с ошибкой хранится в стороне, никак не связано с самим значением на уровне типов. Не могу сказать, что это самый прекрасный способ описать типы.

Это не дело вкуса, и тут не «всё в одном», а всего-лишь самый необходимый минимум для того, чтобы на уровне типов отражалось доступность значения. Т.е. это по сути технически возможный минимум данных (опять же, в контексте корректно-типизированных свойств). На уровне типов у вас прямая связь с состоянием загрузки значения, либо ошибкой, произошедшей во время загрузки.

Про «не актуально» в начале это относилось к:


Наверное, всем надоели завывания про многословность Redux, но это реальная проблема.

Случайно стёр цитату.

Пример на MobX как это могло бы выглядеть:


class NewsStore {
  @observable public todayHotNewsId: DataRequest<{ id: number }> = 
    { status: 'idle' };
  @observable public news: DataRequest<{ list: ReadonlyArray<NewsItem> }> =
    { status: 'idle' };

  public fetchNews = (): Promise<void> =>
    Promise.resolve()
      .then(async () => {
        if (this.news.status === 'loading') return Promise.resolve();

        await action(() => {
          this.news = { status: 'loading' };
        });

        await action((response: AxiosResponse<{
          readonly news: ReadonlyArray<NewsItem>
          readonly hotId: number
        }>) => {
          this.news = { status: 'success', list: response.news };
          this.todayHotNewsId = { status: 'success', id: response.hotId };
        });
      })
      .catch(
        action((err: Error) => {
          this.news = { status: 'failure', errMessage: err.message };
          this.todayHotNewsId = this.news;
        })
      );
}

В качестве варианта можно рассмотреть сборку всех зависимых данных в одно свойство, но тогда это уже будет не MobX, и все оптимизации observable-ов будут рушиться, будет одно большое значение, которое будет постоянно меняться.

Не актуально, когда в последний раз писал на Redux кода у меня было меньше, чем в Ваших MobX примерах.


Пишется простой helper на 10 строк максимум на базе библиотеки redux-actions:


import { createAction } from 'redux-actions'

const createActions(prefix, actionTypes) =>
  actionTypes.reduce(
    (result, actionType) => ({
      ...result,
      [actionType.toLowerCase().replace(/_([a-z])/g, (x, y) => y.toUpperCase())]:
        createAction(`${prefix}@${actionType}`)
    }),
    {}
  )

А далее допустим есть flow загрузки новостей (в качестве примера), создаём:


  1. Action
  2. Для него функцию action-creator

На каждый action по одной строчке:


const newsActions = createActions('NEWS', [
  'LOAD_REQUEST',
  'LOAD_SUCCESS',
  'LOAD_FAILURE',
])

В качестве ключей newsActions получаем:


  1. loadRequest
  2. loadSuccess
  3. loadFailure

В качестве значений — функции с переопределённым методом toString, таким образом newsActions.toString() вернёт NEWS@LOAD_REQUEST, а newsActions(123) вернёт { type: 'NEWS@LOAD_REQUEST', payload: 123 }


Т.е. желать меньшего, чем одна строчка на action невозможно (технически все эти 3 action-а можно и в одну строку записать и уложиться в 80 символов на строку, ещё останется). И это не только action, а сразу и action-creator.


Ваш пример:


export function increment() {
  return {
    type: 'INCREMENT'
  }
}

export function decrement() {
  return {
    type: 'DECREMENT'
  }
}

Превратится в:


export default createActions('INCREMENTOR', ['INCREMENT', 'DECREMENT'])

А что до редьюсеров, в лучших случаях тут тоже всё до одной строчки сводится (ключи объекта внутри квадратных скобок автоматически преобразовываются к строке через вызов метода toString):


import { handleActions } from 'redux-actions'

handleActions({
  [newsActions.loadRequest]: state =>
    { ...state, isLoading: true, isLoaded: false, isFailed: false },
  [newsActions.loadSuccess]: (state, { payload: news }) =>
    { ...state, isLoading: false, isLoaded: true, news },
  [newsActions.loadFailure]: state =>
    { ...state, isLoading: false, isFailed: true },
}, { isLoading: false, isLoaded: false, isFailed: false, news: [] })

Также нужно заметить, что Вы манипулируете синтаксическими ухищрениями используя ES6, но игнорируя его особенности, вот Ваш пример:


const mapStateToProps = (state) => {
  return {
    count: state.count
  }
}

const mapDispatchToProps = (dispatch) => {
  return {
    onIncrement: () => {
      dispatch(increment())
    },
    onDecrement: () => {
      dispatch(decrement())
    }
  }
}

Вместе с action-creator-ами выше это могло быть записано так:


export const increment = () => ({ type: 'INCREMENT' });
export const decrement = () => ({ type: 'DECREMENT' });
// другой файл
const mapStateToProps = state => ({ count: state.count });

const mapDispatchToProps = dispatch => ({
  onIncrement: () => { dispatch(increment()) },
  onDecrement: () => { dispatch(decrement()) },
});

Что уже совсем не похоже на подчёркиваемую многословность, напротив.


Но главная проблема MobX, на мой взгляд, это плохая интегрируемость с системой типов. Вы написали что у вас TypeScript среди используемых инструментов. И судя по всему ваша команда, как и подавляющее большинство пользователей TypeScript просто тратите своё время, т.к. если вы не используете мощь типизации (которую TypeScript может дать), вы просто тратите время на резрешение ошибок типов, в то время как Ваш код остаётся плохо типизированным и у вас открытое поле для багов в рантайме.


Взять к примеру то же состояние загрузки, есть у вас допустим какая-нибудь страница, где есть id условной горячей новости дня, из этого id вы генерируете запрос на сервер. Пока вы этот id ниоткуда ещё не загрузили, вам нужно туда что-то записать, ну большинство просто запишет 0, а это в корне неверно. Если вы, полагаясь на собственную внимательность, забудете вручную проверить, загружены ли данные и/или что этот id не равен 0, то получите рантайм баг, т.к. на сервере не будет такого элемента (и это не воображаемый сценарий, а случай из реальной жизни, из личной практики). А в это время TypeScript будет говорить вам что вы всё сделали правильно.


Это можно легко решить на TypeScript с помощью generic-ов и sum-type. Вот мой личный экспериментальный пример с Redux:


https://github.com/unclechu/typescript-redux-and-data-request-flow-proper-typing-experiment


Суть в том, что у вас будет несколько базовых generic-ов:


export type Idle<T> =
  { readonly [K in keyof T]: T[K] } & { readonly status: 'idle' };

export type Request<T> =
  { readonly [K in keyof T]: T[K] } & { readonly status: 'loading' };

export type Success<T> =
  { readonly [K in keyof T]: T[K] } & { readonly status: 'success' };

export type Failure<T> =
  { readonly [K in keyof T]: T[K] } & { readonly status: 'failure' };

export type CommonFailure =
  Failure<{ errMessage: string }>;

// Самый главный, подходящий для большинства случаев, когда специфичный payload
// есть только для case-а успешной загрузки данных с сервера.
// А для ошибок только текстовое представление ошибки.
export type DataRequest<T> =
  Idle<{}> | Request<{}> | Success<T> | CommonFailure;

С помощью пары helper-ов можно проверять исчерпываемость switch-case проверок, а также ограничивать scope проверки action-ов в reducer-ах, т.е. action-ы должны быть обработаны эксплицитно все, но только те, которые имеют отношение к конкретной ветке store-а (можно почитать комментарии к коду по ссылке выше, они оттуда, а также посмотреть примеры использования для большей ясности):


export const ProveExhaustiveness =
  <T extends never>(x: T): void => {}

export const ImpossibleCase =
  <T extends never>(x: T): never => {
    throw new Error(`Unexpected case, value: "${x}"`)
  }

Выброс исключения в ImpossibleCase как правило на самом деле никогда не происходит (если не было использовано магии с any), т.к. type-checker проверяет, что до этой точки выполнения невозможно дойти.


Таким образом можно описать reducer как (можно также смело полагаться на строки в качестве action-type, т.к. type-checker сразу выпадет с ошибкой на опечатку):


interface NewsItem {
  readonly date: string
  readonly title: string
}

type NewsState = DataRequest<{ news: ReadonlyArray<NewsItem> }>

type NewsAction =
  | { readonly type: 'NEWS@LOAD_REQUEST' }
  | {
    readonly type: 'NEWS@LOAD_SUCCESS'
    readonly news: ReadonlyArray<NewsItem>
  }
  | {
    readonly type: 'NEWS@LOAD_FAILURE'
    readonly message: string
  }

const newsReducer =
  ( state: NewsState = { status: 'idle' }
  , action: NewsAction
  ): NewsState => {
    switch (action.type) {
      case 'NEWS@LOAD_REQUEST':
        return { status: 'loading' }
      case 'NEWS@LOAD_SUCCESS':
        return { status: 'success', news: action.enws }
      case 'NEWS@LOAD_FAILURE':
        return { status: 'failure', errMessage: action.message }
      default:
        ProveExhaustiveness(action)
        return state
    }
  }

ProveExhaustiveness тут проверяет только на уровне типов, в runtime любой action проходит через все reducer-ы. Но в данном случае ограничивается подмножество action-ов, которые могут быть обработаны в данной конкретной ветке стора (намеренно, это необязательно, просто показана такая возможность, и всё во время type-checking-а).


А уже в компоненте потом можно просто проверить в начале:


if (news.status !== 'success') {
  if (news.status === 'failure') return <div>We are fucked up: {news.errMessage}</div>;
  else return <div>Loading</div>;
}

И всё, дальше уже type-checker-ом будет доказано, что news — это Request<{news: ReadonlyArray<NewsItem>}>. Любые опечатки или обращения к поляем другого типа из общего sum-type-а в то время, как доказано что это другой тип (либо не доказано, что тот, к полю которого обращаемся, либо не исключены проверками типы, у которых такого поля с соответствующим типом этого поля нет), — приведут к ошибке type-checking-а. То, что доктор прописал, машины делают за людей их работу и ловят баги!


Под всё это ещё можно и генерализованный компонент сделать, который будет в одну строчку обрабатывать ошибку или состояние загрузки компонента. Т.е. как-то вроде этого:


if (smth.status !== 'success') return <Failover thatSumType={smth}/>;

А теперь попробуйте переложить эту модель на MobX и тут же поймёте почему это на MobX ложится плохо. Потому что при описании свойств объекта MobX мы имеем фиксированную модель данных, и обернуть её целиком в DataRequest не получится, а получится лишь оборачивать каждое зависимое от состояния загрузки с сервера данных поле в такой generic. В итоге проверять в компоненте на состояние загруженности данных уже придётся каждое поле, к которому обращаемся, хотя они по смыслу сгруппированы. И то вышеописанное для примера поле id горячей новости дня, и список новостей и всё остальное, что зависит от наличия данных, полученных от сервера.


P.S. Местами где-то сумбурно и поверхностно написал, просто тут материала можно на целую статью выделить, если расписывать в деталях.

Мимо чего? Мимо взрослого мира? Да, мимо. Сплошной детский сад, куличики и смешные детские обиды.

Ладно, я понял, ваша аргументация скатилась к реверсивному навязыванию "детского сада" (я уже и не прикину сколько раз вы это повторили, но точно больше десяти, вероятно пытаясь нащупать какое-то уязвимое место?), обвинению собеседника в собственных комплексах, и банальному придуриванию. Я не впечатлён и удаляюсь, мне такой диалог не интересен. Подумайте об этом на досуге, когда в очередной раз будете претендовать на "взрослость" в радостную обнимку с теми "кому плевать".

Да откуда вы взяли что не можем? Можем. Нет у "нас" такой проблемы. Всё нормально увязывается между собой. Используемые из той или иной "стандартной библиотеки" не диктуют функционал внешнего API, как это связано у вас, я вообще не понял. Сигнатура типа вашей библиотеки от этого не меняется. Если где-то что-то не склеивается, и приходится как-то самому композировать разные функции из библиотек, то это не по причине использования той или иной "стандартной библиотеки".

Гарантии вам вообще вряд ли какие-то о поддержке столь долгосрочные даст. Одни языки внезапно и скоротечно умирают, другие, вроде COBOL, могут легко, на удивление, пережить создателей, сильно не изменяя облика. Это сильно эфемернее, чем вам, вероятно, кажется. Перед вами кто-то расписался кровью, что C++ или Java скоротечно не умрут? У каких-то компаний перед вами обязательства?


Если же говорить о чём-то предполагаемом, а не о воображаемых "гарантиях". То за все эти десятилетия жизни Haskell, новые экстеншены только добавлялись и поддерживались, и редко когда какие-то удалялись. Вроде что-то было, но единичное, и экстеншены почти или совсем не используемые и сомнительного назначения.


А соберётся она точно, т.к. я использую параллельно как Stackage-снапшот, который привязан как к версии пакета, так и к GHC, в качестве эталона для сборки. А также Nix, который с ещё большей вероятностью даст собрать и запустить мою программу, как если бы она была собрана в последний раз. Гарантия reproducibility.

Так ведь, выражась более скромно, тем, кто "типизацией не смог проникнуться", — и не место в Haskell, вы разве сами с этим не согласны? Они ведь будут только мучаться что "компилятор их изнуряет своими ошибками, скомпилируй мне уже хоть что-нибудь". Какой с этого вообще прок, идти писать на языке с одной из самых мощных практически используемых систем типов при этом считая что строгая типизация тебе совершенно не нужна?


Это ведь получается всё-равно что TypeScript, который может (хоть и далеко не всё, что мне нужно), но на котором не хотят. В итоге ну какой с этого прок, если при симпатии к самому инструменту я получаю антипатию к проектам, на нём написанном? И вообще не вижу зачем эти люди себя насилуют и рожают обыкновенный JavaScript shitcode тратя свои программисто-часы на бесполезную расстановку as any? Ничего хорошего из этого не вышло, мне такой инструмент не нужен, я могу лишь, как вы ранее сказали, "свои наколенные эксперименты палочкой тыкать, пока никто не видит", а в проекты контрибьютить желания никакого нет. Получилась "мёртвая нагрузка", которую используют потому что модно и так принято в этот год.

Information

Rating
Does not participate
Registered
Activity