оригинал:и nature.GitHub.IO/gowave/invoice/a…
Плевать первым
В общем, функциональное программирование Голанга примет дурную форму. На первый взгляд, зло заключается в том, что в Golang отсутствует необходимый синтаксический сахар; по сути, зло связано с отсутствием возможностей высокоуровневой абстракции, как и отсутствием дженериков.
зло
Где некрасивое? Вот пример:
func main() {
var list = []string{"Orange", "Apple", "Banana", "Grape"}
// we are passing the array and a function as arguments to mapForEach method.
var out = mapForEach(list, func(it string) int {
return len(it)
})
fmt.Println(out) // [6, 5, 6, 5]
}
// The higher-order-function takes an array and a function as arguments
func mapForEach(arr []string, fn func(it string) int) []int {
var newArray = []int{}
for _, it := range arr {
// We are executing the method passed
newArray = append(newArray, fn(it))
}
return newArray
}
Отлично, упаковка выглядит хорошо, не так ли? Форма fp также выглядит более удобной. Я подумал... ну, я хотел завернуть его, сделать общим и сделать доступным для других. Так плохо, что поддержка int64 требует этого:
func mapInt64ForEach(arr []int64, fn func(it int64) int) []int {
var newArray = []int{}
for _, it := range arr {
// We are executing the method passed
newArray = append(newArray, fn(it))
}
return newArray
}
Это только начало, вы начинаете писать n версий для bool, uint64, .
Контраст: реализация шаблона C++
Так что я бы сказал, что функции более высокого порядка в golang, функциональные, на самом деле очень злые.
Видит бог, сейчас я использую golang для проектирования архитектуры в большинстве случаев, но у меня всегда в душе неописуемое разочарование. Если в С++ 11:
class Print {
public:
void operator()(int elem) const {
std::cout << elem << " ";
}
};
func a(){
std::vector<int> vect;
for (int i=1; i<10; ++i) {
vect.push_back(i);
}
Print print_it;
std::for_each (vect.begin(), vect.end(), print_it);
std::cout << std::endl;
}
Чтобы сэкономить байты, я заимствую for_each из stdlib вместо того, чтобы реализовывать его самостоятельно, но реализация foreach на самом деле очень проста.
Дело в том, что теперь, когда я хочу манипулировать строкой, мне нужно только переписать один Print, мне не нужно делать n копий реализации for_each. При необходимости я могу реализовать общий класс шаблона Print, поэтому мне не нужно заново реализовывать копию и просто использовать ее.
Закрыть
Не начал изучать красоту функционального программирования Golang, но сначала отрекся от него, это действительно последнее средство!
Хорошо, теперь о хорошем использовании функционала.
Хотя повторно использовать универсальные шаблоны в функциональном режиме непросто, это лучший способ улучшить структуру, внешний вид, содержимое и качество программы в конкретных типах или косвенных универсальных моделях, абстрагированных через интерфейс.
Таким образом, вы увидите, что в зрелых библиотеках классов, будь то стандартные или сторонние, функциональный шаблон широко применяется.
Итак, эти приложения обобщены и представлены ниже с целью демонстрации ряда лучших практик и, надеюсь, помогут улучшить ваши конкретные навыки кодирования.
Что такое функциональное программирование
Сначала нам нужно изучить, что такое функциональное программирование более высокого порядка? Так называемое функциональное программирование, обычно переводимое как функциональное программирование (с исчислением λ).3в качестве основы).
Функциональное программирование, что означает игнорирование (обычно запрещение) изменяемых данных (чтобы избежать побочных эффектов, вызванных изменяемыми данными в другом месте), игнорирование состояния выполнения программы (отсутствие неявного, скрытого, невидимого состояния), парадигма программирования, в которой функция используется в качестве входных данных. параметр и функция используются в качестве возвращаемого значения для вычисления, и посредством непрерывного продвижения (итерации, рекурсии) этого вычисления выход получается из входа. В парадигме функционального программирования отсутствуют общие для процедурного программирования понятия: операторы, процедурные элементы управления (условия, циклы и т.п.). Кроме того, в парадигме функционального программирования он имеет особенность ссылочной прозрачности, Смысл этой концепции в том, что работа функции связана только с входными параметрами.Если входные параметры одинаковы, выходные параметры всегда должны быть одинаковым. Сама функция (рассматриваемая какf(x)) выполненное преобразование является детерминированным.
Кстати, карри4Это очень важная теория и технология функционального программирования. Отказ от процедурного программирования, такого как if, then, while и т. д., полностью функциональной итерации, как правило, является фаворитом чисто функциональных сторонников, в то время как такие вещи, как
Start(...).Then(...).Then(...).Else(...).Finally(...).Stop()
Такие стили часто рассматриваются как языческие.Это действительно интересно. Фундаментализм (а именно: не относящийся к первоначальному религиозному значению этого термина, используемый здесь только в расширенном значении для обозначения Чистой Партии) установлен и существует повсюду.
представление
Подводя итог, функциональное программирование имеет следующие характеристики:
- No Data mutationsНет волатильности данных
- No implicit stateнет неявного состояния
- No side effectsНет побочных эффектов (без побочных эффектов)
- Pure functions onlyТолько чистые функции, никаких процедурных элементов управления или операторов
- Функция первого класса Тождество функции первого класса
- Функции гражданина первого класса имеют гражданство первого класса
- Higher-order functionsФункции высшего порядка, которые могут появляться где угодно
- ClosuresЗамыкания — экземпляры функций с превосходными возможностями захвата среды
- CurryingКаррирование исчисления4- Сократить несколько входных параметров до одного и т. д.
- RecursionРекурсивные операции - вложенные итерации функций для оценки, нет концепции управления процессом
- Lazy evaluations / Evaluation strategyленивая оценка - задерживает оценку захваченной переменной до тех пор, пока она не будет использована
- Referential transparencyСсылочная прозрачность — значение выражения должно быть одинаковым для одного и того же ввода, а его оценка не должна иметь побочных эффектов.
Поскольку основное внимание уделяется не продвинутому программированию FP и связанному с ним обучению, невозможно подробно обсудить преобразование каррирования чистокровного FP, с которым традиционные программисты на C с трудом справляются.
Функциональное программирование на Golang: функции высшего порядка
В Golang концепция функционального программирования была переупакована и уточнена, например, все является функцией, функции являются значениями и т. д. Поэтому в этой статье можно не упоминать функциональное программирование, и часто заменяют ссылкой на функциональное программирование более высокого порядка.
Следует подчеркнуть, что функциональное программирование — это не просто функциональное программирование высокого порядка, а функциональное программирование высокого порядка не может включать в себя функциональное программирование, это два разных понятия, но они перекрывают друг друга с точки зрения их выражений. Для Golang нет ни истинного чистого функционального программирования. Конечно, в Golang нет чистого объектно-ориентированного программирования. Golang использует разные и несколько экстремальные методы для обоих из них. Слияние передовой теории времени. Конечно, в большинстве сценариев мы по-прежнему согласны с тем, что Golang использует собственную философию для поддержки такого мультипарадигмального программирования.
В Golang функции высшего порядка часто являются ключевым связующим звеном для реализации алгоритма.
Например,
- Базовая структура замыкания
- рекурсия
- Функтор/оператор
- ленивое вычисление
- Переменные параметры: функциональные опции
Базовая структура замыкания
В языке программирования, где функции и функции более высокого порядка являются гражданами первого порядка, вы, конечно, можете присвоить функцию переменной, скопировать ее в член, передать ее как параметр (или один) другой функции и использовать ее. как другая функция.Возвращаемое значение (или одно из них).
Golang имеет вышеуказанную поддержку.
Однако в Golang нет синтаксического сахара для расширения или сокращения анонимных функций, На самом деле в Golang нет большей части синтаксического сахара, что определяется его философией дизайна. Таким образом, вы должны написать немного многословный код, не имея возможности поддерживать чистоту синтаксиса. На этом этапе способ, которым С++ использует оператор (), может быть сокращен, а синтаксис захвата [] может использоваться для сокращения функции замыкания.После Java 8 упрощенный синтаксис анонимных замыканий значительно продвинулся вперед, но это не так. хорошо, как Kotlin Kotlin делает шаг вперед, позволяя последнему закрытию вызова функции быть расширенным после синтаксиса вызова и существовать как блок:
fun invoker(p1 string, fn fun(it int)) {
// ...
}
invoker("ok") { /* it int */ ->
// ...
}
Но в Golang вам нужно полностью написать прототип функции высшего порядка, даже если вы определяете ее как тип:
type Handler func (a int)
func xc(pa int, handler Handler) {
handler(pa)
}
func Test1(){
xc(1, func(a int){ // <- 老老实实地再写一遍原型吧
print (a)
})
}
Стоит отметить, что как только прототип Handler изменится, как авторы библиотек, так и пользователи библиотек будут мучительно искать и модифицировать везде.
Да, здесь вы узнаете важный принцип программирования: дизайн интерфейса должен учитывать надежность. Пока интерфейс стабильный, конечно, нет никакой возможности, чтобы прототип обработчика нуждался в корректировке, верно? хе-хе.
Плевать - не мое хобби, поэтому я остановлюсь на этом.
Оператор Функтор
Оператор обычно представляет собой простую функцию (но не обязательно), а общая управляющая часть реализует реальный алгоритм замены бизнес-логики заменой разных операторов:
func add(a, b int) int { return a+b }
func sub(a, b int) int { return a-b }
var operators map[string]func(a, b int) int
func init(){
operators = map[string]func(a, b int) int {
"+": add,
"-": sub,
}
}
func calculator(a, b int, op string) int {
if fn, ok := operators[op]; op && fn!=nil{
return fn(a, b)
}
return 0
}
Рекурсия
Фибоначчи, факториал, Ханойская башня, фракталы и т. д. — типичные рекурсивные задачи.
В языках программирования, поддерживающих рекурсию, как использовать рекурсию, часто сложно понять. С точки зрения личного опыта, думать день и ночь и внезапно становиться просветленным — это неизбежный процесс полного овладения рекурсией.
В функциональном программировании рекурсия — это концепция, которая используется повсюду. Это воплощено в Golang как возвращаемые значения функции более высокого порядка.
Следующий пример просто реализует пошаговые операции:
package main
import "fmt"
func factorial(num int) int {
result := 1
for ; num > 0; num-- {
result *= num
}
return result
}
func main() {
fmt.Println(factorial(10)) // 3628800
}
Но мы должны перереализовать его в стиле функционального программирования:
package main
import "fmt"
func factorialTailRecursive(num int) int {
return factorial(1, num)
}
func factorial(accumulator, val int) int {
if val == 1 {
return accumulator
}
return factorial(accumulator*val, val-1)
}
func main() {
fmt.Println(factorialTailRecursive(10)) // 3628800
}
Большинство современных языков программирования хорошо справляются с хвостовой рекурсией, неявно оптимизированной во время компиляции, что является важным моментом оптимизации в принципе компиляции: хвостовая рекурсия всегда может выродиться в циклические конструкции без вложенных вызовов функций.
Поэтому мы внесли некоторые изменения выше, чтобы реализовать операцию факториала функциональным способом, который не только делает ее читабельной, но и позволяет избежать проблемы использования стека вызовов вложенных функций.
Рекурсия с использованием функций высшего порядка
Заимствуя реализацию Фибоначчи, мы просто реализуем рекурсию, возвращая функцию:
package main
import "fmt"
func fibonacci() func() int {
a, b := 0, 1
return func() int {
a, b = b, a+b
return a
}
}
func main() {
f := fibonacci()
for i := 0; i < 10; i++ {
fmt.Println(f())
}
}
// 依次输出:1 1 2 3 5 8 13 21 34 55
Отложенный расчет Отложенный расчет
Важным применением анонимных функций высшего порядка является захват переменных и отложенное вычисление, также известное как ленивое вычисление (Lazy evaluations).
В приведенном ниже примере
func doSth(){
var err error
defer func(){
if err != nil {
println(err.Error())
}
}()
// ...
err = io.EOF
return
}
doSth() // printed: EOF
В функции более высокого порядка defer фиксируется переменная err во внешней области видимости, и установка err во всем рабочем цикле doSth наконец может быть правильно вычислена в теле функции defer. Если нет механизма перехвата и вычисления задержки, доступ к err в теле функции высшего порядка получит только значение nil, потому что это конкретное значение err во время перехвата. Обратите внимание, что для уменьшения размера примера кода мы использовали defer, чтобы продемонстрировать, что на самом деле того же эффекта можно добиться с помощью подпрограмм go, другими словами, доступ к внешним областям видимости в функциях более высокого порядка динамически вычисляется лениво.
Исключение: переменная цикла
Конечно, здесь есть известная ямка: переменные цикла не вычисляются лениво (потому что всегда происходит оптимизация цикла, поэтому переменные цикла — это псевдопеременные, не существующие с определенной точки зрения).
func a(){
for i:=0; i<10; i++ {
go func(){
println(i)
}()
}
}
func main(){ a() }
// 1. 结果会是 全部的 0
// 2. 在新版本的 Golang 中,将无法通过编译,报错为:
// loop variable i captured by func literal
Чтобы получить интуитивно понятный результат, вам нужно передать переменную цикла:
func a(){
for i:=0; i<10; i++ {
go func(ix int){
println(ix)
}(i)
}
}
Я буду честен, я ступил на эту яму, прежде чем нашел ее с помощью пошаговой отладки. В большой системе обнаружение такой ошибки может привести к истощению. И это значит, что ваш уровень программирования не на высоте? Не волнуйтесь, это не так, я не понизил стандарт, потому что сам его грыз, Golang действительно отвратительный.
Functional Options
Как автор библиотеки классов, вы рано или поздно столкнетесь с проблемой изменения интерфейса. Либо из-за изменений во внешней среде, либо из-за того, что функция была модернизирована для расширения расширения, либо из-за того, что нужно отказаться от несовершенного дизайна прошлого, либо из-за улучшения личного уровня, независимо от того, какие причины, Вы можете обнаружить, что должны.Чтобы изменить исходный интерфейс, замените его более совершенным новым интерфейсом.
по старому
Представьте себе раннюю библиотеку классов:
package tut
func New(a int) *Holder {
return &Holder{
a: a,
}
}
type Holder struct {
a int
}
Позже мы обнаружили, что нам нужно добавить логическое значение b, поэтому мы изменили библиотеку tut следующим образом:
package tut
func New(a int, b bool) *Holder {
return &Holder{
a: a,
b: b,
}
}
type Holder struct {
a int
b bool
}
Через несколько дней, и теперь мы подумали, что необходимо добавить строковую переменную, библиотеку tut пришлось изменить на:
package tut
func New(a int, b bool, c string) *Holder {
return &Holder{
a: a,
b: b,
c: c,
}
}
type Holder struct {
a int
b bool
c string
}
Представьте, сколько MMP будет выброшено, когда пользователь библиотеки tut трижды столкнется с обновлением интерфейса New().
Для этого нам нужен режим функциональных опций, чтобы спасти его.
новый путь
Предположим, мы реализовали первую версию tut так:
package tut
type Opt func (holder *Holder)
func New(opts ...Opt) *Holder {
h := &Holder{ a: -1, }
for _, opt := range opts {
opt(h)
}
return h
}
func WithA(a int) Opt {
return func (holder *Holder) {
holder.a = a
}
}
type Holder struct {
a int
}
//...
// You can:
func vv(){
holder := tut.New(tut.WithA(1))
// ...
}
Точно так же, после изменения требования, мы добавляем b и c к существующей версии, поэтому текущий tut выглядит следующим образом:
package tut
type Opt func (holder *Holder)
func New(opts ...Opt) *Holder {
h := &Holder{ a: -1, }
for _, opt := range opts {
opt(h)
}
return h
}
func WithA(a int) Opt {
return func (holder *Holder) {
holder.a = a
}
}
func WithB(b bool) Opt {
return func (holder *Holder) {
holder.b = b
}
}
func WithC(c string) Opt {
return func (holder *Holder) {
holder.c = c
}
}
type Holder struct {
a int
b bool
c string
}
//...
// You can:
func vv(){
holder := tut.New(tut.WithA(1), tut.WithB(true), tut.WithC("hello"))
// ...
}
Поскольку код не сложный, мне не нужно объяснять пример кода построчно. Вы получите интуитивное ощущение, что исходный устаревший код на стороне пользователя (например,vv()
) на самом деле может быть полностью неизменным, прозрачно касаясь действия по обновлению самой библиотеки tut.
Характеристики и функции этой парадигмы кодирования, которые следует здесь упомянуть, включают:
А. При создании экземпляра Holder мы теперь можем замаскировать любое количество вариативных параметров различных типов данных.
б) С помощью существующей модели парадигмы мы также можем реализовать произвольные сложные операции инициализации для выполнения различных операций построения для Холдера.
в) Поскольку это парадигма, необходимо изучить ее удобочитаемость и масштабируемость — очевидно, текущая парадигма может получить высокий балл.
г. Интерфейс New(...) достаточно надежен при обновлении основной версии, независимо от того, как вы настраиваете внутренний алгоритм и его реализацию, ничего не нужно менять для вызывающей стороны такой сторонней библиотеки.
резюме
Эта статья относится к dcode7Некоторые из упомянутых знаний, кроме того,7 Easy functional programming techniques in Go 8Также вводится много знаний FP.
Эта статья не претендует на распространение ФП, так как в понимании автора имеет смысл обсуждать ФП в такой языковой среде, как Lisp и Haskell.Хотя в Голанге много склонностей к ФП, он, конечно, процедурный.PL, скажем так. что есть сильная поддержка FP.
Однако эти подробные представления являются лишь академическими различиями. Поэтому в этой статье приведены только некоторые релевантные идиомы с точки зрения конкретной реализации.
Возможно, в будущем этот аспект будет снова обобщен, и, возможно, возникнет более глубокое понимание.
References
🔚