Немного о функторах и функциях высшего порядка в Swift

    Коллекции


    Разработчики, перешедшие на Swift с Objective-C, не могли не заметить удобнейший функционал, который предоставляет Swift для работы с коллекциями. Использование диапазонов в индексах
    let slice = array[1..<10]
    удобный синтаксис инициализации и добавления элемента в коллекцию, расширяемость, и, конечно функции высшего порядка

    Filter


    Самой часто используемой функцией для коллекций, пожалуй, является filter
    let alex = Person(name: "Alex", age: 23)
    let jenny = Person(name: "Jenny", age: 20)
    let jason = Person(name: "Jason", age: 35)
    let persons = [alex, jenny, jason]
    let jNamedPersons = persons.filter { $0.name.hasPrefix("J") } // [jenny, jason]


    Reduce


    Реже используемой, но при этом крайне выразительной и удобной является функция reduce
    
    let ages = persons.map{ Float($0.age) }
    let average = ages.reduce(0, +) / Float(persons.count)


    Можно писать свои функции высшего порядка и это довольно увлекательно:
    func divisible(by numbers: Int...) -> (Int) -> Bool {
        return { input -> Bool in
            return numbers.reduce(true) { divisible, number in
                divisible && input % number == 0
            }
        }
    }
    
    let items = [6, 12, 24, 13]
    let result = items.filter(divisible(by: 2, 3, 4)) // [12, 24]


    Map


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

    Итак, для простоты мы можем считать, что функтор это контейнер, к которому применима функция map, а монада это функтор, к которому применима функция flatMap.

    Поскольку коллекции это контейнеры, и в Swift для них определена функция map, они могут выступать в роли функторов:
    для того, чтобы трансформировать коллекцию одного типа в коллекцию другого типа, возьмем наш массив persons и получим из него массив возрастов типа [Int]
    let ages = array.map{ $0.age } // [23, 20, 35]


    FlatMap


    И в роли монад:
    для того, чтобы из массива oprtional типов вернуть массив не опциональных значений
    let optionalStrings: [String?] = ["a", nil, "b", "c", nil]
    let strings = optionalStrings.flatMap { $0 } // ["a", "b", "c"]

    для того, чтобы расширить первоначальную коллекцию
    let odds = [1,3,5,7,9]
    let evensAndOdds = odds.flatMap { [$0, $0 + 1] } // [1,2,3,4,5,6,7,8,9,10]



    Optionals


    Но map и flatMap можно применять не только к коллекциям. Крайне полезным оказывается использование Optional типов в качестве монад:

    Map


    Если у Optional есть значение, то возвращается результат работы map с этим значением, если же значения нет, то возвращается nil:
    let name: String? = "World"
    let greeting = name.map { "Hello " + $0 + "!" } // "Hello World!"

    но
    let name: String? = nil
    let greeting = name.map { "Hello " + $0 + "!" } // nil 


    FlatMap


    FlatMap работает почти так же, с той лишь разницей, что результат работы flatMap может возвращать nil, а map не может

    let string: String? = "42"
    let number = string.flatMap { Int($0) } // 42

    но со строкой 'сорок два' Int себя инициализировать не может
    Int не может, но Swift может многое
    let formatter = NumberFormatter()
    formatter.numberStyle = .spellOut
    formatter.locale = Locale(identifier: "RU")
    let a = formatter.number(from: "сорок два")
    // 42

    let string: String? = "сорок два"
    let number: Int? = string.flatMap { Int($0) } // nil

    если же мы попытаемся применить map
    let number: Int? = string.map { Int($0) }
    увидим ошибку.

    Заключение


    Использование монад и функторов при работе с Optional переменными может значительно уменьшить объем кода и сделать его более наглядным.
    • +10
    • 8,7k
    • 7
    Поделиться публикацией

    Комментарии 7

      –2

      Пишете про функторы и монады, а что такое "функционал" — не знаете.

        0

        Так а о чем статья-то? О методах работы с коллекциями? О монадах? map vs flatMap? Не нашел ни описания что такое функтор, что такое монада… Не хватило чего-то, в общем.

          0
          Начал писать статью, как туториал по использованию опциональных типов в качестве монад, поскольку обнаружил, что очень многие iOS разработчики не знают об удобствах такого использования. Но не хватило времени хорошо и стройно оформить, в итоге она пролежала в черновиках месяц и я решил, что лучше выпустить хоть то, что есть, чем бесконечно откладывать. Так что, отвечая на ваш вопрос, статья приводит примеры некоторых возможностей коллекций и опциональных типов о которых не все знают, но которые бывают полезны.
            0
            Удобно-то удобно, только вот в реалиях нынешней поддержки Swift пользоваться всем этим накладно.
            Особенно в Xcode, где и время компиляции не растет при использовании кложуров, и подсветка синтаксиса с автокомплитом ломаются.
              0
              Использую Xcode 8.2.1 и в нём не заметил тормозов при использовании замыканий. Предполагаю, что тормоза начинаются при повсеместном применении реактивного программирования, потому что Swift очень долго инферит типы
          +1
          Int не может, но Swift может многое
          let formatter = NumberFormatter()
          formatter.numberStyle = .spellOut
          formatter.locale = Locale(identifier: «RU»)
          let a = formatter.number(from: «сорок два»)
          // 42

          Серьёзно? При чём тут свифт? Это NSNumberFormatter может при использовании NSNumberFormatterSpellOutStyle.
            –2
            Вы говорите об Objective-C, который в свою очередь базируется на C, как глубоко мы будем копать? Кусок кода, который написан в моем примере, написан на Swift.

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

          Самое читаемое