When constructing a string it is very simple to include optional parameters:
julia> "Hallo $(true ? "Nils" : "")"
"Hallo Nils"
julia> "Hallo $(false ? "Nils" : "")"
"Hallo "
However, when trying to apply the same technique to the backticks notation (to run external commands), the following issue arises:
julia> `command $(true ? "--flag" : "")`
`command --flag`
julia> `command $(false ? "--flag" : "")`
`command ''`
In the latter case the command that I execute will fail, as it interpretates '' as an invalid parameter.
julia> `command $(false ? "--flag" : nothing)`
`command nothing`
Also doesn't work, since keyword nothing gets translated to text.
Which syntactic options do I have, to use the backticks notation with optional parameters?
Use an empty array. Strings will always interpolate to one argument, but arrays expand to a variable number of arguments (including possibly 0 arguments).
julia> `command $(false ? "--flag" : [])`
`command`
Related
How can I parse more lines of code?
This is working:
julia> eval(parse("""print("O");print("K")"""))
OK
This is not working:
julia> eval(parse("""print("N");
print("O")"""))
ERROR: ParseError("extra token after end of expression")
Stacktrace:
[1] #parse#235(::Bool, ::Function, ::String) at ./parse.jl:237
[2] parse(::String) at ./parse.jl:232
BTW if I try line by line I have other problems. For example:
julia> parse("""for i in 1:3""")
:($(Expr(:incomplete, "incomplete: premature end of input")))
although:
julia> eval(parse("""for i in 1:2
println(i)
end"""))
1
2
It's a bit dopey, but this will do the trick!:
function parseall(str)
return Meta.parse("begin $str end").args
end
Explanation:
As #Alexander Morley pointed out above, parse is designed to only parse a single expression. So if you just make that single expression able to contain all the expressions in your original string, then it will parse it just fine! :)
The simplest way to do that is to wrap the string in a block, which makes it a valid julia expression. You can see that such an expression parses just fine:
julia> Meta.parse("""
begin
function f3(x)
x + 2
end
print("N")
print(f3(5))
end
""")
>> quote
#= none:2 =#
function f3(x)
#= none:3 =#
x + 2
end
#= none:5 =#
print("N")
#= none:6 =#
print(f3(5))
end
This is in fact the structure that the code in #Alexander Morley's is building. (And that's what made me think of doing it this way! Thanks Alexander!)
However, do note that if you are trying to parse a file, you actually don't want to return a single block expression like that returns, because you can't eval it as a single block: Julia doesn't allow you to nest some "top-level statements" (Modules) inside of anything. Instead you want to return an array of top-level expressions. That's why we return the args out of the Block expression.
With the above, you can eval each of those top-level expressions, and it will work correctly! :)
julia> for expr in parseall("module M f() = 1 end"); Core.eval(Main, expr); end
julia> M.f()
1
This part was figured out together with #misakawa (#thautwarm on Github).
parse is designed to parse a single expression (at least that's what the docs say: given this I'm actually a bit surprised your first example works , without throwing an error...).
If you want to parse mutliple expressions then you can take advantage of the fact that:
parse can take a second argument start that tells it where to
start parsing from.
If you provide this start argument then it returns a tuple containing the expression, and where the expression finished.
to define a parseall function yourself. There used to be one in base but I'm not sure there is anymore. Edit: there is still in the tests see below
# modified from the julia source ./test/parse.jl
function parseall(str)
pos = start(str)
exs = []
while !done(str, pos)
ex, pos = parse(str, pos) # returns next starting point as well as expr
ex.head == :toplevel ? append!(exs, ex.args) : push!(exs, ex) #see comments for info
end
if length(exs) == 0
throw(ParseError("end of input"))
elseif length(exs) == 1
return exs[1]
else
return Expr(:block, exs...) # convert the array of expressions
# back to a single expression
end
end
Although it seems fairly simple, there is a small nuance to the obvious solution.
The following code would cover most situations:
arr_1=(1 2 3 4)
arr_2=($arr_1)
However, empty strings do not copy over. The following code:
arr_1=('' '' 3 4)
arr_2=($arr_1)
print -l \
"Array 1 size: $#arr_1" \
"Array 2 size: $#arr_2"
will yield:
Array 1 size: 4
Array 2 size: 2
How would I go about getting a true copy of an array?
It would be an "Array Subscripts" issue, so you could properly specify an array subscript form to select all elements of an array ($arr_1 for instance) within double quotes:
arr_1=('' '' 3 4)
arr_2=("${arr_1[#]}")
#=> arr_2=("" "" "3" "4")
Each elements of $arr_1 would be properly surrounded with double quotes even if it is empty.
A subscript of the form ‘[*]’ or ‘[#]’ evaluates to all elements of an array; there is no difference between the two except when they appear within double quotes.
‘"$foo[*]"’ evaluates to ‘"$foo[1] $foo[2] ..."’, whereas ‘"$foo[#]"’ evaluates to ‘"$foo[1]" "$foo[2]" ...’.
...
When an array parameter is referenced as ‘$name’ (with no subscript) it evaluates to ‘$name[*]’,
-- Array Subscripts, zshparam(1)
And empty elements of arrays will be removed according to "Empty argument removal", so
arr_2=($arr_1)
#=> arr_2=(${arr_1[*]})
#=> arr_2=(3 4)
Above behavior is not good in this case.
24. Empty argument removal
If the substitution does not appear in double quotes, any resulting zero-length argument, whether from a scalar or an element of an array, is elided from the list of arguments inserted into the command line.
-- Empty argument removal, Rules Expansion zshexpn(1)
I am currently learning sml but I have one question that I can not find an answer for. I have googled but still have not found anything.
This is my code:
fun diamond(n) =
if(n=1) then (
print("*")
) else (
print("*")
diamond(n-1)
)
diamond(5);
That does not work. I want the code to show as many * as number n is and I want to do that with recursion, but I don't understand how to do that.
I get an error when I try to run that code. This is the error:
Standard ML of New Jersey v110.78 [built: Thu Aug 20 19:23:18 2015]
[opening a4_p2.sml] a4_p2.sml:8.5-9.17 Error: operator is not a
function [tycon mismatch] operator: unit in expression:
(print "*") diamond /usr/local/bin/sml: Fatal error -- Uncaught exception Error with 0 raised at
../compiler/TopLevel/interact/evalloop.sml:66.19-66.27
Thank you
You can do side effects in ML by using ';'
It will evaluate whatever is before the ';' and discard its result.
fun diamond(n) =
if(n=1)
then (print "*"; 1)
else (print "*"; diamond(n-1));
diamond(5);
The reason for the error is because ML is a strongly typed language that although you don't need to specify types explicitly, it will infer them based on environmental factors at compile time. For this reason, every evaluation of functions, statements like if else need to evaluate to an unambiguous singular type.
If you were allowed to do the following:
if(n=1)
then 1
else print "*";
then the compiler will get a different typing for the then and else branch respectively.
For the then branch the type would be int -> int whereas the type for the else branch would be int -> unit
Such a dichotomy is not allowed under a strongly typed language.
As you need to evaluate to a singular type, you will understand that ML does not support the execution of a block of instructions as we commonly see in other paradigms which transposed to ML naively would render something like this:
....
if(n=1)
then (print "1"
print "2"
)
else (print "3"
diamond(n-1)
)
...
because what type would the then branch evaluate to? int -> unit? Then what about the other print statement? A statement has to return a singular result(even it be a compound) so that would not make sense. What about int -> unit * unit? No problem with that except that syntactically speaking, you failed to communicate a tuple to the compiler.
For this reason, the following WOULD work:
fun diamond(n) =
if(n=1)
then (print "a", 1) /* A tuple of the type unit * int */
else diamond(n-1);
diamond(5);
As in this case you have a function of type int -> unit * int.
So in order to satisfy the requirement of the paradigm of strongly typed functional programming where we strive for building mechanisms that evaluate to one result-type, we thus need to communicate to the compiler that certain statements are to be executed as instructions and are not to be incorporated under the typing of the function under consideration.
For this reason, you use ';' to communicate to the compiler to simply evaluate that statement and discard its result from being incorporated under the type evaluation of the function.
As far as your actual objective is concerned, following is a better way of writing the function, diamond as type int -> string:
fun diamond(n) =
if(n=1)
then "*"
else "*" ^ diamond(n-1);
print( diamond(5) );
The above way is more for debugging purposes.
I am trying to learn Erlang and I am working on the practice problems Erlang has on the site. One of them is:
Write the function time:swedish_date() which returns a string containing the date in swedish YYMMDD format:
time:swedish_date()
"080901"
My function:
-module(demo).
-export([swedish_date/0]).
swedish_date() ->
[YYYY,MM,DD] = tuple_to_list(date()),
string:substr((integer_to_list(YYYY, 3,4)++pad_string(integer_to_list(MM))++pad_string(integer_to_list(DD)).
pad_string(String) ->
if
length(String) == 1 -> '0' ++ String;
true -> String
end.
I'm getting the following errors when compiled.
demo.erl:6: syntax error before: '.'
demo.erl:2: function swedish_date/0 undefined
demo.erl:9: Warning: function pad_string/1 is unused
error
How do I fix this?
After fixing your compilation errors, you're still facing runtime errors. Since you're trying to learn Erlang, it's instructive to look at your approach and see if it can be improved, and fix those runtime errors along the way.
First let's look at swedish_date/0:
swedish_date() ->
[YYYY,MM,DD] = tuple_to_list(date()),
Why convert the list to a tuple? Since you use the list elements individually and never use the list as a whole, the conversion serves no purpose. You can instead just pattern-match the returned tuple:
{YYYY,MM,DD} = date(),
Next, you're calling string:substr/1, which doesn't exist:
string:substr((integer_to_list(YYYY,3,4) ++
pad_string(integer_to_list(MM)) ++
pad_string(integer_to_list(DD))).
The string:substr/2,3 functions both take a starting position, and the 3-arity version also takes a length. You don't need either, and can avoid string:substr entirely and instead just return the assembled string:
integer_to_list(YYYY,3,4) ++
pad_string(integer_to_list(MM)) ++
pad_string(integer_to_list(DD)).
Whoops, this is still not right: there is no such function integer_to_list/3, so just replace that first call with integer_to_list/1:
integer_to_list(YYYY) ++
pad_string(integer_to_list(MM)) ++
pad_string(integer_to_list(DD)).
Next, let's look at pad_string/1:
pad_string(String) ->
if
length(String) == 1 -> '0' ++ String;
true -> String
end.
There's a runtime error here because '0' is an atom and you're attempting to append String, which is a list, to it. The error looks like this:
** exception error: bad argument
in operator ++/2
called as '0' ++ "8"
Instead of just fixing that directly, let's consider what pad_string/1 does: it adds a leading 0 character if the string is a single digit. Instead of using if to check for this condition — if isn't used that often in Erlang code — use pattern matching:
pad_string([D]) ->
[$0,D];
pad_string(S) ->
S.
The first clause matches a single-element list, and returns a new list with the element D preceded with $0, which is the character constant for the character 0. The second clause matches all other arguments and just returns whatever is passed in.
Here's the full version with all changes:
-module(demo).
-export([swedish_date/0]).
swedish_date() ->
{YYYY,MM,DD} = date(),
integer_to_list(YYYY) ++
pad_string(integer_to_list(MM)) ++
pad_string(integer_to_list(DD)).
pad_string([D]) ->
[$0,D];
pad_string(S) ->
S.
But a simpler approach would be to use the io_lib:format/2 function to just format the desired string directly:
swedish_date() ->
io_lib:format("~w~2..0w~2..0w", tuple_to_list(date())).
First, note that we're back to calling tuple_to_list(date()). This is because the second argument for io_lib:format/2 must be a list. Its first argument is a format string, which in our case says to expect three arguments, formatting each as an Erlang term, and formatting the 2nd and 3rd arguments with a width of 2 and 0-padded.
But there's still one more step to address, because if we run the io_lib:format/2 version we get:
1> demo:swedish_date().
["2015",["0",56],"29"]
Whoa, what's that? It's simply a deep list, where each element of the list is itself a list. To get the format we want, we can flatten that list:
swedish_date() ->
lists:flatten(io_lib:format("~w~2..0w~2..0w", tuple_to_list(date()))).
Executing this version gives us what we want:
2> demo:swedish_date().
"20150829"
Find the final full version of the code below.
-module(demo).
-export([swedish_date/0]).
swedish_date() ->
lists:flatten(io_lib:format("~w~2..0w~2..0w", tuple_to_list(date()))).
UPDATE: #Pascal comments that the year should be printed as 2 digits rather than 4. We can achieve this by passing the date list through a list comprehension:
swedish_date() ->
DateVals = [D rem 100 || D <- tuple_to_list(date())],
lists:flatten(io_lib:format("~w~2..0w~2..0w", DateVals)).
This applies the rem remainder operator to each of the list elements returned by tuple_to_list(date()). The operation is needless for month and day but I think it's cleaner than extracting the year and processing it individually. The result:
3> demo:swedish_date().
"150829"
There are a few issues here:
You are missing a parenthesis at the end of line 6.
You are trying to call integer_to_list/3 when Erlang only defines integer_to_list/1,2.
This will work:
-module(demo).
-export([swedish_date/0]).
swedish_date() ->
[YYYY,MM,DD] = tuple_to_list(date()),
string:substr(
integer_to_list(YYYY) ++
pad_string(integer_to_list(MM)) ++
pad_string(integer_to_list(DD))
).
pad_string(String) ->
if
length(String) == 1 -> '0' ++ String;
true -> String
end.
In addition to the parenthesis error on line 6, you also have an error on line 10 where yo use the form '0' instead of "0", so you define an atom rather than a string.
I understand you are doing this for educational purpose, but I encourage you to dig into erlang libraries, it is something you will have to do. For a common problem like this, it already exists function that help you:
swedish_date() ->
{YYYY,MM,DD} = date(), % not useful to transform into list
lists:flatten(io_lib:format("~2.10.0B~2.10.0B~2.10.0B",[YYYY rem 100,MM,DD])).
% ~X.Y.ZB means: uses format integer in base Y, print X characters, uses Z for padding
According to man zshexpn (5.0.2):
~ Force string arguments to any of the flags below that follow within the parentheses to be treated as
patterns.
For example, using the s flag to perform field splitting requires a string argument:
% print -l ${(s:9:):-"foo893bar923baz"}
foo8
3bar
23baz
My reading of the ~ flag suggests that I should be able to specify a pattern in place of a literal string to split on, so that the following
% print -l ${(~s:<->:):-"foo893bar923baz"}
should produce
foo
bar
baz
Instead, it behaves the same as if I omit the ~, performing no splitting at all.
% print -l ${(s:<->:):-"foo893bar923baz"}
foo893bar923baz
% print -l ${(~s:<->:):-"foo893bar923baz"}
foo893bar923baz
Ok, rereading the question, it's the difference between this:
$ val="foo???bar???baz"
$ print -l ${(s.?.)val}
foo
bar
baz
And this:
$ val="foo???bar???baz"
$ print -l ${(~s.?.)val}
foo???bar???baz
It operates on the variable, i.e. the "argument" to the split (from your documentation quote). In the first example, we substitute literal ?, and in the second, we treat the variable as a glob, and there are no literal ?, so nothing gets substituted.
Still, though, split works on characters and not globs in the substution itself, from the documentation:
s:string:
Force field splitting (see the option SH_WORD_SPLIT) at the separator string.
So, it doesn't look like you can split on a pattern. The ~ character modifies the interpretation of the string to be split.
Also, from the same pattern expansion documentation you reference, it continutes:
Compare with a ~ outside parentheses, which forces the entire
substituted string to be treated as a pattern. [[ "?" = ${(~j.|.)array} ]]
with the EXTENDED_GLOB option set succeeds if and
only if $array contains the string ‘?’ as an element. The argument may
be repeated to toggle the behaviour; its effect only lasts to the end
of the parenthesised group.
The difference between ${(~j.|.)array} and ${(j.|.)~array} is that the former treats the values inarray as global, and the latter treats the result as a glob.
See also:
${~spec} Turn on the GLOB_SUBST option for the evaluation of spec; if
the ‘~’ is doubled, turn it off. When this option is set, the string
resulting from the expansion will be interpreted as a pattern anywhere
that is possible, such as in filename expansion and filename
generation and pattern-matching contexts like the right hand side of
the ‘=’ and ‘!=’ operators in conditions.
Here is a demo that shows the differences:
$ array=("foo???bar???baz" "foo???bar???buz")
$ [[ "foo___bar___baz" = ${(~j.|.)array} ]] && echo true || echo false
false
$ [[ "foo___bar___baz" = ${(j.|.)~array} ]] && echo true || echo false
true
And for completeness:
$ [[ "foo___bar___baz" = ${(~j.|.)~array} ]] && echo true || echo false
true