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.
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:
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).
Oh ... maybe this is a basic-cli issue reading from Stdin
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
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())),
}
}
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.
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.
Yeah, stupidly small intentionally. For basic CLI should be at least 4k, if not 16k
I'll add that change in the purity inference branch :thumbs_up:
While I'm there, I'll remove some of the RocStr hacks we've got for handling errors.
@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())),
}
}
Shouldn't that return Eof at least as an error?
I think end of file returns Ok
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.
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,
}
impl From<std::io::Error> for InternalIOErr { ... }
I don't think you will ever hit WouldBlock
I've got this same type for all the Stdio effects -- sorry forgot to mention that
Yeah, but basic CLI doesn't support async io
So wouldblock shouldn't ever happen as an error
Also, I wonder what a write zero error is
Just doing a quick review of all the error kinds.. 1 min
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.
]
Is timedout also async io specific?
Also, wow, so many experimental
And otherwise, the list looks good to me
Brendan Hansknecht said:
Is timedout also async io specific?
I'll move it to OTHER category
@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.
My thoughts are that it's not an error condition, reading has succeeded there is just nothing further to read.
I'm definitely on the fence here... because it would be easy to just return another Err tag for EndOfFile
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()),
}
}
I definitely wouldn't return a valid string. that will just confuse users
It needs to be a tag of some sort
It can be in the ok tag, but that is probably less convenient than putting it in the error tag
I do agree that it generally isn't an actual error
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 {}
But I think a tag is clearer
Found an API that feels nice
line! : {} => Result Str [EndOfFile, StdinErr Err]
bytes! : {} => Result (List U8) [StdinErr Err]
readToEnd! : {} => Result (List U8) [StdinErr Err]
It feels a bit weird to put EndOfFile
in the error group of the Result.
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.
Also, bytes vs read to end feels a bit strange
Shouldn't it just be read and read to end?
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