Skip to content

Commit

Permalink
CONTRIBUTING file
Browse files Browse the repository at this point in the history
  • Loading branch information
kenbot committed Feb 24, 2017
1 parent 736060f commit e917346
Show file tree
Hide file tree
Showing 2 changed files with 147 additions and 26 deletions.
59 changes: 59 additions & 0 deletions CONTRIBUTING.md
@@ -0,0 +1,59 @@
# Contributor guidelines
If you are considering contributing - thank you! Your time is much appreciated.

## Features
New features have a good chance of being rejected; the limited scope of
the library is one of its strengths. Before submitting a PR, please read
the "Design principles" section below, and raise an issue for discussion.

## Bugs
Patches welcome! Unhelpful compilation error messages are bugs,
and will be given high priority.

## PRs
The usual hygiene stuff. Keep your commits focused on a single task,
avoid messing up whitespace, keep the imports clean, run the tests.

## Design principles
Please try to align your feature requests and PRs with
these general principles.

### "You already know how to use it"
Ease-of-use as a "missing piece of the puzzle" in lens libraries is
a central motivation for Goggles. The syntax used in the
string format should be familiar to developers, and preferably
attested in other popular languages or technologies.

### Incomplete is fine
Goggles does not aspire to provide syntax to cover Monocle's
entire surface area; features should be chosen based on their
utility and whether they can be represented with familiar syntax
attested elsewhere. For additional features, Monocle can be used
in the usual way.

### First class error messages
Helpful, detailed error messages are a first-class requirement
of Goggles functionality. The messages should help beginners
understand and discover optics concepts, while not hindering expert users.

Because we know a lot more about what the user is trying to
achieve here than scalac does, we can construct much better
messages using the OpticInfo data that the macro accumulates
internally.

### Anti-magic
The behaviour of the DSL should not be surprising, nor feel "magic".
In particular, this means that Scala language elements must behave like
normal Scala; the macro should not pun on code-tree structure for semantics.

### Compile-time over run-time complexity
Where a tradeoff is required, it is preferable that the macro code accrue
complexity than the code that is generated to be run by the user. For example,
compile-time reflection is acceptable, but runtime reflection is not.

### Tests
As many cases as possible should be tested, using Scalacheck where sensible
invariants can be discovered. Compilation errors should be directly
tested, using the `testdsl` front end. End-to-end tests (ie actually running
the macro) are preferred, but unit tests can be added where they clarify
local functionality.
114 changes: 88 additions & 26 deletions README.md
@@ -1,6 +1,8 @@
# Goggles
## Principled, typesafe optics DSL
Optics libraries are either too limited, or too hard to use. Goggles builds on Scala's powerful Monocle library, making immutability easy, fun, and boring, like it should be.
Optics libraries are either too limited, or too hard to use.
Goggles builds on Scala's powerful Monocle library, making
immutability easy, fun, and boring, like it should be.

You already know how to use it.

Expand All @@ -25,17 +27,23 @@ set"$myBakery.cakes*.toppings[0].cherries" := 7
// Cake(Nil)))

```
The DSL runs in the compiler, and is completely typesafe. It generates plain Monocle code.
The DSL runs in the compiler, and is completely typesafe.
It generates plain Monocle code.

## Motivation
### 1. Functional programming needs optics
In imperative programming, a game world might be updated like this:
In imperative programming, a game world might be updated like this:
```
game.currentLevel.player.health += 20
```
Even just holding a reference to the player, we can be confident that the change will be seen by everyone observing, without knowing anything about the external environment. However, mutability conveys a severe penalty in complexity of behaviour, and our human ability to reason about the code.

On the other hand, naively using immutable structures leads to unfortunate problems.
Even just holding a reference to the player, we can be confident
that the change will be seen by everyone observing, without
knowing anything about the external environment. However,
mutability conveys a severe penalty in complexity of
behaviour, and our human ability to reason about the code.

On the other hand, naively using immutable structures leads
to unfortunate problems.
```
game.copy(currentLevel =
game.currentLevel.copy(player =
Expand All @@ -45,22 +53,42 @@ On the other hand, naively using immutable structures leads to unfortunate probl
)
)
```
Ugly, yes, but it gets worse: recreating the object graph is a dire failure of modularity. We must now know exactly where the object sits in the world-structure, and how to recreate every detail up to the root. Modularity is supposed to be a flagship benefit of FP - how embarrassing!
Ugly, yes, but it gets worse: recreating the object graph is
a dire failure of modularity. We must now know exactly where
the object sits in the world-structure, and how to recreate
every detail up to the root. Modularity is supposed to be
a flagship benefit of FP - how embarrassing!

_Optics_ are a family of pure-functional techniques that model access and mutation with composable abstractions. They are the best answer that has emerged to this dilemma; without them FP is dismally unsuited to a range of mundane problems.
_Optics_ are a family of pure-functional techniques that
model access and mutation with composable abstractions.
They are the best answer that has emerged to this
dilemma; without them FP is dismally unsuited to a
range of mundane problems.


### 2. Power vs ease-of-use. Why choose?
The modifying-immutable-structures problem has been addressed in a variety of ways.
The modifying-immutable-structures problem has been addressed
in a variety of ways.

Dynamic environments such as Clojure, jq, and Javascript have features that allow easy manipulation of structures without mutation, but in a very constrained, domain-specific context.
Dynamic environments such as Clojure, jq, and Javascript have
features that allow easy manipulation of structures without
mutation, but in a very constrained, domain-specific context.

Haskell's `Control.Lens` is wonderfully powerful and abstract, but has a reputation for being difficult to learn and use. Why can't we have our cake and eat it too?
Haskell's `Control.Lens` is wonderfully powerful and abstract,
but has a reputation for being difficult to learn and use.
Why can't we have our cake and eat it too?

### 3. Monocle + Goggles
Monocle is the leading optics library in Scala; it has a small, well-designed core, but its day-to-day user experience leaves a little to be desired. It has much of the power of `Control.Lens`, and also has much of the conceptual weight and learning curve.
Monocle is the leading optics library in Scala; it has a small,
well-designed core, but its day-to-day user experience leaves
a little to be desired. It has much of the power of
`Control.Lens`, and also has much of the conceptual weight
and learning curve.

This makes it an ideal core for building an optics DSL. Goggles aims to provide an intuitive, discoverable interface over Monocle for beginners, while helping experienced users get the job done with a minimum of fuss.
This makes it an ideal core for building an optics DSL.
Goggles aims to provide an intuitive, discoverable interface
over Monocle for beginners, while helping experienced users
get the job done with a minimum of fuss.

## Features
### Navigate case class-like fields by name
Expand All @@ -77,7 +105,8 @@ get"$state.capital.population"
set"$state.capital.population" += 1
// State("Victoria", City("Melbourne", 4087001))
```
The `+=` is syntax sugar; it requires an implicit `scala.Numeric` in scope for the result type.
The `+=` is syntax sugar; it requires an implicit `scala.Numeric`
in scope for the result type.

### Interpolate any Monocle optic
```scala
Expand Down Expand Up @@ -119,7 +148,8 @@ set"$polygon*.x" += 1.5
List(Point(1.5, 0.0), Point(1.5, 1.0), Point(2.5, 1.0), Point(2.5, 0.0))

```
Any type for which an implicit `monocle.function.Each` is in scope can use `*`
Any type for which an implicit `monocle.function.Each` is
in scope can use `*`


### Select optional values
Expand All @@ -139,7 +169,8 @@ set"$estate.farm?.prizeChicken?.egg?.weight" *= 2
// Estate(Some(Farm(Some(Chicken(Some(Egg(4.6)))))))

```
Any type for which an implicit `monocle.function.Possible` is in scope can use `?`
Any type for which an implicit `monocle.function.Possible` is
in scope can use `?`

### Select indexed values
```scala
Expand Down Expand Up @@ -167,10 +198,13 @@ set"$ticTac[2][0]" := O
// Vector(O, X, -),
// Vector(O, O, O))
```
Any type for which an implicit `monocle.function.Index` is in scope can use `[i]` with an index.
Any type for which an implicit `monocle.function.Index` is
in scope can use `[i]` with an index.

### Great compilation error messages
Helpful compiler errors are a first class part of Goggles' design, hopefully encouraging exploration, clarifying optics concepts and allowing the functionality to be discoverable.
Helpful compiler errors are a first class part of Goggles'
design, hopefully encouraging exploration, clarifying optics
concepts and allowing the functionality to be discoverable.
```
scala> get"$myBasket.items*.qty.foo"
<console>:18: error: Int doesn't have a 'foo' method
Expand All @@ -194,34 +228,62 @@ scala> get"$myBasket.items*.qty.foo"
set"$myBakery.cakes*.toppings[0].cherries" := 7
```

Goggles is not an optics library itself; it is only a new user interface built on a subset of Monocle, and interoperates seamlessly with the rest. It uses whitebox macros, meaning that the contents of the macro decide the static return type.
Goggles is not an optics library itself; it is only a new user
interface built on a subset of Monocle, and interoperates
seamlessly with the rest. It uses whitebox macros, meaning
that the contents of the macro decide the static return type.

Goggles takes the view that macros that base their behaviour on the structure of code rather than its value are not referentially-transparent, and not consistent with the best traditions of FP. Repurposing Scala's syntax to do things that aren't Scala is surprising to users and imposes an unnecessary cognitive burden.
Goggles takes the view that macros that base their behaviour
on the structure of code rather than its value are not
referentially-transparent, and not consistent with the
best traditions of FP. Repurposing Scala's syntax to do
things that aren't Scala is surprising to users and
imposes an unnecessary cognitive burden.

Extensions to `StringContext` form the main mechanism, because:
* It isn't Scala, and the String clearly demarcates regular Scala from the designated DSL area.
* It isn't Scala, and the String clearly demarcates regular
Scala from the designated DSL area.
* This gives us enormous flexibility to choose the syntax we want.

There are some disadvantages:
* There is no IDE support out of the box: it just looks like a string to IDEs. (Could this be remedied with plugins?)
* Because interpolated optics get evaluated before the rest of the macros, the type inference is poor for arguments.
* There is no IDE support out of the box: it just looks like
a string to IDEs. (Could this be remedied with plugins?)
* Because interpolated optics get evaluated before the rest
of the macros, the type inference is poor for arguments.

### [QuickLens](https://github.com/adamw/quicklens)
```scala
modify(myBakery)(_.cakes.each.toppings.at(0).cherries).setTo(7)
```

QuickLens is designed to be a lightweight alternative to Monocle; it is solely focused on manipulating case classes. It uses a fluent API with blackbox macros, which deconstruct the given code tree to discover path information. It supports several features like "each" traversal and indexing, but lacks an overarching, cohesive optics model outside of the DSL. In addition, the fluent API supports manipulating several points in the path at once, and Prism-style navigation of sum types.
QuickLens is designed to be a lightweight alternative to
Monocle; it is solely focused on manipulating case classes.
It uses a fluent API with blackbox macros, which deconstruct
the given code tree to discover path information. It supports
several features like "each" traversal and indexing, but lacks
an overarching, cohesive optics model outside of the DSL.
In addition, the fluent API supports manipulating several
points in the path at once, and Prism-style navigation of
sum types.

### [Monocle's internal DSL](https://github.com/julien-truffaut/Monocle/blob/master/macro/shared/src/main/scala/monocle/macros/syntax/GenApplyLensSyntax.scala)
```
myBakery.lens(_.cakes)
```
Monocle itself contains some internal syntactic helpers, including a simple DSL for convenient case class manipulation. Currently it uses a blackbox macro to deconstruct a code tree, which generates a `monocle.Lens`.
Monocle itself contains some internal syntactic helpers,
including a simple DSL for convenient case class manipulation.
Currently it uses a blackbox macro to deconstruct a code
tree, which generates a `monocle.Lens`.


### [Shapeless Lenses](https://github.com/milessabin/shapeless/blob/master/core/src/main/scala/shapeless/lenses.scala)
```
lens[Bakery].cakes.get(myBakery)
```
Shapeless offers Lens and Prisms, which allow automatic navigation of case classes and sum types. It uses Dynamic to allow Scala-like syntax, and uses a thicket of typeclasses to prove validity. It supports indexing and products, but not traversals. As with many of Shapeless' concepts, understanding the mechanism used requires a high level of proficiency, and the compile errors are quite unhelpful.
Shapeless offers Lens and Prisms, which allow automatic
navigation of case classes and sum types. It uses Dynamic
to allow Scala-like syntax, and uses a thicket of typeclasses
to prove validity. It supports indexing and products, but
not traversals. As with many of Shapeless' concepts,
understanding the mechanism used requires a high level
of proficiency, and the compile errors are quite unhelpful.

0 comments on commit e917346

Please sign in to comment.