Skip to content

Instantly share code, notes, and snippets.

@evancz
Last active March 3, 2017 14:58
Show Gist options
  • Star 10 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save evancz/78293dc6a4ac2547676c to your computer and use it in GitHub Desktop.
Save evancz/78293dc6a4ac2547676c to your computer and use it in GitHub Desktop.
Potential outline of a simple and useful lens library for Elm

Focus

A Focus is a way to work with particular parts of a large chunk of data. On the most basic level, it lets you get and set fields of a record in a simple and composable way. This means you could avoid writing special record update syntax and use something that composes much more elegantly.

This API is inspired by the concept of Bidirectional Lenses as described by Nate Foster and seen in a modified form in Haskell as "lenses" and in ClojureScript as "cursors". My personal opinions and understanding comes from this talk by Simon Peyton Jones, discussions with @seliopou, and a basic understanding of Nate Foster's PhD thesis on bidirectional lenses. I chose the name "Focus" for this outline because it is sort of like a lens that only lets you see in one direction.

Here's the pseudocode that describes the basic API:

module Focus where

{-| A focus is a value. It describes a strategy for getting, setting,
and updating things. It lets you take a big chunk of data and work with
some small part of it, such as a subfield of a record.
-}
type Focus big small

{-| Get a small part of a big thing.

    name : Focus { r | name:String } String

    get name { name = "Tom", age = 42 } == "Tom"
-} 
get : Focus big small -> big -> small

{-| Set a small part of a big thing.

    set name "Steve" { name = "Tom", age = 42 } == { name = "Steve", age = 42 }
-}
set : Focus big small -> small -> big -> big

{-| Update a small part of a big thing.

    update age sqrt { name = "Tom", age = 49 } == { name = "Tom", age = 7 }
-}
update : Focus big small -> (small -> small) -> big -> big

-- COMPOSING FOCI

{-| The power of this library comes from the fact that you can compose
many foci. For example, say we want to update a deep field of some nested
records, such as object.physics.velocity.x

    physics  : Focus { r | physics:a } a
    velocity : Focus { r | velocity:a } a
    x        : Focus { r | x:a } a

    update (physics => velocity => x) (\x -> x + 1) object

    set (physics => velocity => x) 0 object
-}
(=>) : Focus big medium -> Focus medium small -> Focus big small

Possible Anti-Pattern?

It is possible that the concept of a Focus is harmful to code quality in that it can help you to be lax with abstraction boundaries. A modular architecture as described here happens quite naturally in a world without Focus, but with the concept of Focus there's a feeling that "maybe modularity is for suckers... Maybe I can get away without it!"

The deeper problem may be that lenses are best when they are bidirectional, whereas a Focus is only in one direction. The issue is then that making proper lenses is not necessarily possible without changing the language itself.

Steps to making Focus possible

Implementations of this idea in JS and Clojure rely heavily on the fact that the languages are dynamically typed and you can do runtime introspection. Haskell relies heavily on Template Haskell (a sort of macro system) to generate all of the necessary code. In Elm, I think we'd need to implement type-derived macros to make this usable. The primary motivation for doing that feature is still for serialization to JSON, XML, binary, etc.

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