Stream: ideas

Topic: Ambient enviornment


view this post on Zulip Locria Cyber (Oct 28 2021 at 18:48):

Using ambient variables, program behavior traditionally considered "side effect" can be expressed easily. It allows you to express, ambient authority, global variables, context variables with the same idea.

view this post on Zulip Folkert de Vries (Oct 28 2021 at 18:55):

the whole point of pure functional programming is to not do that exact thing, right?

view this post on Zulip Locria Cyber (Oct 28 2021 at 18:55):

Well, it has monad, and I think it's counter-intuitive.

view this post on Zulip Folkert de Vries (Oct 28 2021 at 18:59):

monads aren't really tied to functional programming (at least in theory, in practice I guess they are)

view this post on Zulip Locria Cyber (Oct 28 2021 at 19:01):

Here's a simple example with strange syntax for doing operations on array.

a = [1, 2, 3, 4, 5, 98, 99]
sumbelow10 : (List[int]) -> (int)
sumbelow10 (ambient List[int]) = (ambient int)
  filter it -> it <= 10
  sum
result = sumbelow10 a
# result would be 15

view this post on Zulip Folkert de Vries (Oct 28 2021 at 19:05):

what is this meant to show?

view this post on Zulip Locria Cyber (Oct 28 2021 at 19:07):

If you write it like this

sumbelow10 (ambient List[int]) = (ambient int)
  filter it -> it <= 10
  0

the compiler will complain with

inside sumbelow10:
  List[int] returned by filter is made ambient isn't used afterwards

view this post on Zulip Locria Cyber (Oct 28 2021 at 19:14):

Basically,

  1. every function can have arbitrary number of input and output values
  2. every function can only have one input of a type
  3. every function can only have one output of a type
  4. when one type of input isn't specified explicitly, it is taken from the ambient environment (where the compiler keeps track of)
  5. when one type of output isn't assigned to a variable or discarded, it is put into the ambient environment
  6. the order of input doesn't matter, so f a b is the same as f b a
  7. the order of output doesn't matter, except when the compiler is asked to infer the type of assigned variable, it chooses in the order where the function is declared

view this post on Zulip Locria Cyber (Oct 28 2021 at 19:17):

In the above example, filter has the signature List[int] -> (int -> bool) -> List[int], and the compiler just fills in the first argument.

view this post on Zulip Locria Cyber (Oct 28 2021 at 19:18):

Realistic example of using ambient variables to reduce boilerplate:
Raylib is a c library for making games with a single window.
Here is Raylib API for reference.

view this post on Zulip Folkert de Vries (Oct 28 2021 at 19:21):

"explicit is better than implicit"

view this post on Zulip Locria Cyber (Oct 28 2021 at 19:22):

Its API is designed for ease of use, but isn't quite correct, because it allows you to do this:

Texture2D texture = LoadTexture("example.png");
InitWindow(800, 600, "Hello world");

The program type checks but SIGSEGV without any error message. The problem? LoadTexture requires GPU access, which is provided by an invisible global variable, and you have to call InitWindow to initialize GPU for rendering.

view this post on Zulip Locria Cyber (Oct 28 2021 at 19:28):

You can make the variable representing GPU access not global by making the library user have to write this:

Context ctx = InitWindow(800, 600, "Hello world");
Texture2D texture = LoadTexture(ctx, "example.png");

But you need to write ctx every time you need to draw a line on screen, draw an image, or read a file. You know you have the permission to draw something, but the compiler is too dumb to remember anything between function calls unless you assign it to a variable.

view this post on Zulip Locria Cyber (Oct 28 2021 at 19:28):

This is even more problematic when you don't have mutable variables. You have to write this:

Texture2D texture, Context ctx = LoadTexture(ctx, "example.png");

view this post on Zulip Locria Cyber (Oct 28 2021 at 19:30):

Haskell's solution to this is Monad. It is very hard to understand and use; for example, you have to define a new type of monad for array operation to be able to write sumbelow10 like above.
It can also represent "side effects".

view this post on Zulip Folkert de Vries (Oct 28 2021 at 19:35):

in haskell you can also totally write something like this, without knowing anything about monads

a = [1, 2, 3, 4, 5, 98, 99]

sumBelow10 = \list ->
    list
        |> List.keepIf (\element -> element < 10)
        |> List.sum

view this post on Zulip Folkert de Vries (Oct 28 2021 at 19:35):

(using roc syntax here btw)

view this post on Zulip Locria Cyber (Oct 28 2021 at 19:35):

To handle "side effects", we must also be able to mark a type as unique (cannot be copied).
This is used in the programming language Clean.
https://en.wikipedia.org/wiki/Uniqueness_type

view this post on Zulip Folkert de Vries (Oct 28 2021 at 19:35):

actually in haskell you can even do sumBelow10 = sum . filter (< 10)

view this post on Zulip Folkert de Vries (Oct 28 2021 at 19:36):

I know, I did a whole thesis on it :smiley:

view this post on Zulip Locria Cyber (Oct 28 2021 at 19:36):

Folkert de Vries said:

in haskell you can also totally write something like this, without knowing anything about monads

a = [1, 2, 3, 4, 5, 98, 99]

sumBelow10 = \list ->
    list
        |> List.keepIf (\element -> element < 10)
        |> List.sum

How do you interleave |> on different variables?

view this post on Zulip Folkert de Vries (Oct 28 2021 at 19:37):

its a function, so sumBelow10 a then later sumBelow10 [ 1,3,3,4,5,6,5,6,7,8,88, ... ]

view this post on Zulip Locria Cyber (Oct 28 2021 at 19:38):

I mean something like using different authorities to do different things.
Let's say you want to read a file and draw it's content as text on the screen, take a screenshot of that and save as an image.

view this post on Zulip Locria Cyber (Oct 28 2021 at 19:40):

You would need a variable for File IO and one for rendering (which depends on a buffer).

view this post on Zulip Locria Cyber (Oct 28 2021 at 19:44):

My idea is some type of compiler trick that turns

ctx = newRenderingContext
ctx = drawLine ctx ((0, 0) (0, 1))
ctx = drawLine ctx ((0, 0) (1, 0))

into

newRenderingContext
drawLine ((0, 0) (0, 1))
drawLine ((0, 0) (1, 0))

view this post on Zulip Folkert de Vries (Oct 28 2021 at 19:46):

sure, well the simple answer is

newRenderingContext
    |> drawLine ((0, 0) (0, 1))
    |> drawLine ((0, 0) (1, 0))

but that only works if drawLine really returns a new ctx and not some Task Ctx * or IO Ctx

view this post on Zulip Locria Cyber (Oct 28 2021 at 19:47):

think writing simple functions and letting compiler keep track of "global" state is easier to write and learn what is monad.

view this post on Zulip Locria Cyber (Oct 28 2021 at 19:49):

Folkert de Vries said:

sure, well the simple answer is

newRenderingContext
    |> drawLine ((0, 0) (0, 1))
    |> drawLine ((0, 0) (1, 0))

but that only works if drawLine really returns a new ctx and not some Task Ctx * or IO Ctx

In Roc's case, drawLine is provided by the platform.

view this post on Zulip Folkert de Vries (Oct 28 2021 at 19:49):

I think you'll really like effect handlers

view this post on Zulip Folkert de Vries (Oct 28 2021 at 19:49):

those are not in roc, but are in e.g. unison or koka

view this post on Zulip Folkert de Vries (Oct 28 2021 at 19:50):

and they do this while guaranteeing that ultimately the "global"/"hidden" state is always there (no segfaults or other bad things)

view this post on Zulip Locria Cyber (Oct 28 2021 at 19:51):

thanks


Last updated: Jun 16 2026 at 16:19 UTC