Focus is an Optics library for Swift (where Optics includes Lens, Prisms, and Isos)

Related tags

Utility Focus
Overview

Carthage compatible Build Status Gitter chat

Focus

Focus is an Optics library for Swift (where Optics includes Lens, Prisms, and Isos) that is inspired by Haskell's Lens library.

Introduction

Focus exports a number of primitives that make it easy to establish relations between types. Practically, a relation can be thought of as a particular way of viewing and modifying a structure. The most famous of these is a Lens or Functional Reference. While there are an abundance of representations of a Lens (see [van Laarhoven 09], [Kmett et al. 12], [Eidhof et al. 09], we have chosen a data-lens-like implementation using the Indexed Store Comonad. If all of that makes no sense, don't worry! We have hidden all of this behind a simple interface.

Programming With Lenses

The easiest way to explain a lens is with a pair of functions

func get(structure : S) -> A
func set(pair : (self : S, newValue : A)) -> S

This should look quite familiar to you! After all, Swift includes syntax for this very pattern

final class Foo {
	var bar : Qux {
		get { //.. }
		set(newValue) { //.. }
	}
}

So what a lens actually lets you do is decouple the ability to focus on particular bits and pieces of your data types. Moreover, lenses, like properties, compose freely with other compatible lenses but with normal function composition (denoted ) instead of the usual dot-notation. What sets Lenses apart from straight properties is every part of the process is immutable. A lens performs replacement of the entire structure, a property performs replacement of a mutable value within that structure.

All of these properties, flexibility immutability, and composability, come together to enable a powerful set of operations that allow the programmer to view a structure and its parts at any depth and any angle, not simply those provided by properties.

Practical Lenses

For example, say we have this set of structures for working with a flight tracking app:

import Foundation
import Focus

enum Status {
	case Early
	case OnTime
	case Late
}

struct Plane {
	let model : String
	let freeSeats : UInt
	let takenSeats : UInt
	let status : Status
	var totalSeats : UInt {
		return self.freeSeats + self.takenSeats
	}
}

struct Gate {
	let number : UInt
	let letter : Character
}

struct BoardingPass {
	let plane : Plane
	let gate : Gate
	let departureDate : NSDate
	let arrivalDate : NSDate
}

Starting with a BoardingPass, getting our flight status is trivial

let plane = Plane(model: "SpaceX Raptor", freeSeats: 4, takenSeats: 0, status: .OnTime)
let gate = Gate(number: 1, letter: "A")
let pass = BoardingPass(plane: plane
					, gate: gate
					, departureDate: NSDate.distantFuture()
					, arrivalDate: NSDate.distantFuture())
let status = pass.plane.status

However, in order to update the status on the boarding pass without lenses, we'd have to go through this rigamarole every time:

let oldPass = BoardingPass(/* */)
// Apparently, we're actually flying Allegiant
let newFlight = Plane(model: oldPass.plane.model
					, freeSeats: oldPass.plane.freeSeats
					, takenSeats: oldPass.plane.takenSeats
					, status: .Late)
let newPass = BoardingPass(plane: newFlight
						, gate: oldPass.gate
						, departureDate: oldPass.departureDate
						, arrivalDate: oldPass.arrivalDate)

After defining a few lenses, this is what we can do instead:

// The composite of two lenses is itself a lens
let newPass = (BoardingPass._plane  Plane._status).set(oldPass, .Late)

Here's the definition of those lenses:

extension BoardingPass {
	static var _plane : SimpleLens {
		return SimpleLens(get: {
			return $0.plane
		}, set: { (oldPass, newP) in
			return BoardingPass(plane: newP
							, gate: oldPass.gate
							, departureDate: oldPass.departureDate
							, arrivalDate: oldPass.arrivalDate)
		})
	}
}

extension Plane {
	static var _status : SimpleLens {
		return SimpleLens(get: {
			return $0.status
		}, set: { (oldP, newS) in
			return Plane( model: oldP.model
						, freeSeats: oldP.freeSeats
						, takenSeats: oldP.takenSeats
						, status: newS)
		})
	}
}

We've only scratched the surface of the power of Lenses, and we haven't even touched the other members of the family of optics exported by Focus. For more on the Lens Family, check out the additional sources below and the implementation files for each family member.

Further Reading

System Requirements

Focus supports OS X 10.9+ and iOS 8.0+.

License

Focus is released under the MIT license.

Comments
  • Use vars to enable terser lens declarations

    Use vars to enable terser lens declarations

    Is there any reason vars shouldn't be used in data model structs? The struct will still be immutable in the sense that there is no shared mutable state, and it would permit lenses to be expressed without having the call the struct's initializer for each individual lens.

    opened by marcprux 5
  • Optics protocols

    Optics protocols

    This pull request introduces the IsoType, LensType, PrismType, and OpticFamilyType protocols, and moves a lot of functionality to extensions on them rather than methods on the underlying structs.

    This shouldn’t affect any current usages of the library, other than no longer requiring asLens or asPrism calls (thus, those functions have been removed). However, it will allow for much easier expansion in the future, as well as greater fluidity of the interface when it comes to the “multiple inheritance” of various optic types.

    opened by pthariensflame 5
  • Monomorphic Lenses?

    Monomorphic Lenses?

    The other Swift Lens framework, Monocle, has taken to a data-accessor-esque implementation of Lenses, and I think there's something to the idea of including a record-accessor structure with Focus. Especially since Swift doesn't allow polymorphic type aliases, and Monomorphic lenses are just easier to reason about for the general Swift programmer.

    opened by CodaFi 5
  • Add Setter implementation

    Add Setter implementation

    Let me know if there is a different approach you had in mind. I could also use some feedback on making the documentation summary more complete and clear.

    opened by r-peck 4
  • Test With SwiftCheck

    Test With SwiftCheck

    I am thoroughly disappointed with the way this turned out. I need to think a bit more about how to generate inverse functions with SwiftCheck before this gets merged.

    opened by CodaFi 4
  • Set

    Set "Require Only App-Extension-Safe API" to YES

    Without this setting, any application extensions will warn on linking with this framework and can result in app store rejection. Adding the setting only prevents use of APIs that are unavailable in the context of an extension (such as UIApplication.sharedApplication() on iOS).

    opened by r-peck 2
  • [WIP]Documentation Modernization

    [WIP]Documentation Modernization

    The majority of changes in this pull request have to do with the README. It was always bothering me that we just kept around the same old thing from Swiftz and didn't bother to change it. I intend this as a work in progress, so additions, removals, changes, everything, are open.

    opened by CodaFi 2
  • ENABLE_BITCODE set to false during restructuring for SPM

    ENABLE_BITCODE set to false during restructuring for SPM

    Adding an issue for this before submitting a PR since I'm not sure if disabling was intentional or not. If it was intentional, was there a reason for disabling it? It seems like being in the Xcode project settings it would not impact SPM builds.

    opened by r-peck 1
  • Focus no longer builds after change to dependencies in Package.swift.

    Focus no longer builds after change to dependencies in Package.swift.

    Fetching https://github.com/typelift/Focus error: the package https://github.com/typelift/Focus @ 0.4.0 contains revisioned dependencies: https://github.com/typelift/SwiftCheck.git @ master https://github.com/typelift/Operadics.git @ master

    opened by BrianDoig 0
  • importing Focus and Swiftz cause operators such as ||| to fail to compile

    importing Focus and Swiftz cause operators such as ||| to fail to compile

    import Swiftz 
    import Focus
    
    func fanin<A, B, C>(_ l: @escaping (A) -> C, _ r: @escaping (B) -> C) -> Function<Either<A, B>, C> {
        return Function.arr(l) ||| Function.arr(r)
    }
    

    The above fails to compile and there doesn't seem to be a way to specify which library you want the operator to come from since Swiftz.||| fails to compile.

    ***: error: ambiguous operator declarations found for operator return Function.arr(l) ||| Function.arr(r) ^ :0: note: found this matching operator declaration :0: note: found this matching operator declaration ***: error: operator is not a known binary operator return Function.arr(l) ||| Function.arr(r)

    opened by BrianDoig 4
  • Optional traversal

    Optional traversal

    Is there no way to lens through an optional? I've tried with Party.lpartyCaterer() • User.userName and Party.lpartyCaterer() • _Some • User.userName, to no avail.

    // A party has a host, an optional caterer and an array of guests
    class Party {
        var host : User
        var caterer : User?
        var guests : [User]
    
        init(h : User, c : User? = nil, g : [User] = []) {
            host = h
            caterer = c
            guests = g
        }
    
        class func lpartyHost() -> Lens<Party, Party, User, User> {
            let getter = { (party : Party) -> User in party.host }
            let setter = { (party : Party, host : User) -> Party in Party(h: host, c: party.caterer, g: party.guests) }
            return Lens(get: getter, set: setter)
        }
    
        class func lpartyCaterer() -> Lens<Party, Party, User?, User?> {
            let getter = { (party : Party) -> User? in party.caterer }
            let setter = { (party : Party, caterer : User?) -> Party in Party(h: party.host, c: caterer, g: party.guests) }
            return Lens(get: getter, set: setter)
        }
    
        class func lpartyGuests() -> Lens<Party, Party, [User], [User]> {
            let getter = { (party : Party) -> [User] in party.guests }
            let setter = { (party : Party, guests : [User]) -> Party in Party(h: party.host, c: party.caterer, g: guests) }
            return Lens(get: getter, set: setter)
        }
    }
    
    class PartySpec : XCTestCase {
        func testLens() {
            let party = Party(h: User("max", 1, [], "one"))
            let hostnameLens = Party.lpartyHost() • User.userName
    
            XCTAssert(hostnameLens.get(party) == "max")
    
            let updatedParty: Party = (Party.lpartyHost() • User.userName).set(party, "Max")
            XCTAssert(hostnameLens.get(updatedParty) == "Max")
    
            let catererLens = Party.lpartyCaterer() • User.userName
    
            let cateredParty: Party = (Party.lpartyCaterer() • User.userName).set(party, "Marc")
            XCTAssert(catererLens.get(updatedParty) == "Marc")
    
        }
    }
    
    opened by marcprux 6
Releases(0.4.0)
  • 0.4.0(Sep 23, 2017)

    ⚠️ Breaking Changes Ahead ⚠️

    Focus now builds with Xcode 9.0 and will be developed in Swift 4.0. While this required no changes to the public API, it means that Xcode 8 toolchains are no longer supported.

    Source code(tar.gz)
    Source code(zip)
  • 0.3.2(Apr 3, 2017)

  • 0.3.1(Sep 18, 2016)

  • v0.2.0(Mar 22, 2016)

  • v0.1.1(Jan 4, 2016)

  • v0.1.0(Sep 17, 2015)

  • v0.0.2(Jul 14, 2015)

    This release extracts the Lens Family into a number of protocols and protocol extensions to enable more general future versions of Focus.

    Source code(tar.gz)
    Source code(zip)
  • v0.0.1(Jul 5, 2015)

    Focus is an Optics library for Swift that includes Lenses, Prisms, Isos, and the underlying Indexed Monads and Comonads that make it all happen.

    The Lens family is a useful way of describing first-class traversals, references, and relationships between data structures and their component parts. In Swift, this presents a fantastic opportunity to recast setters and getters of even mutable objects as immutable views.

    Source code(tar.gz)
    Source code(zip)
Owner
TypeLift
Libraries to simplify development of Swift programs by utilising the type system.
TypeLift
Small app that checks focus status under macOS 12

infocus What Small app for Mac Admins that checks focus status under macOS 11 and 12 and can be used to add Do Not Disturb support to management scrip

Bart Reardon 12 Nov 8, 2022
Another Virtualization.framework demo project, with focus to iBoot (WIP)

Virtual iBoot Fun This is just another Virtualization.framework sample project (WIP), but with focus on iBoot (iOS/macOS/tvOS/etc. bootloader) For a m

john 119 Dec 7, 2022
Sync Slack status to macOS Monterey Focus mode

SyncFocusWithSlack Sync Slack status to macOS Monterey Focus mode ⚠️ This app ac

Yusuf Özgül 14 Nov 23, 2022
Cross-Platform, Protocol-Oriented Programming base library to complement the Swift Standard Library. (Pure Swift, Supports Linux)

SwiftFoundation Cross-Platform, Protocol-Oriented Programming base library to complement the Swift Standard Library. Goals Provide a cross-platform in

null 620 Oct 11, 2022
A Swift micro library for generating Sunrise and Sunset times.

Solar A Swift helper for generating Sunrise and Sunset times. Solar performs its calculations locally using an algorithm from the United States Naval

Chris Howell 493 Dec 25, 2022
Plugin and runtime library for using protobuf with Swift

Swift Protobuf Welcome to Swift Protobuf! Apple's Swift programming language is a perfect complement to Google's Protocol Buffer ("protobuf") serializ

Apple 4.1k Dec 28, 2022
A Swift package for rapid development using a collection of micro utility extensions for Standard Library, Foundation, and other native frameworks.

ZamzamKit ZamzamKit is a Swift package for rapid development using a collection of micro utility extensions for Standard Library, Foundation, and othe

Zamzam Inc. 261 Dec 15, 2022
A cross-platform library of Swift utils to ease your iOS | macOS | watchOS | tvOS and Linux development.

Mechanica A library of Swift utils to ease your iOS, macOS, watchOS, tvOS and Linux development. Requirements Documentation Installation License Contr

Alessandro 28 Aug 28, 2022
A μframework of extensions for SequenceType in Swift 2.0, inspired by Python's itertools, Haskell's standard library, and other things.

SwiftSequence Full reference here. (If you're looking for data structures in Swift, those have been moved to here) SwiftSequence is a lightweight fram

Donnacha Oisín Kidney 376 Oct 12, 2022
Swift library and command line tool that interacts with the mach-o file format.

MachO-Reader Playground project to learn more about the Mach-O file format. How to run swift run MachO-Reader <path-to-binary> You should see a simila

Gonzalo 5 Jun 25, 2022
A Swift library for converting to and from opus/ogg format

swift-ogg This library provides a very simple API to convert between opus/ogg and MPEG4AAC/m4a files. It uses opus's libopus libogg to convert between

Element 13 Dec 31, 2022
swift-highlight a pure-Swift data structure library designed for server applications that need to store a lot of styled text

swift-highlight is a pure-Swift data structure library designed for server applications that need to store a lot of styled text. The Highlight module is memory-efficient and uses slab allocations and small-string optimizations to pack large amounts of styled text into a small amount of memory, while still supporting efficient traversal through the Sequence protocol.

kelvin 4 Aug 14, 2022
ZIP Foundation is a library to create, read and modify ZIP archive files.

ZIP Foundation is a library to create, read and modify ZIP archive files. It is written in Swift and based on Apple's libcompression for high performa

Thomas Zoechling 1.9k Dec 27, 2022
Butterfly is a lightweight library for integrating bug-report and feedback features with shake-motion event.

Butterfly is a lightweight library for integrating bug-report and feedback features with shake-motion event. Goals of this project One of th

Zigii Wong 410 Sep 9, 2022
📘A library for isolated developing UI components and automatically taking snapshots of them.

A library for isolated developing UI components and automatically taking snapshots of them. Playbook Playbook is a library that provides a sandbox for

Playbook 969 Dec 27, 2022
Hammer is a touch, stylus and keyboard synthesis library for emulating user interaction events

Hammer is a touch, stylus and keyboard synthesis library for emulating user interaction events. It enables better ways of triggering UI actions in unit tests, replicating a real world environment as much as possible.

Lyft 626 Dec 23, 2022
Pigeon is a SwiftUI and UIKit library that relies on Combine to deal with asynchronous data.

Pigeon ?? Introduction Pigeon is a SwiftUI and UIKit library that relies on Combine to deal with asynchronous data. It is heavily inspired by React Qu

Fernando Martín Ortiz 369 Dec 30, 2022
μ-library enabling if/else and switch statements to be used as expressions.

swift-expression Many languages such as Scala, Rust and Kotlin support using if/else and switch statements as expressions – meaning that they can by t

Nikita Mounier 1 Nov 8, 2021
macOS system library in Swift

SystemKit A macOS system library in Swift based off of libtop, from Apple's top implementation. For an example usage of this library, see dshb, a macO

null 323 Jan 5, 2023