-
Notifications
You must be signed in to change notification settings - Fork 299
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
067dff6
commit 5061ecc
Showing
4 changed files
with
205 additions
and
0 deletions.
There are no files selected for viewing
194 changes: 194 additions & 0 deletions
194
0060-composable-parsing-flat-map/Composable Parsers.playground/Contents.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,194 @@ | ||
import Foundation | ||
|
||
struct Parser<A> { | ||
let run: (inout Substring) -> A? | ||
} | ||
|
||
let int = Parser<Int> { 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<Double> { 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<Void> { | ||
return Parser<Void> { str in | ||
guard str.hasPrefix(literal) else { return nil } | ||
str.removeFirst(literal.count) | ||
return () | ||
} | ||
} | ||
|
||
func always<A>(_ a: A) -> Parser<A> { | ||
return Parser<A> { _ 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<A>) -> F<B> | ||
|
||
// F<A> = Parser<A> | ||
// map: ((A) -> B) -> (Parser<A>) -> Parser<B> | ||
|
||
// map(id) = id | ||
|
||
[1, 2, 3] | ||
.map { $0 } | ||
|
||
Optional("Blob") | ||
.map { $0 } | ||
|
||
|
||
// map: (Parser<A>, (A) -> B) -> Parser<B> | ||
|
||
extension Parser { | ||
func map<B>(_ f: @escaping (A) -> B) -> Parser<B> { | ||
return Parser<B> { str -> B? in | ||
self.run(&str).map(f) | ||
} | ||
} | ||
|
||
func fakeMap<B>(_ f: @escaping (A) -> B) -> Parser<B> { | ||
return Parser<B> { _ in nil } | ||
} | ||
func fakeMap2<B>(_ f: @escaping (A) -> B) -> Parser<B> { | ||
return Parser<B> { 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<Character> { str in | ||
guard !str.isEmpty else { return nil } | ||
return str.removeFirst() | ||
} | ||
|
||
let northSouth = Parser<Double> { 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<B>) -> (M<A>) -> M<B> | ||
|
||
|
||
extension Parser { | ||
func flatMap<B>(_ f: @escaping (A) -> Parser<B>) -> Parser<B> { | ||
return Parser<B> { 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<Double> { 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") |
4 changes: 4 additions & 0 deletions
4
0060-composable-parsing-flat-map/Composable Parsers.playground/contents.xcplayground
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?> | ||
<playground version='5.0' target-platform='macos'> | ||
<timeline fileName='timeline.xctimeline'/> | ||
</playground> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters