В последний день мне пришлось выполнить задачу в go.
Как вы, возможно, уже знаете из этого поста, я недавно начал программировать на Go, поэтому довольно часто первый вопрос, который возникает у меня в голове: как бы я это сделал на Java?
Я не уверен, хорошо это или нет, но я нахожу, что мне как-то полезно смотреть на одно и то же с разных точек зрения. Речь идет не об использовании Go или Java, использовании функционального или императивного стиля, а о смешении различных концепций, в которых я вижу реальную ценность. Как и в другом посте, в этом я расскажу о том, как взгляд на проблему под другим углом сформировал решение.
Проблема
Я просто немного усложню задачу, чтобы выделить самое интересное в этом посте. Часть задачи включала загрузку в память раздела файла: супер простой синтаксический анализатор.
Файл выглядит следующим образом:
// a comment here #HEADER Key1=value1 //another comment Key2=value2 Key3=value3 Key4=value4
Нас интересуют два ключа, которые принадлежат разделу. Раздел начинается с заголовка, который начинается с хэштега. Нам дается название раздела и название двух ключей. т.е. раздел: “лицо”, ключи: [“имя”, “фамилия”] Поэтому мы хотим найти раздел “личность” в файле и загрузить значения имени и фамилии в память.
Пара ошибок по поводу файла:
- Заголовок может отсутствовать в файле
- имя и/или фамилия могут отсутствовать в файле
- имя всегда предшествует фамилии в файле
- Он может содержать комментарии (//) или другие маркеры
Одно из возможных решений – просто прочитать файл построчно и:
- если строка начинается с #, то это начало раздела, приготовьтесь прочитать пару ключей для этого заголовка
- Прочитайте имя и фамилию
- Игнорировать комментарии и остальные ключи
Итак, давайте на секунду наденем шляпу Java-программиста.
Итак, чтение файла, строка за строкой, фильтрация некоторых строк…
Хорошо, это похоже на работу для java.nio.файла. Файлы
, a java.util.поток. Поток
и пользовательская java.util.функция. Предикат
для фильтрации потока. Я говорю обычай, имея в виду, что нам нужно сохранить некоторое состояние в нашем предикате. Состояние – это просто строка, которой должен соответствовать предикат при следующем вызове, т.Е. Если он уже соответствует заголовку, то затем должен соответствовать имени.
В основном это означает две вещи: потоковые и лямбда-функции.
Давайте короче, в Голанге нет такой вещи, как ручей. Позвольте мне здесь немного отвлечься, весь смысл этого умственного упражнения не в том, чтобы реализовать что-то, что противоречит самому языку, а в том, чтобы извлечь из этого максимум пользы. Так что никакого ручья. Мы собираемся жить без этого, ибо петли приходят на помощь.
Но как насчет лямбд? Что ж, функции в Go – это граждане первого класса. Мы можем назначать их переменным, создавать массив функций, передавать их, как и любой другой аргумент, так что, я думаю, у нас есть наши лямбды:)
Ладно, хватит разговоров, давайте посмотрим код… Подождите, прежде чем смотреть на код, давайте резюмируем, что мы хотим сделать. Таким образом, мы ожидаем, что у нас будет цикл for, в котором мы читаем содержимое из источника построчно и для каждой строки проверяем, должно ли оно быть где-то написано. Бит, который проверяет, должна ли строка быть записана, является фильтром, который внутренне сохраняет состояние.
Хорошо, давайте посмотрим на этот кусочек:
func copy(profileName string, in io.Reader, out io.Writer) error { var ( line string readError error writeError error ) profileFilter := newProfileFilter(profileName) reader := bufio.NewReader(in) for { line, readError = reader.ReadString('\n') if profileFilter.match(line) { _, writeError = io.WriteString(out, line) } if readError != nil || writeError != nil { break } } if writeError != nil { return writeError } if readError != io.EOF { return readError } return nil }
Это в значительной степени “эквивалентно” Java
Files.lines(resource()) .filter(profileFilter) .forEach(ProfileWriter::write);
Давайте немного разберем это:
- Файлы.строки – это цикл
для
- Фильтр – это условие if
фильтр профиля.совпадение(строка)
- forEach – это просто тело условия if
ввода-вывода. Строка записи(выход, строка)
Итак, давайте посмотрим на фильтр:
func newProfileFilter(profileName string) *profileFilter { var matchers [](func(line string) bool) matchers = append(matchers, matcher(startsWith).apply("#"+profileName), matcher(startsWith).apply(), matcher(startsWith).apply(), ) return &profileFilter{matchers, profileName} } func startsWith(line string, toMatch string) bool { return strings.HasPrefix( strings.ToLower(strings.TrimSpace(line)), strings.ToLower(toMatch), ) }
Это конструктор фильтра профиля, отвечающий за определение заголовка раздела и ключей. По сути, это набор функций или лямбд, если хотите. Каждый раз, когда фильтр совпадает со строкой в файле, мы извлекаем элемент из массива, поэтому на следующей итерации фильтр будет соответствовать другой строке. Что мне действительно нравится в этом коде, так это то, как мы строим лямбды:
matcher(startsWith).apply()
Итак, мы создаем сопоставитель, который соответствует строке, начинающейся с “имя =”, Что на самом деле соответствует?
type matcher func(line string, toMatch string) bool
Это псевдоним типа для функции. Так что это просто функция, которая получает в качестве входных данных строку и строку для сопоставления и возвращает логическое значение. Это оно. Синтаксис сопоставитель(начинается с)
когда я это писал, я был такой…что ж, функции в go чертовски крутые
Что это за метод применения?
func (f matcher) apply(toMatch string) func(line string) bool { return func(line string) bool { return f(line, toMatch) } }
Это просто способ выполнить свою функцию. Короче говоря, у нас есть функция с двумя аргументами, и нам действительно нужна функция с одним аргументом, в которой другой уже установлен.
Итак, здесь мы применяем строку, которую мы хотим сопоставить, например “имя =”, и возвращаем функцию, которая имеет в качестве параметра только строку. Мы прикрепили метод apply к совпадениям, поэтому конечным результатом является то, что при вызове:
matcher(startsWith).apply()
Мы возвращаем функцию, которая возвращает логическое значение (предикат на языке java), которое будет истинным, если строка начинается с name.
Это остальная часть кода здесь просто для полноты картины.
type profileFilter struct { matchers []func(line string) bool profileName string } func (p *profileFilter) match(text string) bool { if len(p.matchers) == 0 { return false } shouldFilter := p.matchers[0](text) if shouldFilter { p.matchers = p.matchers[1:len(p.matchers)] } return shouldFilter }
Версия Java
Во время написания статьи я решил, что для развлечения я мог бы написать ту же самую программу на Java. Вот быстрая реализация:
public static void main(String[] args) throws IOException, URISyntaxException{ Matcher startsWith = (line, toMatch) -> startsWith(line, toMatch); ProfileFilter profileFilter = new ProfileFilter( startsWith.apply("#some-profile"), startsWith.apply(), startsWith.apply() ); Files.lines(resource()) .filter(profileFilter) .forEach(System.out::println); } static class ProfileFilter implements Predicate{ private LinkedList > predicates; ProfileFilter(Predicate...predicates) { this.predicates = new LinkedList<>(Arrays.asList(predicates)); } @Override public boolean test(String s) { if (predicates.size() == 0) { return false; } boolean shouldFilter = predicates.getFirst().test(s); if (shouldFilter) { predicates.removeFirst(); } return shouldFilter; } } interface Matcher extends BiPredicate { default Predicate apply(String applied) { return s -> this.test(s, applied); } } static Boolean startsWith(String line, String toMatch) { return line.toLowerCase().trim().startsWith(toMatch.toLowerCase()); }
Очевидно, что две версии действительно похожи, за исключением того факта, что в версии java я использую поток вместо цикла for и печатаю строки на консоли вместо того, чтобы записывать их в Writer.
Примечание здесь: Потоки действительно классные, но они не ладят с исключениями. Запись в файл, например, является одной из тех операций, которые вызывают исключение, поэтому в реальном случае использования я думаю, что я бы использовал цикл для вместо потока или RxJava Наблюдаемый .
Выводы
Ладно, это оказалось длиннее, чем я ожидал, учитывая, что я хотел просто сказать, что, вероятно, Голанг не так уж плох XD. Шутки в сторону, надеюсь, вам понравилось.
Спасибо!
Ресурсы
Код для статьи: github Я нашел два интересных выступления о функциях в Golang. Полностью стоит того, чтобы посмотреть на них: Замыкания являются обобщениями go Не выполняйте функцию первого класса
Оригинал: "https://dev.to/napicella/go-for-java-developers-lambda-functions-to-the-rescue-655"