# Proposal: coeffects for Elm

**URL:** <https://discourse.elm-lang.org/t/proposal-coeffects-for-elm/7878>\
**Category:** Request Feedback\
**Created:** [November 3, 2021, 10:17pm UTC](https://discourse.elm-lang.org/t/proposal-coeffects-for-elm/7878 "2021-11-03T22:17:18Z")\
**Posts on this page:** 7\
**Page:** 1

<div class="post-metadata">

**Author:** ![pontaoski](https://yyz1.discourse-cdn.com/flex035/user_avatar/discourse.elm-lang.org/pontaoski/32/4336_2.png) [@pontaoski](https://discourse.elm-lang.org/u/pontaoski)\
**Post date:** [November 3, 2021, 10:17pm UTC](https://discourse.elm-lang.org/t/proposal-coeffects-for-elm/7878/1 "2021-11-03T22:17:19Z")

</div>

slightly inaccurate TLDR: type-safe dynamic scoping

give this a read for context: [Coeffects: Context-aware programming languages](http://tomasp.net/coeffects/)

Coeffects are essentially a way of reasoning about things that are required from the context by program. For example, Elm programs reside in the browser, which has a viewport dimension: width by height. UI elements in an Elm program may require the viewport dimensions, for e.g. responsive design. Coeffects let the compiler reason about the fact that an element needs to get the viewport from its context.

Without coeffects, passing the viewport into a component is trivial enough:

```elm
responsiveThing : (Int, Int) -> ... -> Html msg
responsiveThing dimensions ... =
    ...

```

Simple enough. The problem begins when you already have an existing codebase, and want to make a non-responsive element responsive.

```elm
view model =
    table model.data

table data =
    List.map row data

row rowData =
    [ cell rowData.foo
    , cell rowData.bar
    , button rowData.baz "hi" (DeleteClicked rowData.id)
    ]

button data text msg =
    ...

```

Say you want to make `button` responsive to the viewport size, increasing its dimensions on mobile form-factors, or maybe you want to show an icon instead of a full-text label on mobile.

Now you have to go through every function that leads to `button` somehow, passing in the viewport from the model.

```elm
view model =
    table model.viewport model.data

table viewport data =
    List.map (row viewport) data

row viewport rowData =
    [ cell rowData.foo
    , cell rowData.bar
    , button viewport rowData.baz "hi" (DeleteClicked rowData.id)
    ]

button viewport data text msg =
    ...

```

You’ve changed a lot of lines other than the element you wanted to change, cluttering up the git diff. The new argument may also fit awkwardly into already existing dataflow between functions, using partial application, `>>`/`<<`, etc.

While the compiler does give you a nice trail to follow when you make changes like these, letting you be confident that you didn’t miss any of the pieces, following the trail it lays out takes non-trivial time that could be better spent actually working on the program instead of wiring data through it.

Wouldn’t it be nice if we could punt the job of wiring data through long chains of function calls to the computer, so we could spend less time manually piping variable through function calls and more time working on high-level stuff?

That’s exactly what coeffects do: give the computer the necessary tools to reason about wiring data through functions through their context in a type-safe manner, freeing programmers from having to do that themselves.

Given a function like:

```elm
button data text msg =
    if dimensionsAreMobile @viewport then
        ...
    else
        ...

```

a coeffectful system would be able to infer that the expression needs a `viewport: (Int, Int)` in its _context_. Since the expression’s environment doesn’t provide a `viewport`, the compiler infers the need for one in the function’s context. If the function was called from a context that lacked a `viewport: (Int, Int)`, the compiler raises an error.

The rest of this post will explore various options for the syntax needed to express coeffects in Elm for three areas:

- signatures of coeffectful functions
- using a context variable
- introducing a context variable

## Signatures

```elm
button : Data -> String -> msg -> Html msg @ viewport : (Int, Int)

```

This reads somewhat naturally, “button takes data, string, msg, and returns html msg AT a context with viewport of (Int, Int).”

It suffers from lack of visual separation.

```elm
button : Data -> String -> msg -> Html msg
       @ viewport : (Int, Int)

```

Maybe a multiline syntax would look better.

Alternative characters to use would be `^`, or perhaps a keyword `context`. Coeffects could be listed in a record-style notation, like `@ { viewport : (Int, Int) }`. Maybe they could even use type aliases:

```elm
type alias Context =
  { viewport : (Int, Int)
  , darkMode : Bool
  }

button : Data -> String -> msg -> Html msg @ Context

```

## Usage

I think for “code using this feature shouldn’t spend a lot of characters on it every time,” the only feasible option is a sigil. The linked explanation page uses `?variable`, and I think `@variable` looks nice as well. There’s a lot of unused characters that can become a sigil, like `$`, `~`, etc.

There’s also the possibility of introducing a new keyword, though this would break existing code. Wouldn’t recommend.

```elm
button =
  if isMobileViewport (context viewport) then
    ...
  else
    ...

```

## Introducing Variables Into The Context

I think there’s two main options:

sigilled `let`, syntax-compatible:

```elm
  let
      ?viewport =
          model.viewport
  in
  tableView model.data

```

or

new keyword:

```elm
  introduce
    viewport =
      model.viewport
  in
  tableView model.data

```

Same discussion as with sigil or keyword for access. Sigil is backwards-compatible, keyword is not.

---

<div class="post-metadata">

**Author:** ![Janiczek](https://yyz1.discourse-cdn.com/flex035/user_avatar/discourse.elm-lang.org/janiczek/32/4516_2.png) [@Janiczek](https://discourse.elm-lang.org/u/Janiczek)\
**Post date:** [November 3, 2021, 11:29pm UTC](https://discourse.elm-lang.org/t/proposal-coeffects-for-elm/7878/2 "2021-11-03T23:29:02Z")

</div>

Hey there @pontaoski, thanks for the food for thought and this proposal!

(I feel like I’ll sound a bit dismissive here – please don’t take it that way – I’m glad for your post and I’m just thinking aloud and trying to give this a fair chance in my head without a way to try it hands-on 🙂 )

Having not yet read the paper I’m a bit worried about “spooky action at a distance” (changing a top-level context and getting an error somewhere in the leaf parts of the program), or non-obvious “what context do I have here” situations if we don’t use type annotations everywhere.

On the other hand, if we do use type annotations everywhere, it feels to me like this proposal loses a bit of it’s appeal compared to an explicit function argument - you still need to mention the context on every step of the way from (some) root to the leaf that needs it, right?

* * *

Thinking about this snippet of new syntax:

> [@pontaoski](#):
>
> ```auto
> let
> ?viewport =
> model.viewport
> in
> tableView model.data
> 
> ```

I think it’s a little unintuitive / unclear on first sight how does this `let ?foo = ... in ...` interact with how `let` currently works: bindings can depend on each other (in arbitrary order). Can I mix context bindings and normal bindings in the same `let..in`? Can context bindings depend on each other? Does the context binding only apply to the let body, or also to the other let bindings?

---

<div class="post-metadata">

**Author:** ![gampleman](https://yyz1.discourse-cdn.com/flex035/user_avatar/discourse.elm-lang.org/gampleman/32/43_2.png) [@gampleman](https://discourse.elm-lang.org/u/gampleman)\
**Post date:** [November 4, 2021, 10:00am UTC](https://discourse.elm-lang.org/t/proposal-coeffects-for-elm/7878/3 "2021-11-04T10:00:18Z")

</div>

Also to add to this, you can already do this with Elm in the present. For instance, there is

[https://package.elm-lang.org/packages/miniBill/elm-ui-with-context/1.1.0/](https://package.elm-lang.org/packages/miniBill/elm-ui-with-context/1.1.0/)

But the basic trick is that you add the context to the top of the view tree, then pass it down via lambdas:

```auto
type MyHtml context msg =
     MyHtml (context -> Html msg)

div : List (Attribute msg) -> List (MyHtml context msg) -> MyHtml context msg
div attrs children =
    MyHtml (\context -> 
        Html.div attrs (List.map (\(MyHtml child) -> child context) children)
    )

with : (context -> MyHtml context msg) -> MyHtml context msg
with fn =
     MyHtml (\context ->
        let 
           (MyHtml result) = fn context
        in
        result context
     )

render : context -> MyHtml context msg -> Html msg
render context (MyHtml html) =
    html context

```

(This lambda implementation is the way to do it if you are wrapping an existing rendering system like `elm/html` or `mdgriffith/elm-ui`. At work we use a two-pass rendering system where the user’s view functions produce a list of our custom types, then these get transformed into Html in a second pass; which is where the context injection can happen, making the code a lot more straightforward).

---

<div class="post-metadata">

**Author:** ![romper](https://avatars.discourse-cdn.com/v4/letter/r/ed8c4c/32.png) [@romper](https://discourse.elm-lang.org/u/romper)\
**Post date:** [November 5, 2021, 7:07am UTC](https://discourse.elm-lang.org/t/proposal-coeffects-for-elm/7878/4 "2021-11-05T07:07:31Z")

</div>

> [@pontaoski](#):
>
> Now you have to go through every function that leads to `button` somehow, passing in the viewport from the model.

When you go through something like this you eventually understand that even your “large app” is not actually that big and complex in its structure. Such exposure of the inner complexity leads to a deeper understanding of what you are doing. The top-level “context” passed downstream to the “button” is also the context for everything else surrounding this button. It is consideration of the requirements and planning ahead that helps.

---

<div class="post-metadata">

**Author:** ![rupert](https://yyz1.discourse-cdn.com/flex035/user_avatar/discourse.elm-lang.org/rupert/32/1775_2.png) [@rupert](https://discourse.elm-lang.org/u/rupert)\
**Post date:** [November 5, 2021, 10:38am UTC](https://discourse.elm-lang.org/t/proposal-coeffects-for-elm/7878/5 "2021-11-05T10:38:46Z")

</div>

`@Context` - Looks a lot like Angular annotations and dependency injection to me. Tried to learn Angular 4 at one point, gave up when I realised I needed to understand what obscure thing 20 or so badly documented and mysterious annotations do.

I really like the way Elm makes everything absolutely explicit even though it takes a little more work sometimes to do it.

---

<div class="post-metadata">

**Author:** ![Herteby](https://yyz1.discourse-cdn.com/flex035/user_avatar/discourse.elm-lang.org/herteby/32/1329_2.png) [@Herteby](https://discourse.elm-lang.org/u/Herteby)\
**Post date:** [November 5, 2021, 12:33pm UTC](https://discourse.elm-lang.org/t/proposal-coeffects-for-elm/7878/6 "2021-11-05T12:33:34Z")

</div>

Especially if you use extensible records for the context, it’s very similar, with different view functions being able to say they only depend on certain things from the context.

We tried this context approach at our company, when we needed to support multiple languages. We also used it to pass down window dimensions and image urls from flags generated by the bundler. Half a year later we removed it though and instead passed the stuff as arguments, due to people having trouble with confusing type errors. In retrospect though I think the main reason for the type problems was that we had been using extensible records for the context, which might not actually have been necessary.

> [@Janiczek](#):
>
> Having not yet read the paper I’m a bit worried about “spooky action at a distance” (changing a top-level context and getting an error somewhere in the leaf parts of the program), or non-obvious “what context do I have here” situations if we don’t use type annotations everywhere.

I think if you just have a single context for your whole app, and don’t do stuff like “mapping” the context, you’d avoid most confusion. Like having a set of global variables which can be read in many places but only updated in one place.

---

<div class="post-metadata">

**Author:** ![system](https://canada1.discourse-cdn.com/flex035/uploads/elm_lang/original/1X/50a05e53677a2c3b47776d7abd0f113eb50193a1.png) [@system](https://discourse.elm-lang.org/u/system)\
**Post date:** [November 15, 2021, 12:34pm UTC](https://discourse.elm-lang.org/t/proposal-coeffects-for-elm/7878/7 "2021-11-15T12:34:02Z")

</div>

This topic was automatically closed 10 days after the last reply. New replies are no longer allowed.
