Git Product home page Git Product logo

swift-overture's Introduction

๐ŸŽผ Overture

CI

A library for function composition.

Table of Contents

Motivation

We work with functions all the time, but function composition is hiding in plain sight!

For instance, we work with functions when we use higher-order methods, like map on arrays:

[1, 2, 3].map { $0 + 1 }
// [2, 3, 4]

If we wanted to modify this simple closure to square our value after incrementing it, things begin to get messy.

[1, 2, 3].map { ($0 + 1) * ($0 + 1) }
// [4, 9, 16]

Functions allow us to identify and extract reusable code. Let's define a couple functions that make up the behavior above.

func incr(_ x: Int) -> Int {
  return x + 1
}

func square(_ x: Int) -> Int {
  return x * x
}

With these functions defined, we can pass them directly to map!

[1, 2, 3]
  .map(incr)
  .map(square)
// [4, 9, 16]

This refactor reads much better, but it's less performant: we're mapping over the array twice and creating an intermediate copy along the way! While we could use lazy to fuse these calls together, let's take a more general approach: function composition!

[1, 2, 3].map(pipe(incr, square))
// [4, 9, 16]

The pipe function glues other functions together! It can take more than two arguments and even change the type along the way!

[1, 2, 3].map(pipe(incr, square, String.init))
// ["4", "9", "16"]

Function composition lets us build new functions from smaller pieces, giving us the ability to extract and reuse logic in other contexts.

let computeAndStringify = pipe(incr, square, String.init)

[1, 2, 3].map(computeAndStringify)
// ["4", "9", "16"]

computeAndStringify(42)
// "1849"

The function is the smallest building block of code. Function composition gives us the ability to fit these blocks together and build entire apps out of small, reusable, understandable units.

Examples

pipe

The most basic building block in Overture. It takes existing functions and smooshes them together. That is, given a function (A) -> B and a function (B) -> C, pipe will return a brand new (A) -> C function.

let computeAndStringify = pipe(incr, square, String.init)

computeAndStringify(42)
// "1849"

[1, 2, 3].map(computeAndStringify)
// ["4", "9", "16"]

with and update

The with and update functions are useful for applying functions to values. They play nicely with the inout and mutable object worlds, wrapping otherwise imperative configuration statements in an expression.

class MyViewController: UIViewController {
  let label = updateObject(UILabel()) {
    $0.font = .systemFont(ofSize: 24)
    $0.textColor = .red
  }
}

And it restores the left-to-right readability we're used to from the method world.

with(42, pipe(incr, square, String.init))
// "1849"

Using an inout parameter.

update(&user, mut(\.name, "Blob"))

concat

The concat function composes with single types. This includes composition of the following function signatures:

  • (A) -> A
  • (inout A) -> Void
  • <A: AnyObject>(A) -> Void

With concat, we can build powerful configuration functions from small pieces.

let roundedStyle: (UIView) -> Void = {
  $0.clipsToBounds = true
  $0.layer.cornerRadius = 6
}

let baseButtonStyle: (UIButton) -> Void = {
  $0.contentEdgeInsets = UIEdgeInsets(top: 12, left: 16, bottom: 12, right: 16)
  $0.titleLabel?.font = .systemFont(ofSize: 16, weight: .medium)
}

let roundedButtonStyle = concat(
  baseButtonStyle,
  roundedStyle
)

let filledButtonStyle = concat(roundedButtonStyle) {
  $0.backgroundColor = .black
  $0.tintColor = .white
}

let button = with(UIButton(type: .system), filledButtonStyle)

curry, flip, and zurry

These functions make up the Swiss army knife of composition. They give us the power to take existing functions and methods that don't compose (e.g, those that take zero or multiple arguments) and restore composition.

For example, let's transform a string initializer that takes multiple arguments into something that can compose with pipe.

String.init(data:encoding:)
// (Data, String.Encoding) -> String?

We use curry to transform multi-argument functions into functions that take a single input and return new functions to gather more inputs along the way.

curry(String.init(data:encoding:))
// (Data) -> (String.Encoding) -> String?

And we use flip to flip the order of arguments. Multi-argument functions and methods typically take data first and configuration second, but we can generally apply configuration before we have data, and flip allows us to do just that.

flip(curry(String.init(data:encoding:)))
// (String.Encoding) -> (Data) -> String?

Now we have a highly-reusable, composable building block that we can use to build pipelines.

let stringWithEncoding = flip(curry(String.init(data:encoding:)))
// (String.Encoding) -> (Data) -> String?

let utf8String = stringWithEncoding(.utf8)
// (Data) -> String?

Swift also exposes methods as static, unbound functions. These functions are already in curried form. All we need to do is flip them to make them more useful!

String.capitalized
// (String) -> (Locale?) -> String

let capitalized = flip(String.capitalized)
// (Locale?) -> (String) -> String

["hello, world", "and good night"]
  .map(capitalized(Locale(identifier: "en")))
// ["Hello, World", "And Good Night"]

And zurry restores composition for functions and methods that take zero arguments.

String.uppercased
// (String) -> () -> String

flip(String.uppercased)
// () -> (String) -> String

let uppercased = zurry(flip(String.uppercased))
// (String) -> String

["hello, world", "and good night"]
  .map(uppercased)
// ["HELLO, WORLD", "AND GOOD NIGHT"]

get

The get function produces getter functions from key paths.

get(\String.count)
// (String) -> Int

["hello, world", "and good night"]
  .map(get(\.count))
// [12, 14]

We can even compose other functions into get by using the pipe function. Here we build a function that increments an integer, squares it, turns it into a string, and then gets the string's character count:

pipe(incr, square, String.init, get(\.count))
// (Int) -> Int

prop

The prop function produces setter functions from key paths.

let setUserName = prop(\User.name)
// ((String) -> String) -> (User) -> User

let capitalizeUserName = setUserName(capitalized(Locale(identifier: "en")))
// (User) -> User

let setUserAge = prop(\User.age)

let celebrateBirthday = setUserAge(incr)
// (User) -> User

with(User(name: "blob", age: 1), concat(
  capitalizeUserName,
  celebrateBirthday
))
// User(name: "Blob", age: 2)

over and set

The over and set functions produce (Root) -> Root transform functions that work on a Value in a structure given a key path (or setter function).

The over function takes a (Value) -> Value transform function to modify an existing value.

let celebrateBirthday = over(\User.age, incr)
// (User) -> User

The set function replaces an existing value with a brand new one.

with(user, set(\.name, "Blob"))

mprop, mver, and mut

The mprop, mver and mut functions are mutable variants of prop, over and set.

let guaranteeHeaders = mver(\URLRequest.allHTTPHeaderFields) { $0 = $0 ?? [:] }

let setHeader = { name, value in
  concat(
    guaranteeHeaders,
    { $0.allHTTPHeaderFields?[name] = value }
  )
}

let request = update(
  URLRequest(url: url),
  mut(\.httpMethod, "POST"),
  setHeader("Authorization", "Token " + token),
  setHeader("Content-Type", "application/json; charset=utf-8")
)

zip and zip(with:)

This is a function that Swift ships with! Unfortunately, it's limited to pairs of sequences. Overture defines zip to work with up to ten sequences at once, which makes combining several sets of related data a snap.

let ids = [1, 2, 3]
let emails = ["[email protected]", "[email protected]", "[email protected]"]
let names = ["Blob", "Blob Junior", "Blob Senior"]

zip(ids, emails, names)
// [
//   (1, "[email protected]", "Blob"),
//   (2, "[email protected]", "Blob Junior"),
//   (3, "[email protected]", "Blob Senior")
// ]

It's common to immediately map on zipped values.

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

zip(ids, emails, names).map(User.init)
// [
//   User(id: 1, email: "[email protected]", name: "Blob"),
//   User(id: 2, email: "[email protected]", name: "Blob Junior"),
//   User(id: 3, email: "[email protected]", name: "Blob Senior")
// ]

Because of this, Overture provides a zip(with:) helper, which takes a tranform function up front and is curried, so it can be composed with other functions using pipe.

zip(with: User.init)(ids, emails, names)

Overture also extends the notion of zip to work with optionals! It's an expressive way of combining multiple optionals together.

let optionalId: Int? = 1
let optionalEmail: String? = "[email protected]"
let optionalName: String? = "Blob"

zip(optionalId, optionalEmail, optionalName)
// Optional<(Int, String, String)>.some((1, "[email protected]", "Blob"))

And zip(with:) lets us transform these tuples into other values.

zip(with: User.init)(optionalId, optionalEmail, optionalName)
// Optional<User>.some(User(id: 1, email: "[email protected]", name: "Blob"))

Using zip can be an expressive alternative to let-unwrapping!

let optionalUser = zip(with: User.init)(optionalId, optionalEmail, optionalName)

// vs.

let optionalUser: User?
if let id = optionalId, let email = optionalEmail, let name = optionalName {
  optionalUser = User(id: id, email: email, name: name)
} else {
  optionalUser = nil
}

FAQ

  • Should I be worried about polluting the global namespace with free functions?

    Nope! Swift has several layers of scope to help you here.

    • You can limit exposing highly-specific functions beyond a single file by using fileprivate and private scope.
    • You can define functions as static members inside types.
    • You can qualify functions with the module's name (e.g., Overture.pipe(f, g)). You can even autocomplete free functions from the module's name, so discoverability doesn't have to suffer!
  • Are free functions that common in Swift?

    It may not seem like it, but free functions are everywhere in Swift, making Overture extremely useful! A few examples:

    • Initializers, like String.init.
    • Unbound methods, like String.uppercased.
    • Enum cases with associated values, like Optional.some.
    • Ad hoc closures we pass to map, filter, and other higher-order methods.
    • Top-level Standard Library functions like max, min, and zip.

Installation

You can add Overture to an Xcode project by adding it as a package dependency.

https://github.com/pointfreeco/swift-overture

If you want to use Overture in a SwiftPM project, it's as simple as adding it to a dependencies clause in your Package.swift:

dependencies: [
  .package(url: "https://github.com/pointfreeco/swift-overture", from: "0.5.0")
]

๐ŸŽถ Prelude

This library was created as an alternative to swift-prelude, which is an experimental functional programming library that uses infix operators. For example, pipe is none other than the arrow composition operator >>>, which means the following are equivalent:

xs.map(incr >>> square)
xs.map(pipe(incr, square))

We know that many code bases are not going to be comfortable introducing operators, so we wanted to reduce the barrier to entry for embracing function composition.

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 ideas in this episode were first explored in Episode #11:

video poster image

License

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

swift-overture's People

Contributors

a2 avatar avdwerff avatar divinedominion avatar ferologics avatar fredericruaudel avatar iamszabo avatar jbarros35 avatar mbrandonw avatar nekrich avatar ollitapa avatar robjaq avatar ruiaaperes avatar saltylight avatar stephencelis avatar ugoarangino avatar vknabel 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  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

swift-overture's Issues

Compilation failure using mut on a navigation controller

A bit confused with the following code:

  let baseNavigationBarStyle: (UINavigationBar) -> Void = { ... }

    var navigationController: UINavigationController = update(
      UINavigationController(rootViewController: controller),
      mver(\.navigationBar, baseNavigationBar)
    )

fails to compile:

Key path value type 'WritableKeyPath<UINavigationController, UINavigationBar>' cannot be converted to contextual type 'KeyPath<UINavigationController, UINavigationBar>'

The following works fine:

let baseNavigationBarStyle: (UINavigationBar) -> Void = { ... }

let baseNavigationBar: (inout UINavigationBar) -> Void = {
  with($0, baseNavigationBarStyle)
}
var navigationController = UINavigationController(rootViewController: controller)

with(navigationController.navigationBar, baseNavigationBarStyle)

Adding the operators counterpart

Small intro, before getting to the point:

From some of tweets @stephencelis sent and the overall idea of this library, is to make FP more accessible. There is also this tweet:

If our Prelude library seems a lil too intense with all of its custom operators, then perhaps its Overture is more your tune!

โ€” Point-Free (@pointfreeco) April 9, 2018

There is indeed a big difference between them, both in terms of size, but also necessary knowledge to use one over the other.

What I propose is to provide the operators counterparts of these functions (e.g. pipe, composition). When an Overture user feels confident about using operators, he/she can still use Overture to make that switch, instead of importing Prelude.

In my particular case, I use a very small subset of what Prelude provides, so Overture actually makes sense for my use case. Not having operators, is upsetting.

What do you think?

Currying higher order functions obscures type.

Swift (4.2.1 and 5.0-SNAPSHOT 12/28/18) seems to get really confused about the types of curry'd higher-order functions:

func lowerOrder(_ x: String, _ y: String) { }
lowerOrder is (String, String) -> Void //> true
curry(lowerOrder) is (String) -> (String) -> Void //> true

func higherOrder(_ x: String, _ y: (String) -> Int) { }
higherOrder is (String, (String) -> Int) -> Void //> true
curry(higherOrder) is (String) -> ((String) -> Int) -> Void //> false
print(type(of: curry(higherOrder))) //> "(String) -> ((String) -> Int) -> ()\n" 

Is this a problem with curry? A problem with Swift? A problem with me not grokking some subtlety of function types?

`with` is different from how it's portrayed in readme

Issue

Considering the example code from the readme:
image

Expected behavior
Expected label to have type UILabel

Actual behavior
label is of type Void:
image

Current workaround

adding return $0 below $0.textColor = .red however, this results in a lot of unneeded boilerplate when with is used often.

Proposed solution

Replacing with(UILabel()) with update(UILabel) in the readme. Since update does result in the expected behavior.

Throwing overloads

First of all: thank you for your great work!

Currently there are no overloads for chain, compose, pipe, concat, ... that work with throwing functions.

If you want, I can open a PR that adds these overloads.

Redefine `set` without `over`

While it's really nice that set can be derived from over, the implicit key-path get is not only doing extra work, but can crash when dealing with implicitly-unwrapped optionals.

let formatter = NumberFormatter()
formatter.locale = Locale(identifier: "")
with(formatter, set(\.currencyCode, "USD"))
// crash due to `currencyCode` being a `String!`

We should redefine our setters to not leverage over.

Compile times of with, concat & mut functions in swift4 are very high!!

I inherited some code, and compile time was crazy (90s - 120s, incremental. 3.5 min new) . So i ran
https://github.com/RobertGummesson/BuildTimeAnalyzer-for-Xcode

on our codebase and all the functions or lazy vars that used concat, mut, with from swift overture were taking considerable time to compile.

For ex. this stackview getter was taking >5 seconds to compile!!!!

var stackView = with(UIStackView(), concat(
        mut(\.axis, .vertical),
        mut(\.isLayoutMarginsRelativeArrangement, true),
        mut(\.layoutMargins, UIEdgeInsets(top: 0, left: 16, bottom: 0, right: 16)),
        mut(\.spacing, 16),
        mut(\.alignment, .center)
    ))

Some other examples:

var titleLabel: UILabel = with(UILabel(), concat(
        autolayoutStyle,
        mut(\.font, .sg_font(size: 24, weight: .bold)),
        mut(\.textAlignment, .center),
        mut(\.numberOfLines, 0),
        mut(\.lineBreakMode, .byWordWrapping)
        )
    )

    var promptLabel: UILabel = with(UILabel(), concat(
        autolayoutStyle,
        mut(\.font, .sg_font(size: 14, weight: .regular)),
        mut(\.textAlignment, .center),
        mut(\.numberOfLines, 0),
        mut(\.lineBreakMode, .byWordWrapping)
        )
    )

    var stackView: UIStackView = with(UIStackView(), concat(
        autolayoutStyle,
        mut(\.axis, .vertical),
        mut(\.spacing, 7),
        mut(\.isLayoutMarginsRelativeArrangement, true),
        mut(\.layoutMargins, .init(top: 29, left: 16, bottom: 27, right: 16))
        )
    )

As this is a new framework/library for me, I'd love to hear if I'm the only one experiencing this?

Add @discardableResult to update()

Currently @discardableResult is set on the AnyObject update methods. I think having @discardableResult everywhere would would clear up the warnings result of call 'update' is not used:
image

WDYT?

Potentially add `rethrows` to `over` and similar functions

Context

I'm trying to update some code that I think could really benefit from a functional style. Here's the current code:

let keyPaths: [WritableKeyPath<Config, String>] // ...

for keyPath in keyPaths {
  config[keyPath: keyPath] = try evaluator.evaluateExpression(config[keyPath: keyPath])
}

The problem

I've tried a few different approaches and none of them compile because evaluateExpression is a throwing function, and the overture functions I'm trying to use don't have rethrows. Here's my best attempt:

let evaluators = keyPaths.map { over($0, evaluator.evaluateExpression) }

for evaluateField in evaluators {
  config = try evaluateField(config)
}

Potential solutions

  1. I'm missing something and there's a better way to do this with a function that does have rethrows
  2. Add rethrows to all of the methods except where it doesn't make sense (I feel like over and co make sense to have rethrows)

getting EXC_BAD_ACCESS while calling overture methods in static vars

here are some examples of EXC_BAD_ACCESS crashes i'm getting while running my unit tests locally

image

image

I fixed the later one by changing:

static var burningDesirePostItem = with(BurningDesireFeedCellItem(), concat(
        mut(\.post, .mockRegular)
    ))

to

    static var burningDesirePostItem : BurningDesireFeedCellItem = {
        let burningDesireItem = BurningDesireFeedCellItem()
        burningDesireItem.post = SGPost.mockRegular
        return burningDesireItem
    }()

But I'm not sure if i even fully understand what the problem is? I'm assuming that in the static instances, something hasn't been setup for overture, which is expecting to be called in instance methods?

The former one i can't seem to figure out a good way to fix - it seems the last line in:

static let mock = {
        return with(
            CoachingAPI(),
            concat(
                mut(\.selectedCoachId, { return .init(String(Coach.mockCoaches.first!.id)) }),
                mut(\.subscriptionStatus, { return .enabled }),
                mut(\.fetchCoaches, { $0(.success(Coach.mockCoaches)) })
            )
        )
    }()

Is it effectively mocking out the return of fetchCoaches function on CoachingAPI?

iOS target for Carthage

I know, SwiftPM does not support iOS yet, but do you want to support it for Carthage?

Currently Carthage works fine until one tries to create an archive and distribute it. Builds work and run on device via Xcode. When converting archive using bitcode option I get this odd error:

 warning: Failed to resolve linkage dependency Overture arm64 -> @rpath/libswiftCore.dylib: Could not find MachO for /Applications/Xcode-10.1.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/swift/macosx/libswiftCore.dylib

Somehow Overture tries to link to macosx libs of Swift. I was able to resolve this by manually making a separate iOS framework target to the xcodeproj. Not an optimal solution. Any ideas?

Is there a better way to style UINavigation's bar appearance?

Loved your Overture showcase video and started to apply to one of my project.

I was wondering if there was a better way to initialise \UINavigationBar.*Appearance to avoid the verbosity and stuttering?

let baseNavigationBarStyle = concat(
  mut(\UINavigationBar.standardAppearance, .init()),
  mut(\UINavigationBar.compactAppearance, .init()),
  mut(\UINavigationBar.scrollEdgeAppearance, .init()),

  mver(\UINavigationBar.standardAppearance, opaqueNavigationBar),
  mver(\UINavigationBar.compactAppearance!, opaqueNavigationBar),
  mver(\UINavigationBar.scrollEdgeAppearance!, opaqueNavigationBar),

  mut(\UINavigationBar.titleTextAttributes, .init()),
  mut(\UINavigationBar.largeTitleTextAttributes, .init()),
  mut(\UINavigationBar.prefersLargeTitles, true)
)

Also setting those titleTextAttributes was a bit confusing.

Here is the full code so far:

import Foundation
import Overture
import AsyncDisplayKit
import UIKit

let neutralTintColor = mut(\UIView.tintColor, .label)

// TabBar
let baseTabBarStyle = concat(
  neutralTintColor,
  mut(\UITabBar.standardAppearance, .init()),
  mver(\UITabBar.standardAppearance, opaqueTabBar)
)

let opaqueTabBar: (inout UITabBarAppearance) -> Void = {
  with($0, baseBarAppearance)
}

let opaqueNavigationBar: (inout UINavigationBarAppearance) -> Void = {
  with($0, baseBarAppearance)
}

// NavigationBar
let baseNavigationBarStyle = concat(
  mut(\UINavigationBar.standardAppearance, .init()),
  mut(\UINavigationBar.compactAppearance, .init()),
  mut(\UINavigationBar.scrollEdgeAppearance, .init()),

  mver(\UINavigationBar.standardAppearance, opaqueNavigationBar),
  mver(\UINavigationBar.compactAppearance!, opaqueNavigationBar),
  mver(\UINavigationBar.scrollEdgeAppearance!, opaqueNavigationBar),

  mut(\UINavigationBar.titleTextAttributes, .init()),
  mut(\UINavigationBar.largeTitleTextAttributes, .init()),
  mut(\UINavigationBar.prefersLargeTitles, true)
)

// UIBarAppearance
let baseBarAppearance = concat(
  mut(\UIBarAppearance.shadowColor, nil),
  { $0.configureWithOpaqueBackground() }
)

let baseNavigationBarAppearance = concat(
  baseBarAppearance,
  mut(\UINavigationBarAppearance.largeTitleTextAttributes, labelTextAttribute()),
  mut(\UINavigationBarAppearance.titleTextAttributes, labelTextAttribute())
)

let labelTextAttribute: () -> [NSAttributedString.Key : Any] = {
  [.foregroundColor: UIColor.label]
}

// UIView
let baseNavigationBar: (inout UINavigationBar) -> Void = {
  with($0, baseNavigationBarStyle)
}

Ambiguous use of `with`

on latest version (xcode 10.1, Swift4.2, Overture 0.3.1), even the basic example from your readme fails:

import Overture
class MyViewController: UIViewController {
    let label = with(UILabel()) {
        $0.font = .systemFont(ofSize: 24)
        $0.textColor = .red
    }
}

I think theres ambiguity between the standard and inout versions of with. I would have to annotate the type, e.g let with(UILabel) { (l: inout UILabel) in ...} to fix it.

But its such an obvious issue, Im sure you or others must have seen it. Any suggestions?

zip(with:) are not public ?

Hi!

First, thanks for this great library, very helpful!

I was wondering why all the zip(with:) methods on sequence are not public ? Is it intentional? if yes, why?

Xcode requiring `@escaping` for closure passed to `update`

Writing a function that uses update, and the compiler complains that I have to annotate my closure as @escaping to use it with update, but update doesn't declare the parameter as @escaping. Any idea why this is or how to work around this? I can definitely make my updateClient parameter @escaping, but it seems like this should be unnecessary.

    @MainActor open func testStore(
        initialState: Component.State = .mock,
        updateClient: (inout Component.Client) -> Void
    ) -> TestStoreOf<Component> where Component.State: Equatable & HasMock, Component.Client: HasMock {
        Component.testStore(
            initialState: initialState,
            client: update(.mock, updateClient) // Passing non-escaping parameter 'updateClient' to function expecting an @escaping closure
        )
    }

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.