Stream: contributing

Topic: basic-cli roc_fx_stdoutLine


view this post on Zulip Luke Boswell (Nov 08 2024 at 01:08):

I'm looking for some assistance with this solution...

app [main] {
    pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.15.0/SlwdbJ-3GR7uBWQo6zlmYWNYOxnvo8r6YABXD-45UOw.tar.br",
    aoc: "https://github.com/lukewilliamboswell/aoc-template/releases/download/0.1.0/DcTQw_U67F22cX7pgx93AcHz_ShvHRaFIFjcijF3nz0.tar.br",
}

import pf.Stdin
import pf.Stdout
import pf.Utc
import aoc.AoC {
    stdin: Stdin.bytes,
    stdout: Stdout.write,
    time: \{} -> Utc.now {} |> Task.map Utc.toMillisSinceEpoch,
}

main =
    AoC.solve {
        year: 2020,
        day: 1,
        title: "Report Repair",
        part1,
        part2,
    }

part1 : Str -> Result Str Str
part1 = \input ->

    numbers : List U128
    numbers = parse input

    combined : List { x : U128, y : U128, mul : U128 }
    combined =
        List.joinMap numbers \x ->
            List.keepOks numbers \y ->
                if (x + y) == 2020 then
                    Ok { x, y, mul: x * y }
                else
                    Err NotValid

    combined
    |> List.first
    |> Result.map \{ x, y, mul } -> "$(Num.toStr x) * $(Num.toStr y) = $(Num.toStr mul)"
    |> Result.mapErr \_ -> "Expected at least one pair to have sum of 2020"

part2 : Str -> Result Str Str
part2 = \input ->

    numbers : List U128
    numbers = parse input

    combined : List { x : U128, y : U128, z : U128, mul : U128 }
    combined =
        List.joinMap numbers \x ->
            List.joinMap numbers \y ->
                List.keepOks numbers \z ->
                    if (x + y + z) == 2020 then
                        Ok { x, y, z, mul: x * y * z }
                    else
                        Err NotValid

    combined
    |> List.first
    |> Result.map \{ x, y, z, mul } -> "$(Num.toStr x) * $(Num.toStr y) * $(Num.toStr z) = $(Num.toStr mul)"
    |> Result.mapErr \_ -> "Expected at least one triple to have sum of 2020"

parse : Str -> List U128
parse = \input ->
    input
    |> Str.split "\n"
    |> List.keepOks Str.toU128

expect
    result = part1 example
    result == Ok "1721 * 299 = 514579"

expect
    result = part2 example
    result == Ok "979 * 366 * 675 = 241861950"

expect parse example == [1721, 979, 366, 299, 675, 1456]

example =
    """
    1721
    979
    366
    299
    675
    1456
    """
$ roc --optimize 2020/01.roc < input/2020_01.txt
--- ADVENT OF CODE 2020-1: Report Repair ---

INPUT:
Reading input from STDIN...

PART 1:
1939 * 81 = 157059

PART 2 ERROR:
"Expected at least one triple to have sum of 2020"

TIMING:
READING INPUT:  <1ms
SOLVING PART 1: <1ms
SOLVING PART 2: 4ms
---

For some reason I can't get the correct answer for Part 2... and I'm really scratching my head trying to figure out what is wrong here.

view this post on Zulip Luke Boswell (Nov 08 2024 at 01:09):

I'm going through an upgrading my solutions to use latest Roc, so people can use these as examples for ideas. But this one has me stumped. :sweat_smile:

view this post on Zulip Luke Boswell (Nov 08 2024 at 01:10):

I know what the answer is, I have a simple python script which also gives the correct answer (so I know it's not an issue with my input file).

view this post on Zulip Luke Boswell (Nov 08 2024 at 01:12):

Oh ... maybe this is a basic-cli issue reading from Stdin

view this post on Zulip Luke Boswell (Nov 08 2024 at 01:12):

Adding some dbg statements

parse : Str -> List U128
parse = \input ->
    input
    |> Str.split "\n"
    |> \lines ->
        dbg List.len lines
        lines
    |> List.keepOks Str.toU128
    |> \numbers ->
        dbg List.len numbers
        numbers
[2020/01.roc:70] List.len lines = 52
[2020/01.roc:74] List.len numbers = 52

I can see only 52 numbers/lines are being parsed when the input file is 200 lines long

view this post on Zulip Luke Boswell (Nov 08 2024 at 01:14):

Looks like I may have found the issue...

#[no_mangle]
pub extern "C" fn roc_fx_stdinBytes() -> RocResult<RocList<u8>, ()> {
    let stdin = std::io::stdin();
    let mut buffer: [u8; 256] = [0; 256];

    match stdin.lock().read(&mut buffer) {
        Ok(bytes_read) => RocResult::ok(RocList::from(&buffer[0..bytes_read])),
        Err(_) => RocResult::ok(RocList::from(([]).as_slice())),
    }
}

view this post on Zulip Brendan Hansknecht (Nov 09 2024 at 06:42):

Reading at most 256 bytes....was this effect copied from my false interpreter years ago? I added that restriction to false to force extra io for testing purposes.

view this post on Zulip Luke Boswell (Nov 09 2024 at 06:50):

I don't have much to go off here... but 256 bytes seems ultra low to me for this kind of thing. It was good to discover though, as it's not the right effect for reading a whole file from stdin.

view this post on Zulip Brendan Hansknecht (Nov 09 2024 at 07:26):

Yeah, stupidly small intentionally. For basic CLI should be at least 4k, if not 16k

view this post on Zulip Luke Boswell (Nov 09 2024 at 07:27):

I'll add that change in the purity inference branch :thumbs_up:

view this post on Zulip Luke Boswell (Nov 09 2024 at 07:29):

While I'm there, I'll remove some of the RocStr hacks we've got for handling errors.

view this post on Zulip Luke Boswell (Nov 09 2024 at 07:31):

@Brendan Hansknecht

#[no_mangle]
pub extern "C" fn roc_fx_stdinBytes() -> RocResult<RocList<u8>, ()> {
    const BUF_SIZE: usize = 16_384; // 16 KiB = 16 * 1024 = 16,384 bytes
    let stdin = std::io::stdin();
    let mut buffer: [u8; BUF_SIZE] = [0; BUF_SIZE];

    match stdin.lock().read(&mut buffer) {
        Ok(bytes_read) => RocResult::ok(RocList::from(&buffer[0..bytes_read])),
        Err(_) => RocResult::ok(RocList::from(([]).as_slice())),
    }
}

view this post on Zulip Brendan Hansknecht (Nov 09 2024 at 07:33):

Shouldn't that return Eof at least as an error?

view this post on Zulip Luke Boswell (Nov 09 2024 at 07:34):

I think end of file returns Ok

view this post on Zulip Luke Boswell (Nov 09 2024 at 07:35):

There's an ErrorKind::UnexpectedEof

An error returned when an operation could not be completed because an "end of file" was reached prematurely.
This typically means that an operation could only succeed if it read a particular number of bytes but only a smaller number of bytes could be read.

view this post on Zulip Luke Boswell (Nov 09 2024 at 07:36):

I'm going to change that effect to handle errors, this is the set I've got that I think are helpful.

InternalIOErr : {
    tag : [
        BrokenPipe,
        WouldBlock,
        WriteZero,
        Unsupported,
        Interrupted,
        OutOfMemory,
        UnexpectedEof,
        InvalidInput,
        Other,
    ],
    msg : Str,
}

view this post on Zulip Luke Boswell (Nov 09 2024 at 07:37):

impl From<std::io::Error> for InternalIOErr { ... }

view this post on Zulip Brendan Hansknecht (Nov 09 2024 at 07:38):

I don't think you will ever hit WouldBlock

view this post on Zulip Luke Boswell (Nov 09 2024 at 07:39):

I've got this same type for all the Stdio effects -- sorry forgot to mention that

view this post on Zulip Brendan Hansknecht (Nov 09 2024 at 07:39):

Yeah, but basic CLI doesn't support async io

view this post on Zulip Brendan Hansknecht (Nov 09 2024 at 07:40):

So wouldblock shouldn't ever happen as an error

view this post on Zulip Brendan Hansknecht (Nov 09 2024 at 07:40):

Also, I wonder what a write zero error is

view this post on Zulip Luke Boswell (Nov 09 2024 at 07:43):

Just doing a quick review of all the error kinds.. 1 min

view this post on Zulip Luke Boswell (Nov 09 2024 at 07:48):

https://doc.rust-lang.org/std/io/enum.ErrorKind.html

#[non_exhaustive]
pub enum ErrorKind {
    NotFound, // YES
    PermissionDenied, // YES
    ConnectionRefused, // OTHER -- uncommon server related
    ConnectionReset, // OTHER -- uncommon server related
    HostUnreachable, // NO - experimental
    NetworkUnreachable, // NO - experimental
    ConnectionAborted, // OTHER -- uncommon
    NotConnected, // OTHER -- uncommon
    AddrInUse, // OTHER -- uncommon
    AddrNotAvailable, // OTHER -- uncommon
    NetworkDown,  // NO - experimental
    BrokenPipe, // YES - pipe was closed
    AlreadyExists, // YES
    WouldBlock, // OTHER -- async not used in basic-cli
    NotADirectory,  // NO - experimental
    IsADirectory,  // NO - experimental
    DirectoryNotEmpty,  // NO - experimental
    ReadOnlyFilesystem,  // NO - experimental
    FilesystemLoop,  // NO - experimental
    StaleNetworkFileHandle,  // NO - experimental
    InvalidInput,  // NO -- parameter was incorrect, unlikely to be hit
    InvalidData, // NO -- operation parameters were valid, however the error was caused by malformed input data, unlikely to be hit
    TimedOut, // YES -- operation’s timeout expired, causing it to be canceled
    WriteZero, // OTHER -- uncommon
    StorageFull, // NO - experimental
    NotSeekable, // NO - experimental
    FilesystemQuotaExceeded, // NO - experimental
    FileTooLarge, // NO - experimental
    ResourceBusy, // NO - experimental
    ExecutableFileBusy, // NO - experimental
    Deadlock, // NO - experimental
    CrossesDevices, // NO - experimental
    TooManyLinks, // NO - experimental
    InvalidFilename, // NO - experimental
    ArgumentListTooLong, // NO - experimental
    Interrupted, // YES - Interrupted operations can typically be retried
    Unsupported, // YES - operation is unsupported on this platform
    UnexpectedEof, // OTHER - unlikely to be hit
    OutOfMemory, // YES
    Other, // OTHER -- catch all
}

Leaves us with the following...

RocIOError [
    NotFound, // YES
    PermissionDenied, // YES
    BrokenPipe, // YES - pipe was closed
    AlreadyExists, // YES
    TimedOut, // YES -- operation’s timeout expired, causing it to be canceled
    Interrupted, // YES - Interrupted operations can typically be retried
    Unsupported, // YES - operation is unsupported on this platform
    OutOfMemory, // YES
    ++ OTHER Str for everything else.
]

view this post on Zulip Brendan Hansknecht (Nov 09 2024 at 07:50):

Is timedout also async io specific?

view this post on Zulip Brendan Hansknecht (Nov 09 2024 at 07:50):

Also, wow, so many experimental

view this post on Zulip Brendan Hansknecht (Nov 09 2024 at 07:51):

And otherwise, the list looks good to me

view this post on Zulip Luke Boswell (Nov 09 2024 at 07:55):

Brendan Hansknecht said:

Is timedout also async io specific?

I'll move it to OTHER category

view this post on Zulip Luke Boswell (Nov 09 2024 at 08:10):

@Brendan Hansknecht

Just looking at this

#[no_mangle]
pub extern "C" fn roc_fx_stdinLine() -> RocResult<RocStr, glue::IOErr> {
    let stdin = std::io::stdin();

    match stdin.lock().lines().next() {
        None => RocResult::err(RocStr::from("EOF")),
        Some(Ok(str)) => RocResult::ok(RocStr::from(str.as_str())),
        Some(Err(io_err)) => RocResult::err(io_err.into()),
    }
}

Instead of returning an Err EndOfFile I'm thinking we should return an Ok "EOT" where EOT is the ASCII 04 for end of transmission.

view this post on Zulip Luke Boswell (Nov 09 2024 at 08:11):

My thoughts are that it's not an error condition, reading has succeeded there is just nothing further to read.

view this post on Zulip Luke Boswell (Nov 09 2024 at 08:12):

I'm definitely on the fence here... because it would be easy to just return another Err tag for EndOfFile

view this post on Zulip Luke Boswell (Nov 09 2024 at 08:14):

So like this..

#[no_mangle]
pub extern "C" fn roc_fx_stdinLine() -> RocResult<RocStr, glue::IOErr> {
    let stdin = std::io::stdin();

    match stdin.lock().lines().next() {
        None => RocResult::ok("\x04".into()),
        Some(Ok(str)) => RocResult::ok(str.as_str().into()),
        Some(Err(io_err)) => RocResult::err(io_err.into()),
    }
}

view this post on Zulip Brendan Hansknecht (Nov 09 2024 at 08:29):

I definitely wouldn't return a valid string. that will just confuse users

view this post on Zulip Brendan Hansknecht (Nov 09 2024 at 08:29):

It needs to be a tag of some sort

view this post on Zulip Brendan Hansknecht (Nov 09 2024 at 08:29):

It can be in the ok tag, but that is probably less convenient than putting it in the error tag

view this post on Zulip Brendan Hansknecht (Nov 09 2024 at 08:30):

I do agree that it generally isn't an actual error

view this post on Zulip Luke Boswell (Nov 09 2024 at 08:32):

This is how I was thinking of handling it in the example

when Stdin.line! {} is

    # End of Transmission (EOT) / File (EOF)
    Ok str if str == "\x04" ->
        try Stdout.line! (echo "Received end of input.")
        Ok {}

    Ok str ->
        try Stdout.line! (echo str)
        tick! {}

    Err (StdinErr err) ->
        try Stdout.line! (echo "Unable to read input $(Inspect.toStr err)")
        Ok {}

view this post on Zulip Luke Boswell (Nov 09 2024 at 08:33):

But I think a tag is clearer

view this post on Zulip Luke Boswell (Nov 09 2024 at 08:46):

Found an API that feels nice

line! : {} => Result Str [EndOfFile, StdinErr Err]
bytes! : {} => Result (List U8) [StdinErr Err]
readToEnd! : {} => Result (List U8) [StdinErr Err]

view this post on Zulip Anton (Nov 09 2024 at 11:16):

It feels a bit weird to put EndOfFile in the error group of the Result.

view this post on Zulip Brendan Hansknecht (Nov 09 2024 at 15:50):

I feel like this was iterated on before. We had Eof in the ok side. Even though it technically makes more sense, it is less convenient to use in general.

view this post on Zulip Brendan Hansknecht (Nov 09 2024 at 15:51):

Also, bytes vs read to end feels a bit strange

view this post on Zulip Brendan Hansknecht (Nov 09 2024 at 15:51):

Shouldn't it just be read and read to end?

view this post on Zulip Anton (Nov 09 2024 at 16:08):

I feel like this was iterated on before.

Yeah, me too but I could not find it on github


Last updated: Jul 05 2025 at 12:14 UTC