[BuckleScript 8.1 New Syntax]: Arrays and Lists


#21

Such is the nature of language design I suppose. The [1;2;3] is a good suggestion as it bridges js with [1,2,3] for arrays and ocaml for lists [1;2;3]. (not sure if it’s distinct enough from each other though). Also single element lists to consider.
Bob’s #[1,2,3] is not bad either but I always found # symbol extremely noisy. I’m not sure what possible options are with the parser but a less noisy prefix would be good. Something short would be a benefit as list[....] is fairly verbose.

borrowing from above:

let ls = #[0, ...tail]

switch ls {
| #[] => 0
| #[1] => 1
| #[x, ..._] => x
}

#22

I’m mostly ok with this. I do prefer lists, but I’ve been working on performance-sensitive code for the last few months and the more we switch to arrays the better it performs. For functional recursion lists are essential, and we still do that where performance doesn’t matter, but I’ve found that for most list-based algorithms an equivalent (but less pure) array-based algorithm is usually much faster in JS. What I lose in write speed I more than make back in read speed (likely due to memory locality and CPU caches).

Pattern matching works on [||] arrays today, with the same caveats and warnings. It’s an underlying OCaml feature. Array patterns are fixed length so it’s not widely useful, but I have found it to be handy at times.


#23

I understand that Reason Native and Buckleson are diverging and keeping them inline is a non-goal but maybe we can have our cake and eat it too? Based on this it’s within OCaml’s “powers” to redefine the [] so we can have array in “pervasives” and use List.[] when we need to. Can this be an option? This way there is a “sensible default” and no additional syntax required. But then again I might be not seeing the bigger picture.

NB. Purescript that has been brought up in a conversation about “reason forgetting its functional roots” has [] for arrays and not lists ironically


#24

this means we can’t create an array called list

Don’t call an array list =)
Right now you can see that there’s nothing preventing you from doing let list = .... But after this solidifies we’ll properly remove that possibility. So you won’t be able to use list as an identifier, which removes this naming possibility but avoids the mistake of trying to do what you said.


#25

It is a very explicit goal to prioritize array over list. The current array ergonomics can use some work, but semantically they’re a better fit.

I think the occasional nicety of pattern matching for list and its syntax is making people forget that in general programming we use way more array than linked list, and for good reason: semantics, perf, interop, etc. And yes, that plus all the other impedance mismatches with JS users and really, users coming from most other languages.


#26

@chenglou In most of the languages (with a emphasis on declarative languages) which have support for both arrays and lists, lists are more used in general (Scala, ML based languages, …) I think we mostly do transformations instead of random access.

Another thing to mention; One of the cool features of reason / bucklescript is immutability, prioritizing a mutable collection is against the goal of limiting mutations. A compiler option which emits errors / warnings on array mutation would be good.


#27

I’m aware of the languages that prioritize lists over arrays. Said languages usually do it to simplify research.

The mutability part of array is indeed unfortunate, though many of array’s nature can be fixed. Clojure prioritizes array while keeping immutability.

Defaulting to linked list as a general-purpose collection recommendation over array is a disservice to the mentality (even considering the mutability drawback), modern performance and algorithms. Especially on the JS backend where you’re allocating a linear amount of arrays per list item. One of our foremost goals is to make JS users care about performance; list kinda goes against that.

And that’s before we consider that folks do List.reverse (and sometime doubly reverse), fold over list, etc. None of that is even good ergonomic! Yet they’re an essential part of list (whereas mutability isn’t an essential part of array).


#28

Same here! When targeting JS, wherever you get input from (e.g. HTTP request + JSON parsing) or write your output to (e.g. rendering to a React component, or persisting data as JSON somewhere), you will be reading or writing arrays. It just doesn’t make sense performance-wise to convert the data to lists and back to arrays on the way, nor does this make for better code.

Using only the Belt.Array APIs, I have never accidentally mutated an array in my code. Still, I wish there was some support for immutable arrays.


#29

+1 for arrays as default.

I have written a ton of UI code now, and there were only a few cases were I used list data types… mostly in small states where I need to keep track of a small collection of items (e.g. user selected tags) where pattern matching might make it more convenient, or some algorithms where linked lists made more sense (even there I always had to convert to an array after computing the result, since i needed to display it in React).

Other than that, I am always opting for arrays in almost every case, especially when interoping (HTTP / bindings). The mutability aspect is unfortunate, but with Belt.Array defaulting to non-mutating behavior, I think it’s pretty hard to unintentionally mutate an array.


#30

Lemme also take this occasion to address a likely undertone here: that prioritizing array makes the language less “functional”.

First, being functional is a mean to an end. Usually it works out well, but we’re not here to write functional code first: it’s product first, functional second. If being too FP gets in the way of shipping, then FP has to make concessions. This isn’t new; it’s called OCaml after all. The O is such concession, even if used less often.

Second, array is not less functional. The mutability of it is, but as said above, it can be avoided. Prioritizing random access also isn’t less functional. Array can also be pattern matched on, albeit list with the exhaustive check does help avoid more problems.

Back on topic: it’s been just a day since the release of the new syntax, but we’re already getting some pretty good feedback regarding the syntax decisions. We might revisit list. Thanks.


#31

Also, locally scoped mutability can boost some perf sensitive parts. I use it quite often where it makes sense.


#32

Update: we might indeed revisit the current list syntax. Please voice your feedback here: https://github.com/BuckleScript/syntax/issues/8


#33

How do you deal with conversions? Do you use List.toArray a lot or do you have helpers that map elements and convert lists to arrays in one pass? Do you use List.revert a lot?


#34

^ As an aside, this is why I’ve resisted adding React.toList to ReasonReact. For a little bit of convenience, we’d have hidden a big chunk of cognitive and performance problems.


#35

We do toArray when we need to render and persist. So looking at this discussion we might as well just use arrays:) And new syntax actually helps. Seeing that there is a github issue with a very nice suggestion of list{1, 2, 3} I think I’m pretty much sold on this:)


#36

Maybe a variation on bob’s suggestion of #[1,2,3] would be nice and use the ^ dropped from dereference syntax, (it looks like a little fold anyway).

let ls = ^[0, ...tail]

switch ls {
| ^[] => 0
| ^[1] => 1
| ^[x, ..._] => x
}

#37

Just thinking if reason / refmt 4 also gonna apply this change. something like #[x, y] syntax is more likely to be applied than list{}.


#38

Second, array is not less functional. The mutability of it is, but as said above, it can be avoided.

I often have to write recursive code to traverse a tree and accumulate results, and I’ve relied on fast list prepend to do this. Is there an effective way to push elements to an immutable array without having to copy and re-allocate memory for the entire collection each time?


#39

@jasim local mutability there is totally fine. Get the upper bound of the recursion (you should know the total size of your computation anyway), allocate an array that big, then mutate as you go. That’s a great example of non-harmful usage of mutation actually. And you’ve just saved yourself O(n) allocations.


#40

Got it. It is not as ergonomic as a linked list. But I’ve anyway started to treat Reason (and talk about it to newcomers) more as an ergonomic imperative programming language, where such operations can be bubble-wrapped inside a functional shell and tossed around freely. This just pushes the spectrum further towards imperative land.