All streams
Search
Write a publication
Pull to refresh
5
0
Александр Данилов @gen1lee

13 лет опыта коммерческой разработки

Send message

Он занимает ровно те же 78 строк

Удобно не реализовывать полностью интерфейс и конструктор сделать пустым и однострочным, что еще удалили?

И это на элементарном примере, заточенном для "У вас это не получится. В этом был весь смысл примера с кодеками."

Вы согласились написать полный пример для демонстрации вашего процедурного подхода? Согласились.

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

Ну попробуйте заменить swEncoder.requestKeyFrame() на вызов процедуры. У вас это не получится. В этом был весь смысл примера с кодеками.

Я ее заменил на вызов функции, без классов и без проблем из статьи. Хотя даже и на вызов процедуры заменить не проблема, и самое смешное что вы далее сами это делаете. Не зря статью читали (с).

Глобальные функции можно вызывать и с ООП

Во-первых функция может быть необязательно глобальная, а создана в функции конструкторе или еще где то.

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

User.getDisplayName() из вашего примера в статье. У него тоже нет ссылок на другие методы,

Вы видимо таки не поняли о чем речь, попробую объяснить еще проще: this - это объект класса где есть все из этого класса. А значит метод жестов завязан на тип этого класса, и его нельзя переиспользовать для данных, у которых нет всего, что есть в this. То есть если в User есть метод goFckYourself, то чтобы переисолзовать getDisplayName из User нужно будет добавить в мои данные, например Dog, этот же метод, иначе никак. Более того, единственный способ их добавить это отнаследовать Doc от User, добавив таки эти методы. Надеюсь теперь понятно, если нет - тут уже ничем не помогу.

Это можно сделать и с ООП, но вы в статье все равно это указали как проблему ООП.

В статье речь про то, что в методах класса выбора нет, а в ФП выбор есть, и даже пример есть с getDisplayName - советую почитать еще раз.

В итоге ни одно ваше утверждение не доказано, ни про "у вас это не получится", ни про "у метода нет ссылок на другие методы", ни то что можно сделать this опциональным в ООП, и даже переписать на классы JS без читинга ваш собственный, якобы заточенный на превосходство ООП пример.

Полный провал.

Но хотя бы пришли к тому, что в С++ тоже можно использовать процедуры если нужно гибко и просто. Хоть что то хорошее.

PS. Сложность кода оценивается не только строчками, а в том числе количеством символов языка (типа class со всеми вытекающими из них this, наследованием, модификаторами доступа и тп), багоемкостью (типа попытки передать метода класса как параметр, получая вместо this undefined) и прочими проблемами, перечисленными в статье. Тут у вас тоже полный провал.

Все различия связаны с синтаксисом языков JavaScript/CPP

Нет, в JS тоже есть классы и вся эта ерундистика. Я использовал более лаконичный и простой подход без лишних символов. И по сторчкам, и в целом по символам вышло меньше. Даже на специально выбранном вами примере и с точно таким же подходом без особого рефакторинг.

Методы encode и requestKeyFrame в вашей реализации тоже приколочены к this. Например если вы захотите вызывать один метод из другого, вы будете использовать this

  1. Они вообще никуда не приколочены и там нет ни одного символа this.

  2. Более того у стрелочных функций что я использую в принципе нет this.

  3. Этот пример полная копия вашего, только без классов.

Эти функции можно без проблем вынести в процедуры если возникнет такая необходимость, например:

// Аргументы только те, что используются, ничего лишнего
const encodeVp9S = (frame: Frame) => {
  // ...
}

const makeVp9SWEncoder = (): Encoder => ({
  encode: encodeVp9S
});

У вас эти методы зависят даже не от класса, а вообще от конкретного объекта.

Ограничений здесь нет, см. пред. пример.

У вас то же самое, методы encode и requestKeyFrame "требуют для работы" все методы, которые есть в объекте где они объявлены.

Опять ошибка - где они это требуют? У них даже ссылки нет друг на друга))

У вас аналогично, нельзя использовать метод без создания объекта где он содержится.

И тут ошибка - если нужно то можно - см. пред. пример.

Сможете обработать в ваших методах ситуацию, когда encoder null или undefined? Я в этом сомневаюсь.

const encodeVp9S = (frame?: Frame) => {
  if (!frame) return null
  // ...
}

encoder?.encode()
encoder?.requestFrame?.()

А в замыкании ситуакция когда оно null невозможна.

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

Какой то детский сад а не код)) Даже не буду рефакторить, выложу то же самое (78 строк).

type Encoder = {
  encode: (frame: VideoFrame) => BitStream;
  requestKeyFrame?: () => void;
};

const makeVp9SWEncoder = (): Encoder => ({
  encode: (frame) => {
    // кодируем через libvpx
  },
});

const makeVp9HWEncoder = (): Encoder => ({
  encode: (frame) => {
    // Вызываем функции ОС чтобы драйвер видеокарты что-то закодировал
  },
});

const makeSoftwareFallbackEncoder = (swEncoder: Encoder, hwEncoder: Encoder): Encoder => {
  let isFallback = false;
  
  return {
    encode: (frame) => {
      if (!isFallback) {
        const encoded = hwEncoder.encode(frame);
        if (!encoded.isError) return encoded;
        isFallback = true;
      }
      return swEncoder.encode(frame);
    },
    requestKeyFrame: () => {
      if (isFallback) swEncoder.requestKeyFrame?.();
      else hwEncoder.requestKeyFrame?.();
    }
  };
};

const makeEncoder = (codec: Codec): Encoder => {
  switch (codec) {
    case 'vp9f': return makeSoftwareFallbackEncoder(
      makeVp9SWEncoder(), makeVp9HWEncoder()
    );
    case 'vp8f': return makeSoftwareFallbackEncoder(
      makeVp9SWEncoder(), makeVp9HWEncoder()
    );
    case 'vp9s': return makeVp9SWEncoder();
    case 'vp9h': return makeVp9HWEncoder();
    case 'vp8h': return makeVp9HWEncoder();
    case 'vp8': return makeVp9SWEncoder();
    case 'vp9': return makeVp9SWEncoder();
    case 'h264': return makeVp9HWEncoder();
  }
};

const main = () => {
  // ...
  const codecs = getCodecs(); 
  const encoders = codecs.map(makeEncoder)

  while (true) {
    const event = getEvent();
    if (!event) break;
    
    switch (event.type) {
      case 'gotFrame': {
        for (const encoder of encoders) {
          encoder.encode(event.getFrame());
        }
        break;
      }
      case 'requestedKeyFrame': {
        for (const encoder of encoders) {
          encoder.requestKeyFrame?.();
        }
        break;
      }
    }
  }
};


Вы сами скинули ссылки на два куска кода, и плюс еще просите учесть свой кусок кода. У меня аналогичные куски кода.

Приведите сами ВЕСЬ код а не несколько кусков, потом поговорим.

Какой кусок кода нужно еще привести, чтобы вы смогли увидеть проблемы?)

И где ваш полный пример этого кода?)

код тоже надо переписывать

То что можно функцию разбить на несколько функций это называется обычный рефакторинг. Если код слишком простой - нет смысла городить абстракции, если начинает усложняться то смысл появляется.

Правильно. А если больше, то хуже. В вашем коде проблем больше.

Я бы вам ткнул пальцем,

Ключевые слова "я бы". Я думаю можно этими двумя словами подытожить все потуги ООПшников))

ООП даст вам, по сути, только одно: объект сможет пользоваться реализацией родительского класса, при этом сам не зная, кто его родитель

В ФП с этим нет проблем - функция может пользоваться другой функцией, не зная конкретно что это за функция.

установки is_fallback нет

В вашем последнем куске кода тоже нет.

  Как у вас сделать вот такую конструкцию?

const makeEncoder = (encoding: SoftwareEncoding | HardwareEncoding | FallbackEncoding): Encoder => {
  switch (encoding) {
    case 'vp9f': return makeSoftwareFallbackEncoder('vp9h', 'vp9s');
    case 'vp8f': return makeSoftwareFallbackEncoder('vp8h', 'vp8s');
    case 'vp9s': return makeVp9SWEncoder()
    case 'vp8s': return makeVp8SWEncoder()
    case 'vp9h': return makeVp9HWEncoder()
    case 'vp8h': return makeVp8HWEncoder()  
    // ...
  }
}

const makeSoftwareFallbackEncoder = (primary: HardwareEncoding, fallback: SoftwareEncoding): Encoder => {
  let encoder = makeEncoder(primary)
  let isFallback = false

  return () => {
    encode: (frame) => {
      if (isFallback) {
         return encoder.encode(frame) 
      }

      try {
        return encoder.encode(frame)  
      } catch {
         encoder = makeEncoder(fallback)
         isFallback = true
         return encoder.encode(frame)
      }
    },
    // ...
  }
}

вы реализовали ООП вручную

Я все проблемы ООП, перечисленные в статье, не реализовывал в своих примерах - и их там нет. А если, как вы заявляете, код очень похож, но в одном из вариантов сильно меньше проблем чем во втором - значит он лучше.

Он работает так же [плохо] как и в вашем примере.

Если это был типа "плохой" пример, то надо учиться яснее общаться. Второй ваш пример это вообще непонятно что на вашем фреймворке, в котором 1) я разбираться не имею никакого желания 2) вообще не ясно как массив редактируется и как используется, и почему у игрушки зачем то есть count().

Если вы топите за мутабельное состояние [от которого проблем часто больше, при схожей производительности], то вот простейший пример вообще без фреймворков:

type Toy = {
  count: 0
}

type State = {
  toys: Toy[],
  filteredToys: Toy[],
  filter: (x) => boolean
}

const makeToysStore = () => {
   const state: State = {
    toys: [],
    filter: (x) => x.count > 0
  }

  const setFilter = (filter: State['filter']) => {
    state.filter = filter
    state.filteredToys = state.toys.filter(filter)
  
    EventEmitter.emit('toysStoreChanged', ['filter', 'filteredToys'])
  } 

  return {
    getState: () => state,
    setFilter
  }
}

export const toysStore = makeToysStore()


Можно было бы вынести повторяющиеся паттерны в отдельный фреймворк, либо использовать готовые решения, но суть в том, что это все прекрасно реализуется в ФП.

Я сомневаюсь, что такие библиотеки перевешивают долю по реализации

Так статистику поищите и не сомневайтесь больше.

Когда Вы уже осознаете, что язык/фреймворк это инструмент

Как только хоть один человек скинет код, на котором будет видно, как забивать гвозди связанными скотчем костылями (ООП) лучше, чем молотком (ФП).

Я привел аж три способа реализации полиморфизма (union type, обобщение и интерфейс), и даже указал в каких случаях их лучше использовать, и даже реализовал где то здесь в комментариях код на обобщениях, который якобы "нельзя реализовать на ФП".

Вот фраза из статьи, с дальнейшими примерами кода:

Далее в примере для этого будут использоваться union type, обобщение и интерфейс:

Я только и делаю, что пишу большие системы с абстракциями, и как я уже говорил, сейчас практически все сайты, в тч самые сложные и высоконагруженные, пишутся в парадигме ФП, если вы не знали.

Вот пример мобильного приложения https://github.com/mattermost/mattermost-mobile. Там же есть и десктоп, и веб.

Вы не специалист, наконец то признали, тогда есть чему поучиться.

Во-первых ООП в моем видении, описаном в статье, в приведенном коде отсутствует. Это просто псевдо-декларативное описание модели и поведения, на основе которой генерируется ООП код, а мог бы генерироваться и ФП с тем же "успехом".

Почему псевдо - потому что если слишком много логики запихнуть в декларативность, то как работает код поймет только его автор [и Майкл Джексон]. Так же он будет обладать большим количеством ограничений, на который наткнешься уже по факту, и чтоб от них избавиться придется все переписывать. И чтобы реализовать даже простейшую задачу, придется очень долго и нудно читать документацию этого фреймворка. Поэтому ваш фреймворк обречен, к сожалению.

Другими словами, вы не выполнили главное требование инженера - чем проще, тем лучше.

Вот как выглядит хороший, простой код (псевдокод) на функциях:

// Модель отделена от логики
type User = {
  id: number
  name?: string
  description?: string
  created_at?: number
  avatarUrl?: string
  hobbies?: string
}

// Хранение состояния отделено от UI и слоя доступа к данным.
// Здесь мог бы быть любой стор - мутабельный, иммутабельный, на прокси или евент емиттерах и тп.

type EntitiesState = {
  users: User[]
  userConfigs: UserConfig[]
}

const entitiesStore = create<EntitiesState>({
  users: [],
  userConfigs: [],
})

// Бизнес логика.

const changeUserConfig: (userConfig: Partial<UserConfig> & Pick<UserConfig, "id">) => {
  // Меняем значение в entitiesStore.
}

// UI - любая библиотека на усмотрение.

const UserPage = ({id}: {id: string}) => {
  const user = useEntitiesStore((state) => selectUser(state, id))

  if (!user) return <Loader />
    
  return (
    <Html>
      <Title value={user.name} />
      <Description value={user.description} />
      // ...
    </Html>
  )
}

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

Дождались наконец.

  1. При чем тут процедурная?

  2. Примеров все еще нет.

Если хотелось реализовать мемоизированное вычисляемое состояние, то это делается элементарно в каком нибудь zustand / redux через reselect, если устраивает иммутабельный стор, если не устраивает есть и мутабельные сторы в парадигме ФП.

Более того, можно хоть на ивент еммитерах это реализовать если нужна супер произвотидельность.

Но самое смешное что даже RX из ваших примеров можно использовать в парадигме ФП, и первый кусок кода в ней и написан, если не считать создание BehaviorSubject через new, который мог бы создаваться через функцию make*.

Вот код что сгенерировал GPT, я не проверял но плюс минус то же самое но без классов - меньше, понятнее и без проблем из статьи:

const mem = pipe(distinctUntilChanged(), debounce(0), shareReplay(1))

const makeToys = () => {
   const toysSource = new Rx.BehaviorSubject([]);
   const toys = toysSource.pipe(mem);
   
   const filterSource = new Rx.BehaviorSubject(toy => toy.count() > 0);
   const filter = filterSource.pipe(mem);
  
   const filteredToys = filter.pipe(
     switchMap(currentFilter => toys.pipe(
       map(toysArray => toysArray.filter(currentFilter))
     )),
     mem
   );
  
   return {
     toys,
     filteredToys,
     setFilter: (newFilter) => filterSource.next(newFilter)
   } 
}


Поищите на маркетплейсах «мини-UV УФ стерилизатор для аквариума, С таймером Ультрафиолетовая аквариумная лампа, 3 Вт».

Если не пахнет значит часто моете)

У меня такая же, маленькая лампа влезла ровно под бак. Запах у воды с тех пор пропал.

Купите мойку, киньте ультрафиолетовую лампу для аквариумов - все.

1
23 ...

Information

Rating
Does not participate
Location
Санкт-Петербург, Санкт-Петербург и область, Россия
Registered
Activity

Specialization

Frontend Developer, Mobile Application Developer
Lead