HN user

tw25513397

12 karma
Posts0
Comments7
View on HN
No posts found.

Hey, thanks for replying!

But in a nutshell if something is pure it should support substitution.

Neat, hadn't thought about it like that before.

If you substitute x into the body of y you get a different result!

Heh, I guess that depends on "which" x. I recognize Flix doesn't allow shadowing as a defensive choice, but with respect to substitution, wouldn't this be equivalent to something like:

    let x = [1, 2, 3]
    let x = insert(0, 5, x) # treating it like Map [1]
    let y = get(0, x)
IOW, it's not clear to me why the `x[0] = 5` step would be skipped when considering substitution. Hmm, per the substitution principle, is `x[0] = 5` pure?

We are very conservative and say that anything which touches an array is impure.

Understandable given how difficult it is to reason about mutable things.

----

[1] Given the uniform function call syntax, I found it odd that the map is the last arg in the function. I originally assumed it would be m.insert(k, v) === insert(m, k, v)

That always has to be true, no? Reading from a mutable datastructure is impure in the same way that reading from standard input is.

If the function internally reads from stdin, I'd agree. But if a fn takes an input stream as an arg, and if given input streams that yield the same bytes the fn always yields the same result, then why consider it an impure fn? The input stream is just a fancy data structure for bytes.

Two different arrays with the same members are not generally equivalent.

Derp, of course! And though I guess it would be possible to have the `Mut*` collections use value equality instead of reference equality, that'd probably conflict with the performance goals of the mutable variant.

It just struck me as odd for reads like `find` (which itself requires a pure fn arg). Is that because given the same reference, the function could yield different outputs? Or because it could be mutated concurrently? Would that imply every function that takes a mutable data structure will be impure?

Edit: I just noticed that `List.toArray` -- which just returns a new array, so no concern over references or mutation -- is also marked as impure. This seemed wrong to me, but then I noticed that even `Array.new()` is marked impure. To my mind, a function that allocates a new mutable collection is itself not inherently impure.

Pure speculation on my part (since I couldn't find any details either): their "Closed world assumption" and "No reflection" principles [1] could mean the compiler can output radically different bytecode, e.g., eliminating the tail call by eliminating the "call" altogether. That said, I might be wrong given that the FAQ [2] notes that tail call elimination "has some run-time performance cost"; maybe they're baking in a trampoline.

[1] https://flix.dev/principles/

[2] https://flix.dev/faq/

Personally I find the inclusion of Datalog as a first-class citizen in an otherwise functional language to be a pretty powerful feature that I personally haven't really seen done before.

It's neat, but IIUC, it needing to be a language feature (versus being included as a library) emerges from the lack of macro support. That's one of my issues with non-macro languages, I have to wait for the language owners to add this stuff.

Reading through the Design Principles [1]:

Separate pure and impure code

So IIUC, the effect system [2] means impurity is contagious. This seems reasonable to me, though I wonder what that means in practice. `Console.printLine` is impure [3], and presumably any debug logging would be too; the contagion might spread pretty far. I wonder if this is why seemingly pure functions like `Array.find` are flagged as impure [4].

Principle of least surprise: ... and when there is no immediately obvious default, we should not have a default at all, but force the programmer to be explicit about his or her intention.

I like that they called this part out. I'd wager most code really doesn't have sane defaults, just the defaults that made sense to the developer at the time.

Local type inference

I find that in languages with type signatures (without inference), folks lean on the type to convey information, and in dynamic languages like Clojure, folks lean on good naming. Based on my not-much experience with type inference, the code ends up with neither. Sure the compiler can figure out what it is, but the humans not so much.

Uniform function call syntax: ... the function call length(xs) can also be written as xs.length()

I wonder what their motivation was for this; my guess is to appeal to both FP and OO devs. IMO, having multiple ways of writing the same thing is likely to lead to unnecessary style wars.

Private by default: ... declarations are hidden by default (i.e. private) and cannot be accessed from outside of their namespace

I like this a lot, though I foresee running into issues with unit testing, and thereby arguments over at what level things should be tested.

Bugs are not recoverable errors: ... For recoverable errors, we should enforce that they are checked and handled. For program bugs, we should terminate execution as quickly as possible

This seems reasonable to me, but I'm curious what counts as a "program bug", or how to know that some error is recoverable.

No null value

Meh. I've think Option is just null with extra steps. NPEs arise because of method invocation, e.g., `x.foo()` while `x` is null. In FP languages like Clojure, NPEs don't really happen since `x` is just a value being passed to a function. With nil-punning and reasonable behavior of core functions (e.g., `(get m k)` yields nil when `m` is nil), an unexpected nil is really a type error.

No dead or unreachable code / No unused variables

Perhaps it's from my REPL-driven, exploratory style of development, but I think having to constantly comment out code I'm ambivalent about just to get it to compile would be a bit annoying. I'd think something like this should be a compile option, but I think that runs afoul of their "One Language"/"no flags" principle.

No variadic (varargs) functions

Having worked with enough Clojure, and seen enough bugs when using `apply` on vararg fns, I think this is a good choice. IIUC, their approach would replace, e.g., `(+ x y z ...)` with `(sum [x y z ...])`.

No binary or octal literals: ... It is our understanding that these features are rarely used in practice.

Until you need to do some bit-twiddling; maybe they still have hex. I wonder what they considered the downside of including it.

[1] https://flix.dev/principles/

[2] https://flix.dev/innovations/

[3] https://api.flix.dev/Console

[4] https://api.flix.dev/Array