From 5061ecceea70735eff2526f473a761c4519c175d Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Sun, 2 Jun 2019 17:52:40 -0400 Subject: [PATCH] 60 --- .../Contents.swift | 194 ++++++++++++++++++ .../contents.xcplayground | 4 + 0060-composable-parsing-flat-map/README.md | 5 + README.md | 2 + 4 files changed, 205 insertions(+) create mode 100644 0060-composable-parsing-flat-map/Composable Parsers.playground/Contents.swift create mode 100644 0060-composable-parsing-flat-map/Composable Parsers.playground/contents.xcplayground create mode 100644 0060-composable-parsing-flat-map/README.md diff --git a/0060-composable-parsing-flat-map/Composable Parsers.playground/Contents.swift b/0060-composable-parsing-flat-map/Composable Parsers.playground/Contents.swift new file mode 100644 index 00000000..45fdbf51 --- /dev/null +++ b/0060-composable-parsing-flat-map/Composable Parsers.playground/Contents.swift @@ -0,0 +1,194 @@ +import Foundation + +struct Parser { + let run: (inout Substring) -> A? +} + +let int = Parser { str in + let prefix = str.prefix(while: { $0.isNumber }) + guard let int = Int(prefix) else { return nil } + str.removeFirst(prefix.count) + return int +} + +let double = Parser { str in + let prefix = str.prefix(while: { $0.isNumber || $0 == "." }) + guard let match = Double(prefix) else { return nil } + str.removeFirst(prefix.count) + return match +} + +func literal(_ literal: String) -> Parser { + return Parser { str in + guard str.hasPrefix(literal) else { return nil } + str.removeFirst(literal.count) + return () + } +} + +func always(_ a: A) -> Parser { + return Parser { _ in a } +} + +extension Parser { + static var never: Parser { + return Parser { _ in nil } + } +} + +struct Coordinate { + let latitude: Double + let longitude: Double +} + + +extension Parser { + func run(_ str: String) -> (match: A?, rest: Substring) { + var str = str[...] + let match = self.run(&str) + return (match, str) + } +} + + +// map: ((A) -> B) -> (F) -> F + +// F = Parser +// map: ((A) -> B) -> (Parser) -> Parser + +// map(id) = id + +[1, 2, 3] + .map { $0 } + +Optional("Blob") + .map { $0 } + + +// map: (Parser, (A) -> B) -> Parser + +extension Parser { + func map(_ f: @escaping (A) -> B) -> Parser { + return Parser { str -> B? in + self.run(&str).map(f) + } + } + + func fakeMap(_ f: @escaping (A) -> B) -> Parser { + return Parser { _ in nil } + } + func fakeMap2(_ f: @escaping (A) -> B) -> Parser { + return Parser { str in + let matchB = self.run(&str).map(f) + str = "" + return matchB + } + } +} + +int.map { $0 } +int.fakeMap { $0 }.run("123") +int + .fakeMap2 { $0 }.run("123 Hello World") +int + .run("123 Hello World") + +let even = int.map { $0 % 2 == 0 } + +even.run("123 Hello World") +even.run("42 Hello World") + +let char = Parser { str in + guard !str.isEmpty else { return nil } + return str.removeFirst() +} + +let northSouth = Parser { str in + guard + let cardinal = str.first, + cardinal == "N" || cardinal == "S" + else { return nil } + str.removeFirst(1) + return cardinal == "N" ? 1 : -1 +} + +// flatMap: ((A) -> M) -> (M) -> M + + +extension Parser { + func flatMap(_ f: @escaping (A) -> Parser) -> Parser { + return Parser { str -> B? in + let original = str + let matchA = self.run(&str) + let parserB = matchA.map(f) + guard let matchB = parserB?.run(&str) else { + str = original + return nil + } + return matchB + } + } +} + + +let eastWest = Parser { str in + guard + let cardinal = str.first, + cardinal == "E" || cardinal == "W" + else { return nil } + str.removeFirst(1) + return cardinal == "E" ? 1 : -1 +} + +func parseLatLong(_ str: String) -> Coordinate? { + var str = str[...] + + guard + let lat = double.run(&str), + literal("° ").run(&str) != nil, + let latSign = northSouth.run(&str), + literal(", ").run(&str) != nil, + let long = double.run(&str), + literal("° ").run(&str) != nil, + let longSign = eastWest.run(&str) + else { return nil } + + return Coordinate( + latitude: lat * latSign, + longitude: long * longSign + ) +} + +print(String(describing: parseLatLong("40.6782° N, 73.9442° W"))) + + +"40.6782° N, 73.9442° W" + +let coord = double + .flatMap { lat in + literal("° ") + .flatMap { _ in + northSouth + .flatMap { latSign in + literal(", ") + .flatMap { _ in + double + .flatMap { long in + literal("° ") + .flatMap { _ in + eastWest + .map { longSign in + return Coordinate( + latitude: lat * latSign, + longitude: long * longSign + ) + } + } + } + } + } + } +} + +coord.run("40.6782° N, 73.9442° W") +coord.run("40.6782° Z, 73.9442° W") diff --git a/0060-composable-parsing-flat-map/Composable Parsers.playground/contents.xcplayground b/0060-composable-parsing-flat-map/Composable Parsers.playground/contents.xcplayground new file mode 100644 index 00000000..63b6dd8d --- /dev/null +++ b/0060-composable-parsing-flat-map/Composable Parsers.playground/contents.xcplayground @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/0060-composable-parsing-flat-map/README.md b/0060-composable-parsing-flat-map/README.md new file mode 100644 index 00000000..647db4a9 --- /dev/null +++ b/0060-composable-parsing-flat-map/README.md @@ -0,0 +1,5 @@ +## [Point-Free](https://www.pointfree.co) + +> #### This directory contains code from Point-Free Episode: [Composable Parsing: Flat-Map](https://www.pointfree.co/episodes/ep60-composable-parsing-flat-map) +> +> The `map` function on parsers is powerful, but there are still a lot of things it cannot do. We will see that in trying to solve some of its limitations we are naturally led to our old friend the `flatMap` function. diff --git a/README.md b/README.md index 71ab14f1..afbaa091 100644 --- a/README.md +++ b/README.md @@ -61,3 +61,5 @@ This repository is the home of code written on episodes of 1. [What Is a Parser?: Part 1](0056-what-is-a-parser-pt1) 1. [What Is a Parser?: Part 2](0057-what-is-a-parser-pt2) 1. [What Is a Parser?: Part 3](0058-what-is-a-parser-pt3) +1. [Composable Parsing: Map](0059-composable-parsing-map) +1. [Composable Parsing: Flat-Map](0060-composable-parsing-flat-map)