feat: Add an equivalent @or method for arrays. With this addition both arrays and string can have alternative default values. How would it work? An empty array would trigger the @or method to return the second argument instead of the first. It could work with array slicing, but it's forbidden to create empty slices of arrays as of right now, thus the method would never trigger and return the second argument.
BREAKING CHANGE: None that I know of.
perf: impact
performance none
usability increase
maintainability increase
code: input
let array = []
# Inline array in @or method
echo @or(@array [foo bar])
# single value
echo @or(@array baz)
# variable expansion
let default = foobar
echo @or(@array $default)
# method would not trigger
let array ++= faz
echo @or(@array $default)
expect: output
foo bar
baz
foobar
faz
reason: Strings already have this feature and arrays should to. Parameter substitution are very common in bash scripts and should be ported over fully.
context: This is a stripped down version of a script I have today. I have to add the default argument in a check when I could have written it like: "${@-status}" in bash. I can't demonstrate how I would use the @or method as I still depend on array slicing.
fn mix_status
if bool $(pamixer --get-mute)
echo "--%"
else
printf "%2s%%\n" $(pamixer --get-volume)
end
end
test $len(@args) = 1 && let args ++= "status"
for arg in @args[1..]
match $arg
case status; mix_status
case toggle-mute; pamixer --toggle-mute
case mute; pamixer --mute
case _; echo "Usage: $(basename @args[0]) [status|toggle-mute|mute] ... (default: status)"
end
end
behavior of bash
Here is bash solution to parameter substitution: https://tldp.org/LDP/abs/html/parameter-substitution.html
No I no longer have the time. Go ahead.
Yeah you can call me Nils :) My request to join Mattermost was lost somewhere so I'm sending a new one. You did not miss anything from what I can tell. Just a bunch of work getting the parser to work and porting over existing methods.
Does clippy need to work on redox for ion development to continue? Also is there a language specification on matter most or elsewhere?
feat: ArrayMethod and StringMethod are two structures with identical members (StringMethod members are all public).
These two are doing largely the same work (supplying methods to one of the objects, string or array) but the implementation differs completely how the two structures does this. It feels like both of the files were written by two different persons at two different times.
ArrayMethod's methods largely defines each in-shell method in a separate source code method. StringMethod only has one handle method and uses builtin macros, within the scope of the method. All logic are decided in a huge match block.
The logic behind Array and String methods has to work with the name of the variable directly and resolve it's value manually. Ideally you want to write functions that takes typed arguments which works with values directly and does not have to de reference variable names or initiate array expressions. That should be the job of a parser.
The method structures are kind of parsed, but it could be better. If we look at ArrayMethod structure we will see a couple of problems. (StringMethod structure is the same)
pub struct ArrayMethod<'a> {
method: &'a str,
variable: &'a str,
pattern: Pattern<'a>,
selection: Option<&'a str>,
}
pub enum Pattern<'a> {
StringPattern(&'a str),
Whitespace,
}
I've tested what's put inside it and it seams like anything after the first argument in a in-shell method is put inside StringPattern as raw text. (Correct me if I'm wrong). Here is the rest of the variables I want my method to work with, in raw script source text. Now I have to manually parse them. 4. The ArrayMethod has a selection member. I don't know what that is, but I have a feeling that it should be pre-parsed away.
I suggest we go back to the drawing board with how method parsing should be done as it is a bit of a mess of how it's implemented.
My suggestion of a generic method structure is just to have an array of preparsed variables. No variable names needed to be de-referenced. No array expression needing to be created. The array should be typed and its contents must match the intended methods signature to be able to run.
Example:
pub enum Typed<'a> {
Array(&'a [str]),
String(&'a str),
}
pub struct MethodStruct<'a> {
variables: &'a [Typed]
}
The three culprit files are hidden in src/lib/expansion/methods/
BREAKING CHANGE: Changes in the datastructure, internal
performance: Might improve
usability: Adding methods for either ArrayMethod or StringMethod should be easier.
maintainability: Should remove code, making it easier to maintain.
reason: Refactoring for refactorings sake.
PS.
I've been rambling on about ArrayMember and StringMember. I just think there can be a lot of improvements to the method parsing in the files mentioned and that something should be done before even more methods needs to be written.
Oh and also testing tools should be rewritten. Why not test with the real ion shell instead of manually inserting variables into the shell heap with the DummyExpander?
Im starting to implement the subst
method for both string and array.
Then again, its not easy to implement and the syntax probably needs to be escaped or be wrapped around by parentheses which a function already does.
The ternary comparator could be written as a function like:
tern(condition: bool, value1: T, value2: T) -> T
Falsy and Trythy cascading could be useful in other areas like in branching, although this will result in less explicit code with a greater learning curve.
It could also just be localized to just the ternary operator.
I thought about writing something else about types, but it got me thinking. Why not replace subst
method (or the or
method) with the ternary operator and trythy and falsy cascading like in javascript? If the ternary operator is out of fashion we can use the rust way of "everything is a statement".
let array = []
let array = array ? array : [default]
# alternately
let array = if array; array; else [default]; end
Ternary operators does not need to use the expression as the first return value either which comes in handy in handy for putting statements inside functional parameters. For this to also work, the ternary operator must know what is a falsy or trythy value.
To avoid repeating the first argument (for the previous example) one could use a shortend form of the ternary operator:
let array = array ?: [default]
This is just an idea I had. It would solve the this issue and add useful features to the language. It might conflict with some other syntax.
We should require each method of subst to be typed on all fields, so that subst(input: T, default: T) -> T
hold true.
Im not used to ion, but I guess creating arrays inside methods is allowed so that:
# input
let array = []
echo @subst(array, [default])
# output
default
What I'm saying is that there is no need for a typeless second argument as we just have to wrap it around square brackets.
Out of index accesses are generally an indication of failures in the code
I agree. Out of index accesses are failures in the code. When is an out of index access not a failure?
But I don't think empty end slicing is out of index and it's far more useful and readable than it's harmful. You are only at risk of misusing empty end slicing if and only if:
You don't expect the slice to be empty AND
You don't know the minimum array length OR
I say that you are far more likely to do real out of indexing access errors than to do misuse empty end slicing. You are either don't know the minimum array length (you are far more likely to overshoot your indexing than to hit the array length), using variables as pointers (which are prone to out of indexing failures due to 0 based indexing mistakes or other indexing mistakes) or don't know that slices can be empty.
To avoid doing empty end slicing, either: handle empty slices OR assert the minimum size of the array AND assert that your bounds for the case numbered can't be equal to the array length. If you ever find yourself stuck with empty end slicing issues you can do 1 of 2 simple things to resolve the issue.
Again there is more to gain from empty end slicing than there is surface area for bugs (bugs which are much more likely to be out of index types than something else entirely, bugs that will hang around regardless of this issue).
I would not call it silent failing if one can accept that any subset of a set can be empty. There is no use case where checking the length beforehand is impossible. It's just that it's another thing you have to check before doing some computation or parsing.
Not having end slicing means that any and all ion scripts taking arguments must check the size of args
list before reading from it (due to the scripts name taking up index 0) which just adds complexity.
if test $len(@args) -gt 1
for arg in @args[1..]
match $arg
case status; mix_status
case raise; mix_change 5
case lower; mix_change -5
case _; echo "Usage: $(basename @args[0]) [status|raise|lower|help] ..."
end
end
end
If you can have empty arrays and empty slices, you should be able to create empty slices which just so happens to start at the end index.
Looking at other programming languages with optional value types...
Rust has the unwrap_or(self, default: T) -> T
which has the or keyword in it already.
Cpp std::optional has value_or
Haskell has maybe :: b -> (a -> b) -> Maybe a -> b
or the maybe function, which I dont think is a very good name for what it does.
There is no short synonyms for the word substitute that can fit the role.
But you can have an abbreviation of the word substitute with either sub
or subst
as the function name. I argue for the latter as sub
can be misstaken for subtraction.
I personaly think or
is good enough as it's somewhat standardized in other languages even if those languages are working with generic container types, instead of comparing lengths of arrays/strings.
bug: It's not possible to create an empty slice when the start range starts with the arrays current length.
expect: That the slicing range 1.. would skip the first element and print nothing if the array only had one element.
related: none
code: input
let foo=[bar]
echo @foo[0..]
echo @foo[0..0]
echo @foo[0..1]
echo @foo[1..]
echo @foo[1..1]
let foo=[bar baz]
echo @foo[0..]
echo @foo[0..0]
echo @foo[0..1]
echo @foo[1..]
echo @foo[1..1]
expect: output
bar
\n
bar
\n
\n
bar baz
\n
bar
baz
\n
actual: output
bar
\n
bar
ion: expansion error: invalid index
ion: expansion error: invalid index
bar baz
\n
bar
baz
\n
kernel: 5.12.4-arch1-2
version: ion 1.0.0-alpha (x86_64-unknown-linux-gnu) rev 1170b845
interaction: Run each command after the other
behavior of rust:
code:
fn main() {
let foo=vec!["bar"];
println!("{:?}", &foo[0..]);
println!("{:?}", &foo[0..0]);
println!("{:?}", &foo[0..1]);
println!("{:?}", &foo[1..]);
println!("{:?}", &foo[1..1]);
let foo=vec!["bar", "baz"];
println!("{:?}", &foo[0..]);
println!("{:?}", &foo[0..0]);
println!("{:?}", &foo[0..1]);
println!("{:?}", &foo[1..]);
println!("{:?}", &foo[1..1]);
}
output:
["bar"]
[]
["bar"]
[]
[]
["bar", "baz"]
[]
["bar"]
["baz"]
[]
feat: Add an equivalent @or method for arrays. With this addition both arrays and string can have alternative default values. How would it work? An empty array would trigger the @or method to return the second argument instead of the first. It could work with array slicing, but it's forbidden to create empty slices of arrays as of right now, thus the method would never trigger and return the second argument.
BREAKING CHANGE: None that I know of.
perf: impact
performance none
usability increase
maintainability increase
code: input
let array = []
# Inline array in @or method
echo @or(@array [foo bar])
# single value
echo @or(@array baz)
# variable expansion
let default = foobar
echo @or(@array $default)
# method would not trigger
let array ++= faz
echo @or(@array $default)
expect: output
foo bar
baz
foobar
faz
reason: Strings already have this feature and arrays should to. Parameter substitution are very common in bash scripts and should be ported over fully.
context: This is a stripped down version of a script I have today. I have to add the default argument in a check when I could have written it like: "${@-status}" in bash. I can't demonstrate how I would use the @or method as I still depend on array slicing.
fn mix_status
if bool $(pamixer --get-mute)
echo "--%"
else
printf "%2s%%\n" $(pamixer --get-volume)
end
end
test $len(@args) = 1 && let args ++= "status"
for arg in @args[1..]
match $arg
case status; mix_status
case toggle-mute; pamixer --toggle-mute
case mute; pamixer --mute
case _; echo "Usage: $(basename @args[0]) [status|toggle-mute|mute] ... (default: status)"
end
end
behavior of bash
Here is bash solution to parameter substitution: https://tldp.org/LDP/abs/html/parameter-substitution.html