Function introduction:

Octree /pretty. Generates a dependency map based on podfile. lock

Functional programming to build data pipelines

The following code converts the string in podfile. lock to a dictionary [library: [dependency library]],

To novice, have very strong dissuasion power

let (dependency, _) = PodLockFileParser.parse(Substring(string))
Copy the code

The PodLockFileParser

let PodLockFileParser = pods *> dependencyItems
Copy the code

The dependencyItems

private let item = (indentation *> hyphon *> space *> quote.optional *> word) <* (space.followed(by: version)).optional <* quote.optional <* colon.optional <* newLine private let subItem = indentation *> item private let dependencyItem = curry(dependencyCombine) <^> item <*> subItem.many.optional private let dependencyItems = dependencyItem.many.map { x -> [String : [String]] in var map = [String: [String]]() x.freach {map[$0.0] = $0.1} return map}Copy the code

Understanding the above functional programming code seems a bit more difficult than implementing this feature

Rough sets

Functional programming, extensive use of anonymous function units, and operators to implement data pipelines.

Each anonymous function unit is a trusted link in the data stream

Operator to simplify method writing. Operator expansion is a chain function

The actual line of data

Moya/Core (13.0.1) :Copy the code

That corresponds to this anonymous function

// (two Spaces *> hyphen *> space *> may have a quote *> word) <* May have a quote <* May have a colon <* newline let item = (indentation *> hyphon *> space *> quote.optional *> word) <* (space.followed(by: version)).optional <* quote.optional <* colon.optional <* newLineCopy the code

Feel, functional programming, has a lot of twisting power. Enforce a high degree of consistency between data flow and data format

  • * >, means, remove the data on the left hand side

Because what we care about is the name of the library, like Moya/Core, the actual word

  • < *, represents, remove the data on the right hand side
To see a

An actual row of submodule data

- the Result (4.1) ~ >Copy the code

That corresponds to this anonymous function

Let subItem = indentation *> item because the submodule, that is, is preceded by two more SpacesCopy the code

Three basic text processing unit construction methods:

// Parses the specified character, fails, returns nil // parses the specified character, returns nil // Parses the specified character, returns a tuple (the remaining string) // Parses the specified character, returns a tuple (the remaining string) // Parses the specified character, returns a tuple (the remaining string) // Parses the specified character, returns a tuple (the remaining string). Parser(matching condition: @escaping (character) -> Bool) -> Parser< character > {return Parser(parse: { input in guard let char = input.first, condition(char) else { return nil } return (char, Input.dropfirst ())})} // parse the specified character // this simply calls the above // this method, with the above, can merge the func character(_ ch: Character) -> Parser<Character> {return Character {$0 == ch}} Func string(_ string: func string(_ string: func string)) String) -> Parser<String> { return Parser { input in guard input.hasPrefix(string) else { return nil } return (string, input.dropFirst(string.count)) } }Copy the code

As you can see above, a line of word processing

private let item = (indentation *> hyphon *> space *> quote.optional *> word)
    <* (space.followed(by: version)).optional <* quote.optional <* colon.optional <* newLine
Copy the code

The first anonymous function handles indentation

/// space private let space = character(" ") private let indentation = space. Followed (by: space)Copy the code

Implementation:

* >, means to remove the data on the left

How is this operator implemented

func *><A, B>(lhs: Parser<A>, rhs: Parser - > < B >) Parser < B > {/ / here will have A change of return curry ({_, y in y}) < ^ > LHS < * > RHS} func < ^ > < A, B > (LHS: @escaping (A) -> B, rhs: Parser<A>) -> Parser<B> { return rhs.map(lhs) }Copy the code

Expand once, open <^>

func *><A, B>(lhs: Parser<A>, rhs: Parser - > < B >) Parser < B > {/ / there are changes in the return LHS. The map (curry ({_, y in y})) < * > RHS} func < * > < A, B > (LHS: Parser<(A) -> B>, rhs: Parser<A>) -> Parser<B> { return lhs.followed(by: rhs).map { $0($1) } }Copy the code

Expand 2, open <*>

Func *><A, B>(LHS: Parser<A>, RHS: Parser<B>) -> Parser<B> {// return lhs.map(curry({_, y in y})). Followed (by: rhs).map { $0($1) } } public func curry<T, U, V>(_ f: @escaping (T, U) -> V) -> (T) -> (U) -> V { return { x in { y in f(x, y) } } }Copy the code

Expand 3 and open Curry

It’s hard to use curry well

It’s equivalent to currying. It doesn’t work

Func *><A, B>(LHS: Parser<A>, RHS: Parser<B>) -> Parser<B> {return lhs.map({x in {y in y}}). Followed (by: RHS).map {$0($1)}} github repo func map<T>(_ transform: @escaping (Result) -> T) -> Parser<T> { return Parser<T> { input in guard let (result, remainder) = self.parse(input) else { return nil } return (transform(result), remainder) } }Copy the code

Expand 4 to open the first map

Here it’s easier to get that the deformation function passed in to the first map is {y in y}

The signature of this function can ostensibly be (B) -> B or (A) -> A

The details are from the following context.

*> implementation, can be clearly seen

The parsed result on the left is not used, which is equivalent to discarding it directly

The downside of functional programming is that, using pipes, data must be modified to make it compliant.

Pipelining, need grammar sugar, also quite a lot

In this case, currying is all about data compliance

func *><A, B>(lhs: Parser<A>, rhs: Parser<B>) -> Parser<B> {return Parser<(B) -> B> {input in guard let (result, remainder) = lhs.parse(input) else { return nil } return ({ y in y }, remainder) }.followed(by: RHS).map {$0($1)}} func followed<A>(by other: Parser<A>) -> Parser<(Result, A)> {return Parser<(Result, A)> {input in // self Parser<(Result, A)> + + = self. Parse (input), let (second) newReminder) = other.parse(reminder) else { return nil } return ((first, second), newReminder) } }Copy the code

Expand 5, followed

func *><A, B>(lhs: Parser<A>, rhs: Parser<B>) -> Parser<B> {let first = Parser<(B) -> B> {input in // Parse (input) else {return nil} return ({y in y}, remainder) Parser<((B) -> B, B)> {input in guard let (_, reminder) = first.parse(input)); let (second, newReminder) = rhs.parse(reminder) else { return nil } return (({ y in y }, second), newReminder) } return second.map { $1 } }Copy the code

Is equivalent to

// followed, A simple connector func *><A, B>(LHS: Parser<A>, RHS: Parser<B>) -> Parser<B> {let second = Parser<((B) -> B, B)> {input in reminder) = lhs.parse(input), let (second, newReminder) = rhs.parse(reminder) else { return nil } return (({ y in y }, second), newReminder) } return second.map { $1 } }Copy the code

Is equivalent to

Curried, this is just for formatting

func *><A, B>(lhs: Parser<A>, rhs: Parser<B>) -> Parser<B> { let second = Parser<B> { input in guard let (_, reminder) = lhs.parse(input), let (second, Parser (+) else {return nil}} Transform (result) 'func map<T>(_ transform:) {$0}} @escaping (Result) -> T) -> Parser<T> {return Parser<T> {input in // map does something, very little. The next sentence is, Let (result, remainder) = self.parse(input) else {return nil} return (transform(result), remainder)}}Copy the code

Expand 6 to go to the map at the end

Because the map above is not doing anything, can be directly removed

In the following code, the logic is clear

Ignoring the left-hand side is equivalent to saying,

On the left, it doesn’t,

On the right hand side, it stays

func *><A, B>(lhs: Parser<A>, rhs: Parser<B>) -> Parser<B>{return Parser<B>{input in guard let (_, reminder) = lhs.parse(input), let (second,) newReminder) = rhs.parse(reminder) else { return nil } return (second, newReminder) } }Copy the code

Summary: Functional programming, not as magical as imagined, routines are actually quite clear

Code optimization

Optimization operatormany

extension Parser { private var _many: Parser<[Result]> { return Parser<[Result]> { input in var result: [Result] = [] var remainder = input while let (element, newRemainder) = self.parse(remainder) { result.append(element) remainder = newRemainder } return (result, }} var many: Parser<[Result]> return curry {[$0] + $1} <^> self <*> self._many}}Copy the code

Expand a to <^>

   var many: Parser<[Result]> {
        return map(curry { [$0] + $1 }) <*> self._many
    }
Copy the code

Expand B, go to <*>

var many: Parser<[Result]> {
        return  map(curry { [$0] + $1 }).followed(by: self._many).map { $0($1) }
    }

Copy the code

Expand C, go to the first map

var many: Parser<[Result]> {let one = Parser<([Result]) -> [Result]> {input in guard let (Result, remainder) = self.parse(input) else { return nil } return (curry { [$0] + $1 }(result), remainder) } return one.followed(by: self._many).map { $0($1) } }Copy the code

Expand D, go to the first followed

var many: Parser<[Result]> {Parser<[Result]> Let one = Parser<([Result]) -> [Result]> {input in guard let (Result, Parse (input) else {return nil} return (curry {[$0] + $1}(result), remainder)} Let two = Parser<(([Result]) -> [Result], [Result])>{input in guard let (first, reminder) = one.parse(input), let (second, newReminder) = self._many.parse(reminder) else { return nil } return ((first, second), newReminder) } return two.map { $0($1) } }Copy the code

Can be combined

var many: Parser<[Result]> {let two = Parser<(([Result]) -> [Result], [Result])>{input in reminder) = parse(input), let (second, newReminder) = self._many.parse(reminder) else { return nil } return (((curry { [$0] + $1 })(first), second), newReminder) } return two.map { $0($1) } }Copy the code

Expand E to go to the map at the end

var many: Parser<[Result]> { let two = Parser<(([Result]) -> [Result], [Result])>{ input in guard let (first, reminder) = parse(input), let (second, newReminder) = self._many.parse(reminder) else { return nil } return (((curry { [$0] + $1 })(first), second), Parser<[Result]> {input in guard let (Result, remainder) = two.parse(input) else { return nil } return (result.0(result.1), remainder) } }Copy the code

Expand f, curry

var many: Parser<[Result]> { let two = Parser<(([Result]) -> [Result], [Result])>{ input in guard let (first, reminder) = parse(input), let (second, NewReminder) = self. _many. Parse (on) the else {return nil} / / there are changes to return ({x in {y in [x] + y}} (first), second), newReminder) } return Parser<[Result]> { input in guard let (result, remainder) = two.parse(input) else { return nil } return (result.0(result.1), remainder) } }Copy the code

Is equivalent to

var many: Parser<[Result]> {let two = Parser<(([Result]) -> [Result], [Result])>{input in guard let (first, reminder) = parse(input), let (second, Return ((({y in [first] + y}, second)) = ((y in [first] + y}, second)); Parser<[Result]> {input in guard let (Result, +)} Parser<[Result]> {input in guard let (Result, +) remainder) = two.parse(input) else { return nil } return (result.0(result.1), remainder) } }Copy the code

Expand g, merge

And when I was done, I realized,

The “many” operator, which is just processing itself, passes _many through the loop

private var _many: Parser<[Result]> { return Parser<[Result]> { input in var result: [Result] = [] var remainder = input while let (element, newRemainder) = self.parse(remainder) { result.append(element) remainder = newRemainder } return (result, remainder) } } var many: Parser<[Result]> return Parser<[Result]>{input in guard let (first, reminder) = parse(input), let (second, Parser) newReminder) = self._many.parse(reminder) else { return nil } return ( [first] + second, newReminder) } }Copy the code

_many is equivalent to many,

var many: Parser<[Result]> {
        
        return Parser<[Result]> {
            input in
            var result: [Result] = []
            var remainder = input
            
            while let (element, newRemainder) = self.parse(remainder) {
                
                result.append(element)
                remainder = newRemainder
            }
            return (result, remainder)
        }
    }

Copy the code

Summary:manyFirst processing, recycling processing, is unnecessary

Functional programming is magic at first glance. Play more, return to ordinary

github repo one

github repo two: octree/pretty