Git Product home page Git Product logo

swift-case-paths's Introduction

๐Ÿงฐ CasePaths

CI Slack

Case paths extends the key path hierarchy to enum cases.

Motivation

Swift endows every struct and class property with a key path.

struct User {
  let id: Int
  var name: String
}

\User.id    // KeyPath<User, Int>
\User.name  // WritableKeyPath<User, String>

This is compiler-generated code that can be used to abstractly zoom in on part of a structure, inspect and even change it, all while propagating those changes to the structure's whole. They are the silent partner of many modern Swift APIs powered by dynamic member lookup, like SwiftUI bindings, but also make more direct appearances, like in the SwiftUI environment and unsafe mutable pointers.

Unfortunately, no such structure exists for enum cases.

enum UserAction {
  case home(HomeAction)
  case settings(SettingsAction)
}

\UserAction.home  // ๐Ÿ›‘

๐Ÿ›‘ key path cannot refer to static member 'home'

And so it's not possible to write generic code that can zoom in and modify the data of a particular case in the enum.

Using case paths in libraries

By far the most common use of case paths is as a tool inside a library that is distributed to other developers. Case paths are used in the Composable Architecture, SwiftUI Navigation, Parsing, and many other libraries.

If you maintain a library where you expect your users to model their domains with enums, then providing case path tools to them can help them break their domains into smaller units. For example, consider the Binding type provided by SwiftUI:

struct Binding<Value> {
  let get: () -> Value
  let set: (Value) -> Void
}

Through the power of dynamic member lookup we are able to support dot-chaining syntax for deriving new bindings to members of values:

@dynamicMemberLookup
struct Binding<Value> {
  โ€ฆ
  subscript<Member>(dynamicMember keyPath: WritableKeyPath<Value, Member>) -> Binding<Member> {
    Binding<Member>(
      get: { self.get()[keyPath: keyPath] },
      set: { 
        var value = self.get()
        value[keyPath: keyPath] = $0
        self.set(value)
      }
    )
  }
}

If you had a binding of a user, you could simply append .name to that binding to immediately derive a binding to the user's name:

let user: Binding<User> = // ...
let name: Binding<String> = user.name

However, there are no such affordances for enums:

enum Destination {
  case home(HomeState)
  case settings(SettingsState)
}
let destination: Binding<Destination> = // ...
destination.home      // ๐Ÿ›‘
destination.settings  // ๐Ÿ›‘

It is not possible to derive a binding to just the home case of a destination binding by using simple dot-chaining syntax.

However, if SwiftUI used this CasePaths library, then they could provide this tool quite easily. They could provide an additional dynamicMember subscript that uses a CaseKeyPath, which is a key path that singles out a case of an enum, and use that to derive a binding to a particular case of an enum:

import CasePaths

extension Binding {
  public subscript<Case>(dynamicMember keyPath: CaseKeyPath<Value, Case>) -> Binding<Case>?
  where Value: CasePathable {
    Binding<Case>(
      unwrapping: Binding<Case?>(
        get: { self.wrappedValue[case: keyPath] },
        set: { newValue, transaction in
          guard let newValue else { return }
          self.transaction(transaction).wrappedValue[case: keyPath] = newValue
        }
      )
    )
  }
}

With that defined, one can annotate their enum with the @CasePathable macro and then immediately use dot-chaining to derive a binding of a case from a binding of an enum:

@CasePathable
enum Destination {
  case home(HomeState)
  case settings(SettingsState)
}
let destination: Binding<Destination> = // ...
destination.home      // Binding<HomeState>?
destination.settings  // Binding<SettingsState>?

This is an example of how libraries can provide tools for their users to embrace enums without losing out on the ergonomics of structs.

Basics of case paths

While library tooling is the biggest use case for using this library, there are some ways that you can use case paths in first-party code too. The library bridges the gap between structs and enums by introducing what we call "case paths": key paths for enum cases.

Case paths can be enabled for an enum using the @CasePathable macro:

@CasePathable
enum UserAction {
  case home(HomeAction)
  case settings(SettingsAction)
}

And they can be produced from a "case-pathable" enum through its Cases namespace:

\UserAction.Cases.home      // CaseKeyPath<UserAction, HomeAction>
\UserAction.Cases.settings  // CaseKeyPath<UserAction, SettingsAction>

And like any key path, they can be abbreviated when the enum type can be inferred:

\.home as CaseKeyPath<UserAction, HomeAction>
\.settings as CaseKeyPath<UserAction, SettingsAction>

Case paths vs. key paths

Extracting, embedding, modifying, and testing values

As key paths package up the functionality of getting and setting a value on a root structure, case paths package up the functionality of optionally extracting and modifying an associated value of a root enumeration.

user[keyPath: \User.name] = "Blob"
user[keyPath: \.name]  // "Blob"

userAction[case: \UserAction.Cases.home] = .onAppear
userAction[case: \.home]  // Optional(HomeAction.onAppear)

If the case doesn't match, the extraction can fail and return nil:

userAction[case: \.settings]  // nil

Case paths have an additional ability, which is to embed an associated value into a brand new root:

let userActionToHome = \UserAction.Cases.home
userActionToHome(.onAppear)  // UserAction.home(.onAppear)

Cases can be tested using the is method on case-pathable enums:

userAction.is(\.home)      // true
userAction.is(\.settings)  // false

let actions: [UserAction] = [โ€ฆ]
let homeActionsCount = actions.count(where: { $0.is(\.home) })

And their associated values can be mutated in place using the modify method:

var result = Result<String, Error>.success("Blob")
result.modify(\.success) {
  $0 += ", Jr."
}
result  // Result.success("Blob, Jr.")

Composing paths

Case paths, like key paths, compose. You can dive deeper into the enumeration of an enumeration's case using familiar dot-chaining:

\HighScore.user.name
// WritableKeyPath<HighScore, String>

\AppAction.Cases.user.home
// CaseKeyPath<AppAction, HomeAction>

Or you can append them together:

let highScoreToUser = \HighScore.user
let userToName = \User.name
let highScoreToUserName = highScoreToUser.append(path: userToName)
// WritableKeyPath<HighScore, String>

let appActionToUser = \AppAction.Cases.user
let userActionToHome = \UserAction.Cases.home
let appActionToHome = appActionToUser.append(path: userActionToHome)
// CaseKeyPath<AppAction, HomeAction>

Identity paths

Case paths, also like key paths, provide an identity path, which is useful for interacting with APIs that use key paths and case paths but you want to work with entire structure.

\User.self              // WritableKeyPath<User, User>
\UserAction.Cases.self  // CaseKeyPath<UserAction, UserAction>

Property access

Since Swift 5.2, key path expressions can be passed directly to methods like map. Case-pathable enums that are annotated with dynamic member lookup enable property access and key path expressions for each case.

@CasePathable
@dynamicMemberLookup
enum UserAction {
  case home(HomeAction)
  case settings(SettingsAction)
}

let userAction: UserAction = .home(.onAppear)
userAction.home      // Optional(HomeAction.onAppear)
userAction.settings  // nil

let userActions: [UserAction] = [.home(.onAppear), .settings(.purchaseButtonTapped)]
userActions.compactMap(\.home)  // [HomeAction.onAppear]

Dynamic case lookup

Because case key paths are bona fide key paths under the hood, they can be used in the same applications, like dynamic member lookup. For example, we can extend SwiftUI's binding type to enum cases by extending it with a subscript:

extension Binding {
  subscript<Member>(
    dynamicMember keyPath: CaseKeyPath<Value, Member>
  ) -> Binding<Member>? {
    guard let member = self.wrappedValue[case: keyPath]
    else { return nil }
    return Binding<Member>(
      get: { self.wrappedValue[case: keyPath] ?? member },
      set: { self.wrappedValue[case: keyPath] = $0 }
    )
  }
}

@CasePathable enum ItemStatus {
  case inStock(quantity: Int)
  case outOfStock(isOnBackOrder: Bool)
}

struct ItemStatusView: View {
  @Binding var status: ItemStatus

  var body: some View {
    switch self.status {
    case .inStock:
      self.$status.inStock.map { $quantity in
        Section {
          Stepper("Quantity: \(quantity)", value: $quantity)
          Button("Mark as sold out") {
            self.item.status = .outOfStock(isOnBackOrder: false)
          }
        } header: {
          Text("In stock")
        }
      }
    case .outOfStock:
      self.$status.outOfStock.map { $isOnBackOrder in
        Section {
          Toggle("Is on back order?", isOn: $isOnBackOrder)
          Button("Is back in stock!") {
            self.item.status = .inStock(quantity: 1)
          }
        } header: {
          Text("Out of stock")
        }
      }
    }
  }
}

Note The above is a simplified version of the subscript that ships in our SwiftUINavigation library.

Computed paths

Key paths are created for every property, even computed ones, so what is the equivalent for case paths? Well, "computed" case paths can be created by extending the case-pathable enum's AllCasePaths type with properties that implement the embed and extract functionality of a custom case:

@CasePathable
enum Authentication {
  case authenticated(accessToken: String)
  case unauthenticated
}

extension Authentication.AllCasePaths {
  var encrypted: AnyCasePath<Authentication, String> {
    AnyCasePath(
      embed: { decryptedToken in
        .authenticated(token: encrypt(decryptedToken))
      },
      extract: { authentication in
        guard
          case let .authenticated(encryptedToken) = authentication,
          let decryptedToken = decrypt(token)
        else { return nil }
        return decryptedToken
      }
    )
  }
}

\Authentication.Cases.encrypted
// CaseKeyPath<Authentication, String>

Case studies

  • SwiftUINavigation uses case paths to power SwiftUI bindings, including navigation, with enums.

  • The Composable Architecture allows you to break large features down into smaller ones that can be glued together user key paths and case paths.

  • Parsing uses case paths to turn unstructured data into enums and back again.

Do you have a project that uses case paths that you'd like to share? Please open a PR with a link to it!

Community

If you want to discuss this library or have a question about how to use it to solve a particular problem, there are a number of places you can discuss with fellow Point-Free enthusiasts:

Documentation

The latest documentation for CasePaths' APIs is available here.

Credit and thanks

Special thanks to Giuseppe Lanza, whose EnumKit inspired the original, reflection-based solution this library used to power case paths.

Interested in learning more?

These concepts (and more) are explored thoroughly in Point-Free, a video series exploring functional programming and Swift hosted by Brandon Williams and Stephen Celis.

The design of this library was explored in the following Point-Free episodes:

video poster image

License

All modules are released under the MIT license. See LICENSE for details.

swift-case-paths's People

Contributors

andrewvebster avatar brianmichel avatar djangovanderheijden avatar ferologics avatar heiberg avatar hj56775 avatar iampatbrown avatar kodok1988 avatar lukeredpath avatar mayoff avatar mbrandonw avatar mrackwitz avatar mtfum avatar nnsnodnb avatar nonameplum avatar ryu0118 avatar stephencelis avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

swift-case-paths's Issues

Enable Bitcode for target build

When building my application with bitcode enabled, it tells me the CasePath.o was built without bitcode. Was this intentional or should there be an updated release with bitcode enabled?

[Question] unreproducible double-dot syntax example on README

Hello, I am new to swift-case-paths and reading README.md.

regarding section Case paths vs. key paths, I would like you to conform my usage is correct or not, because I can't use double-dot syntax to dive into deeper enum case.

Case paths, like key paths, compose. Where key paths use dot-syntax to dive deeper into a structure, case paths use a double-dot syntax:

\HighScore.user.name
// WritableKeyPath<HighScore, String>

/Result<Authentication, Error>..Authentication.authenticated
// CasePath<Result<Authentication, Error>, String>

here is my code trying above syntax.

import CasePaths
import Foundation

enum Selection: String {
    case alice
    case bob
}

/**
 To dive into a structure, keyPath uses dot (`.`) syntax, but case-paths uses double-dot (`..`) syntax.
 */
func example02() {
    /Result<Selection, Error>..Selection.alice

}
Screenshot 2023-10-22 at 17 24 54

instead, I could use below way but I would like to check if README is old or not.

import CasePaths
import Foundation

enum Selection: String {
    case alice
    case bob
}

/**
 To dive into a structure, keyPath uses dot (`.`) syntax, but case-paths uses double-dot (`..`) syntax.
 */
func example02() {
    /Result<Selection, Error>.success(.alice)
}

xcodebuild fails with version 1.1.0

Describe the bug
We get this error when running xcodebuild operations. We specifically pinned all our dependencies and only updated swift-case-paths to 1.1.0 and the error occurred in the package resolution phase. Could there be some versioning issue causing this?

 The following build commands failed:
  	ComputeTargetDependencyGraph
  (1 failure)

Environment

  • swift-case-paths 1.1.0
  • Xcode 15.0.1
  • OS: macOS 14.0 (arm64)

Additional context
Add any more context about the problem here.

Should I make a PR? ๐Ÿ˜…

I'm using a type, called CasePathValueDetector which can detect if the case is an expected case, but it erases the Value type from CasePath to enable me to use arrays of CasePath for cases with different associated values

public struct CasePathValueDetector<Root> {
  private let _detect: (Root) -> Bool
  
  public func `is`(_ action: Root) -> Bool {
    _detect(action)
  }
  
  public static func detector<Value>(for casePath: CasePath<Root, Value>) -> CasePathValueDetector {
    CasePathValueDetector(_detect: { casePath.extract(from: $0) == nil })
  }
}

public prefix func / <Root, Value>(
  embed: @escaping (Value) -> Root
) -> CasePathValueDetector<Root> {
  .detector(for: /embed)
}

public prefix func / <Root>(
  case: Root
) -> CasePathValueDetector<Root> {
  .detector(for: /`case`)
}

with TCA to enable dismissOn higher order reducer for handling modals in UIKit (but it may usefull for creating generalized .on(_ action: Action, _ reduce: (inout State, Action, Environment) -> Effect) -> Reducer reducer for example)

public let invitationModalReducer = Reducer<
  Modal<InvitationState>,
  ModalAction<InvitationAction>,
  InvitationEnvironment
>.combine(
  Reducer.modal(
    invitationReducer,
    state: \.self,
    action: /.self,
    environment: { $0 }
  )
  .dismissOn(
    /InvitationAction.confirmChoice,
    /InvitationAction.close
  )
)

it does the same as

public let quizModalReducer = Reducer<
  Modal<QuizState>,
  ModalAction<QuizAction>,
  QuizEnvironment
>.combine(
  Reducer.modal(
    quizReducer,
    state: \.self,
    action: /.self,
    environment: { $0 }
  )
  .lifecycleBasedRouting(/QuizAction.lifecycle)
  .dismissOn(.exit, .timeout, .interactiveTimeout)
}

but the second example has no associated values so I can just pass enum cases.

Method's signatures are

extension Reducer {
  public func dismissOn<LocalAction>(_ actions: LocalAction...) -> Reducer
  where Action == ModalAction<LocalAction>, LocalAction: Equatable { ... }

  public func dismissOn<LocalAction>(_ paths: CasePathValueDetector<LocalAction>...) -> Reducer
  where Action == ModalAction<LocalAction>, LocalAction: Equatable { ... }
}

Failed to extract enum with AnyHashable associated value

Describe the bug
Hello guys, I have a project using CasePaths with version 0.1.3 and updating it with a huge leap into 0.8.1. After the update enum with the associated value of AnyHashable will fail to extract. The pattern in enum looks like this:

enum ParentAction {
    case loadPage
    case childrenAction(id: AnyHashable, action: ChildrenAction)
}

To Reproduce
We can reproduce this by running tests in CasePathTests and changing the associated value from String to AnyHashable in this test

  func testPathExtractFromOptionalRoot() {
    enum Authentication {
      case authenticated(token: AnyHashable) // change from String to AnyHashable
      case unauthenticated
    }

    let root: Authentication? = .authenticated(token: "deadbeef")
    let path: CasePath<Authentication?, String> = /Authentication.authenticated
    for _ in 1...2 {
      let actual = path.extract(from: root)
      XCTAssertEqual(actual, "deadbeef")  // XCTAssertEqual failed: ("nil") is not equal to ("Optional("deadbeef")")
    }
  }

Expected behavior
The extracted result should be not nil in the latest version and on (version 0.1.3), The tests above succeed both for the associated value String and AnyHashable

Environment

  • swift-case-paths [e.g. 0.8.1]
  • Xcode [e.g. 13.4]
  • Swift [e.g. 5.5.2]
  • OS: [e.g. iOS 15]

Xcode 14 previews sees the / operator as the start of a regex literal

My projects using Case Paths are compiling and running fine. However Xcode previews believe that the / operator is the start of the new RegEx literal, and throws an error about it being unterminated.

So, this is not an issue in Case Paths per se, but I wonder, is there some work around? I'd happily use a function call in the meantime, but unfortunately I've been stuck trying to figure out all the overloads for / and how I might wrap them with a function instead.

Documentation links 404

While the link to the documentation itself works, from within the documentation everything 404s. For instance, this is the link to the .. operator.

Is it possible to use the / operator for dealing with nested enums?

Let's say I have these three enums:

enum First {
    case second(Second)
}

enum Second {
    case third(Third)
}

enum Third {
    case hello
}

I know that I can create a case path from first to second like this:

let casePath: CasePath<First, Second> = /First.second

And I also know that I can create a case path from first to third using the constructor like this:

let nestedCasePath = CasePath(
    embed: { third in
        First.second(Second.third(third))
    }, extract: { first in
        if case let First.second(Second.third(third)) = first {
            return third
        }
        return nil
    }
)

What I'd like to know is if it's possible to use the / operator to deal with these nested case paths, or if the only way is to create them using the constructor.

Thanks.

[![Mend Renovate](https://app.renovatebot.com/images/banner.svg)](https://renovatebot.com)

Mend Renovate

This PR contains the following updates:

Package Change Age Adoption Passing Confidence
semantic-release 19.0.5 -> 20.1.0 age adoption passing confidence

Release Notes

semantic-release/semantic-release

v20.1.0

Compare Source

Features

v20.0.4

Compare Source

Bug Fixes

v20.0.3

Compare Source

Reverts

v20.0.2

Compare Source

Bug Fixes

v20.0.1

Compare Source

Bug Fixes
  • deps: update dependency cosmiconfig to v8 (f914c1e)
  • deps: update dependency hosted-git-info to v6 (c4da008)

v20.0.0

Compare Source

BREAKING CHANGES
  • esm: semantic-release is now ESM-only. since it is used through its own executable, the impact on consuming projects should be minimal
  • esm: references to plugin files in configs need to include the file extension because of executing in an ESM context
  • node-versions: node v18 is now the minimum required version of node. this is in line with our node support policy. please see our recommendations for releasing with a different node version than your project normally uses, if necessary.
Features
Bug Fixes

Configuration

๐Ÿ“… Schedule: Branch creation - At any time (no schedule defined), Automerge - At any time (no schedule defined).

๐Ÿšฆ Automerge: Disabled by config. Please merge this manually once you are satisfied.

โ™ป Rebasing: Whenever PR is behind base branch, or you tick the rebase/retry checkbox.

๐Ÿ”• Ignore: Close this PR and you won't be reminded about this update again.


  • If you want to rebase/retry this PR, check this box

This PR has been generated by Mend Renovate. View repository job log here.

Originally posted by @renovate in vidavidorra/repo-template#227

Syntax error compiling CasePath expression with '/' in Swift 5.7

image

Seems like it's colliding with RegEx literal syntax. Any recommended workarounds? @stephencelis demonstrates use of initialiser here in Swift Forums:

-.pullback(state: \Struct.property, action: /Enum.case)
+.pullback(state: \Struct.property, action: CasePath(Enum.case))

Here's another workaround, based off Revised RegEx parsing behaviour:

-(\HomeState.route).appending(path: /HomeRoute.game).extract(from:)
+(\HomeState.route).appending(path: (/HomeRoute.game)).extract(from:)

Related discussion was in #72

PS Seems like neither workarounds are making Xcode 14 previews happy.

Fail to extract cases with 1-case enums and `Any` associated values

I've extracted from TCA's #1086 the following minimal suite that doesn't pass when built in Release mode:

import CasePaths
import XCTest

public enum RootAction: Equatable {
  case branch(BranchAction)
//  case noop // Add this and extraction succeeds
}

public enum BranchAction: Equatable {
  case firstAction(Bool)
  case secondAction(Bool)
  case leaf(LeafAction) // Remove this and extraction succeeds
}

public enum LeafAction: Equatable {
  case value(Value) // Or case value(Any) with explicit `Equatable` conformance.
}

public struct Value: Equatable {
  var value: Any
  public static func == (lhs: Value, rhs: Value) -> Bool { true }
}

class CasePathIssueTests: XCTestCase {
  func testFirst() throws {
    XCTAssertEqual(
      (/RootAction.branch).extract(from: .branch(.firstAction(true))),
      .firstAction(true)
    )
  }
  func testSecond() throws {
    XCTAssertEqual(
      (/RootAction.branch).extract(from: .branch(.secondAction(true))),
      .secondAction(true)
    )
  }
}

A few comments:

  • It passes in Debug mode
  • If .firstAction has no associated value, it becomes the one that fails to extract, and second passes.

I'm not knowledgeable enough with runtime and the intricacies of the extraction algorithm to fix this issue.

Build fails if enum is defined in a public extension

Describe the bug
I have an enum that is defined in an extension that is public. The enum inherits the public access modifier from the extension, however, the AllCasePaths struct and allCasePaths properties do not. This causes the build to fail since these two pieces do not have the same access modifier as the enum that is configured with CasePathable.

If I add the public access modifier directly to the enum then everything works as expected. Even if I move the public keyword, we use SwiftFormat, that has a rule that moves that modifier to the extension. We would have to disable that rule to make it work. Would it be possible that this could work in both contexts, or is this a limitation of macros?

// This will fail to compile
public extension ABTest {
    @CasePathable
    enum Identifier {
    }
}

// This will compile without any issues
extension ABTest {
    @CasePathable
    public enum Identifier {
    }
}

Environment

  • swift-case-paths 1.2.3
  • Xcode 15.1
  • Swift 5.9

Reflection fails for enums with 2 or more associated values

Reflection seems to fail for enums with 2 or more associated values.

Can be reproduced by opening a new Xcode project (Xcode 12.5), adding swift-case-paths as a dependency (0.6.2) and running the following code in main:

import CasePaths
enum E { case c(Int, Int) }
/E.c

This triggers a "value must be a valid enum case" fatalError from the new reflection code. Removing one of the ints resolves the issue.

I would expect this to work and produce a CasePath<E, (Int, Int)>. Is this not meant to be supported?

Reflection crash on empty enum in Swift 5.3

If you create a case path that goes to Never (or any empty enum) using our reflection-based API, any attempted extraction will crash on Swift 5.3 (which is included in Xcode 12 beta):

enum Foo {
  case bar
  case baz(Never)
}

(/Foo.baz).extract(from: Foo.bar)

The issue boils down to this check being wrong:

if MemoryLayout<Value>.size == 0 {
return (["\(root)"], unsafeBitCast((), to: Value.self))
}

Both enum Foo { case bar } and enum Foo {} have a memory layout of zero. So the unsafe bitcast works for the first but not for the second. In order to distinguish the two we need to look at the type metadata and count the number of cases.

There are a few libraries that accomplish this, including:

But adding one of these as a dependency is heavyweight, so it'd be better to make the check in a streamlined way. It is relatively straightforward to check if a given type is an enum:

enum MyType {}

let ptr = unsafeBitCast(MyType.self as Any.Type, to: UnsafeRawPointer.self)
ptr.load(as: Int.self) == 513 // true

But unpacking the rest of the work will take some time, so I'm going to put it aside. If anyone is interested in tackling this, please chime in!

`..` operator associativity

Hey,

I wonder why the CasePathCompositionPrecedence precedence group has configured right associativity.

Especially the shortcoming is visible to me in such case:

enum Case {
    case one(One)
    case none

    enum One {
        case two(Two)
        case none

        enum Two {
            case value(String)
            case none
        }
    }
}

let root = /Case.one..Case.One.two..Case.One.Two.value // error: cannot convert value of type '(Case.One.Two) -> Case.One' to expected argument type 'CasePath<Case.One, Case.One.Two>'

// brakets required
let root = (/Case.one..Case.One.two)..Case.One.Two.value // this compiles

If the associativity would be configured to be from left to right by:

precedencegroup CasePathCompositionPrecedence {
  associativity: left
}

then

let root = /Case.one..Case.One.two..Case.One.Two.value

compiles just fine.

EXC_BAD_ACCESS when extracting a `Result.success` from a failure, if the result is `Result<_, Error>`

This happens in 0.7.0, I haven't tried other releases.

I've encountered this crash in the wild, which I had to work around by not using case paths for any Result where the Failure generic type is Error. If you try to add this unit test to the test suite in this repo, you can reproduce it:

  func testExtractSuccessFromFailedResultWithErrorProtocolError() throws {
    struct MyError: Error {}
    let x = Result<String, Error>.failure(MyError()) 

    // since it's `Error`, execution traps at the following line with an EXC_BAD_ACCESS. 
    // If the  result type was over `MyError`, it would work correctly.
    XCTAssertNil((/Result.success).extract(from: x))
  }

There must be some swift runtime issue with Error protocol types vs concrete types that conform to Error, since it works if the type is Result<String, MyError> instead. I'm not too well versed in the swift runtime details to know how to fix this, except to work around it by not using case paths for these kinds of Result types for now.

If the value was a .success instead of a .failure, extraction does not crash, only when trying to extract a .success from this specific kind of Result if the value is a .failure. Extracting a .failure from this value also seems to work as expected.

Sendable Case Paths?

I've been getting some warnings when I capture a 'non-sendable' case path in a @Sendable closure.

It there any reason CasePath can't be marked Sendable where the Root is already Sendable? I think this will be okay, but would like to make sure

๐Ÿค”

Regression between 0.5.0 and 0.6.x

From 0.1.3 through 0.5.0 the following test passes. From 0.6.0 it fails.

Apologies for the somewhat verbose scenario for the test, I wanted it to be as close to what we are using in our project as possible. We are using this extraction in a TCA .scope().ifLet(then:else:) so it's causing the then block to always be executed when it should be executing the else block.

Our workaround for now is to hand code computed properties to determine if we are in a specific enum case.

It's possible this was not exactly the intended way of using CasePaths, but it is nevertheless, a regression.

import CasePaths
import XCTest

precedencegroup ForwardComposition {
    associativity: left
    higherThan: AssignmentPrecedence
}

infix operator >>>: ForwardComposition

/// Forward composition eg:
/// Instead of doing `anArray.map(String.init).map(Int.init)`
/// you can do `anArray.map(String.init >>> Int.init)`
public func >>> <A, B, C>(_ a2b: @escaping (A) -> B, _ b2c: @escaping (B) -> C) -> (A) -> C {
    { a in b2c(a2b(a)) }
}

/// this is a cut down version of a real state in our application
public struct AccountCreationState: Equatable {
    public struct Basic: Equatable {
        public enum Stage: Equatable {
            case basicAccount
            case basicAccountComplete(name: String)
            case checkingEmailVerifiedLoader
            case confirmingEmail(email: String)
        }

        public var stage: Stage
    }

    public struct Real: Equatable {
        enum Stage {
            case emailConfirmed
            case profileIntro
            case profileLearnMoreAboutBonus
            case usResidentCheck
            case nonUSResident
            case realAccount
        }

        var stage: Stage
    }

    public enum Stage: Equatable {
        case initial
        case basic(Basic)
        case real(Real)
    }

    public var stage: Stage = .initial
    public var basic: Basic? {
        get {
            (/AccountCreationState.Stage.basic).extract(from: stage)
        }
        set {
            guard let value = newValue else { return }
            stage = .basic(value)
        }
    }

    var real: Real? {
        get {
            (/AccountCreationState.Stage.real).extract(from: stage)
        }
        set {
            guard let value = newValue else { return }
            stage = .real(value)
        }
    }
}

final class BugCheck: XCTestCase {
    func testNestedExtraction() {
        let state = AccountCreationState(stage: .basic(.init(stage: .basicAccount)))

        // the objective is to return non-nil if the state is the basic stage, with the `.checkingEmailVerifiedLoader`
        // sub-state, and otherwise, to return nil
        let extractor: (AccountCreationState) -> Void? = \.basic?.stage >>> (/AccountCreationState.Basic.Stage.checkingEmailVerifiedLoader)

        // in Releases 0.1.3 through 0.5.0 this returns `nil`
        // in Releases 0.6.x this returns `Optional(())`
        let result: Void? = extractor(state)

        XCTAssertNil(result)
    }
}

New CasePath initializer doesn't work with optional Root

I've just noticed that CasePaths created with the new CasePath initializer are unable to extract values from an optional Root. The created CasePaths use extractHelp instead of optionalPromotedExtractHelp unlike their predecessors created with the / operator.

You can reproduce the problem by performing the following change in the testPathExtractFromOptionalRoot test from

let path: CasePath<Authentication?, String> = /Authentication.authenticated

to

let path: CasePath<Authentication?, String> = CasePath(Authentication.authenticated)

and see the test fail afterwards. I'd expect the result to be the same after the change. The behavior is the same in Xcode 13.4 and Xcode 14b2.

I'm not sure at the moment what the right course of action would be other than moving to static functions instead of the initializer and how this should be reflected in the unit tests.

Comparing extracted optional to non-optional value

This is a follow up to the regression discussed in #40 - the fix in #41 fixed the issues we were seeing with 0.5.0 when using a case path in a pullback, however I still had the following issue with this fix.

We have an interface that returns a Result<T?, Error> which we were using in a test assertion which is now failing unless I unwrap the optional. For example, given something like this:

func fetch() -> Result<String?, Error> {
  return .success("hello world")
}

The following assertion now fails, but used to pass:

XCTAssertEqual(
    "hello world",
    extract(case: Result.success, from: fetch())
)

However, this does pass:

XCTAssertEqual(
    .some("hello world"),
    extract(case: Result.success, from: fetch())
)

I can of course just do this:

XCTAssertEqual(.success("hello world"), fetch()))

I don't know if this is a blocker but I think its worth highlighting the behaviour change. @mayoff mentioned he had a possible fix for this.

EXC_BREAKPOINT from version 0.6.0

Hi,

i would like to ask you, if you have any idea what we are doing wrong. Some changes made from 0.5.0 to 0.6.0 cause us some issues. In our case it happens when we sent a action to present alert. I don't see anything special. The action has a basic, not complicated structure with few attributes as the associated value. The action is observed by parent reducer and it presents alert. I tried to found the changes made from 0.5.0 to 0.6.0 which can cause the problem. You can find them on the screenshot.

Thx for help.
Snรญmek obrazovky 2021-07-29 vย 13 36 52
Snรญmek obrazovky 2021-07-29 vย 13 51 34

Extraction from optional root

Hi,
updating from 0.2.0 to 0.5.0 resulted in an unexpected behavior for me. It seems that is no longer possible to extract a CasePath from from an optional value.
The following test case works fine in 0.2.0, but fails on 0.5.0:

  func testExtractFromOptionalRoot() {
    enum Authentication {
      case authenticated(token: String)
      case unauthenticated
    }
    
    // Fails
    let optionalAuthentication: Authentication? = .authenticated(token: "deadbeef")
    XCTAssertNotNil(extract(case: Authentication.authenticated, from: optionalAuthentication))
  }

It has probably something to do with moving away from Mirror, but i didn't dig deeper.

Would be good to know if you are aware of this change in the behavior and if it's intended.

Code Parser Can Fail When a Line Contains Both a CasePath Literal and a String Literal with a / Character

Describe the bug
If a single line in a Swift source file contains a case path literal, which is started with a /, and then later in the same line, a string literal contains a / character, the code parser will act strangely, highlighting the code incorrectly (as if everything between the two / characters is a string literal), and sometimes (but not always) fail to compile. The parser is probably assuming the two / are demarcating a regex literal.

I discovered this when I was trying to commit a file and kept getting an "unexpected end of file" error, which turned out to be coming from a pre-commit hook that runs a formatting tool. In that case, the file compiled fine, although the code was highlighted incorrectly. In the sample projected I am attaching, it fails to compile.

A simple workaround is to split the expression into multiple lines.

This is probably a bug in the parser, but I wanted to bring attention to it here first.

To Reproduce
Sample project attached shows the workaround and the problem. For whatever reason, where I originally encountered this in my code base, the compiler accepted it but then the parser in Xcode, and a tool that parses code, choked. But in this sample, the compiler chokes too.

TestCasePathParse.zip

    enum TestEnum {
        case someCase
    }

    func breakMe() {
        let working: (CasePath<TestEnum, Void>, String) = (
            /TestEnum.someCase,
             "Uh/Oh"
        )

        let broken: (CasePath<TestEnum, Void>, String) = (/TestEnum.someCase, "Uh/Oh")
                                                          
    }

Expected behavior
The parser should work the same way for a single line as it does when the expression is split onto multiple lines.

Screenshots
Screenshot 2023-07-25 at 1 11 52 PM

Environment

  • swift-case-paths: 0.14.1
  • Xcode: 14.3.1
  • Swift: 5.8
  • OS: N/A

using closure with parameters as associatedValue

first of all, sorry for my bad English

Describe the bug

when a closure with parameters is used as an associated value, parameters of extracted closure can't be received normally

To Reproduce

// And/or enter code that reproduces the behavior here.

func testEnumsWithParameterClosure() {
    enum Foo {
        case baz((Int) -> Void)
    }
    
    let arg = 1
    let closure: (Int) -> Void = { param in
        XCTAssertTrue(param == arg)
    }
    

    // if CasePath is not used
    guard case .baz(let extractedClosure) = Foo.baz(closure) else {
        XCTFail()
        return
    }
    extractedClosure(arg) // success


    // if CasePath is used
    guard let extracted = (/Foo.baz).extract(from: Foo.baz(closure)) else {
        XCTFail()
        return
    }
    extracted(arg) // fail
}

Expected behavior

param has the same value as arg(== 1)

Screenshots
If applicable, add screenshots to help explain your problem.

Environment

  • swift-case-paths [0.9.0]
  • Xcode [13.4]
  • Swift [5.6]
  • OS: [iOS 15]

Additional context
Add any more context about the problem here.

EXC_BAD_ACCESS extracting existential

This was partially solved in #53 but it seems that the fix is needed for all object-bound existential types.

Here's a rudimentary case that reproduces the crash (tested with Xcode 13.1, iOS 13.5 simulator):

private protocol TestObjectProtocol: AnyObject {}

func testObjectBoundExistentialPayload() {
  class Class: TestObjectProtocol {}
  enum Enum {
    case int(Int)
    case objectBoundExistential(TestObjectProtocol)
  }
  let intPath = /Enum.int
  let object = Class()
  for _ in 1...2 {
      XCTAssertNil(intPath.extract(from: .objectBoundExistential(object)))
  }
}

EXC_BAD_ACCESS in 0.6.0

Hey there,

I just recently bumped the case-path version to 0.6.0 and I seem to be hitting some weird issue. I'm relatively new to swift, so it could totally be something on my end that I'm doing wrong. The stack trace does look a bit peculiar.

Code that triggered the issue

public struct ConvoListCard: View {
    public var store: Store<ConvoState, ConvoAction>

    public init (store: Store<ConvoState, ConvoAction>) {
        self.store = store
    }
    
    public var body: some View {
        return WithViewStore(self.store) { viewStore in
            VStack(alignment: .leading, spacing: 6) {
               // ...
            }
            .onAppear {
                viewStore.send(.fetchConvo)
            }
        }
    }
}

Specifically, what seems to be the issue is

 viewStore.send(.fetchConvo)

Stack Trace

* thread #1, queue = 'com.apple.main-thread', stop reason = EXC_BAD_ACCESS (code=2, address=0x16d493ff0)
    frame #0: 0x00000001a78d04b4 libswiftCore.dylib`std::__1::pair<swift::HashMapElementWrapper<(anonymous namespace)::GenericCacheEntry>*, unsigned int> swift::ConcurrentReadableHashMap<swift::HashMapElementWrapper<(anonymous namespace)::GenericCacheEntry>, swift::StaticMutex>::find<swift::MetadataCacheKey>(swift::MetadataCacheKey const&, swift::ConcurrentReadableHashMap<swift::HashMapElementWrapper<(anonymous namespace)::GenericCacheEntry>, swift::StaticMutex>::IndexStorage, unsigned long, swift::HashMapElementWrapper<(anonymous namespace)::GenericCacheEntry>*) + 4
    frame #1: 0x00000001a78cdea4 libswiftCore.dylib`swift_checkMetadataState + 976
    frame #2: 0x00000001a7882124 libswiftCore.dylib`type metadata completion function for Swift.ClosedRange< where ฯ„_0_0: Swift.Strideable, ฯ„_0_0.Stride: Swift.SignedInteger>.Index + 28
    frame #3: 0x00000001a78d0ad4 libswiftCore.dylib`swift::MetadataCacheEntryBase<(anonymous namespace)::GenericCacheEntry, void const*>::doInitialization(swift::ConcurrencyControl&, swift::MetadataCompletionQueueEntry*, swift::MetadataRequest) + 308
    frame #4: 0x00000001a78c30a4 libswiftCore.dylib`_swift_getGenericMetadata(swift::MetadataRequest, void const* const*, swift::TargetTypeContextDescriptor<swift::InProcess> const*) + 1608
    frame #5: 0x00000001a78a3d00 libswiftCore.dylib`__swift_instantiateCanonicalPrespecializedGenericMetadata + 36
  * frame #6: 0x0000000102ce1100 Pineapple AF`Strategy.init(tag=3, assumedAssociatedValueType=Any) at EnumReflection.swift:292:50
    frame #7: 0x0000000102ce1120 Pineapple AF`Strategy.init(tag=3, assumedAssociatedValueType=Any) at EnumReflection.swift:292:50
    frame #8: 0x0000000102ce1120 Pineapple AF`Strategy.init(tag=3, assumedAssociatedValueType=Any) at EnumReflection.swift:292:50
   ...
   ...
    frame #2011: 0x0000000102ce1120 Pineapple AF`Strategy.init(tag=3, assumedAssociatedValueType=Any) at EnumReflection.swift:292:50
    frame #2012: 0x0000000102ce1120 Pineapple AF`Strategy.init(tag=3, assumedAssociatedValueType=Any) at EnumReflection.swift:292:50
    frame #2013: 0x0000000102ce1120 Pineapple AF`Strategy.init(tag=3, assumedAssociatedValueType=Any) at EnumReflection.swift:292:50
    frame #2014: 0x0000000102ce1120 Pineapple AF`Strategy.init(tag=3, assumedAssociatedValueType=Any) at EnumReflection.swift:292:50
    frame #2015: 0x0000000102ce1120 Pineapple AF`Strategy.init(tag=3, assumedAssociatedValueType=Any) at EnumReflection.swift:292:50
    frame #2016: 0x0000000102ce1120 Pineapple AF`Strategy.init(tag=3, assumedAssociatedValueType=Any) at EnumReflection.swift:292:50
    frame #2017: 0x0000000102ce1120 Pineapple AF`Strategy.init(tag=3, assumedAssociatedValueType=Any) at EnumReflection.swift:292:50
    frame #2018: 0x0000000102ce1120 Pineapple AF`Strategy.init(tag=3, assumedAssociatedValueType=Any) at EnumReflection.swift:292:50
    frame #2019: 0x0000000102ce1120 Pineapple AF`Strategy.init(tag=3, assumedAssociatedValueType=nil) at EnumReflection.swift:292:50
    frame #2020: 0x0000000102ce0388 Pineapple AF`closure #2 in extractHelp<Root, Value>(root=fetchConvoResponse, metadata=CasePaths.EnumMetadata @ 0x000000016d588428, cachedTag=nil, cachedStrategy=nil, embed=0x10309bf58) at EnumReflection.swift:149:44
    frame #2021: 0x0000000102cdcd24 Pineapple AF`CasePath.extract(root=fetchConvoResponse, self=(_embed = 0x000000010309bf58 Pineapple AF`partial apply forwarder for implicit closure #2 (ConvoFeature.MessagesAction) -> ConvoFeature.ConvoAction in implicit closure #1 (ConvoFeature.ConvoAction.Type) -> (ConvoFeature.MessagesAction) -> ConvoFeature.ConvoAction in variable initialization expression of ConvoFeature.convoReducer : ComposableArchitecture.Reducer<ConvoFeature.ConvoState, ConvoFeature.ConvoAction, ConvoFeature.ConvoEnvironment> at <compiler-generated>, _extract = 0x0000000102ce2038 Pineapple AF`partial apply forwarder for closure #2 (A) -> Swift.Optional<B> in CasePaths.extractHelp<A, B>((B) -> A) -> (A) -> Swift.Optional<B> at <compiler-generated>)) at CasePath.swift:32:10
    frame #2022: 0x0000000102c169d4 Pineapple AF`closure #1 in Reducer.pullback<State, Action, Environment>(globalState=ConvoFeature.ConvoState @ 0x000000010474d730, globalAction=fetchConvoResponse, globalEnvironment=ConvoFeature.ConvoEnvironment @ 0x000000016d588bb0, toLocalAction=(_embed = 0x000000010309bf58 Pineapple AF`partial apply forwarder for implicit closure #2 (ConvoFeature.MessagesAction) -> ConvoFeature.ConvoAction in implicit closure #1 (ConvoFeature.ConvoAction.Type) -> (ConvoFeature.MessagesAction) -> ConvoFeature.ConvoAction in variable initialization expression of ConvoFeature.convoReducer : ComposableArchitecture.Reducer<ConvoFeature.ConvoState, ConvoFeature.ConvoAction, ConvoFeature.ConvoEnvironment> at <compiler-generated>, _extract = 0x0000000102ce2038 Pineapple AF`partial apply forwarder for closure #2 (A) -> Swift.Optional<B> in CasePaths.extractHelp<A, B>((B) -> A) -> (A) -> Swift.Optional<B> at <compiler-generated>), self=(reducer = 0x0000000102c18d38 Pineapple AF`partial apply forwarder for closure #1 (inout Swift.Optional<A>, B, C) -> ComposableArchitecture.Effect<B, Swift.Never> in ComposableArchitecture.Reducer.optional(breakpointOnNil: Swift.Bool, _: Swift.StaticString, _: Swift.UInt) -> ComposableArchitecture.Reducer<Swift.Optional<A>, B, C> at <compiler-generated>), toLocalState=0x0000000281804930, toLocalEnvironment=0x10309b9ac) at Reducer.swift:282:45
    frame #2023: 0x0000000102c16d90 Pineapple AF`partial apply for closure #1 in Reducer.pullback<A, B, C>(state:action:environment:) at <compiler-generated>:0
    frame #2024: 0x0000000102c162cc Pineapple AF`closure #1 in closure #1 in static Reducer.combine($0=(reducer = 0x0000000102c16d18 Pineapple AF`partial apply forwarder for closure #1 (inout A1, B1, C1) -> ComposableArchitecture.Effect<B1, Swift.Never> in ComposableArchitecture.Reducer.pullback<A, B, C>(state: Swift.WritableKeyPath<A1, A>, action: CasePaths.CasePath<B1, B>, environment: (C1) -> C) -> ComposableArchitecture.Reducer<A1, B1, C1> at <compiler-generated>), value=ConvoFeature.ConvoState @ 0x000000010474d730, action=fetchConvoResponse, environment=ConvoFeature.ConvoEnvironment @ 0x000000016d588bb0) at Reducer.swift:173:32
    frame #2025: 0x0000000102c16324 Pineapple AF`thunk for @callee_guaranteed (@guaranteed Reducer<A, B, C>) -> (@owned Effect<B, Never>, @error @owned Error) at <compiler-generated>:0
    frame #2026: 0x0000000102c1cb54 Pineapple AF`partial apply for thunk for @callee_guaranteed (@guaranteed Reducer<A, B, C>) -> (@owned Effect<B, Never>, @error @owned Error) at <compiler-generated>:0
    frame #2027: 0x00000001a7550688 libswiftCore.dylib`Swift.Collection.map<ฯ„_0_0>((ฯ„_0_0.Element) throws -> ฯ„_1_0) throws -> Swift.Array<ฯ„_1_0> + 752
    frame #2028: 0x0000000102c16058 Pineapple AF`closure #1 in static Reducer.combine(value=ConvoFeature.ConvoState @ 0x000000010474d730, action=fetchConvoResponse, environment=ConvoFeature.ConvoEnvironment @ 0x000000016d589360, reducers=4 values) at Reducer.swift:173:23
    frame #2029: 0x0000000102c19d38 Pineapple AF`closure #1 in Reducer.forEach<State, Action, Environment, D>(globalState=ConvosFeature.ConvosState @ 0x000000016d58b7a8, globalAction=convo, globalEnvironment=ConvosFeature.ConvosEnvironment @ 0x000000016d589ce0, toLocalAction=(_embed = 0x00000001030c7f98 Pineapple AF`partial apply forwarder for reabstraction thunk helper from @escaping @callee_guaranteed (@guaranteed Swift.String, @in_guaranteed ConvoFeature.ConvoAction) -> (@out ConvosFeature.ConvosAction) to @escaping @callee_guaranteed (@in_guaranteed (Swift.String, ConvoFeature.ConvoAction)) -> (@out ConvosFeature.ConvosAction) at <compiler-generated>, _extract = 0x0000000102ce2038 Pineapple AF`partial apply forwarder for closure #2 (A) -> Swift.Optional<B> in CasePaths.extractHelp<A, B>((B) -> A) -> (A) -> Swift.Optional<B> at <compiler-generated>), toLocalState=0x0000000281804d20, breakpointOnNil=true, file="/Users/michaeldesa/go/src/github.com/pineappleaf/juicy/Shaka/Library/Sources/ConvosFeature/ConvosView.swift", line=87, self=(reducer = 0x0000000102c16218 Pineapple AF`partial apply forwarder for closure #1 (inout A, B, C) -> ComposableArchitecture.Effect<B, Swift.Never> in static ComposableArchitecture.Reducer.combine(Swift.Array<ComposableArchitecture.Reducer<A, B, C>>) -> ComposableArchitecture.Reducer<A, B, C> at <compiler-generated>), toLocalEnvironment=0x1030c7fe8) at Reducer.swift:799:10
    frame #2030: 0x0000000102c1a064 Pineapple AF`partial apply for closure #1 in Reducer.forEach<A, B, C, D>(state:action:environment:breakpointOnNil:_:_:) at <compiler-generated>:0
    frame #2031: 0x0000000102c162cc Pineapple AF`closure #1 in closure #1 in static Reducer.combine($0=(reducer = 0x0000000102c19fb8 Pineapple AF`partial apply forwarder for closure #1 (inout A1, B1, C1) -> ComposableArchitecture.Effect<B1, Swift.Never> in ComposableArchitecture.Reducer.forEach<A, B, C, D where D1: Swift.Hashable>(state: Swift.WritableKeyPath<A1, IdentifiedCollections.IdentifiedArray<D1, A>>, action: CasePaths.CasePath<B1, (D1, B)>, environment: (C1) -> C, breakpointOnNil: Swift.Bool, _: Swift.StaticString, _: Swift.UInt) -> ComposableArchitecture.Reducer<A1, B1, C1> at <compiler-generated>), value=ConvosFeature.ConvosState @ 0x000000016d58b7a8, action=convo, environment=ConvosFeature.ConvosEnvironment @ 0x000000016d589ce0) at Reducer.swift:173:32
    frame #2032: 0x0000000102c16324 Pineapple AF`thunk for @callee_guaranteed (@guaranteed Reducer<A, B, C>) -> (@owned Effect<B, Never>, @error @owned Error) at <compiler-generated>:0
    frame #2033: 0x0000000102c1cb54 Pineapple AF`partial apply for thunk for @callee_guaranteed (@guaranteed Reducer<A, B, C>) -> (@owned Effect<B, Never>, @error @owned Error) at <compiler-generated>:0
    frame #2034: 0x00000001a7550688 libswiftCore.dylib`Swift.Collection.map<ฯ„_0_0>((ฯ„_0_0.Element) throws -> ฯ„_1_0) throws -> Swift.Array<ฯ„_1_0> + 752
    frame #2035: 0x0000000102c16058 Pineapple AF`closure #1 in static Reducer.combine(value=ConvosFeature.ConvosState @ 0x000000016d58b7a8, action=convo, environment=ConvosFeature.ConvosEnvironment @ 0x000000016d58a0a0, reducers=2 values) at Reducer.swift:173:23
    frame #2036: 0x0000000102c16b10 Pineapple AF`closure #1 in Reducer.pullback<State, Action, Environment>(globalState=HomeFeature.HomeState @ 0x000000016d58b730, globalAction=convos, globalEnvironment=HomeFeature.HomeEnvironment @ 0x000000016d58a730, toLocalAction=(_embed = 0x0000000103105050 Pineapple AF`partial apply forwarder for implicit closure #2 (ConvosFeature.ConvosAction) -> HomeFeature.HomeAction in implicit closure #1 (HomeFeature.HomeAction.Type) -> (ConvosFeature.ConvosAction) -> HomeFeature.HomeAction in variable initialization expression of HomeFeature.homeReducer : ComposableArchitecture.Reducer<HomeFeature.HomeState, HomeFeature.HomeAction, HomeFeature.HomeEnvironment> at <compiler-generated>, _extract = 0x0000000102ce2038 Pineapple AF`partial apply forwarder for closure #2 (A) -> Swift.Optional<B> in CasePaths.extractHelp<A, B>((B) -> A) -> (A) -> Swift.Optional<B> at <compiler-generated>), self=(reducer = 0x0000000102c16218 Pineapple AF`partial apply forwarder for closure #1 (inout A, B, C) -> ComposableArchitecture.Effect<B, Swift.Never> in static ComposableArchitecture.Reducer.combine(Swift.Array<ComposableArchitecture.Reducer<A, B, C>>) -> ComposableArchitecture.Reducer<A, B, C> at <compiler-generated>), toLocalState=0x0000000281804db0, toLocalEnvironment=0x103104aa4) at Reducer.swift:283:19
    frame #2037: 0x0000000102c16d90 Pineapple AF`partial apply for closure #1 in Reducer.pullback<A, B, C>(state:action:environment:) at <compiler-generated>:0
    frame #2038: 0x0000000102c162cc Pineapple AF`closure #1 in closure #1 in static Reducer.combine($0=(reducer = 0x0000000102c16d18 Pineapple AF`partial apply forwarder for closure #1 (inout A1, B1, C1) -> ComposableArchitecture.Effect<B1, Swift.Never> in ComposableArchitecture.Reducer.pullback<A, B, C>(state: Swift.WritableKeyPath<A1, A>, action: CasePaths.CasePath<B1, B>, environment: (C1) -> C) -> ComposableArchitecture.Reducer<A1, B1, C1> at <compiler-generated>), value=HomeFeature.HomeState @ 0x000000016d58b730, action=convos, environment=HomeFeature.HomeEnvironment @ 0x000000016d58a730) at Reducer.swift:173:32
    frame #2039: 0x0000000102c16324 Pineapple AF`thunk for @callee_guaranteed (@guaranteed Reducer<A, B, C>) -> (@owned Effect<B, Never>, @error @owned Error) at <compiler-generated>:0
    frame #2040: 0x0000000102c1cb54 Pineapple AF`partial apply for thunk for @callee_guaranteed (@guaranteed Reducer<A, B, C>) -> (@owned Effect<B, Never>, @error @owned Error) at <compiler-generated>:0
    frame #2041: 0x00000001a7550688 libswiftCore.dylib`Swift.Collection.map<ฯ„_0_0>((ฯ„_0_0.Element) throws -> ฯ„_1_0) throws -> Swift.Array<ฯ„_1_0> + 752
    frame #2042: 0x0000000102c16058 Pineapple AF`closure #1 in static Reducer.combine(value=HomeFeature.HomeState @ 0x000000016d58b730, action=convos, environment=HomeFeature.HomeEnvironment @ 0x000000016d58ab30, reducers=4 values) at Reducer.swift:173:23
    frame #2043: 0x0000000102c16b10 Pineapple AF`closure #1 in Reducer.pullback<State, Action, Environment>(globalState=AppFeature.AppState @ 0x000000016d58b6c0, globalAction=home, globalEnvironment=AppFeature.AppEnvironment @ 0x000000016d58b1c0, toLocalAction=(_embed = 0x0000000102898b28 Pineapple AF`partial apply forwarder for implicit closure #6 (HomeFeature.HomeAction) -> AppFeature.AppAction in implicit closure #5 (AppFeature.AppAction.Type) -> (HomeFeature.HomeAction) -> AppFeature.AppAction in variable initialization expression of AppFeature.appReducer : ComposableArchitecture.Reducer<AppFeature.AppState, AppFeature.AppAction, AppFeature.AppEnvironment> at <compiler-generated>, _extract = 0x0000000102ce2038 Pineapple AF`partial apply forwarder for closure #2 (A) -> Swift.Optional<B> in CasePaths.extractHelp<A, B>((B) -> A) -> (A) -> Swift.Optional<B> at <compiler-generated>), self=(reducer = 0x0000000102c16218 Pineapple AF`partial apply forwarder for closure #1 (inout A, B, C) -> ComposableArchitecture.Effect<B, Swift.Never> in static ComposableArchitecture.Reducer.combine(Swift.Array<ComposableArchitecture.Reducer<A, B, C>>) -> ComposableArchitecture.Reducer<A, B, C> at <compiler-generated>), toLocalState=0x0000000281805140, toLocalEnvironment=0x102898554) at Reducer.swift:283:19
    frame #2044: 0x0000000102c16d90 Pineapple AF`partial apply for closure #1 in Reducer.pullback<A, B, C>(state:action:environment:) at <compiler-generated>:0
    frame #2045: 0x0000000102c162cc Pineapple AF`closure #1 in closure #1 in static Reducer.combine($0=(reducer = 0x0000000102c16d18 Pineapple AF`partial apply forwarder for closure #1 (inout A1, B1, C1) -> ComposableArchitecture.Effect<B1, Swift.Never> in ComposableArchitecture.Reducer.pullback<A, B, C>(state: Swift.WritableKeyPath<A1, A>, action: CasePaths.CasePath<B1, B>, environment: (C1) -> C) -> ComposableArchitecture.Reducer<A1, B1, C1> at <compiler-generated>), value=AppFeature.AppState @ 0x000000016d58b6c0, action=home, environment=AppFeature.AppEnvironment @ 0x000000016d58b1c0) at Reducer.swift:173:32
    frame #2046: 0x0000000102c16324 Pineapple AF`thunk for @callee_guaranteed (@guaranteed Reducer<A, B, C>) -> (@owned Effect<B, Never>, @error @owned Error) at <compiler-generated>:0
    frame #2047: 0x0000000102c1cb54 Pineapple AF`partial apply for thunk for @callee_guaranteed (@guaranteed Reducer<A, B, C>) -> (@owned Effect<B, Never>, @error @owned Error) at <compiler-generated>:0
    frame #2048: 0x00000001a7550688 libswiftCore.dylib`Swift.Collection.map<ฯ„_0_0>((ฯ„_0_0.Element) throws -> ฯ„_1_0) throws -> Swift.Array<ฯ„_1_0> + 752
    frame #2049: 0x0000000102c16058 Pineapple AF`closure #1 in static Reducer.combine(value=AppFeature.AppState @ 0x000000016d58b6c0, action=home, environment=AppFeature.AppEnvironment @ 0x0000000282a7d238, reducers=3 values) at Reducer.swift:173:23
    frame #2050: 0x0000000102c18478 Pineapple AF`Reducer.run(state=AppFeature.AppState @ 0x000000016d58b6c0, action=home, environment=AppFeature.AppEnvironment @ 0x0000000282a7d238, self=(reducer = 0x0000000102c16218 Pineapple AF`partial apply forwarder for closure #1 (inout A, B, C) -> ComposableArchitecture.Effect<B, Swift.Never> in static ComposableArchitecture.Reducer.combine(Swift.Array<ComposableArchitecture.Reducer<A, B, C>>) -> ComposableArchitecture.Reducer<A, B, C> at <compiler-generated>)) at Reducer.swift:889:10
    frame #2051: 0x0000000102c1d3d4 Pineapple AF`closure #1 in Store.init<State>(state=AppFeature.AppState @ 0x000000016d58b6c0, action=home, reducer=(reducer = 0x0000000102c16218 Pineapple AF`partial apply forwarder for closure #1 (inout A, B, C) -> ComposableArchitecture.Effect<B, Swift.Never> in static ComposableArchitecture.Reducer.combine(Swift.Array<ComposableArchitecture.Reducer<A, B, C>>) -> ComposableArchitecture.Reducer<A, B, C> at <compiler-generated>), environment=AppFeature.AppEnvironment @ 0x0000000282a7d238) at Store.swift:136:47
    frame #2052: 0x0000000102c1e784 Pineapple AF`Store.send(action=home, self=0x0000000283567ac0) at Store.swift:381:25
    frame #2053: 0x0000000102c1e058 Pineapple AF`closure #1 in Store.scope<State, Action>(localState=HomeFeature.HomeState @ 0x000000016d58c4e0, localAction=convos, _0=() @ scalar, isSending=true, self=0x0000000283567ac0, fromLocalAction=0x102886794, toLocalState=0x10288678c) at Store.swift:288:14
    frame #2054: 0x0000000102c1e268 Pineapple AF`partial apply for closure #1 in Store.scope<A, B>(state:action:) at <compiler-generated>:0
    frame #2055: 0x0000000102c1ebc0 Pineapple AF`thunk for @escaping @callee_guaranteed (@inout A1, @in_guaranteed B1) -> (@owned Effect<B1, Never>) at <compiler-generated>:0
    frame #2056: 0x0000000102c1ec74 Pineapple AF`partial apply for thunk for @escaping @callee_guaranteed (@inout A1, @in_guaranteed B1) -> (@owned Effect<B1, Never>) at <compiler-generated>:0
    frame #2057: 0x0000000102c18478 Pineapple AF`Reducer.run(state=HomeFeature.HomeState @ 0x000000016d58c4e0, action=convos, environment=() @ 0xffffffffffffffff, self=(reducer = 0x0000000102c1ec28 Pineapple AF`partial apply forwarder for reabstraction thunk helper <A, B><A1, B1> from @escaping @callee_guaranteed (@inout A1, @in_guaranteed B1) -> (@owned ComposableArchitecture.Effect<B1, Swift.Never>) to @escaping @callee_guaranteed (@inout A1, @in_guaranteed B1, @in_guaranteed ()) -> (@owned ComposableArchitecture.Effect<B1, Swift.Never>) at <compiler-generated>)) at Reducer.swift:889:10
    frame #2058: 0x0000000102c1d3d4 Pineapple AF`closure #1 in Store.init<State>(state=HomeFeature.HomeState @ 0x000000016d58c4e0, action=convos, reducer=(reducer = 0x0000000102c1ec28 Pineapple AF`partial apply forwarder for reabstraction thunk helper <A, B><A1, B1> from @escaping @callee_guaranteed (@inout A1, @in_guaranteed B1) -> (@owned ComposableArchitecture.Effect<B1, Swift.Never>) to @escaping @callee_guaranteed (@inout A1, @in_guaranteed B1, @in_guaranteed ()) -> (@owned ComposableArchitecture.Effect<B1, Swift.Never>) at <compiler-generated>), environment=() @ 0xffffffffffffffff) at Store.swift:136:47
    frame #2059: 0x0000000102c1e784 Pineapple AF`Store.send(action=convos, self=0x000000028355e580) at Store.swift:381:25
    frame #2060: 0x0000000102c1e058 Pineapple AF`closure #1 in Store.scope<State, Action>(localState=ConvosFeature.ConvosState @ 0x000000016d58d0c0, localAction=convo, _0=() @ scalar, isSending=true, self=0x000000028355e580, fromLocalAction=0x103103a88, toLocalState=0x1030f2444) at Store.swift:288:14
    frame #2061: 0x0000000102c1e268 Pineapple AF`partial apply for closure #1 in Store.scope<A, B>(state:action:) at <compiler-generated>:0
    frame #2062: 0x0000000102c1ebc0 Pineapple AF`thunk for @escaping @callee_guaranteed (@inout A1, @in_guaranteed B1) -> (@owned Effect<B1, Never>) at <compiler-generated>:0
    frame #2063: 0x0000000102c1ec74 Pineapple AF`partial apply for thunk for @escaping @callee_guaranteed (@inout A1, @in_guaranteed B1) -> (@owned Effect<B1, Never>) at <compiler-generated>:0
    frame #2064: 0x0000000102c18478 Pineapple AF`Reducer.run(state=ConvosFeature.ConvosState @ 0x000000016d58d0c0, action=convo, environment=() @ 0xffffffffffffffff, self=(reducer = 0x0000000102c1ec28 Pineapple AF`partial apply forwarder for reabstraction thunk helper <A, B><A1, B1> from @escaping @callee_guaranteed (@inout A1, @in_guaranteed B1) -> (@owned ComposableArchitecture.Effect<B1, Swift.Never>) to @escaping @callee_guaranteed (@inout A1, @in_guaranteed B1, @in_guaranteed ()) -> (@owned ComposableArchitecture.Effect<B1, Swift.Never>) at <compiler-generated>)) at Reducer.swift:889:10
    frame #2065: 0x0000000102c1d3d4 Pineapple AF`closure #1 in Store.init<State>(state=ConvosFeature.ConvosState @ 0x000000016d58d0c0, action=convo, reducer=(reducer = 0x0000000102c1ec28 Pineapple AF`partial apply forwarder for reabstraction thunk helper <A, B><A1, B1> from @escaping @callee_guaranteed (@inout A1, @in_guaranteed B1) -> (@owned ComposableArchitecture.Effect<B1, Swift.Never>) to @escaping @callee_guaranteed (@inout A1, @in_guaranteed B1, @in_guaranteed ()) -> (@owned ComposableArchitecture.Effect<B1, Swift.Never>) at <compiler-generated>), environment=() @ 0xffffffffffffffff) at Store.swift:136:47
    frame #2066: 0x0000000102c1e784 Pineapple AF`Store.send(action=convo, self=0x000000028355e260) at Store.swift:381:25
    frame #2067: 0x0000000102c1e058 Pineapple AF`closure #1 in Store.scope<State, Action>(localState=ConvoFeature.ConvoState @ 0x000000016d58db60, localAction=fetchConvo, _0=() @ scalar, isSending=true, self=0x000000028355e260, fromLocalAction=0x1030be2a4, toLocalState=0x1030bddc8) at Store.swift:288:14
    frame #2068: 0x0000000102c1e268 Pineapple AF`partial apply for closure #1 in Store.scope<A, B>(state:action:) at <compiler-generated>:0
    frame #2069: 0x0000000102c1ebc0 Pineapple AF`thunk for @escaping @callee_guaranteed (@inout A1, @in_guaranteed B1) -> (@owned Effect<B1, Never>) at <compiler-generated>:0
    frame #2070: 0x0000000102c1ec74 Pineapple AF`partial apply for thunk for @escaping @callee_guaranteed (@inout A1, @in_guaranteed B1) -> (@owned Effect<B1, Never>) at <compiler-generated>:0
    frame #2071: 0x0000000102c18478 Pineapple AF`Reducer.run(state=ConvoFeature.ConvoState @ 0x000000016d58db60, action=fetchConvo, environment=() @ 0xffffffffffffffff, self=(reducer = 0x0000000102c1ec28 Pineapple AF`partial apply forwarder for reabstraction thunk helper <A, B><A1, B1> from @escaping @callee_guaranteed (@inout A1, @in_guaranteed B1) -> (@owned ComposableArchitecture.Effect<B1, Swift.Never>) to @escaping @callee_guaranteed (@inout A1, @in_guaranteed B1, @in_guaranteed ()) -> (@owned ComposableArchitecture.Effect<B1, Swift.Never>) at <compiler-generated>)) at Reducer.swift:889:10
    frame #2072: 0x0000000102c1d3d4 Pineapple AF`closure #1 in Store.init<State>(state=ConvoFeature.ConvoState @ 0x000000016d58db60, action=fetchConvo, reducer=(reducer = 0x0000000102c1ec28 Pineapple AF`partial apply forwarder for reabstraction thunk helper <A, B><A1, B1> from @escaping @callee_guaranteed (@inout A1, @in_guaranteed B1) -> (@owned ComposableArchitecture.Effect<B1, Swift.Never>) to @escaping @callee_guaranteed (@inout A1, @in_guaranteed B1, @in_guaranteed ()) -> (@owned ComposableArchitecture.Effect<B1, Swift.Never>) at <compiler-generated>), environment=() @ 0xffffffffffffffff) at Store.swift:136:47
    frame #2073: 0x0000000102c1e784 Pineapple AF`Store.send(action=fetchConvo, self=0x000000028355d400) at Store.swift:381:25
    frame #2074: 0x0000000102cd9aac Pineapple AF`implicit closure #2 in implicit closure #1 in ViewStore.init(action=fetchConvo, self=0x000000028355d400) at ViewStore.swift:73:24
    frame #2075: 0x0000000102cd9fdc Pineapple AF`ViewStore.send(action=fetchConvo, self=0x0000000280306340) at ViewStore.swift:110:10
    frame #2076: 0x00000001030b0c44 Pineapple AF`closure #2 in closure #1 in ConvoListCard.body.getter(viewStore=0x0000000280306340) at ConvoListCard.swift:64:27
    frame #2077: 0x00000001aa3cd03c SwiftUI`reabstraction thunk helper from @escaping @callee_guaranteed () -> () to @escaping @callee_guaranteed () -> (@out ()) + 28
    frame #2078: 0x00000001aa3cd064 SwiftUI`reabstraction thunk helper from @escaping @callee_guaranteed () -> (@out ()) to @escaping @callee_guaranteed () -> () + 28
    frame #2079: 0x00000001aa3cd03c SwiftUI`reabstraction thunk helper from @escaping @callee_guaranteed () -> () to @escaping @callee_guaranteed () -> (@out ()) + 28
    frame #2080: 0x00000001aa3b5eb8 SwiftUI`static SwiftUI.Update.end() -> () + 504
    frame #2081: 0x00000001aa1de3c4 SwiftUI`static __C.NSRunLoop.flushObservers() -> () + 176
    frame #2082: 0x00000001aa1de30c SwiftUI`closure #1 () -> () in closure #1 (Swift.Optional<__C.CFRunLoopObserverRef>, __C.CFRunLoopActivity, Swift.Optional<Swift.UnsafeMutableRawPointer>) -> () in static __C.NSRunLoop.addObserver(() -> ()) -> () + 16
    frame #2083: 0x00000001aa1d9284 SwiftUI`function signature specialization <Arg[1] = [Constant Propagated Function : closure #1 () -> () in closure #1 (Swift.Optional<__C.CFRunLoopObserverRef>, __C.CFRunLoopActivity, Swift.Optional<Swift.UnsafeMutableRawPointer>) -> () in static (extension in SwiftUI):__C.NSRunLoop.addObserver(() -> ()) -> ()]> of reabstraction thunk helper from @callee_guaranteed () -> (@error @owned Swift.Error) to @escaping @callee_guaranteed () -> (@out (), @error @owned Swift.Error) + 24
    frame #2084: 0x00000001c9fe0f24 libswiftObjectiveC.dylib`ObjectiveC.autoreleasepool<ฯ„_0_0>(invoking: () throws -> ฯ„_0_0) throws -> ฯ„_0_0 + 64
    frame #2085: 0x00000001aa1de2ec SwiftUI`closure #1 (Swift.Optional<__C.CFRunLoopObserverRef>, __C.CFRunLoopActivity, Swift.Optional<Swift.UnsafeMutableRawPointer>) -> () in static __C.NSRunLoop.addObserver(() -> ()) -> () + 64
    frame #2086: 0x00000001aa1de444 SwiftUI`@objc closure #1 (Swift.Optional<__C.CFRunLoopObserverRef>, __C.CFRunLoopActivity, Swift.Optional<Swift.UnsafeMutableRawPointer>) -> () in static __C.NSRunLoop.addObserver(() -> ()) -> () + 56
    frame #2087: 0x00000001a39535e0 CoreFoundation`__CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__ + 36
    frame #2088: 0x00000001a394d704 CoreFoundation`__CFRunLoopDoObservers + 572
    frame #2089: 0x00000001a394dcb0 CoreFoundation`__CFRunLoopRun + 1052
    frame #2090: 0x00000001a394d360 CoreFoundation`CFRunLoopRunSpecific + 600
    frame #2091: 0x00000001baf8b734 GraphicsServices`GSEventRunModal + 164
    frame #2092: 0x00000001a63c8584 UIKitCore`-[UIApplication _run] + 1072
    frame #2093: 0x00000001a63cddf4 UIKitCore`UIApplicationMain + 168
    frame #2094: 0x00000001aa8a3370 SwiftUI`closure #1 (Swift.UnsafeMutablePointer<Swift.Optional<Swift.UnsafeMutablePointer<Swift.Int8>>>) -> Swift.Never in SwiftUI.KitRendererCommon(Swift.AnyObject.Type) -> Swift.Never + 112
    frame #2095: 0x00000001aa8a32fc SwiftUI`SwiftUI.runApp<ฯ„_0_0 where ฯ„_0_0: SwiftUI.App>(ฯ„_0_0) -> Swift.Never + 224
    frame #2096: 0x00000001aa39bb6c SwiftUI`static SwiftUI.App.main() -> () + 144
    frame #2097: 0x0000000102879764 Pineapple AF`static AppApp.$main(self=Pineapple_AF.AppApp) at AppApp.swift:39:1
    frame #2098: 0x0000000102879870 Pineapple AF`main at AppApp.swift:0
    frame #2099: 0x00000001a3609cf8 libdyld.dylib`start + 4

Composing CaseKeyPaths and KeyPaths

Thanks to the new macro implementation, CaseKeyPaths are naturally composable via dot syntax just as KeyPaths, but when trying to compose CaseKeyPaths with normal KeyPaths unfortunately the compiler errors out.

@CasePathable
enum PaymentType {
	case creditCard(CreditCard)
}

struct CreditCard {
	var number: String
	// ...
}

\PaymentType.Cases.creditCard.number // ๐Ÿ›‘ Generic parameter 'AppendedValue' could not be inferred
                                     // ๐Ÿ›‘ Subscript 'subscript(dynamicMember:)' requires that 'CreditCard' conform to 'CasePathable'

Is this a technical limitation that can't be circumvented, or something pending to be implemented in the future perhaps?

Potential performance bottleneck when using reflection.

I'm reporting an issue I had with TCA. My app was lagging a lot and instruments showed that a bunch of the time was spent on the main thread computing debug description strings. This is caused by Mirror(reflecting:) which is used when trying to reassign local cases (EnumReflection.swift#L61).

I have huge data structs, with many containers and NSAttributedString, and generating the debug description doesn't come for free. The app is quite complex, but in the current state, ~80% of its work on the main thread is spent extracting CasePath's. I temporarily improved the situation by carped-bombing my data structs with a some protocol extending CustomReflectable and providing a very small default implementation, but this is far from optimal.

I think that this kind of call is not expected to happen on the main thread, furthermore at a high frequency rate like it happens in a reducer.

Maybe the result can be cached, or another approach to reflection can be used, with some kind of (optional) protocol or prescription over Action enums?

Xcode 14 beta 1: issues with Regex and Xcode previews

Note: If you've encountered any of the issues mentioned below, please share the details on the Swift forums as outlined here.

If you've tried to use Case Paths in the first Xcode 14 beta, you may have noticed a few issues around the Regex literals. Please note that these are not issues with Case Paths the library, and are instead more general bugs with Apple's software.

While Apple appears to be aware of these issues, feel free to file feedback if any of them affect you, and we can hope for a speedy resolution in the next beta.

Issues (and some workarounds) are as follows:

  • Xcode previews in files that contain case paths will fail to run. This is a known issue. A workaround involves introducing a trailing comment to any line that contains a case path:

    -.compactMap(/Result.success)
    +.compactMap(/Result.success)  // NB: comment works around Xcode 14b1 previews bug
  • Usage of case paths can break Xcode syntax highlighting and indentation. There's no known workaround at this time (outside of using Xcode 13, instead), but if you find one, please share in a comment!

  • Xcode projects may default to "bare" syntax, which can break usage of case paths, especially when multiple are used on the same line. To fix, explicitly wrap any additional case path that appears on a single line with parentheses:

    -.pullback(state: /AppState.login, action: /AppAction.login)
    +.pullback(state: /AppState.login, action: (/AppAction.login))

    Or rewrite the same expressions over multiple lines to avoid having more than one case path per line:

    -.pullback(state: /AppState.login, action: /AppAction.login)
    +.pullback(
    +  state: /AppState.login,
    +  action: /AppAction.login
    +)

Related discussions:

Xcode 14 beta 1 - CasePath prefix / syntax

Hello guys,

It's not really a library-related bug but we should submit Feedback at Apple for an editor bug.
Since regex has been introduced in the new Xcode, all indentation / coloration stuff is totally broken when we use case paths.

For example :
Screenshot 2022-06-07 at 16 44 29

All the coloration below the case path is broken, and you can see the cursor in the screenshot : it's indenting there just when we hit enter after the function... Totally weird.

And it's even worse in reducers with multiple pullbacks, I let you imagine...

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.