Stream: beginners

Topic: Modeling GADTs in Roc?


view this post on Zulip Joshua Warner (Oct 30 2024 at 04:48):

Hi! I'm experimenting with building a simple elm-like library for rendering & updating TUI apps, e.g. vim-like text editors, etc.

I'm trying to model Signal's something like the following (haskell), but unsure how to express that in Roc:

data Signal a where
    KeyPressed :: char -> Signal char
    Map        :: Signal b -> (b -> a) -> Signal a

So, a couple questions:

view this post on Zulip Ayaz Hafiz (Oct 30 2024 at 04:57):

No, it's not possible in Roc. If you require the type b to be well known I'm not sure you can model this directly without type erasure. Can you pull out map so that it's a function over Signal rather than a constructor? Guessing not, but curious what the code is like.

view this post on Zulip Joshua Warner (Oct 30 2024 at 05:02):

I don't think I need it, strictly speaking. I was mostly trying to avoid exposing myself to the unbridled power that is Task.

view this post on Zulip Joshua Warner (Oct 30 2024 at 05:04):

In fact yeah; I should just be using Task here. I'm making things unnecessarily difficult for myself :eyeroll:

view this post on Zulip Joshua Warner (Oct 30 2024 at 05:05):

It would be nice to e.g. guarantee that random code that's trying to render editor state can't also be printing to the console except thru sanctioned means (returning a new Node)

view this post on Zulip Joshua Warner (Oct 30 2024 at 05:12):

I don't suppose GADTs have been discussed? On the table for the future? Too complicated to be worth it?

view this post on Zulip Ayaz Hafiz (Oct 30 2024 at 14:29):

I don't think they've been discussed.

view this post on Zulip Dan G Knutson (Oct 30 2024 at 15:27):

re: limiting task
we didn't end up doing it in roc-ray, but an approach I/we thought about was stubbing in fake error types to make, eg, draw calls not compile during init

view this post on Zulip Richard Feldman (Oct 30 2024 at 15:47):

Joshua Warner said:

I don't suppose GADTs have been discussed? On the table for the future? Too complicated to be worth it?

they haven't been discussed because I think they require #ideas > nominal types - which is in the stage of "vague idea" :big_smile:

view this post on Zulip Richard Feldman (Oct 30 2024 at 15:48):

but I think the Signal b -> (b -> a) -> Signal a part of that would require type-erased closures, right @Ayaz Hafiz?

view this post on Zulip Ayaz Hafiz (Oct 30 2024 at 15:49):

that's one option. it requires existentials or that

view this post on Zulip Richard Feldman (Oct 30 2024 at 15:50):

can existentials be compiled to first-order functions? :thinking:

view this post on Zulip Richard Feldman (Oct 30 2024 at 15:50):

I thought they had the same problems in common with rank-N types

view this post on Zulip Ayaz Hafiz (Oct 30 2024 at 15:53):

they can be yes

view this post on Zulip Richard Feldman (Oct 30 2024 at 15:53):

hm, so why would this work with our compilation models when rank-N types wouldn't?

view this post on Zulip Ayaz Hafiz (Oct 30 2024 at 15:53):

rank N types can work

view this post on Zulip Richard Feldman (Oct 30 2024 at 15:54):

hm I must be missing something haha

view this post on Zulip Richard Feldman (Oct 30 2024 at 15:54):

I thought we concluded they couldn't work

view this post on Zulip Richard Feldman (Oct 30 2024 at 15:55):

if I understand correctly, https://www.microsoft.com/en-us/research/wp-content/uploads/2009/09/implication_constraints.pdf?from=https://research.microsoft.com/en-us/um/people/simonpj/papers/gadt/implication_constraints.pdf&type=exact says GADTs can be implemented in way that preserves principal decidable type inference

view this post on Zulip Richard Feldman (Oct 30 2024 at 15:58):

so that wouldn't be a blocker

view this post on Zulip Ayaz Hafiz (Oct 31 2024 at 01:05):

in theory it’s doable

view this post on Zulip Ayaz Hafiz (Oct 31 2024 at 01:05):

the question probably should be is it worth doing

view this post on Zulip Richard Feldman (Nov 16 2024 at 20:42):

it occurred to me that I believe GADTs would make it possible from a types perspective to do Elm Architecture

view this post on Zulip Richard Feldman (Nov 16 2024 at 20:43):

without needing multiple type parameters like you would today

view this post on Zulip Richard Feldman (Nov 16 2024 at 20:45):

but Elm's Html.lazy still relies on persistent data structures to be fast, which remains the thing that would scare me away from using a Roc rendering system based on the Elm Architecture on a large UI

view this post on Zulip Brendan Hansknecht (Nov 16 2024 at 23:46):

Why does lazy need a persistent data structure and not just a dictionary?

view this post on Zulip Richard Feldman (Nov 16 2024 at 23:58):

what Html.lazy does in Elm is:

  1. I call it passing a 1-arg function and also the argument
  2. If this is the first time lazy has been called with that function and that argument, it calls the function passing the argument, and returns the return value.
  3. However, it also writes down the function address, a clone of the argument, and a clone of the return value
  4. The next time it's called, if it sees the same function address and same argument, then it skips calling the function and returns the return value directly.

with persistent data structures, it can do a "shallow equality check" of the cloned argument - that is, assuming it's a persistent data structure (where you can have a different outermost node, but inside it are all the same addresses as the one that was stored, so you don't have to recursively go traverse the entire data structure to see if it's equal), then you can have a very fast equality check as long as you're okay with false negatives (where it says they are unequal but actually if you traversed the entire data structure, you'd discover they were actually equal).

view this post on Zulip Richard Feldman (Nov 17 2024 at 00:00):

with shallow equality, it doesn't really matter if the argument is big nested data structure because it's not traversing the whole thing

view this post on Zulip Richard Feldman (Nov 17 2024 at 00:00):

e.g. in a long linked list (which is what Elm uses for List) you only compare the first elem

view this post on Zulip Richard Feldman (Nov 17 2024 at 00:00):

and if they point to the same next elem, you're done; they're definitely equal

view this post on Zulip Richard Feldman (Nov 17 2024 at 00:00):

if not, then they might not be equal, and rather than traversing the entire thing to find out, you just assume they're unequal and re-evaluate the function

view this post on Zulip Richard Feldman (Nov 17 2024 at 00:01):

but "shallow equality" isn't a thing if you have opportunistic mutation instead of persistent data structures

view this post on Zulip Richard Feldman (Nov 17 2024 at 00:01):

so our only options would be to deeply clone the entire arg, and then deeply compare for equality every time

view this post on Zulip Richard Feldman (Nov 17 2024 at 00:03):

which could potentially wipe out the performance improvement that lazy brings (especially because the happy path is potentially much more expensive), which is a scary prospect to me because lazy was by far the most effective tool I ever had for addressing performance problems in the Elm Architecture

view this post on Zulip Richard Feldman (Nov 17 2024 at 00:03):

without that, I would be afraid that I'd get to a certain point where performance problems were just unsolvable

view this post on Zulip Richard Feldman (Nov 17 2024 at 00:04):

maybe that wouldn't happen in practice, but the only way to really be sure is to build a big complicated application and find out the hard way, which is not something I'd personally be interested in :sweat_smile:

view this post on Zulip Brendan Hansknecht (Nov 17 2024 at 03:44):

Oh, I understand. Yeah, that does make it harder by default

view this post on Zulip Brendan Hansknecht (Nov 17 2024 at 03:44):

Would need to be more carefully applieds

view this post on Zulip Brendan Hansknecht (Nov 17 2024 at 03:47):

May want to make persistent data structures with tags if this kinda structure is needed.

view this post on Zulip Richard Feldman (Nov 17 2024 at 04:53):

I think it just means we need to find a UI architecture that's a better fit for Roc

view this post on Zulip Richard Feldman (Nov 17 2024 at 04:54):

after all, the reason Evan named it "The Elm Architecture" was based on his view that it's the architecture that naturally emerges if you have Elm's design constraints, but Roc has some relevantly different design decisions :big_smile:

view this post on Zulip Brendan Hansknecht (Nov 17 2024 at 06:21):

Fair enough, but lazy is a much more general concept than TEA. Similar things are all over the place, like @cache in python

view this post on Zulip Brendan Hansknecht (Nov 17 2024 at 06:22):

Also, TRA isn't as awesome of an acronym....

view this post on Zulip Pei Yang Ching (Nov 18 2024 at 02:46):

on the topic of TEA, I recently came across https://sutil.dev/#documentation-about-sutil

which is essentially svelte but F#, and has a optional module that "recreates" TEA

I didn't dive deep but I think it's a better approach than the Elm one since we can't really do pointer equality


Last updated: Jul 06 2025 at 12:14 UTC