Русский
Русский
English
Статистика
Реклама

Функции

Перевод Декораторы в JavaScript с нуля

07.12.2020 16:06:42 | Автор: admin

Будущих студентов курса "JavaScript Developer. Professional" приглашаем записаться на открытый урок по теме "Делаем интерактивного telegram бота на Node.js".

А сейчас делимся традиционным переводом полезного материала.


Разбираемся с функциями-декораторами

Что такое декоратор?

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

Декораторы явление не новое. Они используются и в других языках, например в Python, и даже в функциональном программировании на JavaScript. Но об этом мы поговорим позже.

Зачем нужны декораторы?

Они позволяют писать более чистый код, придерживаясь концепции композиции, и распространять единожды разработанную возможность на несколько функций и классов. Используя декораторы, вы сможете писать код, который проще отлаживать и сопровождать.

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

Сейчас предложение к стандарту о декораторах классов находится на 2-м этапе рассмотрения, и к этому предложению еще может добавиться множество полезных дополнений.

Совет. Делитесь компонентами многоразового использования для разных проектов на платформе Bit(Github). Это простой способ документировать и систематизировать независимые компоненты из любых проектов и делиться ими.

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

Bitподдерживает Node, TypeScript, React, Vue, Angular и другие фреймворки JS.

Примеры React-компонентов многоразового использования на Bit.devПримеры React-компонентов многоразового использования на Bit.dev

Декораторы функций

Что такое декораторы функций?

Декораторы функций это такие же функции. Они принимают функцию в качестве аргумента и возвращают другую функцию, которая расширяет поведение функции-аргумента. Новая функция не изменяет функцию-аргумент, но использует ее в своем теле. Как я уже говорил, это во многом напоминает функции высшего порядка.

Как работают декораторы функций?

Рассмотрим пример.

Проверка аргументов обычная практика в программировании. В таких языках, как Java, если функция ожидает два аргумента, а получает три, генерируется исключение. Но в JavaScript ошибки не будет, поскольку лишние параметры попросту игнорируются. Такое поведение функций иногда раздражает, но может быть и полезным.

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

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

//decorator functionconst allArgsValid = function(fn) {  return function(...args) {  if (args.length != fn.length) {      throw new Error('Only submit required number of params');    }    const validArgs = args.filter(arg => Number.isInteger(arg));    if (validArgs.length < fn.length) {      throw new TypeError('Argument cannot be a non-integer');    }    return fn(...args);  }}//ordinary multiply functionlet multiply = function(a,b){return a*b;}//decorated multiply function that only accepts the required number of params and only integersmultiply = allArgsValid(multiply);multiply(6, 8);//48multiply(6, 8, 7);//Error: Only submit required number of paramsmultiply(3, null);//TypeError: Argument cannot be a non-integermultiply('',4);//TypeError: Argument cannot be a non-integer

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

Затем мы объявляем переменную multiply и в качестве значения присваиваем ей функцию, которая перемножает два числа. Мы передаем эту функцию умножения в функцию-декоратор allArgsValid, которая, как мы уже знаем, возвращает другую функцию. Возвращаемая функция снова присваивается переменной multiply. Таким образом, разработанный функционал можно будет без труда использовать повторно.

//ordinary add functionlet add = function(a,b){return a+b;}//decorated add function that only accepts the required number of params and only integersadd = allArgsValid(add);add(6, 8);//14add(3, null);//TypeError: Argument cannot be a non-integeradd('',4);//TypeError: Argument cannot be a non-integer

Декораторы классов: предложение к стандарту, рассматриваемое комитетом TC39

В функциональном программировании на JavaScript декораторы функций используются уже давно. Предложение о декораторах классов находится на 2-м этапе рассмотрения.

Классы в JavaScript на самом деле не классы. Синтаксис классов это всего лишь синтаксический сахар для прототипов, который упрощает работу с ними.

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

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

function log(fn) {  return function() {    console.log("Execution of " + fn.name);    console.time("fn");    let val = fn();    console.timeEnd("fn");    return val;  }}class Book {  constructor(name, ISBN) {    this.name = name;    this.ISBN = ISBN;  }  getBook() {    return `[${this.name}][${this.ISBN}]`;  }}let obj = new Book("HP", "1245-533552");let getBook = log(obj.getBook);console.log(getBook());//TypeError: Cannot read property 'name' of undefined

Ошибка возникает потому, что при вызове метода getBook фактически вызывается анонимная функция, возвращаемая функцией-декоратором log. Внутри анонимной функции вызывается метод obj.getBook. Но ключевое слово this внутри анонимной функции ссылается на глобальный объект, а не на объект Book. Возникает ошибка TypeError.

Это можно исправить, передав экземпляр объекта Book в метод getBook.

function log(classObj, fn) {  return function() {    console.log("Execution of " + fn.name);    console.time("fn");    let val = fn.call(classObj);    console.timeEnd("fn");    return val;  }}class Book {  constructor(name, ISBN) {    this.name = name;    this.ISBN = ISBN;  }  getBook() {    return `[${this.name}][${this.ISBN}]`;  }}let obj = new Book("HP", "1245-533552");let getBook = log(obj, obj.getBook);console.log(getBook());//[HP][1245-533552]

Нам также нужно передать объект Book в функцию-декоратор log, чтобы затем можно было передать его в метод obj.getBook, используя this.

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

Примечание. Для выполнения кода из приведенных ниже примеров можно использовать Babel. JSFiddle более простая альтернатива, которая позволяет опробовать эти примеры в браузере. Предложения еще не дошли до последнего этапа рассмотрения, поэтому использовать их в продакшене не рекомендуется: их функционирование пока не идеально и в синтаксис могут внести изменения.

Декораторы классов

В новых декораторах используется специальный синтаксис с префиксом @. Для вызова функции-декоратора log будем использовать такой синтаксис:

@log

В предложении в функции-декораторы внесли некоторые изменения по сравнению со стандартом. Когда функция-декоратор применяется к классу, она получает только один аргумент. Это аргумент target, который по сути является объектом декорируемого класса.

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

Рассмотрим пример, в котором используется класс Book, мы с ним уже знакомы.

function log(target) {  return function(...args) {    console.log("Constructor called");    return new target(...args);  };}@logclass Book {  constructor(name, ISBN) {    this.name = name;    this.ISBN = ISBN;  }  getBook() {    return `[${this.name}][${this.ISBN}]`;  }}let obj = new Book("HP", "1245-533552");//Constructor Calledconsole.log(obj.getBook());//HP][1245-533552]

Как видите, декоратор logполучает аргумент targetи возвращает анонимную функцию. Она выполняет инструкцию log, а затем создает и возвращает новый экземпляр target, который является классом Book. Можно добавить к target прототипы с помощью target.prototype.property.

Более того, с классом могут использоваться несколько функций-декораторов, как показано в этом примере:

function logWithParams(...params) {  return function(target) {    return function(...args) {      console.table(params);      return new target(...args);    }  }}@log@logWithParams('param1', 'param2')class Book {//Class implementation as before}let obj = new Book("HP", "1245-533552");//Constructor called//Params will be consoled as a tableconsole.log(obj.getBook());//[HP][1245-533552]

Декораторы свойств класса

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

Декораторы методов класса

Аргументы, которые передаются в декоратор метода класса, будут отличаться от аргументов декоратора класса. Декоратор метода класса получает не один, а три параметра:

  • target объект, в котором содержатся конструктор и методы, объявленные внутри класса;

  • name имя метода, для которого вызывается декоратор;

  • descriptor объект дескриптора, соответствующий методу, для которого вызывается декоратор. Одескрипторах свойств можно подробнее почитать здесь.

Большинство манипуляций будет выполняться с аргументом descriptor. При использовании с методом класса объект дескриптора имеет 4 атрибута:

  • configurable логическое значение, которое определяет, можно ли изменять свойства дескриптора;

  • enumerable логическое значение, которое определяет, будет ли свойство видимым при перечислении свойств объекта;

  • value значение свойства. В нашем случае это функция;

  • writable логическое значение, которое определяет, возможна ли перезапись свойства.

Рассмотрим пример с классом Book.

//readonly decorator functionfunction readOnly(target, name, descriptor) {  descriptor.writable = false;  return descriptor;}class Book {  //Implementation here  @readOnly  getBook() {    return `[${this.name}][${this.ISBN}]`;  }}let obj = new Book("HP", "1245-533552");obj.getBook = "Hello";console.log(obj.getBook());//[HP][1245-533552]

В нем используется функция-декоратор readOnly, которая делает метод getBook в классе Bookдоступным только для чтения. С этой целью для свойства дескриптора writable устанавливается значение false. По умолчанию для него установлено значение true.

Если значение writable не изменить, свойство getBook можно будет перезаписать, например, так:

obj.getBook = "Hello";console.log(obj.getBook);//Hello

Декораторы поля класса

Декораторы могут использоваться и с полями классов. Хотя TypeScript поддерживает поля классов, предложение добавить их в JavaScript пока находится на 3-м этапе рассмотрения.

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

Если полю значение не присвоено (undefined), атрибут writable объекта дескриптора использоваться не будет.

Рассмотрим пример. Будем работать с уже знакомым нам классом Book.

function upperCase(target, name, descriptor) {  if (descriptor.initializer && descriptor.initializer()) {    let val = descriptor.initializer();    descriptor.initializer = function() {      return val.toUpperCase();    }  }}class Book {    @upperCase  id = "az092b";  getId() {    return `${this.id}`;  }  //other implementation here}let obj = new Book("HP", "1245-533552");console.log(obj.getId());//AZ092B

В этом примере значение свойства id переводится в верхний регистр. Функция-декоратор upperCase проверяет наличие функции initializer, чтобы гарантировать, что значению поля присвоено значение (тоесть значение не является undefined). Затем она проверяет, является ли присвоенное значение условно истинным (прим. пер.: англ. truthy значение, превращающееся в true при приведении к типу Boolean), и затем переводит его в верхний регистр. При вызове метода getId значение будет выведено в верхнем регистре. При использовании декораторов с полями классов можно передавать параметры точно так же, как и в других случаях.

Варианты использования

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

Декораторы в Angular

Если вы знакомы с TypeScript и Angular, вы наверняка сталкивались с использованием декораторов в классах Angular, например @Component, @NgModule, @Injectable, @Pipe и т.д. Это встроенные декораторы классов.

MobX

Декораторы в MobX широко использовались вплоть до 6-й версии. Среди них @observable, @computed и @action. Но сейчас в MobX использование декораторов не приветствуется, поскольку предложение к стандарту еще не принято. В документации говорится:

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

Библиотека Core Decorators

Это библиотека JavaScript, в которой собраны готовые к использованию декораторы. Хотя она основана на предложении о декораторах этапа 0, ее автор планирует обновление, когда предложение перейдет на 3-й этап.

В библиотеке есть такие декораторы, как @readonly, @time, @deprecate и др. С другими декораторами можно ознакомиться здесь.

Библиотека Redux для React

В библиотеке Redux для React есть метод connect, с помощью которого можно подключить компонент React к хранилищу Redux. Библиотека позволяет использовать метод connectтакже в качестве декоратора.

//Before decoratorclass MyApp extends React.Component {  // ...define your main app here}export default connect(mapStateToProps, mapDispatchToProps)(MyApp);//After decorator@connect(mapStateToProps, mapDispatchToProps)export default class MyApp extends React.Component {  // ...define your main app here}

В ответе пользователя Felix Kling на Stack Overflow можно найти некоторые пояснения.

Хотя connectподдерживает синтаксис декоратора, в настоящее время команда Redux не приветствует его использование. Связано это в основном с тем, что предложение о декораторах находится на 2-м этапе рассмотрения, а значит, в него могут быть внесены изменения.

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

Спасибо, что прочитали, и чистого вам кода!


Узнать подробнее о курсе "JavaScript Developer. Professional".

Записаться на открытый урок по теме "Делаем интерактивного telegram бота на Node.js".

Подробнее..

Перевод Функции эта ошибка дороже, чем null

28.05.2021 10:14:13 | Автор: admin
Каждый день мы используем шаблон программирования, из-за которого без всякой необходимости повышается стоимость создания и поддержки ПО. Он приводит к бесчисленным багам и уязвимостям в безопасности. Он требует постоянного рефакторинга. Его сложно тестировать, утомительно документировать, а его гибкость превращает каждую реализацию в уникальную снежинку, что приводит к бесконечному дублированию кода.

Имя этому шаблону функция.

Если конкретнее, то это интерфейс, который обычно является набором функций.


Какие из языков вы узнали?

Одним из первых при изучении программирования мы узнаём принцип многократного использования логики. Это неизменно приводит нас к функции строительному блоку каждого программного проекта. Сами по себе функции не так уж плохи, но именно из-за использования их в качестве многократно применяемого компонента написание, поддержка и масштабирование ПО становятся такими затратными.

Почему?

Писать многократно используемый код сложно.

Нет, это не так. Это неприемлемо.

Любой разработчик может писать многократно используемый код, вне зависимости от своего опыта. Языки наподобие JavaScript, Python, Ruby и Go составлены из миллионов маленьких общих модулей, демонстрирующих простоту написания простого многократно используемого исходного кода. Писать многократно используемый код легко.

Давайте подвергнем это утверждение рефакторингу.

Писать многократно используемый код сложно. Сложно многократно использовать код.

Но и это не так. Взгляните на библиотеку node.js repeat-string в npm. Она не делает ничего, кроме повторения строки, и разработчики скачивают её каждую неделю по семнадцать миллионов раз.


Количество скачиваний repeat-string в npm

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

Так к чему это я?

Как можно найти модуль наподобие repeat-string для своего проекта на node.js? Нужно поискать repeat string в npm. Может быть, вы введёте string repeat, но результаты будут похожими. Проблему, о которой я говорю, можно заметить во втором поисковом результате. И в четвёртом, и в девятом, и в десятом, и в одиннадцатом.

Посмотрите на эти примеры. Каждая библиотека обеспечивает совершенно идентичное поведение.


Примеры библиотек повторения строк в npm

Вы видите, в чём проблема?

Нет, не в том, что одна из них по каким-то непонятным причинам асинхронна. Не говорю я и о том, что повторение строки уже в течение шести с лишним лет является частью языка JavaScript ("A".repeat(5)). Проблема в том, что каждая библиотека имеет свои отличия:

  1. В сигнатуре входящих данных. Некоторые могут получать (string, int), другие (int, string). Одна принимает числа с плавающей запятой, другой требуется функция обратного вызова.
  2. В сигнатуре выходных данных. Каждая библиотека выводит строку, за исключением одной, которая ничего не выводит и передаёт свой результат в функции обратного вызова. И позвольте мне даже не начинать разбор их ошибок.
  3. В поведении при их выполнении. Одна асинхронна, остальные синхронны.
  4. По способу предоставления доступа. Некоторые предоставляют доступ к одной экспортированной функции, другие предоставляют функцию как метод объекта.

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

А почему это проблема?


Проблема в том, что подобное богатство вариантов не создаёт никакой ценности, а только одни проблемы. Они не влияют на бизнес-логику (то есть на то, что в первую очередь важно). Это подробности реализации. Иногда эти варианты возникают из-за нехватки чего-то в языке или фреймворке, а иногда являются просто спонтанным выбором. Мы пытаемся выбрать вариант, который сейчас или в будущем упростит труд разработчика, но предсказать будущее невозможно. Подобные решения поистине дьявольские каждый выбранный вариант поначалу кажется хорошим, пока всё работает. Мы получаем дофаминовую подпитку и ощущаем себя на вершине мира. Повелителями всех систем! Однако когда нам нужно что-то добавить, заменить или изменить, мы осознаём, что не были такими уж гениями.

Как часто нам нужно что-то менять? Каждый день. Именно этим мы и занимаемся. Как часто ПО из-за этого ломается? Буквально каждый раз. Иногда поломка незначительна, и мы устраняем её без особых усилий. Мы смирились с тем, что поломанное ПО это нормально. Даже когда мы пишем автоматизированные тесты, которые не делают ничего, кроме проверки того, что что-то сломалось.

Проблема с интерфейсами произрастает из того факта, что программист в процессе разработки может принимать множество решений. Таких решений, как выбор между позиционными параметрами, конфигурациями и компоновщиками, асинхронностью и синхронностью, глобальным и локальным, stateful и stateless, конструкторами и фабриками, функциональным и объектно-ориентированным подходом, а также из бесконечного количества других вариантов. Каждый выбор определяется современными best practices и каждый становится песчинкой, попавшей внутрь механизма.

Эта проблема не нова, однако мы миримся с ней и обучаем такому подходу каждое новое поколение. Почему? Проблема не в том, что мы принимаем неверные решения, а в том, что нам предоставлены плохие варианты. Каждый вариант это калейдоскоп компромиссов, и правильный ответ зависит от точки зрения. Когда всё представляет собой компромисс, вариант получше есть всегда. Всегда можно переписать код, сделав его лучше.

Давайте подвергнем наше утверждение рефакторингу в третий раз.

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

Это утверждение не так привлекательно, зато ближе к правде.

Без кода, который можно просто вставить, нам нужно проглаживать каждую складку интерфейса, прежде чем можно будет его использовать. Нужно настраивать каждый фрагмент кода, который попадёт в приложение. Каждый ввод. Каждый вывод. Каждый API. Мы засовываем компоновщики на адаптеры и обёртываем их в фабрики на основе сервисов. Но никакое количество грима не сделает ваш паттерн проектирования красивым.

Мы как будто создаём ПО в 18-м веке. Мы вручную распиливаем деревья на доски произвольного размера. Мы изготавливаем с нуля молотки и штампуем гвозди, просто чтобы построить дом, в точности похожий на соседний. Это стоит слишком дорого и занимает слишком много времени. Даже после завершения проекта нас всё равно тянет к земле бремя поддержки. Размеры нестандарты, проводка бьёт электриков током, а строителям, только что выпустившимся из ремесленного училища, не нравится, как мы сделали гвозди. Уже давно настала пора для появления Леруа-Мерлен и цифровых 2x4 в мире ПО.


Привет, меня зовут Тим. Я работаю техлидом в Google, имею 30-летний опыт кодинга, но вынужден искать, как получить длину строки в Python.

Справку по какому API постоянно приходится искать лично вам?


Не станет неожиданностью, если я скажу, что у нас могли бы быть стандарты, достаточно хорошо подходящие для каждого. Microsoft внедрила в 90-х технологию COM, чтобы обмениваться логикой между приложениями, написанными на любом языке. Технологии JVM почти столько же лет, и она продемонстрировала, как множество языков может обрабатывать один и тот же байт-код, а также взаимодействовать друг с другом. Мир продолжает десятками лет заново открывать для себя Flow и Linda как способ связывания друг с другом распределённых чёрных ящиков, а Docker стал новым взглядом на то, каким может быть современный чёрный ящик.

Эта проблема предоставляет кучу возможностей, и многие пытаются её решить. Многообещающими новыми способами решений выглядят Dapr и WasmCloud. Они частично решают проблему разными способами. Как бы ни было печальным состояние разработки ПО сегодня, у меня сегодня больше надежд, чем в прошлые 10 лет. Краткий период восхитительных перспектив возник благодаря JavaScript, обещавшему создание универсальной платформы, на которой аморфные приложения смогут распространиться по всему миру, но в результате он превратился в кучу Electron, React и потраченной впустую ОЗУ. Для меня новым источником оптимизма стал Web Assembly. Он далёк от идеала, но это и замечательно. Идеал никогда не побеждает.



На правах рекламы


Подыскиваете сервер в аренду для отладки проектов, сервер для разработки и размещения? Вы точно наш клиент :) Посуточная тарификация серверов, создавайте собственную конфигурацию в несколько кликов, антиDDoS уже включен в стоимость.

Подписывайтесь на наш чат в Telegram.

Подробнее..

Функциональный Kotlin. Во имя добра, радуги и всего такого

30.01.2021 00:09:39 | Автор: admin

Введение

Сам по себе Kotlin очень мощный инструмент, но многие часто используют его не на полную мощность, превращая его в какую-то... Java 6. Попробую рассказать почему так делать не надо и как использовать функциональные фичи языка на полную.

Функции высшего порядка

Начну с них. И, заодно, расскажу что это вообще такое: когда функция принимает другую функцию как параметр или возвращает ее - это функция высшего порядка. Просто, не правда ли? Но как этим пользоваться?

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

val foo: () -> Unit = {  }

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

run(foo)

Отлично, но что если мы хотим использовать функцию, которую мы определили нормальным способом? Или вообще передать метод? Что-ж, для этого тоже есть возможность - ссылка на функцию. Вот так мы можем проверить строку на пустоту:

str.run(String::isEmpty)

Перейдем к более конкретному кейсу. Допустим, нам нужно распарсить строки определенным образом, но только в одном месте в программе. Очевидно, что логика будет повторяться. Что делать? Создадим для этого отдельный объект с одним методом? Или пропишем для каждого "ручками" прямо на месте?

Нет, копить технический долг ни к чему, лучше мы сделаем, например, что-то такое:

val parse: (String) -> List<Int> = { it.split(":").map(String::toInt) }val (xMin, yMin) = parse(data["from"])val (xMax, yMax) = parse(data["to"])

Функции области видимости

Теперь, когда мы разобрались с ФВП, перейдем к вещам, которыми, скорее всего, пользовались все. let,run,with,apply, иalso. Знакомые слова? Надеюсь, но все же разберем их.

inline fun <T, R> T.let(block: (T) -> R): Rinline fun <T> T.also(block: (T) -> Unit): T

Сначала let и also. Они наиболее просты и понятны, потому что все что они делают внутри - это вызов block(this) . По сути, мы просто делаем вызов определенной нами "на месте" функции. Разница лишь в том, что они возвращают. also используется когда вы хотите продолжить работу с тем же объектом, у которого решили вызвать функцию и let, если вам нужно вернуть новый объект.

inline fun <R> run(block: () -> R): Rinline fun <T, R> T.run(block: T.() -> R): Rinline fun <T, R> with(receiver: T, block: T.() -> R): Rinline fun <T> T.apply(block: T.() -> Unit): T

Теперь к run, with и apply:
run очень похож на let, apply на also, а with это почти то же самое что и run, просто receiver получаем разными способами. Чем они удобны и зачем нужны, если есть let и also? Все просто, тут вместо it используется this, который вообще можно опустить и не писать.

И как же эффективно использовать все эти функции? Мы можем и в большинстве случаев даже должны писать все наши функции полностью в функциях областей видимости.

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

Вы же заметили слово inline в объявлении? А оно, кстати, очень важно. К сожалению, использование функций высшего порядка влечёт за собой снижение производительности, так как функция является объектом и происходит захват контекста замыканием, то есть функции становятся доступны переменные, объявленные вне её тела.

Разумное применение встроенных функций позволяет решить эту проблему. Но делать блоки встроенных функций слишком большими все равно не стоит.

Классы и Объекты

Все мы уже должны были понять, что плодить иерархии и создавать экземпляры классов там, где никакого специфического поведения у отдельных экземпляров нет - дурацкая идея (хотя есть и исключения)?

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

let {  val some = Some()  it.run(some::doSome)}

Использование объекта позволило бы нам сделать наш код проще:

let(Some::doSome)

Как хорошо, когда нет ничего лишнего, да?

Но допустим мы все же вынуждены иметь дело с классом, у которого есть поведение, но экземпляр класса создавать не хотим? К счастью, и для этого найдется достаточно простое решение. Мы просто скинем всю статику, которая есть в объекте в companion object:

class Some {  companion object {    fun doSome(any: Any) = run {}  }}

Теперь мы можем сделать так же, как и с объектом.

Factory методы

Для начала я приведу пример кода:

val other = Other()val stuff = other.produceStuff()val some = Some(stuff)

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

Конечно, мы можем заинлайнить его:

val some = Some(  Other().produceStuff())

Но у меня есть вариант получше. Было бы неплохо, если бы мы могли привязать логику, нужную только для создания объекта к... созданию объекта? И мы можем, для этого нам всего лишь нужно написать вот такой Factory-метод:

class Some {  companion object Factory {    inline fun <T>create(t: T?, f: (T?) -> Stuff) = Some(f(t))  }}

Теперь мы можем сделать так:

val some = Some(Other()) { it.doStuff() }

Или если класс Other тоже имеет свой фабричный метод:

val some = Some.create(Other) { it.create().doStuff() }

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

Подводные камни функций-расширений

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

fun Some.foo() = run { }

Или если хотите экзотики:

val foo: Some.() -> Unit = {  }

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

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

Давайте посмотрим на вот такой код:

class Some {  fun Other.someExtention() = run { }}

В принципе, ничто не мешает нам так сделать, но есть одно "но", из-за которого я считаю это очень плохой практикой.

Все дело в том, что передать ссылку на эту функцию просто не получится. Никак. Даже внутри самого класса. Если так сделать, то Котлин просто не поймет, как обращаться к этой функции - как к методу класса Some или Other.

Однако, если мы, например, вынесем расширения в отдельный файлик под расширения определённого класса или вообще растащим их по пакетам на свое усмотрение - сможем легко обратиться к функции как Some::someExtention. Естественно, в данном случае класс или объект не важно - поведение будет одинаковым.

P.S.

Кстати, если вы немножко копались в устройстве Котлина, то вот вам еще немного бесполезной информации. Допустим, вы решили непонятно зачем использовать вместо нормального способа передачи функции как параметра класс KFunction.

fun Some.overlay(f: KFunction1<Some, Any>) = f(this)

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

Подробнее..

Перевод Как вернуть сразу несколько значений из функции в Python 3

28.08.2020 14:05:29 | Автор: admin
Сегодня мы делимся с вами переводом статьи, которую нашли на сайте medium.com. Автор, Vivek Coder, рассказывает о способах возврата значений из функции в Python и объясняет, как можно отличить друг от друга разные структуры данных.


Фото с сайта Unsplash. Автор: Vipul Jha

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

def hours_to_write(happy_hours):   week1 = happy_hours + 2   week2 = happy_hours + 4   week3 = happy_hours + 6   return [week1, week2, week3] print(hours_to_write(4))# [6, 8, 10]

Структуры данных в Python используются для хранения коллекций данных, которые могут быть возвращены посредством оператора return. В этой статье мы рассмотрим способы возврата нескольких значений с помощью подобных структур (словарей, списков и кортежей), а также с помощью классов и классов данных (Python 3.7+).

Способ 1: возврат значений с помощью словарей


Словари содержат комбинации элементов, которые представляют собой пары ключ значение (key:value), заключенные в фигурные скобки ({}).

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

people={  'Robin': 24,  'Odin': 26,  'David': 25}

А теперь перейдем к функции, которая возвращает словарь с парами ключ значение.

# A Python program to return multiple values using dictionary# This function returns a dictionary def people_age():     d = dict();     d['Jack'] = 30    d['Kim'] = 28    d['Bob'] = 27    return dd = people_age() print(d)# {'Bob': 27, 'Jack': 30, 'Kim': 28}

Способ 2: возврат значений с помощью списков


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

Списки одна из наиболее универсальных структур данных в Python, потому что им не обязательно сохранять однородность (в них можно включать строки, числа и элементы). Иногда списки даже используют вместе со стеками или очередями.

# A Python program to return multiple values using list def test():     str1 = "Happy"    str2 = "Coding"    return [str1, str2]; list = test() print(list)# ['Happy', 'Coding']

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

def natural_numbers(numbers = []):      for i in range(1, 16):       numbers.append(i)   return numbers print(natural_numbers())# [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]

Способ 3: возврат значений с помощью кортежей


Кортежи это упорядоченные неизменяемые объекты в Python, которые обычно используются для хранения коллекций неоднородных данных.

Кортежи напоминают списки, однако их нельзя изменить после того, как они были объявлены. А еще, как правило, кортежи быстрее в работе, чем списки. Кортеж можно создать, отделив элементы запятыми: x, y, z или (x, y, z).

На этом примере кортеж используется для хранения данных о сотруднике (имя, опыт работы в годах и название компании).

Bob = ("Bob", 7, "Google")

А вот пример написания функции для возврата кортежа.

# A Python program to return multiple values using tuple# This function returns a tuple def fun():     str1 = "Happy"    str2 = "Coding"    return str1, str2; # we could also write (str1, str2)str1, str2= fun() print(str1) print(str2)# Happy  Coding

Обратите внимание: мы опустили круглые скобки в операторе return, поскольку для возврата кортежа достаточно просто отделить каждый элемент запятой (как показано выше).

Не забывайте, что кортеж можно создать с помощью запятой вместо круглых скобок. Круглые скобки требуются только в тех случаях, когда используются пустые кортежи или вам нужно избежать синтаксической неточности.

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

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

def student(name, class):   return (name, class)print(student("Brayan", 10))# ('Brayan', 10)

Повторюсь, кортежи легко перепутать со списками (в конце концов, и те, и другие представляют собой контейнер, состоящий из элементов). Однако нужно помнить о фундаментальном различии: кортежи изменить нельзя, а списки можно.

Способ 4: возврат значений с помощью объектов


Тут все так же, как в C/C++ или в Java. Можно просто сформировать класс (в C он называется структурой) для сохранения нескольких признаков и возврата объекта класса.

# A Python program to return multiple values using class class Intro:  def __init__(self):   self.str1 = "hello"  self.str2 = "world"# This function returns an object of Introdef message():  return Intro()  x = message() print(x.str1) print(x.str2)# hello  world

Способ 5: возврат значений с помощью классов данных (Python 3.7+)


Классы данных в Python 3.7+ как раз помогают вернуть класс с автоматически добавленными уникальными методами, модулем typing и другими полезными инструментами.

from dataclasses import dataclass@dataclassclass Item_list:    name: str    perunit_cost: float    quantity_available: int = 0    def total_cost(self) -> float:        return self.perunit_cost * self.quantity_available    book = Item_list("better programming.", 50, 2)x = book.total_cost()print(x)print(book)# 100  Item_list(name='better programming.', perunit_cost=50,   quantity_available=2)

Чтобы лучше разобраться в классах данных, обратитесь к официальной документации Python 3 (документация приведена на английском языке. Прим. ред.).

Вывод


Цель этой статьи ознакомить вас со способами возврата нескольких значений из функции в Python. И, как вы видите, этих способов действительно много.

Учите матчасть и постоянно развивайте свои навыки программирования. Спасибо за внимание!
Подробнее..

Перевод 8 команд для Python по работе с файлами и файловой системой, которые обязательно нужно знать

17.03.2021 18:23:31 | Автор: admin

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

Эта статья предназначена как раз для начинающих разработчиков. В ней описаны 8 крайне важных команд для работы с файлами, папками и файловой системой в целом. Все примеры из этой статьи размещены в Google Colab Notebook (ссылка на ресурс в конце статьи).

Показать текущий каталог


Самая простая и вместе с тем одна из самых важных команд для Python-разработчика. Она нужна потому, что чаще всего разработчики имею дело с относительными путями. Но в некоторых случаях важно знать, где мы находимся.

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

Так вот, для того чтобы показать текущий каталог, нужна встроенная в Python OS-библиотека:

import osos.getcwd()

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


Имейте в виду, что я использую Google Colab, так что путь /content является абсолютным.

Проверяем, существует файл или каталог


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

Функция os.path.exists () принимает аргумент строкового типа, который может быть либо именем каталога, либо файлом.

В случае с Google Colab при каждом запуске создается папка sample_data. Давайте проверим, существует ли такой каталог. Для этого подойдет следующий код:

os.path.exists('sample_data')


Эта же команда подходит и для работы с файлами:

os.path.exists('sample_data/README.md')

Если папки или файла нет, команда возвращает false.


Объединение компонентов пути


В предыдущем примере я намеренно использовал слеш "/" для разделителя компонентов пути. В принципе это нормально, но не рекомендуется. Если вы хотите, чтобы ваше приложение было кроссплатформенным, такой вариант не подходит. Так, некоторые старые версии ОС Windows распознают только слеш "\" в качестве разделителя.

Но не переживайте, Python прекрасно решает эту проблему благодаря функции os.path.join (). Давайте перепишем вариант из примера в предыдущем пункте, используя эту функцию:

os.path.exists(os.path.join('sample_data', 'README.md'))


Создание директории


Ну а теперь самое время создать директорию с именем test_dir внутри рабочей директории. Для этого можно использовать функцию
os.mkdir():

os.mkdir('test_dir')

Давайте посмотрим, как это работает на практике.



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


Именно поэтому рекомендуется всегда проверять наличие каталога с определенным названием перед созданием нового:

if not os.path.exists('test_dir'):    os.mkdir('test_dir')

Еще один совет по созданию каталогов. Иногда нам нужно создать подкаталоги с уровнем вложенности 2 или более. Если мы все еще используем os.mkdir (), нам нужно будет сделать это несколько раз. В этом случае мы можем использовать os.makedirs (). Эта функция создаст все промежуточные каталоги так же, как флаг mkdir -p в системе Linux:

os.makedirs(os.path.join('test_dir', 'level_1', 'level_2', 'level_3'))

Вот что получается в результате.


Показываем содержимое директории


Еще одна полезная команда os.listdir(). Она показывает все содержимое каталога.

Команда отличается от os.walk (), где последний рекурсивно показывает все, что находится под каталогом. os.listdir () намного проще в использовании, потому что просто возвращает список содержимого:

os.listdir('sample_data')


В некоторых случаях нужно что-то более продвинутое например, поиск всех CSV-файлов в каталоге sample_data. В этом случае самый простой способ использовать встроенную библиотеку glob:

from glob import globlist(glob(os.path.join('sample_data', '*.csv')))



Перемещение файлов


Самое время попробовать переместить файлы из одной папки в другую. Рекомендованный способ еще одна встроенная библиотека shutil.
Сейчас попробуем переместить все CSV-файлы из директории sample_data в директорию test_dir. Ниже пример кода для выполнения этой операции:

import shutilfor file in list(glob(os.path.join('sample_data', '*.csv'))):    shutil.move(file, 'test_dir')



Кстати, есть два способа выполнить задуманное. Например, мы можем использовать библиотеку OS, если не хочется импортировать дополнительные библиотеки. Как os.rename, так и os.replace подходят для решения задачи.

Но обе они недостаточно умные, чтобы позволить перемесить файлы в каталог.



Чтобы все это работало, нужно явно указать имя файла в месте назначения. Ниже код, который это позволяет сделать:

for file in list(glob(os.path.join('test_dir', '*.csv'))):    os.rename(        file,        os.path.join(            'sample_data',            os.path.basename(file)    ))



Здесь функция os.path.basename () предназначена для извлечения имени файла из пути с любым количеством компонентов.

Другая функция, os.replace (), делает то же самое. Но разница в том, что os.replace () не зависит от платформы, тогда как os.rename () будет работать только в системе Unix / Linux.

Еще один минус в том, что обе функции не поддерживают перемещение файлов из разных файловых систем, в отличие от shutil.

Поэтому я рекомендую использовать shutil.move () для перемещения файлов.

Копирование файлов


Аналогичным образом shutil подходит и для копирования файлов по уже упомянутым причинам.

Если нужно скопировать файл README.md из папки sample_data в папку test_dir, поможет функция shutil.copy():

shutil.copy(    os.path.join('sample_data', 'README.md'),    os.path.join('test_dir'))




Удаление файлов и папок


Теперь пришел черед разобраться с процедурой удаления файлов и папок. Нам здесь снова поможет библиотека OS.

Когда нужно удалить файл, нужно воспользоваться командой os.remove():

os.remove(os.path.join('test_dir', 'README(1).md'))

Если требуется удалить каталог, на помощь приходит os.rmdir():

os.rmdir(os.path.join('test_dir', 'level_1', 'level_2', 'level_3'))


Однако он может удалить только пустой каталог. На приведенном выше скриншоте видим, что удалить можно лишь каталог level_3. Что если мы хотим рекурсивно удалить каталог level_1? В этом случае зовем на помощь shutil.


Функция shutil.rmtree() сделает все, что нужно:

shutil.rmtree(os.path.join('test_dir', 'level_1'))

Пользоваться ею нужно с осторожностью, поскольку она безвозвратно удаляет все содержимое каталога.

Собственно, на этом все. 8 важных операций по работе с файлами и каталогами в среде Python мы знаем. Что касается ссылки, о которой говорилось в анонсе, то вот она это Google Colab Network с содержимым, готовым к запуску.

Подробнее..

Перевод Практическое руководство по именованию классов, функций и переменных

24.05.2021 00:09:13 | Автор: admin

Перевод сделан для Hexlet.io

В компьютерном программировании соглашение об именах набор правил для выбора последовательности символов, которая будет использоваться для идентификаторов, которые обозначают переменные, типы, функции и другие объекты в исходном коде и документации, Википедия

Придумывать названия сложно!

В этой статье мы сосредоточимся на методе именования (P)A/HC/LC для того, чтобы улучшить читаемость кода. Эти рекомендации можно применить к любому языку программирования, в статье для примеров кода используется JavaScript.

Что значит (P)A/HC/LC?

В этой практике используется следующий шаблон для именования функции:

префикс? (P) + действие (A) + высокоуровневый контекст (HC) + низкоуровневый контекст? (LC)

Что обозначает префикс (P)?

Префиксрасширяет смысл функции.

- is

Описывает свойство или состояние текущего контекста (обычно логическое значение).

const color = 'blue';const isBlue = (color === 'blue'); // свойствоconst isPresent = true; // состояниеif (isBlue && isPresent) {  console.log('Blue is present!');}
- has

Указывает, имеет ли текущий контекст определенное значение или состояние (обычно логическое значение).

/* Плохо */const isProductsExist = (productsCount > 0);const areProductsPresent = (productsCount > 0);/* Хорошо */const hasProducts = (productsCount > 0);
- should

Отражает положительный условный оператор (обычно логическое значение), связанный с определенным действием.

const shouldUpdateUrl = (url, expectedUrl) => url !== expectedUrl;

Читайте также:

Цикл статей Совершенный код. Ошибки именования в программировании от Кирилла Мокевнина

Действие это сердце функции

Действие это глагольная часть имени функции. Это самая важная часть в описании того, что делает функция.

- get

Получает доступ к данным немедленно (сокращение от getter для внутренних данных).

function getFruitsCount() {  return this.fruits.length;}
- set

Безусловно присваивает переменной со значением A значение B.

let fruits = 0;const setFruits = (nextFruits) => {  fruits = nextFruits;};setFruits(5);console.log(fruits); // 5
- reset

Возвращает переменную к её начальному значению или состоянию.

const initialFruits = 5;let fruits = initialFruits;setFruits(10);console.log(fruits); // 10const resetFruits = () => {  fruits = initialFruits;};resetFruits();console.log(fruits); // 5
- fetch

Выполняет запрос данных, для которого требуется время (например, асинхронный запрос).

const fetchPosts = (postCount) => fetch('https://api.dev/posts', {...});
- remove

Удаляет что-то откуда-то.

Например, если у вас есть коллекция выбранных фильтров на странице поиска, удаление одного из них из коллекции этоremoveFilter, а неdeleteFilter(именно так вы и скажете на английском языке):

const removeFilter = (filterName, filters) => filters.filter((name) => name !== filterName);const selectedFilters = ['price', 'availability', 'size'];removeFilter('price', selectedFilters);
- delete

Полностью стирает что-то. После операции сущность перестаёт существовать.

Представьте, что вы редактор контента, и есть пост, от которого вы хотите избавиться. Как только вы нажали на кнопку delete-post, CMS выполнила действиеdeletePost, а неremovePost.

const deletePost = (id) => database.find({ id }).delete();
- compose

Создает новые данные из существующих. Обычно это применимо к строкам, объектам или функциям.

const composePageUrl = (pageName, pageId) => </span><span style="box-sizing: border-box; background-color: rgb(248, 248, 248);">${</span><span style="box-sizing: border-box; background-color: rgb(248, 248, 248);">pageName</span><span style="box-sizing: border-box; background-color: rgb(248, 248, 248);">.</span><span style="box-sizing: border-box; background-color: rgb(248, 248, 248);">toLowerCase</span><span style="box-sizing: border-box; background-color: rgb(248, 248, 248);">()}</span><span style="box-sizing: border-box; color: rgb(221, 17, 68);">-</span><span style="box-sizing: border-box; background-color: rgb(248, 248, 248);">${</span><span style="box-sizing: border-box; background-color: rgb(248, 248, 248);">pageId</span><span style="box-sizing: border-box; background-color: rgb(248, 248, 248);">}</span><span style="box-sizing: border-box; color: rgb(221, 17, 68);">;
- handle

Обработка действия. Часто используется при именовании обратного вызова.

const handleLinkClick = () => {  console.log('Clicked a link!');};link.addEventListener('click', handleLinkClick);

Контекст

Контекст это область, с которой работает функция.

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

/* Чистая функция, работающая с примитивами */const filter = (list, predicate) => list.filter(predicate);/* Функция, работающая непосредственно с сообщениями */const getRecentPosts = (posts) => filter(posts, (post) => post.date === Date.now());/*Некоторые специфические для языка допущения позволяют опустить контекст.Например, в JavaScript фильтр обычно работает с массивом (Array).Добавление явного filterArray будет избыточным.*/

В итоге

Пять принципов именования переменных

В этом разделе мы предложим некоторые правила именования переменных, которые улучшат читаемость кода.

1. Следуйте S-I-D

Имя должно быть коротким (Short), интуитивно понятным (Intuitive) и описательным (Descriptive).

/* Плохо */const a = 5; // "a" может обозначать что угодноconst isPaginatable = (postsCount > 10); // "Paginatable" звучит крайне неестественноconst shouldPaginatize = (postsCount > 10); // Придуманные глаголы - это так весело!/* Хорошо */const postsCount = 5;const hasPagination = (postsCount > 10);const shouldDisplayPagination = (postsCount > 10); // альтернатива

2. Избегайте сокращений

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

/* Плохо */const onItmClk = () => {};/* Хорошо */const onItemClick = () => {};

3. Избегайте дублирования контекста

Всегда удаляйте контекст из имени, если это не снижает его читабельность.

class MenuItem {  /* Имя метода дублирует контекст (которым является "MenuItem") */  handleMenuItemClick(event) {    ...  }  /* Читается как MenuItem.handleClick() */  handleClick(event) {    ...  }}

4. Отражайте в имени ожидаемый результат

/* Плохо */const isEnabled = (itemsCount > 3);/* Хорошо */const isDisabled = (itemsCount <= 3);

5. Учитывайте единственное/множественное число

Как и префикс, имена переменных могут быть единственного или множественного числа в зависимости от того, имеют ли они одно значение или несколько.

/* Плохо */const friends = 'Bob';const friend = ['Bob', 'Tony', 'Tanya'];/* Хорошо */const friend = 'Bob';const friends = ['Bob', 'Tony', 'Tanya'];

6. Используйте осмысленные и произносимые имена

/* Просто ужасно */const yyyymmdstr = moment().format("YYYY/MM/DD");/* Гораздо лучше */const currentDate = moment().format("YYYY/MM/DD");
Подробнее..

Перевод Оптимизация при помощи линейного поиска на Python

13.06.2021 18:05:09 | Автор: admin

Линейный поиск это алгоритм оптимизации, который может использоваться для целевых функций с одной или несколькими переменными. Он предоставляет возможность использовать алгоритм одномерной оптимизации, например поиск методом деления пополам (бисекции) для многомерной целевой функции, работая с линейным поиском для определения оптимального размера шага в каждом измерении от известной точки до оптимума. Мы уже делились переводами Джейсона Браунли, например статьёй о смешанных ансамблях, а в этом учебном руководстве, которое мы перевели к старту курса о машинном и глубоком обучении, рассказывается об основах: вы узнаете, как на Python с помощью линейного поиска выполнить оптимизацию.


Прочитав это руководство, вы узнаете:

  • что линейный поиск это алгоритм оптимизации для одномерных и многомерных задач оптимизации;

  • что библиотека SciPy предоставляет API выполнения линейного поиска, который требует знания о том, как вычисляется первая производная вашей целевой функции;

  • как выполнить линейный поиск для целевой функции и работать с результатом.

Давайте начнём.

Обзор

Этот учебный материал разделён на три части:

  1. Что такое линейный поиск?

  2. Линейный поиск на Python.

  3. Как выполняется линейный поиск? Он состоит из:

a) определения целевой функции;

б) выполнения линейного поиска;

в) работы со сбоями алгоритма.

Что такое линейный поиск?

Линейный поиск это алгоритм оптимизации для одномерной или многомерной оптимизации. Он требует начальной позиции в пространстве поиска, а также указания направления поиска. Затем он из начальной выбирает следующую позицию в пространстве поиска, которая приведёт к значению лучше или же к наилучшему значению целевой функции.

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

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

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

Алгоритмы оптимизации, 2019. С. 54.

Альфа коэффициент масштаба для направления, поэтому при поиске учитываются только значения в диапазоне от 0,0 до 1,0. Один шаг линейного поиска решает задачу минимизации, которая минимизирует целевую функцию для текущей позиции в сумме с масштабируемым направлением, то есть:

  • Минимизирует objective(position + alpha * direction).

Таким образом, линейный поиск работает в одном измерении за один раз и возвращает расстояние перемещения в выбранном направлении.

Каждая итерация метода линейного поиска вычисляет направление поиска pk, а затем решает, как далеко двигаться в этом направлении.

Численная оптимизация, 2006. С. 30.

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

Решение приблизительно или неточно и в зависимости от формы пространства поиска может не оказаться общим решением. Условия, при которых этот алгоритм подходит, называются условиями Вольфе. Теперь, когда мы знакомы с линейным поиском, давайте посмотрим, как выполнять его на Python.

Линейный поиск на Python

Выполнить линейный поиск на Python можно вручную, с помощью функции line_search(). Она поддерживает одномерную оптимизацию, а также многомерные задачи оптимизации. Эта функция принимает имя целевой функции и имя градиента для целевой функции, а также текущее положение в пространстве поиска и направление движения.

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

...result = line_search(objective, gradient, point, direction)

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

...# retrieve the alpha value found as part of the line searchalpha = result[0]

Альфа, начальная точка и направление могут использоваться при построении конечной точки линейного поиска.

...# construct the end point of a line searchend = point + alpha * direction

Для задач оптимизации с более чем одной входной переменной, например многомерной оптимизации, функция line_search() вернёт одно альфа-значение для всех измерений. Это значит, функция предполагает, что оптимум равноудалён от начальной точки во всех измерениях, такое ограничение существенно. Теперь, после ознакомления с тем, как в Python выполнять линейный поиск, давайте рассмотрим работающий пример.

Как выполняется линейный поиск?

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

Определение целевой функции

Во-первых, мы можем определить целевую функцию. Здесь поработаем с одномерной целевой функцией, а именно со сдвинутой на небольшую величину от нуля функцией x^2. Это выпуклая функция, она была выбрана потому, что её легко понять, а также легко вычислить первую производную.

  • objective(x) = (-5 + x)^2.

Обратите внимание, что линейный поиск не ограничивается одномерными или выпуклыми функциями. Реализация этой функции приведена ниже.

# objective functiondef objective(x):return (-5.0 + x)**2.0

Первая производная этой функции может быть вычислена аналитически следующим образом:

  • gradient(x) = 2 * (-5 + x).

Градиент для каждого входного значения просто указывает наклон к оптимумам в каждой точке. Реализация функции градиента приведена ниже:

# gradient for the objective functiondef gradient(x):return 2.0 * (-5.0 + x)

Можно определить диапазон входных данных для x от -10 до 20 и вычислить целевое значение для каждого входного значения:

...# define ranger_min, r_max = -10.0, 20.0# prepare inputsinputs = arange(r_min, r_max, 0.1)# compute targetstargets = [objective(x) for x in inputs]

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

...# plot inputs vs objectivepyplot.plot(inputs, targets, '-', label='objective')pyplot.legend()pyplot.show()

Связав всё это воедино, получим такой код:

# plot a convex objective functionfrom numpy import arangefrom matplotlib import pyplot # objective functiondef objective(x):return (-5.0 + x)**2.0 # gradient for the objective functiondef gradient(x):return 2.0 * (-5.0 + x) # define ranger_min, r_max = -10.0, 20.0# prepare inputsinputs = arange(r_min, r_max, 0.1)# compute targetstargets = [objective(x) for x in inputs]# plot inputs vs objectivepyplot.plot(inputs, targets, '-', label='objective')pyplot.legend()pyplot.show()

Программа вычисляет входные значения (x) в диапазоне от -10 до 20 и создаёт график, показывающий знакомую U-образную форму параболы. Оптимум функции, по-видимому, находится в точке x=5,0, целевое значение 0,0.

Линейный график выпуклой целевой функцииЛинейный график выпуклой целевой функции

Выполнение линейного поиска

Затем можно выполнить линейный поиск по этой функции. Во-первых, мы должны определить отправную точку поиска и его направление. Здесь воспользуемся начальной точкой x=-5, расстояние от которой до оптимума около 10 единиц. Сделаем большой шаг вправо, в данном случае в 100 единиц (что значительно превышает оптимум), например, в положительном направлении. Напомним, что направление похоже на размер шага и поиск масштабирует размер шага, чтобы найти оптимум:

...# define the starting pointpoint = -5.0# define the direction to movedirection = 100.0# print the initial conditionsprint('start=%.1f, direction=%.1f' % (point, direction))# perform the line searchresult = line_search(objective, gradient, point, direction)

Затем поиск ищет оптимумы и возвращает альфа или расстояние, чтобы изменить направление. Из результата мы можем получить значение альфа, а также количество выполненных вычислений функций:

...# summarize the resultalpha = result[0]print('Alpha: %.3f' % alpha)print('Function evaluations: %d' % result[1])

Мы можем использовать альфа вместе с нашей начальной точкой и размером шага для вычисления местоположения оптимумов и вычисления целевой функции в этой точке (которая, как мы ожидаем, будет равна 0,0):

...# define objective function minima end = point + alpha * direction# evaluate objective function minimaprint('f(end) = %.3f' % objective(end))

Затем, для развлечения, мы можем снова построить график функции и показать начальную точку в виде зелёного квадрата, а конечную точку в виде красного квадрата.

...# define ranger_min, r_max = -10.0, 20.0# prepare inputsinputs = arange(r_min, r_max, 0.1)# compute targetstargets = [objective(x) for x in inputs]# plot inputs vs objectivepyplot.plot(inputs, targets, '--', label='objective')# plot start and end of the searchpyplot.plot([point], [objective(point)], 's', color='g')pyplot.plot([end], [objective(end)], 's', color='r')pyplot.legend()pyplot.show()

Ниже приведён полный пример выполнения линейного поиска для выпуклой целевой функции:

# perform a line search on a convex objective functionfrom numpy import arangefrom scipy.optimize import line_searchfrom matplotlib import pyplot # objective functiondef objective(x):return (-5.0 + x)**2.0 # gradient for the objective functiondef gradient(x):return 2.0 * (-5.0 + x) # define the starting pointpoint = -5.0# define the direction to movedirection = 100.0# print the initial conditionsprint('start=%.1f, direction=%.1f' % (point, direction))# perform the line searchresult = line_search(objective, gradient, point, direction)# summarize the resultalpha = result[0]print('Alpha: %.3f' % alpha)print('Function evaluations: %d' % result[1])# define objective function minimaend = point + alpha * direction# evaluate objective function minimaprint('f(end) = f(%.3f) = %.3f' % (end, objective(end)))# define ranger_min, r_max = -10.0, 20.0# prepare inputsinputs = arange(r_min, r_max, 0.1)# compute targetstargets = [objective(x) for x in inputs]# plot inputs vs objectivepyplot.plot(inputs, targets, '--', label='objective')# plot start and end of the searchpyplot.plot([point], [objective(point)], 's', color='g')pyplot.plot([end], [objective(end)], 's', color='r')pyplot.legend()pyplot.show()

Программа-пример сначала сообщает начальную точку и направление. Поиск выполняется, и обнаруживается изменяющая направление для нахождения оптимума значение альфа, в данном случае найденное после трёх вычислений функции 0.1. Точка оптимума находится на отметке 5,0, значение y, как и ожидалось, равно 0,0:

start=-5.0, direction=100.0Alpha: 0.100Function evaluations: 3f(end) = f(5.000) = 0.000

Наконец, создаётся график функции, показывающий зелёную начальную точку и красную цель.

Линейный график целевой функции с оптимумами и начальной точкой поискаЛинейный график целевой функции с оптимумами и начальной точкой поиска

Работа со сбоями алгоритма

Линейный поиск не гарантирует нахождения оптимумов функции. Он может не найти оптимумы, если задано значение направления, недостаточно большое, чтобы охватить их. Например, найти оптимумы будет невозможно, когда направление имеет значение 3. Продемонстрировать это можно на полном примере ниже:

# perform a line search on a convex objective function with a direction that is too smallfrom numpy import arangefrom scipy.optimize import line_searchfrom matplotlib import pyplot # objective functiondef objective(x):return (-5.0 + x)**2.0 # gradient for the objective functiondef gradient(x):return 2.0 * (-5.0 + x) # define the starting pointpoint = -5.0# define the direction to movedirection = 3.0# print the initial conditionsprint('start=%.1f, direction=%.1f' % (point, direction))# perform the line searchresult = line_search(objective, gradient, point, direction)# summarize the resultalpha = result[0]print('Alpha: %.3f' % alpha)# define objective function minimaend = point + alpha * direction# evaluate objective function minimaprint('f(end) = f(%.3f) = %.3f' % (end, objective(end)))

При выполнении примера поиск достигает предела альфа 1,0, что даёт конечную точку от -2 до 49. При f(5) = 0,0 от оптимумов очень далеко:

start=-5.0, direction=3.0Alpha: 1.000f(end) = f(-2.000) = 49.000

Кроме того, мы можем выбрать неправильное направление, ведущее только к вычислениям хуже стартовой точки. Здесь оно будет отрицательным в сторону от оптимума, например, вверх по склону от начальной точки:

...# define the starting pointpoint = -5.0# define the direction to movedirection = -3.0

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

# perform a line search on a convex objective function that does not convergefrom numpy import arangefrom scipy.optimize import line_searchfrom matplotlib import pyplot # objective functiondef objective(x):return (-5.0 + x)**2.0 # gradient for the objective functiondef gradient(x):return 2.0 * (-5.0 + x) # define the starting pointpoint = -5.0# define the direction to movedirection = -3.0# print the initial conditionsprint('start=%.1f, direction=%.1f' % (point, direction))# perform the line searchresult = line_search(objective, gradient, point, direction)# summarize the resultprint('Alpha: %s' % result[0])

Выполнение программы приводит к предупреждению LineSearchWarning, указывающему на то, что поиск, как и ожидалось, не может сойтись. Альфа возвращённое в результате поиска значение равно None:

start=-5.0, direction=-3.0LineSearchWarning: The line search algorithm did not convergewarn('The line search algorithm did not converge', LineSearchWarning)Alpha: None

Дальнейшее чтение

Если вы хотите глубже погрузиться в тему, смотрите этот раздел.

Книги

API

Статьи

Резюме

Из этого руководства вы узнали, как выполнить оптимизацию линейного поиска на Python. В частности, вы узнали:

  • что линейный поиск это алгоритм оптимизации для одномерных и многомерных задач оптимизации;

  • что библиотека SciPy предоставляет API выполнения линейного поиска, требующий знания о том, как вычисляется первая производная вашей целевой функции;

  • как выполнить линейный поиск для целевой функции и работать с его результатом.

Применяемые в машинном обучении методы оптимизации, конечно же, не ограничиваются одним лишь линейным поиском, они многочисленны, разнообразны и у каждого есть свои недостатки и преимущества. Если вы хотите погрузиться в машинное обучение, изучить оптимизацию глубже, но не хотите ограничивать себя областью ML, вы можете обратить внимание на наш курс "Machine Learning и Deep Learning", партнёр которого, компания NVIDIA, не нуждается в представлении.

Узнайте, как прокачаться и в других специальностях или освоить их с нуля:

Другие профессии и курсы
Подробнее..

Recovery mode Функциональное ядро на Python

01.05.2021 06:18:27 | Автор: admin

Главная задача этого поста показать один мало применяемый в Python подход к работе функциями в рамках более общей функциональной парадигмы, в которой функциями можно манипулировать точно так же, как и любыми другими объектами: присваивать переменным, передавать в качестве аргументов в другие функции, возвращать из функций и включать в последовательности в качестве их элементов.

Конвейер обработки данныхКонвейер обработки данных

Функциональный стиль программирования очень близок к тому, как размышляет человек во время решения задачи. Пусть дано x. В целях решения задачи с этими данными необходимо выполнить серию преобразований. Сначала применить к ним f и получить результирующие данные x'. Затем к новым данным применить f2 и получить новые результирующие данные x'' и т.д.

Как оказалось, такой образ мыслей отлично укладывается в то, что называется конвейером обработки данных. Конвейер обработки данных состоит из связанных между собой узлов, т.е. функций. Узел характеризуется набором входных и выходных каналов, по которым могут передаваться объекты. Узел ожидает появления определенного набора объектов на своем входном канале, после чего проводит вычисления и порождает объект(ы) на своем выходном канале, которые передаются в следующий узел в конвейере.

В функциональных языках конвейеры находят широкое применение, и для их имплементирования даже существуют специальные синтаксические конструкции. Вот как выглядит конвейер в языке F#:

2

|> ( fun x -> x + 5)

|> ( fun x -> x * x)

|> ( fun x -> x.ToString() )

Здесь входные данные, в данном случае число 2, последовательно обрабатываются серией лямбда-функций. Аналогичный конвейер можно имплементировать на языке Python, но для этого нужно написать специальную функцию, и, разумеется, это будет функция более высокого порядка:

# Конвейер обработки данных

def pipe(data, *fseq):

for fn in fseq:

data = fn(data)

return data

Приведенный ниже пример демонстрирует работу конвейера:

pipe(2,

lambda x: x + 5,

lambda x: x * x,

lambda x: str(x))

Число 2 проходит серию преобразований, и в результате будет получено строковое значение '49'. По сравнению с функцией reduce, в которой переданная в качестве аргумента одна единственная редуцирующая функция по очереди применяется к последовательности данных, в функции pipe наоборот последовательность функций применяется к обновляемым данным.

Функция pipe получает два аргумента: входные данные data и последовательность функций fseq. Во время первой итерации цикла for данные передаются в первую функцию из последовательности. Эта функция обрабатывает данные и возвращает результат, замещая переменную data новыми данными. Затем эти новые данные отправляются во вторую функцию и т.д. до тех пор, пока не будут выполнены все функции последовательности. По завершению своей работы функция pipe возвращает итоговые данные. Это и есть конвейер обработки данных.

Примечание. В приведенном выше примере функции pipe использован оператор упаковки*. В зависимости от контекста оператор * служит для упаковки получаемых нескольких аргументов в одну параметрическую переменную либо распаковки списка передаваемых в функцию аргументов.

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

def my_sum(*args): # Упаковка в список

return sum(args)

my_sum(1, 2, 3, 4, 5)

Когда он используется при вызове функции он служит для разложения передаваемого списка на отдельные аргументы. Например,

def fun(a, b, c, d):

print(a, b, c, d)

my_list = [1, 2, 3, 4]

fun(*my_list) # Разложение на четыре аргумента

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

Функциональная имплементация вычисления факториала числа

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

1 # Эта программа демонстрирует

2 # функциональную версию функции factorial из главы 12

3

4 def main():

5 # Конвейер (ядро c нерекурсивным алгоритмом факториала)

6 pipe(int(input('Введите неотрицательное целое число: ')),

7 lambda n: (n, reduce(lambda x, y: x * y, range(1, n + 1))),

8 lambda tup:

9 print(f'Факториал числа {tup[0]} равняется {tup[1]}'))

# Вызвать главную функцию

main()

Вывод программы:

Введите неотрицательное целое число: 4 (Enter)

Факториал числа 4 равняется 24

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

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

1 # Эта программа демонстрирует

2 # функциональную версию функции factorial из главы 12

3

4 def get_int(msg=''):

5 return int(input(msg))

6

7 def main():

8 # Алгоритм 1. Рекурсивная версия с хвостовой рекурсией

9 def factorial_rec(n):

10 fn = lambda n, acc=1: acc if n == 0 else fn(n - 1, acc * n)

11 return n, fn(n)

12

13 # Алгоритм 2. Нерекурсивная версия

14 def factorial(n):

15 return n, reduce(lambda x, y: x * y, range(1, n + 1))

16

17 # Ввод данных

18 def indata():

19 def validate(n): # Валидация входных данных

20 if not isinstance(n, int):

21 raise TypeError("Число должно быть целым.")

22 if not n >= 0:

23 raise ValueError("Число должно быть >= 0.")

24 return n

25 msg = 'Введите неотрицательное целое число: '

26 return pipe(get_int(msg), validate)

27

28 # Вывод данных

29 def outdata():

30 def fn(data):

31 n, fact = data

32 print(f'Факториал числа {n} равняется {fact}')

33 return fn

34

35 # Конвейер (функциональное ядро)

36 pipe(indata(), # вход: - выход: int

37 factorial, # вход: int выход: кортеж

38 outdata()) # вход: кортеж выход: -

39

40 # Вызвать главную функцию

41 main()

Вывод программы:

Введите неотрицательное целое число: 4 (Enter)

Факториал числа 4 равняется 24

Функциональным ядром программы являются строки 36-38:

pipe(indata(),

factorial,

outdata())

Они представлены конвейером из трех узлов, т.е. функциями indata, factorial и outdata. Функция indata занимается получением данных от пользователя, которые затем передаются по конвейеру дальше. Функция factorial является собственно обрабатывающим алгоритмом, в данном случае нерекурсивной функцией вычисления факториала, которая получает данные, их обрабатывает и передает по конвейеру дальше. И функция outdata получает данные и показывает их пользователю. Обратите внимание, что функция indata имеет свой собственный конвейер, который состоит из получения данных от пользователя и их валидации.

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

Такая организация программного кода:

  • Позволяет менять узлы конвейера на другие с целью тестирования различных и более эффективных имплементаций алгоритмов. Например, вместо нерекурсивной функции factorial, можно поместить рекурсивную функцию factorial_rec.

pipe(indata(), factorial_rec, outdata())

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

Например, рассмотрим вторую возможность отладку. В этом случае можно написать вспомогательную функцию check:

def check(data):

print(data)

return data

И затем ее вставить в конвейер, чтобы проверить результаты работы отдельных узлов конвейера:

pipe(indata(), check, factorial, check, outdata())

Если выполнить программу в таком варианте, то будут получены следующие результаты:

Вывод программы:

Введите неотрицательное целое число: 4 (Enter)

4

(4, 24)

Факториал числа 4 равняется 24

Как видно из результатов, на вход в функцию factorial поступает введенное пользователем значение 4, а на выходе из нее возвращается кортеж с исходным числом и полученным результатом (4, 24). Этот результат показывает, что программа работает, как и ожидалось. Как вариант, вместо проверочной функции можно написать функцию-таймер, которая могла бы хронометрировать отдельные узлы конвейера.

Приведем еще пару примеров с аналогичной организацией программного кода на основе функционального ядра в виде конвейера.

Функциональная имплементация вычисления последовательности Фибоначчи

# Эта программа демонстрирует

# функциональную версию функции fibonacci из главы 12

def main():

# Алгоритм

def fibonacci(n, x=0, y=1):

# Функция fib возвращает n-ое число последовательности.

fib = lambda n, x=0, y=1: x if n <= 0 else fib(n - 1, y, x + y)

# Функция reduce собирает результаты в список acc

acc = []

reduce(lambda _, y: acc.append(fib(y)), range(n + 1))

return n, acc

# Валидация входных данных

def validate(n):

if not isinstance(n, int):

raise TypeError("Число должно быть целым.")

if not n >= 0:

raise ValueError("Число должно быть ноль положительным.")

if n > 10:

raise ValueError("Число должно быть не больше 10.")

return n

# Ввод данных

def indata():

msg = 'Введите неотрицательное целое число не больше 10: '

return pipe(get_int(msg), validate)

# Вывод данных

def outdata():

def fn(data):

n, seq = data

msg = f'Первые {n} чисел последовательности Фибоначчи:'

print(msg)

[print(el) for el in seq]

return fn

# Конвейер (функциональное ядро)

pipe(indata(), fibonacci, outdata())

# Вызвать главную функцию.

main()

Вывод программы

Введите неотрицательное целое число не больше 10: 10 (Enter)

Первые 10 чисел последовательности Фибоначчи:

1

1

2

3

5

8

13

21

34

55

Функциональная имплементация суммирования диапазона значений последовательности

# Эта программа демонстрирует

# функциональную версию функции range_sum из главы 12

def main():

# Алгоритм

def range_sum(data):

seq, params = data

fn = lambda start, end: 0 if start > end \

else seq[start] + fn(start + 1, end)

return fn(*params)

# Ввод данных

def indata():

seq = [1, 2, 3, 4, 5, 6, 7, 8, 9]

params = (2,5) # params - это параметры start, end

return seq, params

# Вывод данных

def outdata():

def f(data):

msg = 'Сумма значений со 2 по 5 позиции равняется '

print(msg, format(data), sep='')

return f

# Конвейер (функциональное ядро)

pipe(indata(), range_sum, outdata())

# Вызвать главную функцию.

main()

Вывод программы

Сумма значений со 2 по 5 позиции равняется 18

Приведенный в настоящей главе материал имеет ознакомительный характер и предназначен для того, чтобы продемонстрировать возможности функционального парадигмы программирования на Python с целью дальнейших самостоятельных исследований и побудить программистов дать функциональному стилю шанс. Исходный код поста находится в моем репо на Github.

Подробнее..

Заберите свои скобки

09.06.2021 10:16:43 | Автор: admin

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

В большинстве современных языков программирования скобки используются в качестве оператора применения функции к ее аргументам:

q (x, y z)

Но вHaskellоператор применения функции - это обычный пробел:

q :: a -> b -> c -> dx :: ay :: bz :: yq x y z

Подождите-ка, мы применяем функциюfк аргументам, а пробелов намного больше! Значит ли это, что у нас тут несколько применений функции к аргументам? Да:

(((q x) y) z)

Мы получаем несколько применений функции в каррированной форме:

q x :: b -> c -> dq x y :: c -> dq x y z :: d

Значит ли это, что мы можем отказаться от скобок? Нет, потому что в качестве аргументов нам могут попадаться применения функций, нам может быть просто лень придумывать новое имя для выражения, которое будет лишь промежуточным шагом:

q:: a -> b -> c -> dp:: b -> cq x y (p y)

И вот тут без скобочек нам не обойтись, потому что иначе проверка типов будет считать, что мы подаем на вход функцииfдва разных аргумента:

q :: a -> b -> (b -> c) -> b -> ???q x y p y

К счастью, у нас уже есть инфиксный оператор в базовой библиотеке, который поможет нам избавиться от ненужных скобок:

q:: a -> b -> c -> dp:: b -> cq x y $ p y

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

Одной функции никогда не бывает достаточно, и мы хотим применить другую, но уже вкусив код без скобочек, хочется просто написать:

($) :: a -> (a -> b) -> bo :: d -> eo (q x y $ p y) === o $ q x y $ p y

Неплохо выглядит! Но что, если вместо того, чтобы явно подавать аргументы для их последующего скармливания функциям, мы будем соединять функции между собой без какого-либо упоминания аргументов? Вы, наверное, уже видели эту диаграмму, когда читали статьи о функциональном программировании:

"What is a Category? Definition and Examples" (c) Math3ma

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

f :: a -> bg :: b -> cg . f :: a -> c(.) :: (b -> c) -> (a -> b) -> (a -> c)g (f x) === g . f

Зачастую вопрос о том, использовать ли композицию или применение - стилистический, так как эти два представления приводят к одному и тому же результату:

g . f === g $ f x

Если у нас в выражении есть где-то свободная переменная, мы можем применить ее с помощью одного оператора применения, а дальше использовать композицию (я в большинстве случаев именно так и делаю). Да и код читается намного лучше:

j $ h $ f $ g $ x === j . h . f . g $ x

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

f :: a -> b -> cg :: a -> b -> c -> dh :: c -> d -> eh (f x y) (g x y z)

Можно убрать лишь скобки справа:

h (f x y) (g x y z) === h (f x y) $ g x y z

Как убрать скобки слева? Чтобы понять, как подступиться к этой проблеме, давайте разберем композицию (.) и применение ($). Оба эти оператора - правоассоциативные. Ассоциативность - это про скобки. Ассоциативность справа - значит скобки группируются справа.

f . g . h === f $ g $ h x === f (g (h x))

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

f :: a -> b -> c -> df x :: b -> c -> df x y :: c -> df x y z :: d

Выходит, если мы хотим придумать такой оператор, который мог бы принимать несколько операндов, нам надо сделать его левоасcоциативным, чтобы он мог принимать операнды по-одному справа налево:

(???) :: (a -> b -> c -> ...) -> a -> b -> c -> ...((??? x) y) z) ...

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

maybe :: b -> (a -> b) -> Maybe a -> b

Давайте выберем для нашего нового оператора какой-нибудь ASCII-символ, который еще вроде никак не используется в базовой библиотеке.

(#) :: (a -> b -> c -> ...) -> a -> b -> c -> ...f # x y z ... = ???

Кроме ассоциативности, для оператора нужно выбрать старшинство - это номер от 0 до 9, который определяет приоритет операторов. Чем выше номер, тем ниже приоритет. Например:

infixr 9 .infixr 0 $

Именно поэтому мы группируем скобки вокруг$, а не.:

h . g . f $ x === (h . (g . f)) $ (x)

В общем, для нашего нового оператора мы вольны выбрать любое число между 0 и 9. Давайте выберем что-нибудь среднее - 5.

infixl 5 #

Но как нам его написать? На самом деле, это тоже оператор применения, только сфокусированный на функции, а не на аргументах:

f # x = f x

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

maybe :: b -> (a -> b) -> Maybe a -> bmaybe x :: (a -> b) -> Maybe a -> bmaybe x f :: Maybe a -> bmaybe x f ma :: b

Каррирование... отлично, значит, мы можем группировать скобки слева!

maybe # x # f # ma === ((maybe # x) # f) # mamaybe # "undefined" # show . even # Just 1 === "False"maybe # "undefined" # show . even # Just 2 === "True"maybe # "undefined" # show . even # Nothing === "undefined"

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

string_or_int :: Either String Inteither :: (a -> c) -> (b -> c) -> Either a b -> ceither  # print . ("String: " <>)  # print . ("Int: " <>) . show # string_or_int

Я пока что нигде еще не видел такого оператора, но мне бы он сильно понадобился там, где не справляются$и.. Напишите в комментариях, если подобное уже где-то существует, но по какой-то причине отсутсвует вbase.

Подробнее..

Перевод Двумерные тестовые функции для оптимизации

31.03.2021 22:18:24 | Автор: admin

Оптимизация функций это область исследований, где поставлена задача найти некое входное значение [аргумент функции], результат которого максимум или минимум данной функции. Алгоритмов оптимизации много, поэтому важно развивать алгоритмическое чутьё и исследовать алгоритмы на простых и легко визуализируемых тестовых функциях.


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

Обзор туториала

Двумерная функция это функция, которая принимает две входные переменные и вычисляет целевое значение. Можно представить две входные переменные как две оси на графике, x и y. Каждый вход в функцию является единственной точкой на графике, а результат вычисления функции некоторая высота на графике.

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

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

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

  1. Унимодальные функции.

  1. Унимодальная функция 1.

  2. Унимодальная функция 2.

  3. Унимодальная функция 3.

  1. Мультимодальные функции.

  1. Мультимодальная функция 1.

  2. Мультимодальная функция 2.

  3. Мультимодальная функция 3.

Реализация каждой целевой функции будет представлена на Python в выведена в виде поверхности.

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

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

Унимодальные функции

Унимодальность означает, что функция имеет единственный глобальный оптимум.

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

Унимодальная функция 1

Диапазон ограничен промежутком -5,0 и 5,0 и одним глобальным оптимумом в точке [0,0, 0,0].

# unimodal test functionfrom numpy import arangefrom numpy import meshgridfrom matplotlib import pyplotfrom mpl_toolkits.mplot3d import Axes3D# objective functiondef objective(x, y):return x**2.0 + y**2.0# define range for inputr_min, r_max = -5.0, 5.0# sample input range uniformly at 0.1 incrementsxaxis = arange(r_min, r_max, 0.1)yaxis = arange(r_min, r_max, 0.1)# create a mesh from the axisx, y = meshgrid(xaxis, yaxis)# compute targetsresults = objective(x, y)# create a surface plot with the jet color schemefigure = pyplot.figure()axis = figure.gca(projection='3d')axis.plot_surface(x, y, results, cmap='jet')# show the plotpyplot.show()

Код создаёт поверхность согласно графику функции.

График поверхности на основе унимодальной функции оптимизации 1График поверхности на основе унимодальной функции оптимизации 1

Унимодальная функция 2

Диапазон ограничен значениями -10,0 и 10,0 и одним глобальным оптимумом в точке [0,0, 0,0].

# unimodal test functionfrom numpy import arangefrom numpy import meshgridfrom matplotlib import pyplotfrom mpl_toolkits.mplot3d import Axes3D# objective functiondef objective(x, y):return 0.26 * (x**2 + y**2) - 0.48 * x * y# define range for inputr_min, r_max = -10.0, 10.0# sample input range uniformly at 0.1 incrementsxaxis = arange(r_min, r_max, 0.1)yaxis = arange(r_min, r_max, 0.1)# create a mesh from the axisx, y = meshgrid(xaxis, yaxis)# compute targetsresults = objective(x, y)# create a surface plot with the jet color schemefigure = pyplot.figure()axis = figure.gca(projection='3d')axis.plot_surface(x, y, results, cmap='jet')# show the plotpyplot.show()

Код создаёт поверхность согласно графику функции.

График унимодальной функции оптимизации 2График унимодальной функции оптимизации 2

Унимодальная функция 3

Диапазон ограничен -10,0 и 10,0 и одним глобальным оптимумом при [0,0, 0,0], функция известна как функция Изома.

# unimodal test functionfrom numpy import cosfrom numpy import expfrom numpy import pifrom numpy import arangefrom numpy import meshgridfrom matplotlib import pyplotfrom mpl_toolkits.mplot3d import Axes3D# objective functiondef objective(x, y):return -cos(x) * cos(y) * exp(-((x - pi)**2 + (y - pi)**2))# define range for inputr_min, r_max = -10, 10# sample input range uniformly at 0.01 incrementsxaxis = arange(r_min, r_max, 0.01)yaxis = arange(r_min, r_max, 0.01)# create a mesh from the axisx, y = meshgrid(xaxis, yaxis)# compute targetsresults = objective(x, y)# create a surface plot with the jet color schemefigure = pyplot.figure()axis = figure.gca(projection='3d')axis.plot_surface(x, y, results, cmap='jet')# show the plotpyplot.show()

Код создаёт поверхность согласно графику функции.

График унимодальной функции оптимизации 3График унимодальной функции оптимизации 3

Мультимодальные функции

Мультимодальная функция это функция с более чем одной модой или оптимумом (например долиной на графике).

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

Вот несколько примеров мультимодальных функций.

Мультимодальная функция 1

Диапазон ограничен -5,0 и 5,0 и одним глобальным оптимумом при [0,0, 0,0]. Эта функция известна как функция Экли.

# multimodal test functionfrom numpy import arangefrom numpy import expfrom numpy import sqrtfrom numpy import cosfrom numpy import efrom numpy import pifrom numpy import meshgridfrom matplotlib import pyplotfrom mpl_toolkits.mplot3d import Axes3D# objective functiondef objective(x, y):return -20.0 * exp(-0.2 * sqrt(0.5 * (x**2 + y**2))) - exp(0.5 * (cos(2 * pi * x) + cos(2 * pi * y))) + e + 20# define range for inputr_min, r_max = -5.0, 5.0# sample input range uniformly at 0.1 incrementsxaxis = arange(r_min, r_max, 0.1)yaxis = arange(r_min, r_max, 0.1)# create a mesh from the axisx, y = meshgrid(xaxis, yaxis)# compute targetsresults = objective(x, y)# create a surface plot with the jet color schemefigure = pyplot.figure()axis = figure.gca(projection='3d')axis.plot_surface(x, y, results, cmap='jet')# show the plotpyplot.show()

Код создаёт поверхность согласно графику функции.

Мультимодальная функция оптимизации 1Мультимодальная функция оптимизации 1

Мультимодальная функция 2

Диапазон ограничен [-5,0 и 5,0], а функция имеет четыре глобальных оптимума при [3,0, 2,0], [-2,805118, 3,131312], [-3,779310, -3,283186], [3,584428, -1,848126]. Эта функция известна как функция Химмельблау.

# multimodal test functionfrom numpy import arangefrom numpy import meshgridfrom matplotlib import pyplotfrom mpl_toolkits.mplot3d import Axes3D# objective functiondef objective(x, y):return (x**2 + y - 11)**2 + (x + y**2 -7)**2# define range for inputr_min, r_max = -5.0, 5.0# sample input range uniformly at 0.1 incrementsxaxis = arange(r_min, r_max, 0.1)yaxis = arange(r_min, r_max, 0.1)# create a mesh from the axisx, y = meshgrid(xaxis, yaxis)# compute targetsresults = objective(x, y)# create a surface plot with the jet color schemefigure = pyplot.figure()axis = figure.gca(projection='3d')axis.plot_surface(x, y, results, cmap='jet')# show the plotpyplot.show()

Код создаёт поверхность согласно графику функции.

Мультимодальная функция оптимизации 2Мультимодальная функция оптимизации 2

Мультимодальная функция 3

Диапазон ограничен промежутком [-10,0 и 10,0] и функцией с четырьмя глобальными оптимумами в точках [8,05502, 9,66459], [-8,05502, 9,66459], [8,05502, -9,66459], [-8,05502, -9,66459]. Эта функция известна как табличная функция Хольдера.

# multimodal test functionfrom numpy import arangefrom numpy import expfrom numpy import sqrtfrom numpy import cosfrom numpy import sinfrom numpy import efrom numpy import pifrom numpy import absolutefrom numpy import meshgridfrom matplotlib import pyplotfrom mpl_toolkits.mplot3d import Axes3D# objective functiondef objective(x, y):return -absolute(sin(x) * cos(y) * exp(absolute(1 - (sqrt(x**2 + y**2)/pi))))# define range for inputr_min, r_max = -10.0, 10.0# sample input range uniformly at 0.1 incrementsxaxis = arange(r_min, r_max, 0.1)yaxis = arange(r_min, r_max, 0.1)# create a mesh from the axisx, y = meshgrid(xaxis, yaxis)# compute targetsresults = objective(x, y)# create a surface plot with the jet color schemefigure = pyplot.figure()axis = figure.gca(projection='3d')axis.plot_surface(x, y, results, cmap='jet')# show the plotpyplot.show()

Код создаёт поверхность согласно графику функции.

График мультимодальной функции оптимизации 3График мультимодальной функции оптимизации 3

Резюме

Если вы хотите глубже погрузиться в тему обратите внимание на сопутствующие материалы ниже.

Узнайте, как прокачаться в других специальностях или освоить их с нуля:

Другие профессии и курсы
Подробнее..

Яндекс.Функции, Sublime Text и навыки для Алисы

05.07.2020 20:11:11 | Автор: admin
27 июня Яндекс проводил онлайн-хакатон по разработке навыков для Алисы.

Решил и я принять в нем участие. Ранее навыки для Алисы я уже делал, но хостил их все на Google App Engine. Тут же я решил изучить что-то новое в рамках Хакатон. Яндекс активно продвигает свои Функции в Яндекс.Облаке для разработки навыков. Для навыков они бесплатны (правда, бесплатно не всё).



Да и Google App Engine теперь требует подключить аккаунт для оплаты, чтобы приложение на сервер залить.

Решил я попробовать навык в Яндекс.Облаке разместить. К тому же, решил я, навык должен быть простым чтобы за день в рамках Хакатона успеть сделать. Тут Функции в Облаке очень подходят к сторонним сервисам обращаться не надо (они в функциях платные), данные можно хранить в самом навыке, внешняя БД не нужна.

Раньше я старался делать полезные навыки чтобы оплатить парковку голосом, например (в Яндекс.Навигаторе) или узнать, когда автобус/троллейбус/трамвай придет на ближайшую остановку. Это требовало интеграций со сторонними сервисами, долгой разработки, а Яндексу, судя по премии Алисы, больше игровые-развлекательные навыки по душе. Потому в этот раз я решил делать игру.

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

С командой строкой жить чуть проще. Но файлы нужно каждый раз нужно в zip добавлять, а лишь потом в Облако грузить. Руками неудобно.

Так уж сложилось, что моя IDE это Sublime Text 3. Недавно Google отказался от Google App Engine Launcher и с ним остался единственный вариант загрузка файлов через командную строку. Тогда-то я и узнал о существовании build systems в Sublime Text нажимаешь Ctrl/Cmd+B и Sublime выполняет нужную тебе команду. Для GAE я тогда сделал набор команд, решил, что и тут что-то подобное нужно.

Сначала была сделана функциональность просто для загрузки файлов.
Для GAE я делал так, чтобы передаваемые параметры (а именно название проекта) читалось из файла проекта Sublime Text. Тут же для экономии времени название функции, точка входа и остальные параметры просто зашиты в build system. Не очень хорошо, но для моих целей подходило.


Но если все тестировать на боевом сервере, то и логи надо как-то удобно смотреть. Потому для загрузки и отображения логов было добавлена отдельная команда.

Увы, если логи просто отобразить, то ориентироваться в них довольно сложно.
Пришлось немного подшаманить с командой (чтобы unicode-строки отображались корректно но и то, работает это не всегда), с самим кодом (чтобы JSON выводить в читаемом виде):
    logging.getLogger().setLevel(logging.DEBUG)    logging.debug('REQUEST: ')    for line in json.dumps(event['request'], indent=4).split('\n'):        logging.debug(line)

и создать отдельный файл синтаксиса, чтобы подсвечивать ошибки в логе.


Отдельная удобная фича Sublime Text умеет подсвечивать и саму строку, если нашла ее в коде.

Итого получилось следующее
Файл Yandex Cloud.sublime-build
// Install Yandex CLI - https://cloud.yandex.ru/docs/cli/quickstart#install//// http://www.sublimetext.com/docs/3/build_systems.html// https://cloud.yandex.ru/docs/functions/operations/function/version-manage#version-create{    "file_patterns": ["*.py"],    "syntax": "Packages/User/YCLog.sublime-syntax",    "file_regex": "File \\\"/function/code/(...*?)\\\", line ([0-9]*)",     "variants":        [            {                "name": "Upload",                "shell_cmd": "zip -u -0 yc_upload.zip *.py && yc serverless function version create --function-name=my-function-name --runtime=python27 --entrypoint=main.handler --memory=128m --execution-timeout=2s --source-path=yc_upload.zip",            },            {                "name": "Logs",                "shell_cmd": "printf '%b\n' \"\\$(yc serverless function logs alice-guess-the-language)\""            }        ]}


Файл YCLog.sublime-syntax
%YAML 1.2
---
# See http://www.sublimetext.com/docs/3/syntax.html
name: YC Log
file_extensions: [log]
scope: source.example-c
contexts:
main:
# Request identifiers
- match: '\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2} (START|END|REPORT) RequestID: .*'
scope: storage.type.string.c

# Dates
- match: '\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}'
scope: comment.line.c

- match: '\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{2,3}Z [0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}'
scope: comment.line.c

# Log level
- match: '\[(INFO|DEBUG)\]'
scope: comment.line.example-c

# Log level
- match: '\[(ERROR|WARNING)\]'
scope: keyword.control.flow.break.c

# Strings begin and end with quotes, and use backslashes as an escape
# character
- match: '"'
scope: punctuation.definition.string.begin.c
push: double_quoted_string

double_quoted_string:
- meta_scope: string.quoted.double.example-c
- match: '\\.'
scope: constant.character.escape.example-c
- match: '"'
scope: punctuation.definition.string.end.example-c
pop: true



Редактировать код Функций в Яндекс.Облаке стало гораздо приятнее.

P.S. Мой навык игра Угадай язык.
Подробнее..

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru