Stream: beginners

Topic: What does the left arrow (<-) do?


view this post on Zulip Mythmon (Jun 19 2024 at 16:44):

I read through the tutorial this morning and the record builder example, and I'm left confused about what the left arrow does (<-). Is there more about it that I can read?

view this post on Zulip Brendan Hansknecht (Jun 19 2024 at 17:00):

It is just syntax sugar for lambda.

x <- Task.await myTask
# more code

Is equivalent to:

Task.await myTask \x ->
    #more code

view this post on Zulip Brendan Hansknecht (Jun 19 2024 at 17:07):

Avoids indentation hell. That said, it will likely be removed in favor of only allowing !.

view this post on Zulip Mythmon (Jun 19 2024 at 17:08):

Hm, ok. I'm not quite getting it yet. So if you use <- multiple times in one expression (like in the Record Builder example) it builds a lambda with more than one parameter?

view this post on Zulip Anton (Jun 19 2024 at 17:17):

The : <- and <- work in different ways, others have been confused by it as well, but like Brendan said, we will likely get rid of the (standalone) <-.

view this post on Zulip Mythmon (Jun 19 2024 at 17:19):

Ah, ok. I guess I'm asking about : <- then. I hadn't encountered standalone <-.

view this post on Zulip Anton (Jun 19 2024 at 17:24):

initIDCount {
      aliceID: <- incID,
      bobID: <- incID,
      trudyID: <- incID,
}
|> extractState

Converts to:

initIDCount (\aID -> \bID -> \cID -> { aliceID: aID, bobID: bID, trudyID: cID })
|> incID
|> incID
|> incID
|> extractState

Is there a specific part that does not make sense?

view this post on Zulip Mythmon (Jun 19 2024 at 17:27):

Knowing that : <- is a distinct "operator" and not just assigning a usage of <-helps. I'm still not quite sure how I can apply it to different situations, but I think I might just need to sit with a bit longer

view this post on Zulip Anton (Jun 19 2024 at 17:30):

Yes, the example is quite artificial, it's very useful for use with the Roc postgres library

view this post on Zulip Agus Zubiaga (Jun 19 2024 at 17:33):

Here’s an article about a cool CLI arg parser built using record builders: https://sammohr.dev/blog/announcing-weaver

view this post on Zulip Agus Zubiaga (Jun 19 2024 at 17:34):

Record Builders are basically syntax sugar for applicatives if you know those from other FP languages

view this post on Zulip Mythmon (Jun 19 2024 at 17:37):

Unfortunately I have very little experience with other FP languages. I suspect that an arg parser is actually kind of close to what I'm trying to build though, so I'll read that article. (I'm trying to build something that is sort of like the builder pattern that I've seen in Rust, but with fields that can be defined as delayed functions)

view this post on Zulip Brendan Hansknecht (Jun 19 2024 at 17:46):

I have been using roc for a long time, find record builders to still be magic even if I can desugar them

view this post on Zulip Brendan Hansknecht (Jun 19 2024 at 17:47):

I think that applicatives even if written out can be hard to follow.

view this post on Zulip Brendan Hansknecht (Jun 19 2024 at 17:48):

Someone(@Agus Zubiaga?) had a really good animation at the zig milan meetup in a lightning talk. Was super useful cause it showed the unwrapping as the applicatives where used.

view this post on Zulip Agus Zubiaga (Jun 19 2024 at 17:58):

Yeah, we have some ideas on how to simplify them to work in terms of map2 instead of applicatives. I’ll probably look into that later in the year.

view this post on Zulip Ajai Nelson (Jun 19 2024 at 18:02):

Mythmon said:

Unfortunately I have very little experience with other FP languages.

My first programming language was functional, and I haven’t wrapped my head around : <- yet. So don’t worry, you’re definitely not alone

view this post on Zulip Mythmon (Jun 19 2024 at 18:08):

That's reassuring. If I could take advantage of the attention here, this is the thing I was trying to use : <- for, written out verbosely. I was trying to understant : <- as something like Rust's ?, which I don't think is right now

Slot a : [Undefined, Final a, Delayed a]

Character : {
    name : Str,
    xp : U8,
    level : U8,
}

PartialCharacter : {
    name : Slot Str,
    xp : Slot U8,
    level : Slot U8,
}

finalize : PartialCharacter -> [Ok Character, Err Str]
finalize = \pc ->
    when (pc.name, pc.xp, pc.level) is
        (Final name, Final xp, Final level) -> Ok { name, xp, level }
        (Undefined, _, _) -> Err "Missing name"
        (_, Undefined, _) -> Err "Missing xp"
        (_, _, Undefined) -> Err "Missing level"
        (Delayed _, _, _) | (_, Delayed _, _) | (_, _, Delayed _) -> Err "TODO Can't handle delayed values yet"

I expect the number of fields on Character to grow a lot (the TypeScript code I'm rewriting already has 20+ fields, and isn't even complete). Is there a better way to structure this that won't require huge when definitions?

It would be great if finalize could be something like

finalize = \pc ->
    something {
        name: <- ensure pc.name "name",
        xp: <- ensure pc.xp "xp",
        level: <- ensure pc.level "level",
    }

view this post on Zulip Agus Zubiaga (Jun 19 2024 at 18:33):

Yeah, I think that should work. Let me give it a try.

view this post on Zulip Agus Zubiaga (Jun 19 2024 at 18:40):

@Mythmon This should work:

finalize : PartialCharacter -> Result Character Str
finalize = \pc ->
    Ok {
        name: <- ensure pc.name "name",
        xp: <- ensure pc.xp "xp",
        level: <- ensure pc.level "level",
    }

ensure : Slot a, Str -> (Result (a -> b) Str -> Result b Str)
ensure = \slot, name ->
    \prev ->
        when (prev, slot) is
            (Ok advance, Final value) -> Ok (advance value)
            (Ok _, Undefined) -> Err "Missing $(name)"
            (Ok _, Delayed _) -> crash "todo"
            (Err err, _) -> Err err

expect finalize { name: Final "Alice", xp: Final 42, level: Final 3 } == Ok { name: "Alice", xp: 42, level: 3 }

expect finalize { name: Final "Alice", xp: Final 42, level: Undefined } == Err "Missing level"

view this post on Zulip Agus Zubiaga (Jun 19 2024 at 18:41):

Here's the desugaring animation that @Brendan Hansknecht mentioned:
record-builder-desugaring.mov

view this post on Zulip Agus Zubiaga (Jun 19 2024 at 18:47):

I recommend trying the desugared version yourself and figuring out the types at each level of the pipeline

view this post on Zulip Agus Zubiaga (Jun 19 2024 at 18:51):

Applicatives can be quite mind bending because the type wraps this curried function that’s applied at each level

view this post on Zulip Agus Zubiaga (Jun 19 2024 at 18:52):

As I said earlier, I’d like to try a map2 approach later because that’s much easier to grasp


Last updated: Jul 06 2025 at 12:14 UTC