Hacker News new | ask | show | jobs
by yakubin 1558 days ago
Thank you very much for your detailed explanation.

The reason I asked is because when I saw Background(1:200, ground), I did not know what made Javis use this background. To me it looked like creating an object and immediately discarding it. Global state explains it, but by default I'd assume that I can e.g. create two animations simultaneously (not necessarily meaning parallelism or concurrency, just multiple animation objects coexisting in memory at the same time, with code applying modifications to one in line N and to the other in line M e.g.) In that case I don't know how Javis would distinguish which background should be applied to which animation, as the code doesn't explicitly show any connection between the video variable and the Background(1:200, ground). From your explanation I assume you can only create one animation at a time. It makes sense. It makes it similar to creating plots in gnuplot. Keep in mind that I don't program in Julia (I'm considering learning it though), so I may still be totally off the mark in my understanding.

When I learn a library, tracing how data flows through example programs is the guide I use. When I don't see the explicit connection facilitating this flow of data, the library feels a bit less predictable to me and I need to rely more on copy-pasting ready-made examples to get something working through trial-and-error instead of relying on an accurate mental model of what happens. I know that such explicitness can feel verbose sometimes, but there are methods of escaping the verbosity without losing explicitness. Two examples: iterator pipelines in Rust/streams in Java, builder pattern in any language with method call syntax. In the first example, usually the pipeline starts with the name of the source of data and is followed by transformations connected by dots, e.g.:

  let names = vec!["Jane", "Jill", "Jack", "John"];

  let total_bytes = names
      .iter()
      .map(|name: &&str| name.len())
      .fold(0, |acc, len| acc + len );
The important thing here is that the dots signal that all those operations are applied to the result of the previous operations in pipeline (yet you don't need to name all the intermediate results). It's both terse and explicit.

Builder pattern is similar: you have an object with methods which modify this object and return self/this, so that you don't need to repeat the name of the object being created in every line setting something in this object. E.g. I have this code in one of my Rust project using the clap library to parse CLI arguments:

  let app = App::new(pkg_name)
      .bin_name(bin_name)
      .version(version.as_str())
      .about("Compares directories file by file")
      .arg(Arg::with_name("brief")
          .short("q")
          .long("brief")
          .help("Report only when directories differ")
          .display_order(1))
      .arg(Arg::with_name("null")
          .short("0")
          .long("null")
          .help("Print raw NUL-separated paths")
          .display_order(2))
      .arg(Arg::with_name("color")
          .long("color")
          .value_name("when")
          .help("Print output in color")
          .takes_value(true)
          .possible_values(&["never", "always", "auto"])
          .default_value("auto")
          .display_order(3))
      .arg(Arg::with_name("progress")
          .long("progress")
          .value_name("when")
          .help("Print progress reports")
          .takes_value(true)
          .possible_values(&["never", "always", "auto"])
          .default_value("auto")
          .display_order(4))
      .arg(Arg::with_name("block-size")
          .short("b")
          .long("block-size")
          .value_name("block-size")
          .help("Read files in blocks of <block-size> bytes")
          .takes_value(true)
          .validator(is_valid_block_size)
          .display_order(5))
      .help_message("Print help information and exit")
      .version_message("Print version information and exit")
      .arg(Arg::with_name("old")
          .value_name("old")
          .required_unless_one(&["help", "version"])
          .hidden(true)
          .index(1))
      .arg(Arg::with_name("new")
          .value_name("new")
          .required_unless_one(&["help", "version"])
          .hidden(true)
          .index(2));
I think the builder pattern could be a good fit for Javis. But that's just a complete outsider's opinion who doesn't even program in Julia, so don't take that too seriously. I think the API being a bit more like gnuplot is just fine for Javis' apparent purpose.

To answer your question more directly, yes, I do think that knowing a bit about internals helps me understand how I should be using it.

Cheers

1 comments

For what it's worth, I use Julia frequently and had the same question as you for the same reasons.