Using > and >> as kwargs

Hi,
I would like to be able to use the > and >> characters as valid kwarg names. Example

julia> grd2xyz(G, >="lixo.xyz")
ERROR: syntax: ">=" is not a unary operator

The reason why it fails seems understandable, but I managed to make it work with the pipe operator.

julia> grd2xyz(G, |>="lixo.xyz")

Let me say that the parsing of the above is done by this line

if (haskey(d, :|>))	cmd = string(cmd, " > ", d[:|>])	end

So, neither > nor |> are unary operators but I can use |> as kwarg name but not >.
All would be good if I didn’t want to implement also the >> kwarg for the shell append to file operator. And here nothing that I tried works. |>> seemed the logical one, but again

julia> grd2xyz(G, |>>="lixo.xyz")
ERROR: syntax: "|>" is not a unary operator

Ant deep trick to accomplish these?
Thanks.

Use some space:

julia> f(x; > = 1) = >
f (generic function with 1 method)

julia> f(1, > = 2)
2

Just be aware that you can’t use the > operator in the function f, which seems like a problem to me (unless you go the kwargs Dict route, but that’s more suited for handling an unknown number of keywords which may contain some you don’t want to/can’t handle).

The reason your original didn’t work is because of operator precedence - julia tries to evaluate the expression >="lixo.xyz" during which it doesn’t find a second argument to >=, since that’s a binary operator. Assignments have the lowest precedence, which is why you have to write it this way with the space.

In general though, it’s adviced not to do that. Is there a special reason you want to have that character as an argument?

1 Like

Thanks, and yes, I see the risk of it.
The reason why I want to have those characters is be cause they exactly match what one would do in the shell. To explain better, on the shell this (GMT) command

grd2xyz G > lixo.xyz

Takes a grid (G) and converts it to a Mx3 file with x,y,z values. The closest I can get from the GMT Julia wrapper is to do

grd2xyz(G, > ="lixo.xyz")

Sure that can impose another syntax like

grd2xyz(G, redirect="lixo.xyz")

but then for appending to a previously existing file I would need something like

grd2xyz(G, redirect_append="lixo.xyz")

… humm, but now that I’m looking at it maybe it doesn’t look so bad.

I definitely think having clear names for keyword arguments is more julian - after all, if someone else is going to use that function at some point, they are bound to fall into the same pitfall you did with the operator precedence. Moreover, having a clear to read name helps reduce ambiguity - someone not used to Bash or some other Shell might not know about output redirection using > there. For them, having > as a keyword is surely going to feel alien and unintuitive.

You could also take a look at how open does it with it’s mode argument. As far as I know, having a mode is pretty much universal when it comes to IO (think C).

help?> open
# <snip>
  ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────

  open(filename::AbstractString, [mode::AbstractString]) -> IOStream

  Alternate syntax for open, where a string-based mode specifier is used instead of the five booleans. The values of mode correspond to those from fopen(3) or Perl open, and are
  equivalent to setting the following boolean groups:

Mode Description                   Keywords
–––– ––––––––––––––––––––––––––––– ––––––––––––––––––––––––––––
r    read                          none
w    write, create, truncate       write = true
a    write, create, append         append = true
r+   read, write                   read = true, write = true
w+   read, write, create, truncate truncate = true, read = true
a+   read, write, create, append   append = true, read = true

# snip

Or maybe even check out the do syntax?

1 Like

Thanks again, but the opening mode is not really an alternative here. The > or redirect serves as an alternative output (to a disk file) instead of returning the result of the operation into a julia variable. e.g

D = grd2xyz(G);

will return a Mx3 matrix (wrapped into a GMTdataset) into the D Julia variable.

Use a macro
Or possibly better yet, a string macros for your whole expression.
String macros are designed for implementinv DSLs.

And unlike macros are not tied to only work with things that parse

You can return & write to file independently - a mode keyword with default being return_only or something like that would accomplish that. Different modes can, in addition to the return, also write to the file specified. I just gave open as an example since that has a mode keyword used to distinguish between behaviour.

Thanks but … other than I’m not in metaprograming yet, it’s a bit late for changing things now as I already wrote quite a few code now

That is actually a good starting point for using macros. Since you already coded most of the functionality, now you can just create convenient macros that will rewritte expressions that resembles GMT syntax to your function calls already written.

3 Likes

It is never to late to rewrite code. :stuck_out_tongue:
(It is why I prefer coding to wood working, or sewing, or welding or …)

Anyway, for next time you want to do weird syntax.
Here is how to use a cmd macro (string macros are basically the same)
to put a DSL into julia.

Lets say you have a function, that is fairly standard julia code.

function grd2xyz(G, mode=nothing, file=nothing)
	F = G*G # or what wever operation you need to do
	if mode == ">>"
	   println("appending $F to $file")
	elseif mode == ">"
	   println("overwriting $F to $file")
	end
	return F
end

Here is the macro to wrap a DSL around that function

macro grd2xyz_cmd(str)
	tofile_match = match(r"(.*?)\s*(>+)\s*(.*)", str)
	if tofile_match!==nothing
		mainarg, modearg, filearg = tofile_match.captures
		filearg = Meta.parse(filearg)
	else
		mainarg = str;
		modearg = nothing
		filearg = nothing
	end
	mainarg = Meta.parse(mainarg)
	:(grd2xyz($mainarg, $modearg, $filearg))
end

Demo

julia> grd2xyz`10`
100

julia> grd2xyz`10 >> "a.txt"`
appending 100 to a.txt
100

julia> grd2xyz`10 > "a.txt"`
overwriting 100 to a.txt
100

julia> b = "b.txt"
"b.txt"


julia> grd2xyz`10 > b`       # look variables work
overwriting 100 to b.txt
100
1 Like

I may be wrong ofc, but the issue is that I already coded all the machinery needed to rewrite the expressions into what the syntax that GMT lib expects. And it’s not all strings. For example both of this are valid Julia syntax (to define a bounding box)
limits="0/10/0/10" or limits=[0 10 0 10] or limits=G where G is a grid and extra code will take charge to extract the BB from the grid’s header.

Sure (when one get tired of old bugs and need new ones :wink:)
I’ll keep a trace on this for next time I need something of this kind. That’s the beauty of this forum.
BTW here is the grd2xyz. A relatively small one but there are others with tons of options.

1 Like

I scanned the GMT and GMT.jl projects. Powerful stuff! Thank you for developing a Julia API for GMT.

According to the documentation you support β€œMonolitic” and β€œBy Module” modes. Your original question was about

julia> grd2xyz(G, >="lixo.xyz")

That seems to be β€œBy Module” mode and for that you already translate one letter GMT options to more readable Julia keyword arguments. In that context I see nothing wrong with translating the β€˜>’ operator to keyword argument redirect=.

With respect to your β€œMonolitic” mode, consider adding (as a future enhancement) a string macro (or command macro) that translates any GMT shell command to the API you already have.
For example macro GMT_str could translate

GMT" grd2xyz G > lixo.xyz "

to

grd2xyz(G, redirect="lixo.xyz")

I ended up with write=... and write_append because redirect calls to the shell > concept and as mentioned above, there are people that don’t know much about that.

As for the β€œMonolithic” mode, well that’s how things work. I mean, that’s what the GMT lib actually understands. The β€œBy modules” was a large effort to come up with something less cryptic but under the hood all is translated to the β€œMonolithic” way, which has tight rules on the order of (variable) input data. It’s because of these tight rules that I’m afraid a macro language would be hard to implement. But who knows.