Tracking issue: stdlib audit
Closed this issue · 17 comments
We should work out what the desired standard library interface is. This includes (but is not limited to):
- naming (e.g.
fold
vsfoldLeft
) - typing (e.g.
record.values
currently has contract{ ; Dyn } -> Array Str
, but perhaps could haveforall r. { ; r } -> Array Str
) - filling in any gaps (e.g.
record.map' : forall a b. (Str -> a -> { key: Str, value: b }) -> { _: a } -> { _: b }
from today's Nickel Hour) - removing any functions we don't wish to support long-term
An idea that came up today: there are certain functions involving polymorphic record contratcs which respect parametricity but due to the contracts involved are not supported in Nickel today. An example would be:
removeFoo | forall t r. { foo: t; r } -> { ; r } = fun r => record.remove "foo" r,
This seems valid, but currently fails because record.remove
has type forall a. Str -> {_: a} -> {_: a}
, and that type gets converted into a contract whose implementation calls %record_map%
, which can violate parametricity, so is not allowed.
A workaround is to use the primitive operation %record_remove%
, for which we do not generate a contract.
One way of supporting the above program without encouraging users to use primitive ops would be to expose an unsafe
module from the standard library, which contained untyped versions of various functions. The above function could then be written as:
removeFoo | forall t r. { foo: t; r } -> { ; r } = fun r => unsafe.record.remove "foo" r,
IMO it's a little weird that join : Str -> Array Str -> Str
is accessed via string.join
rather than array.join
. I get that it's not a "proper" join as we can't express it generically, but I was still surprised to find it there.
We should probably consider #1007 part of this issue.
As discussed before, let's gather here proposals for addition to the stdlib as well. Taking those of #321 here (originally proposed by @silverraven691):
-
nums.clamp : Num -> Num -> Num -> Num
-
functions.flip : forall a b c. (a -> b -> c) -> b -> a -> c
-
lists.optional : Bool -> List -> List
-
records.optional : Bool -> { _: Dyn } -> { _: Dyn }
-
records.filter : (Str -> Dyn -> Bool) -> { _: Dyn } -> { _: Dyn }
-
records.to_list : { _: Dyn } -> List { key: Str, value: Dyn } // or: records.entriesOf
-
records.from_list : List { key: Str, value: Dyn } -> { _: Dyn } // or: records.fromEntries
-
string.base64_encode : Str -> Str
/string.base64_decode : Str -> Str
(or replacing one of the type byBase64Str
)
Some more functions to add
-
array.intersperse : forall a. a -> Array a -> Array a
- Something like
string.join
for symbolic strings
Something that I wanted to write an example of generating a record with {servers_1 = ..., server_2 = ..., server_3 = ...}
is variant of map/fold functions which also provides the index. It can be done with an additional call to generate
right now, at least for map_index
, but it could be useful to have as well.
It would be useful to have a contract helper with the following semantics: Given a predicate pred
(or maybe a contract?) check whether the input satisfies pred
. If it doesn't apply a normalization function and check again.
Some more functions:
-
array.slice
ideally via a new primop -
array.split_at
-
array.replicate
-
array.range
- a contract for "either an enum tag or a string, that gets normalized into an enum tag"
-
array.fold1
for non-empty arrays - Contracts for symbolic strings
- Contracts for symbolic strings with a specific tag
array.fold1 for non-empty arrays
To be more in line with the previous naming scheme, I propose fold_left_first
and fold_right_last
, or fold_left_with_first
/fold_right_with_last
. Another possibility is reduce
or reduce_left
/reduce_right
. For the record, here is a similar debate for the naming of this function in Rust: rust-lang/rust#68125
I like reduce_left
/reduce_right
quite a bit actually. It seems to me that fold_left_first
/fold_right_last
doesn't really capture the intent, just like foldl1
doesn't really. And the rust community seems to agree, they stabilized the feature as reduce
.
We need more contract helper functions in general. The following ideas came up in a weekly meeting.
-
contract.or
where this makes sense (say, for predicates) -
contract.Equal
for checking value equality
contract.equal for checking contract equality
I think it was rather contract.Equal
or EqualsTo
to check value equality, as in foo | EqualsTo 5
. Unless I'm misremembering something? But I don't think we had any plan to make contract equality accessible to user code, did we?
You're absolutely right, the perils of not doing things in a timely manner 😅
I agree, this issue is as close to completed as it's ever going to get. Let's open more specific ones when necessary.