New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Functional routers & parsers #5

Merged
merged 16 commits into from Nov 12, 2017

Conversation

Projects
None yet
1 participant
@ilyapuchka
Owner

ilyapuchka commented Nov 2, 2017

No description provided.

.replacingOccurrences(of: "Optional<", with: "")
.replacingOccurrences(of: ">", with: "")
.lowercased()
} else if typeString.contains("Either<") {

This comment has been minimized.

@ilyapuchka

ilyapuchka Nov 2, 2017

Owner

actually this will never be used unless custom parameter type is used, but it's not currently exposed as public API

@ilyapuchka

ilyapuchka Nov 2, 2017

Owner

actually this will never be used unless custom parameter type is used, but it's not currently exposed as public API

return .init(parse: parseRight(lhs, rhs), print: printRight(lhs, rhs), template: templateAnd(lhs, rhs))
}
public static func /?<B>(lhs: RoutePattern, rhs: RoutePattern<B, S>) -> RoutePattern<(A, B?), S> {

This comment has been minimized.

@ilyapuchka

ilyapuchka Nov 2, 2017

Owner

maybe there should be /? and >/? so that it expresses that parameter from left part is carried on or not? but >/? feels like set of random symbols

@ilyapuchka

ilyapuchka Nov 2, 2017

Owner

maybe there should be /? and >/? so that it expresses that parameter from left part is carried on or not? but >/? feels like set of random symbols

public struct RoutePattern<A/*pattern type*/, S: PatternState> {
public let parse: Parser<A> // parses path components exctracting underlying type of pattern
public let print: Printer<A> // converts pattern with passed in value to template component

This comment has been minimized.

@ilyapuchka

ilyapuchka Nov 2, 2017

Owner

print is actually not that useful in context of deep links as we don't need to produce urls in code, so probably can be dropped. Though it can be used for application wide navigation, so that openURL is used always instead of passing manually created intents around.

@ilyapuchka

ilyapuchka Nov 2, 2017

Owner

print is actually not that useful in context of deep links as we don't need to produce urls in code, so probably can be dropped. Though it can be used for application wide navigation, so that openURL is used always instead of passing manually created intents around.

case optionalParam(Int?)
case optionalSecondParam(Int, String?)
func deconstruct<A>(_ constructor: ((A) -> Intent)) -> A? {

This comment has been minimized.

@ilyapuchka

ilyapuchka Nov 5, 2017

Owner

This method is used instead of PartialIso that would otherwise be created separately for each case, but will effectively do the same - match specific case with it's constructor and extract associated values.
It's boilerplate, but seems like less code to write comparing with PartialIso.

This code

if let values = value.deconstruct(Enum.somecase) {...}

is equivalent to this

if case let Enum.somecase(values) = value {...}

but can be mapped and used without if, when standard pattern matching can't be only used in if case/guard case statements.

@ilyapuchka

ilyapuchka Nov 5, 2017

Owner

This method is used instead of PartialIso that would otherwise be created separately for each case, but will effectively do the same - match specific case with it's constructor and extract associated values.
It's boilerplate, but seems like less code to write comparing with PartialIso.

This code

if let values = value.deconstruct(Enum.somecase) {...}

is equivalent to this

if case let Enum.somecase(values) = value {...}

but can be mapped and used without if, when standard pattern matching can't be only used in if case/guard case statements.

}
private func add(_ route: RoutePattern<U, Path>) -> Router {
self.route = self.route.map({ oldValue in

This comment has been minimized.

@ilyapuchka

ilyapuchka Nov 5, 2017

Owner

self.route wraps all individual routes. When matching or printing using this route is equivalent to iterating through array of routes, but this will do it with recursion.

@ilyapuchka

ilyapuchka Nov 5, 2017

Owner

self.route wraps all individual routes. When matching or printing using this route is equivalent to iterating through array of routes, but this will do it with recursion.

@discardableResult
public func add<A, S: ClosedPatternState>(_ intent: @escaping ((A)) -> U, route: RoutePattern<A, S>) -> Router {
return add(route.map({ intent($0) }, { $0.deconstruct(intent) }))

This comment has been minimized.

@ilyapuchka

ilyapuchka Nov 5, 2017

Owner

All routes are mapped to the same generic type, <U, Path>, so that they can be composed into one route.
Input parameter of second closure will be value of U, but router needs A, which is associated value of enum case (if enum is used of course). But we only have constructor of U, so we don't know with what case to match the value. This is done instead with deconstruct method that does pattern matching and returns associated values.
This is only needed to be able to print urls for specific values of U. We could print just using route itself (see this commit), but we can't easily get it back from router, so will need to store all routes in some constants. That might be a good solution, as it will not require boilerplate implementation of deconstruct, but it is not safe: route can be used to print without being added to router and then printed url will be not matched. With mapping and using deconstruct only routes that are added to router can be used to print urls, so it's guarantied that url will be matched.

@ilyapuchka

ilyapuchka Nov 5, 2017

Owner

All routes are mapped to the same generic type, <U, Path>, so that they can be composed into one route.
Input parameter of second closure will be value of U, but router needs A, which is associated value of enum case (if enum is used of course). But we only have constructor of U, so we don't know with what case to match the value. This is done instead with deconstruct method that does pattern matching and returns associated values.
This is only needed to be able to print urls for specific values of U. We could print just using route itself (see this commit), but we can't easily get it back from router, so will need to store all routes in some constants. That might be a good solution, as it will not require boilerplate implementation of deconstruct, but it is not safe: route can be used to print without being added to router and then printed url will be not matched. With mapping and using deconstruct only routes that are added to router can be used to print urls, so it's guarantied that url will be matched.

@discardableResult
public func add<A, B, C, S: ClosedPatternState>(_ intent: @escaping ((A, B, C)) -> U, route: RoutePattern<((A, B), C), S>) -> Router {
return add(route.map({ intent(flatten($0)) }, { $0.deconstruct(intent).map(parenthesize) }))

This comment has been minimized.

@ilyapuchka

ilyapuchka Nov 5, 2017

Owner

Here we also need to regroup values using parenthesize from (A, B, C) to ((A, B), C) as this is what route expects

@ilyapuchka

ilyapuchka Nov 5, 2017

Owner

Here we also need to regroup values using parenthesize from (A, B, C) to ((A, B), C) as this is what route expects

extension Router {
public func url(for route: U) -> URL? {
return self.route?.print(route).flatMap(url(from:))

This comment has been minimized.

@ilyapuchka

ilyapuchka Nov 5, 2017

Owner

now we can just use composite route to print url and we do it with concrete value of U, not a route pattern.

@ilyapuchka

ilyapuchka Nov 5, 2017

Owner

now we can just use composite route to print url and we do it with concrete value of U, not a route pattern.

Show outdated Hide outdated DeeperFunc/String.swift
return { components in
var components = components
var pathPatterns = [RoutePattern<Any, S>]()
while !components.isEmpty {

This comment has been minimized.

@ilyapuchka

ilyapuchka Nov 5, 2017

Owner

can we use the same approach as with routes, using composition and recursion instead of loop?

@ilyapuchka

ilyapuchka Nov 5, 2017

Owner

can we use the same approach as with routes, using composition and recursion instead of loop?

@discardableResult
public func add<S: ClosedPatternState>(_ intent: U, route: RoutePattern<Void, S>) -> Router {
return add(route.map({ intent }, { $0 == intent ? () : nil }))

This comment has been minimized.

@ilyapuchka

ilyapuchka Nov 5, 2017

Owner

for cases without parameters we can just use ==

@ilyapuchka

ilyapuchka Nov 5, 2017

Owner

for cases without parameters we can just use ==

@ilyapuchka ilyapuchka merged commit 19933ea into master Nov 12, 2017

@ilyapuchka ilyapuchka deleted the func branch Nov 12, 2017

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment