goal

package
v0.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Feb 15, 2025 License: BSD-3-Clause Imports: 24 Imported by: 0

README

Goal: Go augmented language

Goal is an augmented version of the Go language, which combines the best parts of Go, bash, and Python, to provide and integrated shell and numerical expression processing experience, which can be combined with the yaegi interpreter to provide an interactive "REPL" (read, evaluate, print loop).

Goal transpiles directly into Go, so it automatically leverages all the great features of Go, and remains fully compatible with it. The augmentation is designed to overcome some of the limitations of Go in specific domains:

  • Shell scripting, where you want to be able to directly call other executable programs with arguments, without having to navigate all the complexity of the standard os.exec package.

  • Numerical / math / data processing, where you want to be able to write simple mathematical expressions operating on vectors, matricies and other more powerful data types, without having to constantly worry about numerical type conversions, and advanced n-dimensional indexing and slicing expressions are critical. Python is the dominant language here precisely because it lets you ignore type information and write such expressions, using operator overloading.

  • GPU-based parallel computation, which can greatly speed up some types of parallelizable computations by effectively running many instances of the same code in parallel across a large array of data. The gosl package (automatically run in goal build mode) allows you to run the same Go-based code on a GPU or CPU (using parallel goroutines). See the GPU docs for an overview and comparison to other approaches to GPU computation.

The main goal of Goal is to achieve a "best of both worlds" solution that retains all the type safety and explicitness of Go for all the surrounding control flow and large-scale application logic, while also allowing for a more relaxed syntax in specific, well-defined domains where the Go language has been a barrier. Thus, unlike Python where there are various weak attempts to try to encourage better coding habits, Goal retains in its Go foundation a fundamentally scalable, "industrial strength" language that has already proven its worth in countless real-world applications.

For the shell scripting aspect of Goal, the simple idea is that each line of code is either Go or shell commands, determined in a fairly intuitive way mostly by the content at the start of the line (formal rules below). If a line starts off with something like ls -la... then it is clear that it is not valid Go code, and it is therefore processed as a shell command.

You can intermix Go within a shell line by wrapping an expression with { } braces, and a Go expression can contain shell code by using $. Here's an example:

for i, f := range goalib.SplitLines($ls -la$) {  // ls executes, returns string
    echo {i} {strings.ToLower(f)}              // {} surrounds Go within shell
}

where goalib.SplitLines is a function that runs strings.Split(arg, "\n"), defined in the goalib standard library of such frequently-used helper functions.

For cases where most of the code is standard Go with relatively infrequent use of shell expressions, or in the rare cases where the default interpretation doesn't work, you can explicitly tag a line as shell code using $:

$ chmod +x *.goal

For mathematical expressions, we use # symbols (# = number) to demarcate such expressions. Often you will write entire lines of such expressions:

# x := 1. / (1. + exp(-wts[:, :, :n] * acts[:]))

You can also intermix within Go code:

for _, x := range #[1,2,3]# {
    fmt.Println(#x^2#)
}

Note that you cannot enter math mode directly from shell mode, which is unlikely to be useful anyway (you can wrap in go mode { } if really needed).

In general, the math mode syntax in Goal is designed to be as compatible with Python NumPy / SciPy syntax as possible, while also adding a few Go-specific additions as well: see the Math mode section for details. All elements of a Goal math expression are tensors, which can represent everything from a scalar to an n-dimenstional tensor. These are called "ndarray" in NumPy terms.

The one special form of tensor processing that is available in regular Go code is n dimensional indexing, e.g., tsr[1,2]. This kind of expression with square brackets [ ] and a comma is illegal according to standard Go syntax, so when we detect it, we know that it is being used on a tensor object, and can transpile it into the corresponding tensor.Value or tensor.Set* expression. This is particularly convenient for gosl GPU code that has special support for tensor data. Note that for this GPU use-case, you actually do not want to use math mode, because that engages a different, more complex form of indexing that does not work on the GPU.

The rationale and mnemonics for using $ and # are as follows:

  • These are two of the three common ASCII keyboard symbols that are not part of standard Go syntax (@ being the other).

  • $ can be thought of as "S" in _S_hell, and is often used for a bash prompt, and many bash examples use it as a prefix. Furthermore, in bash, $( ) is used to wrap shell expressions.

  • # is commonly used to refer to numbers. It is also often used as a comment syntax, but on balance the number semantics and uniqueness relative to Go syntax outweigh that issue.

Examples

Here are a few useful examples of Goal code:

You can easily perform handy duration and data size formatting:

22010706 * time.Nanosecond  // 22.010706ms
datasize.Size(44610930)     // 42.5 MB

Shell mode

Environment variables

  • set <var> <value> (space delimited as in all shell mode, no equals)

Output redirction

  • Standard output redirect: > and >& (and |, |& if needed)

Control flow

  • Any error stops the script execution, except for statements wrapped in [ ], indicating an "optional" statement, e.g.:
cd some; [mkdir sub]; cd sub
  • & at the end of a statement runs in the background (as in bash) -- otherwise it waits until it completes before it continues.

  • jobs, fg, bg, and kill builtin commands function as in usual bash.

Shell functions (aliases)

Use the command keyword to define new functions for Shell mode execution, which can then be used like any other command, for example:

command list {
	ls -la args...
}
cd data
list *.tsv

The command is transpiled into a Go function that takes args ...string. In the command function body, you can use the args... expression to pass all of the args, or args[1] etc to refer to specific positional indexes, as usual.

The command function name is registered so that the standard shell execution code can run the function, passing the args. You can also call it directly from Go code using the standard parentheses expression.

Script Files and Makefile-like functionality

As with most scripting languages, a file of goal code can be made directly executable by appending a "shebang" expression at the start of the file:

#!/usr/bin/env goal

When executed this way, any additional args are available via an args []any variable, which can be passed to a command as follows:

install {args...}

or by referring to specific arg indexes etc.

To make a script behave like a standard Makefile, you can define different commands for each of the make commands, and then add the following at the end of the file to use the args to run commands:

goal.RunCommands(args)

See make for an example, in cmd/goal/testdata/make, which can be run for example using:

./make build

Note that there is nothing special about the name make here, so this can be done with any file.

The make package defines a number of useful utility functions that accomplish the standard dependency and file timestamp checking functionality from the standard make command, as in the magefile system. Note that the goal direct shell command syntax makes the resulting make files much closer to a standard bash-like Makefile, while still having all the benefits of Go control and expressions, compared to magefile.

TODO: implement and document above.

SSH connections to remote hosts

Any number of active SSH connections can be maintained and used dynamically within a script, including simple ways of copying data among the different hosts (including the local host). The Go mode execution is always on the local host in one running process, and only the shell commands are executed remotely, enabling a unique ability to easily coordinate and distribute processing and data across various hosts.

Each host maintains its own working directory and environment variables, which can be configured and re-used by default whenever using a given host.

  • gossh hostname.org [name] establishes a connection, using given optional name to refer to this connection. If the name is not provided, a sequential number will be used, starting with 1, with 0 referring always to the local host.

  • @name then refers to the given host in all subsequent commands, with @0 referring to the local host where the goal script is running.

  • You can use a variable name for the server, like this (the explicit $ $ shell mode is required because a line starting with { is not recognized as shell code):

server := "@myserver"
${server} ls$
Explicit per-command specification of host
@name cd subdir; ls
Default host
@name // or:
gossh @name

uses the given host for all subsequent commands (unless explicitly specified), until the default is changed. Use gossh @0 to return to localhost.

Redirect input / output among hosts

The output of a remote host command can be sent to a file on the local host:

@name cat hostfile.tsv > @0:localfile.tsv

Note the use of the : colon delimiter after the host name here. TODO: You cannot send output to a remote host file (e.g., > @host:remotefile.tsv) -- maybe with sftp?

The output of any command can also be piped to a remote host as its standard input:

ls *.tsv | @host cat > files.txt
scp to copy files easily

The builtin scp function allows easy copying of files across hosts, using the persistent connections established with gossh instead of creating new connections as in the standard scp command.

scp is always run from the local host, with the remote host filename specified as @name:remotefile

scp @name:hostfile.tsv localfile.tsv

Importantly, file wildcard globbing works as expected:

scp @name:*.tsv @0:data/

and entire directories can be copied, as in cp -a or cp -r (this behavior is automatic and does not require a flag).

Close connections
gossh close

Will close all active connections and return the default host to @0. All active connections are also automatically closed when the shell terminates.

Other Utilties

** TODO: need a replacement for findnm -- very powerful but garbage..

Rules for Go vs. Shell determination

These are the rules used to determine whether a line is Go vs. Shell (word = IDENT token):

  • $ at the start: Shell.
  • Within Shell, {}: Go
  • Within Go, $ $: Shell
  • Line starts with go keyword: if no ( ) then Shell, else Go
  • Line is one word: Shell
  • Line starts with path expression (e.g., ./myexec) : Shell
  • Line starts with "string": Shell
  • Line starts with word word: Shell
  • Line starts with word {: Shell
  • Otherwise: Go

TODO: update above

Multiple statements per line

  • Multiple statements can be combined on one line, separated by ; as in regular Go and shell languages. Critically, the language determination for the first statement determines the language for the remaining statements; you cannot intermix the two on one line, when using ;

Math mode

The math mode in Goal is designed to be generally compatible with Python NumPy / SciPy syntax, so that the widespread experience with that syntax transfers well to Goal. This syntax is also largely compatible with MATLAB and other languages as well. However, we did not fully replicate the NumPy syntax, instead choosing to clean up a few things and generally increase consistency with Go.

In general the Goal global functions are named the same as NumPy, without the np. prefix, which improves readability. It should be very straightforward to write a conversion utility that converts existing NumPy code into Goal code, and that is a better process than trying to make Goal itself perfectly compatible.

All elements of a Goal math expression are tensors (i.e., tensor.Tensor), which can represent everything from a scalar to an n-dimenstional tensor, with different views that support the arbitrary slicing and flexible forms of indexing documented in the table below. These are called an ndarray in NumPy terms. See array vs. tensor NumPy docs for more information. Note that Goal does not have a distinct matrix type; everything is a tensor, and when these are 2D, they function appropriately via the matrix package.

The view versions of Tensor include Sliced, Reshaped, Masked, Indexed, and Rows, each of which wraps around another "source" Tensor, and provides its own way of accessing the underlying data:

  • Sliced has an arbitrary set of indexes for each dimension, so access to values along that dimension go through the indexes. Thus, you could reverse the order of the columns (dimension 1), or only operate on a subset of them.

  • Masked has a tensor.Bool tensor that filters access to the underlying source tensor through a mask: anywhere the bool value is false, the corresponding source value is not settable, and returns NaN (missing value) when accessed.

  • Indexed uses a tensor of indexes where the final, innermost dimension is the same size as the number of dimensions in the wrapped source tensor. The overall shape of this view is that of the remaining outer dimensions of the Indexes tensor, and like other views, assignment and return values are taken from the corresponding indexed value in the wrapped source tensor.

    The current NumPy version of indexed is rather complex and difficult for many people to understand, as articulated in this NEP 21 proposal. The Indexed view at least provides a simpler way of representing the indexes into the source tensor, instead of requiring multiple parallel 1D arrays.

  • Rows is an optimized version of Sliced with indexes only for the first, outermost, row dimension.

The following sections provide a full list of equivalents between the tensor Go code, Goal, NumPy, and MATLAB, based on the table in numpy-for-matlab-users.

  • The same: in Goal means that the same NumPy syntax works in Goal, minus the np. prefix, and likewise for or: (where Goal also has additional syntax).
  • In the tensor.Go code, we sometimes just write a scalar number for simplicity, but these are actually tensor.NewFloat64Scalar etc.
  • Goal also has support for string tensors, e.g., for labels, and operators such as addition that make sense for strings are supported. Otherwise, strings are automatically converted to numbers using the tensor.Float interface. If you have any doubt about whether you've got a tensor.Float64 when you expect one, use tensor.AsFloat64Tensor which makes sure.

Tensor shape

tensor Go Goal NumPy MATLAB Notes
a.NumDim() ndim(a) or a.ndim np.ndim(a) or a.ndim ndims(a) number of dimensions of tensor a
a.Len() len(a) or a.len or: np.size(a) or a.size numel(a) number of elements of tensor a
a.Shape().Sizes same: np.shape(a) or a.shape size(a) "size" of each dimension in a; shape returns a 1D int tensor
a.Shape().Sizes[1] same: a.shape[1] size(a,2) the number of elements of the 2nd dimension of tensor a
tensor.Reshape(a, 10, 2) same except no a.shape = (10,2): a.reshape(10, 2) or np.reshape(a, 10, 2) or a.shape = (10,2) reshape(a,10,2) set the shape of a to a new shape that has the same total number of values (len or size); No option to change order in Goal: always row major; Goal does not support direct shape assignment version.
tensor.Reshape(a, tensor.AsIntSlice(sh)...) same: a.reshape(10, sh) or np.reshape(a, sh) reshape(a,sh) set shape based on list of dimension sizes in tensor sh
tensor.Reshape(a, -1) or tensor.As1D(a) same: a.reshape(-1) or np.reshape(a, -1) reshape(a,-1) a 1D vector view of a; Goal does not support ravel, which is nearly identical.
tensor.Flatten(a) same: b = a.flatten() b=a(:) returns a 1D copy of a
b := tensor.Clone(a) b := copy(a) or: b = a.copy() b=a direct assignment b = a in Goal or NumPy just makes variable b point to tensor a; copy is needed to generate new underlying values (MATLAB always makes a copy)
tensor.Squeeze(a) same: a.squeeze() squeeze(a) remove singleton dimensions of tensor a.

Constructing

tensor Go Goal NumPy MATLAB Notes
tensor.NewFloat64FromValues( []float64{1, 2, 3}) [1., 2., 3.] np.array([1., 2., 3.]) [ 1 2 3 ] define a 1D tensor
[[1., 2., 3.], [4., 5., 6.]] or: (np.array([[1., 2., 3.], [4., 5., 6.]]) [ 1 2 3; 4 5 6 ] define a 2x3 2D tensor
[[a, b], [c, d]] or block([[a, b], [c, d]]) np.block([[a, b], [c, d]]) [ a b; c d ]
tensor.NewFloat64(3,4) zeros(3,4) np.zeros((3, 4)) zeros(3,4) 3x4 2D tensor of float64 zeros; Goal does not use "tuple" so no double parens
tensor.NewFloat64(3,4,5) zeros(3, 4, 5) np.zeros((3, 4, 5)) zeros(3,4,5) 3x4x5 three-dimensional tensor of float64 zeros
tensor.NewFloat64Ones(3,4) ones(3, 4) np.ones((3, 4)) ones(3,4) 3x4 2D tensor of 64-bit floating point ones
tensor.NewFloat64Full(5.5, 3,4) full(5.5, 3, 4) np.full((3, 4), 5.5) ? 3x4 2D tensor of 5.5; Goal variadic arg structure requires value to come first
tensor.NewFloat64Rand(3,4) rand(3, 4) or slrand(c, fi, 3, 4) rng.random(3, 4) rand(3,4) 3x4 2D float64 tensor with uniform random 0..1 elements; rand uses current Go rand source, while slrand uses gosl GPU-safe call with counter c and function index fi and key = index of element
TODO: np.concatenate((a,b),1) or np.hstack((a,b)) or np.column_stack((a,b)) or np.c_[a,b] [a b] concatenate columns of a and b
TODO: np.concatenate((a,b)) or np.vstack((a,b)) or np.r_[a,b] [a; b] concatenate rows of a and b
TODO: np.tile(a, (m, n)) repmat(a, m, n) create m by n copies of a
TODO: a[np.r_[:len(a),0]] a([1:end 1],:) a with copy of the first row appended to the end

Ranges and grids

See NumPy docs for details.

tensor Go Goal NumPy MATLAB Notes
tensor.NewIntRange(1, 11) same: np.arange(1., 11.) or np.r_[1.:11.] or np.r_[1:10:10j] 1:10 create an increasing vector; arange in goal is always ints; use linspace or tensor.AsFloat64 for floats
same: np.arange(10.) or np.r_[:10.] or np.r_[:9:10j] 0:9 create an increasing vector; 1 arg is the stop value in a slice
np.arange(1.,11.) [:, np.newaxis] [1:10]' create a column vector
t.NewFloat64 SpacedLinear( 1, 3, 4, true) linspace(1,3,4,true) np.linspace(1,3,4) linspace(1,3,4) 4 equally spaced samples between 1 and 3, inclusive of end (use false at end for exclusive)
np.mgrid[0:9.,0:6.] or np.meshgrid(r_[0:9.], r_[0:6.]) [x,y]=meshgrid(0:8,0:5) two 2D tensors: one of x values, the other of y values
ogrid[0:9.,0:6.] or np.ix_(np.r_[0:9.], np.r_[0:6.] the best way to eval functions on a grid
np.meshgrid([1,2,4], [2,4,5]) [x,y]=meshgrid([1,2,4],[2,4,5])
np.ix_([1,2,4], [2,4,5]) the best way to eval functions on a grid

Basic indexing

See NumPy basic indexing. Tensor Go uses the Reslice function for all cases (repeated tensor. prefix replaced with t. to take less space). Here you can clearly see the advantage of Goal in allowing significantly more succinct expressions to be written for accomplishing critical tensor functionality.

tensor Go Goal NumPy MATLAB Notes
t.Reslice(a, 1, 4) same: a[1, 4] a(2,5) access element in second row, fifth column in 2D tensor a
t.Reslice(a, -1) same: a[-1] a(end) access last element
t.Reslice(a, 1, t.FullAxis) same: a[1] or a[1, :] a(2,:) entire second row of 2D tensor a; unspecified dimensions are equivalent to : (could omit second arg in Reslice too)
t.Reslice(a, Slice{Stop:5}) same: a[0:5] or a[:5] or a[0:5, :] a(1:5,:) 0..4 rows of a; uses same Go slice ranging here: (start:stop) where stop is exclusive
t.Reslice(a, Slice{Start:-5}) same: a[-5:] a(end-4:end,:) last 5 rows of 2D tensor a
t.Reslice(a, t.NewAxis, Slice{Start:-5}) same: a[newaxis, -5:] ? last 5 rows of 2D tensor a, as a column vector
t.Reslice(a, Slice{Stop:3}, Slice{Start:4, Stop:9}) same: a[0:3, 4:9] a(1:3,5:9) The first through third rows and fifth through ninth columns of a 2D tensor, a.
t.Reslice(a, Slice{Start:2, Stop:25, Step:2}, t.FullAxis) same: a[2:21:2,:] a(3:2:21,:) every other row of a, starting with the third and going to the twenty-first
t.Reslice(a, Slice{Step:2}, t.FullAxis) same: a[::2, :] a(1:2:end,:) every other row of a, starting with the first
t.Reslice(a,, Slice{Step:-1}, t.FullAxis) same: a[::-1,:] a(end:-1:1,:) or flipud(a) a with rows in reverse order
t.Clone(t.Reslice(a, 1, t.FullAxis)) b = copy(a[1, :]) or: b = a[1, :].copy() y=x(2,:) without the copy, y would point to a view of values in x; copy creates distinct values, in this case of only the 2nd row of x -- i.e., it "concretizes" a given view into a literal, memory-continuous set of values for that view.
tmath.Assign( t.Reslice(a, Slice{Stop:5}), t.NewIntScalar(2)) same: a[:5] = 2 a(1:5,:) = 2 assign the value 2 to 0..4 rows of a
(you get the idea) same: a[:5] = b[:, :5] a(1:5,:) = b(:, 1:5) assign the values in the first 5 columns of b to the first 5 rows of a

Boolean tensors and indexing

See NumPy boolean indexing.

Note that Goal only supports boolean logical operators (&& and ||) on boolean tensors, not the single bitwise operators & and |.

tensor Go Goal NumPy MATLAB Notes
tmath.Greater(a, 0.5) same: (a > 0.5) (a > 0.5) bool tensor of shape a with elements (v > 0.5)
tmath.And(a, b) a && b logical_and(a,b) a & b element-wise AND operator on bool tensors
tmath.Or(a, b) a || b np.logical_or(a,b) a | b element-wise OR operator on bool tensors
tmath.Negate(a) !a ? ? element-wise negation on bool tensors
tmath.Assign( tensor.Mask(a, tmath.Less(a, 0.5), 0) same: a[a < 0.5]=0 a(a<0.5)=0 a with elements less than 0.5 zeroed out
tensor.Flatten( tensor.Mask(a, tmath.Less(a, 0.5))) same: a[a < 0.5].flatten() ? a 1D list of the elements of a < 0.5 (as a copy, not a view)
tensor.Mul(a, tmath.Greater(a, 0.5)) same: a * (a > 0.5) a .* (a>0.5) a with elements less than 0.5 zeroed out

Advanced index-based indexing

See NumPy integer indexing. Note that the current NumPy version of indexed is rather complex and difficult for many people to understand, as articulated in this NEP 21 proposal.

TODO: not yet implemented:

tensor Go Goal NumPy MATLAB Notes
a[np.ix_([1, 3, 4], [0, 2])] a([2,4,5],[1,3]) rows 2,4 and 5 and columns 1 and 3.
np.nonzero(a > 0.5) find(a > 0.5) find the indices where (a > 0.5)
a[:, v.T > 0.5] a(:,find(v>0.5)) extract the columns of a where column vector v > 0.5
a[:,np.nonzero(v > 0.5)[0]] a(:,find(v > 0.5)) extract the columns of a where vector v > 0.5
a[:] = 3 a(:) = 3 set all values to the same scalar value
np.sort(a) or a.sort(axis=0) sort(a) sort each column of a 2D tensor, a
np.sort(a, axis=1) or a.sort(axis=1) sort(a, 2) sort the each row of 2D tensor, a
I = np.argsort(a[:, 0]); b = a[I,:] [b,I]=sortrows(a,1) save the tensor a as tensor b with rows sorted by the first column
np.unique(a) unique(a) a vector of unique values in tensor a

Basic math operations (add, multiply, etc)

In Goal and NumPy, the standard +, -, *, / operators perform element-wise operations because those are well-defined for all dimensionalities and are consistent across the different operators, whereas matrix multiplication is specifically used in a 2D linear algebra context, and is not well defined for the other operators.

tensor Go Goal NumPy MATLAB Notes
tmath.Add(a,b) same: a + b a .+ b element-wise addition; Goal does this string-wise for string tensors
tmath.Mul(a,b) same: a * b a .* b element-wise multiply
tmath.Div(a,b) same: a/b a./b element-wise divide. important: this always produces a floating point result.
tmath.Mod(a,b) same: a%b a./b element-wise modulous (works for float and int)
tmath.Pow(a,3) same: a**3 a.^3 element-wise exponentiation
tmath.Cos(a) same: cos(a) cos(a) element-wise function application

2D Matrix Linear Algebra

tensor Go Goal NumPy MATLAB Notes
matrix.Mul(a,b) same: a @ b a * b matrix multiply
tensor.Transpose(a) <- or a.T a.transpose() or a.T a.' transpose of a
TODO: a.conj().transpose() or a.conj().T a' conjugate transpose of a
matrix.Det(a) matrix.Det(a) np.linalg.det(a) ? determinant of a
matrix.Identity(3) <- np.eye(3) eye(3) 3x3 identity matrix
matrix.Diagonal(a) <- np.diag(a) diag(a) returns a vector of the diagonal elements of 2D tensor, a. Goal returns a read / write view.
np.diag(v, 0) diag(v,0) returns a square diagonal matrix whose nonzero values are the elements of vector, v
matrix.Trace(a) <- np.trace(a) trace(a) returns the sum of the elements along the diagonal of a.
matrix.Tri() <- np.tri() tri() returns a new 2D Float64 matrix with 1s in the lower triangular region (including the diagonal) and the remaining upper triangular elements zero
matrix.TriL(a) <- np.tril(a) tril(a) returns a copy of a with the lower triangular elements (including the diagonal) from a and the remaining upper triangular elements zeroed out
matrix.TriU(a) <- np.triu(a) triu(a) returns a copy of a with the upper triangular elements (including the diagonal) from a and the remaining lower triangular elements zeroed out
linalg.inv(a) inv(a) inverse of square 2D tensor a
linalg.pinv(a) pinv(a) pseudo-inverse of 2D tensor a
np.linalg.matrix_rank(a) rank(a) matrix rank of a 2D tensor a
linalg.solve(a, b) if a is square; linalg.lstsq(a, b) otherwise a\b solution of a x = b for x
Solve a.T x.T = b.T instead b/a solution of x a = b for x
U, S, Vh = linalg.svd(a); V = Vh.T [U,S,V]=svd(a) singular value decomposition of a
linalg.cholesky(a) chol(a) Cholesky factorization of a 2D tensor
D,V = linalg.eig(a) [V,D]=eig(a) eigenvalues and eigenvectors of a, where [V,D]=eig(a,b) eigenvalues and eigenvectors of a, b where
D,V = eigs(a, k=3) D,V = linalg.eig(a, b) [V,D]=eigs(a,3)
Q,R = linalg.qr(a) [Q,R]=qr(a,0) QR decomposition
P,L,U = linalg.lu(a) where a == P@L@U [L,U,P]=lu(a) where a==P'*L*U LU decomposition with partial pivoting (note: P(MATLAB) == transpose(P(NumPy)))
x = linalg.lstsq(Z, y) x = Z\y perform a linear regression of the form

Statistics

tensor Go Goal NumPy MATLAB Notes
a.max() or max(a) or stats.Max(a) a.max() or np.nanmax(a) max(max(a)) maximum element of a, Goal always ignores NaN as missing data
a.max(0) max(a) maximum element of each column of tensor a
a.max(1) max(a,[],2) maximum element of each row of tensor a
np.maximum(a, b) max(a,b) compares a and b element-wise, and returns the maximum value from each pair
stats.L2Norm(a) np.sqrt(v @ v) or np.linalg.norm(v) norm(v) L2 norm of vector v
cg conjgrad conjugate gradients solver

FFT and complex numbers

todo: huge amount of work needed to support complex numbers throughout!

tensor Go Goal NumPy MATLAB Notes
np.fft.fft(a) fft(a) Fourier transform of a
np.fft.ifft(a) ifft(a) inverse Fourier transform of a
signal.resample(x, np.ceil(len(x)/q)) decimate(x, q) downsample with low-pass filtering

tensorfs

The tensorfs data filesystem provides a global filesystem-like workspace for storing tensor data, and Goal has special commands and functions to facilitate interacting with it. In an interactive goal shell, when you do ## to switch into math mode, the prompt changes to show your current directory in the tensorfs, not the regular OS filesystem, and the final prompt character turns into a #.

Use get and set (aliases for tensorfs.Get and tensorfs.Set) to retrieve and store data in the tensorfs:

  • x := get("path/to/item") retrieves the tensor data value at given path, which can then be used directly in an expression or saved to a new variable as in this example.

  • set("path/to/item", x) saves tensor data to given path, overwriting any existing value for that item if it already exists, and creating a new one if not. x can be any data expression.

You can use the standard shell commands to navigate around the data filesystem:

  • cd <dir> to change the current working directory. By default, new variables created in the shell are also recorded into the current working directory for later access.

  • ls [-l,r] [dir] list the contents of a directory; without arguments, it shows the current directory. The -l option shows each element on a separate line with its shape. -r does a recursive list through subdirectories.

  • mkdir <dir> makes a new subdirectory.

TODO: other commands, etc.

Documentation

Overview

Package goal provides the Goal Go augmented language transpiler, which combines the best parts of Go, bash, and Python to provide an integrated shell and numerical expression processing experience.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func AddHomeExpand

func AddHomeExpand(list []string, adds ...string) []string

AddHomeExpand adds given strings to the given list of strings, expanding any ~ symbols with the home directory, and returns the updated list.

Types

type Goal

type Goal struct {

	// Config is the [exec.Config] used to run commands.
	Config exec.Config

	// StdIOWrappers are IO wrappers sent to the interpreter, so we can
	// control the IO streams used within the interpreter.
	// Call SetWrappers on this with another StdIO object to update settings.
	StdIOWrappers exec.StdIO

	// ssh connection, configuration
	SSH *sshclient.Config

	// collection of ssh clients
	SSHClients map[string]*sshclient.Client

	// SSHActive is the name of the active SSH client
	SSHActive string

	// Builtins are all the builtin shell commands
	Builtins map[string]func(cmdIO *exec.CmdIO, args ...string) error

	// commands that have been defined, which can be run in Exec mode.
	Commands map[string]func(args ...string)

	// Jobs is a stack of commands running in the background
	// (via Start instead of Run)
	Jobs stack.Stack[*Job]

	// Cancel, while the interpreter is running, can be called
	// to stop the code interpreting.
	// It is connected to the Ctx context, by StartContext()
	// Both can be nil.
	Cancel func()

	// Errors is a stack of runtime errors.
	Errors []error

	// CliArgs are input arguments from the command line.
	CliArgs []any

	// Ctx is the context used for cancelling current shell running
	// a single chunk of code, typically from the interpreter.
	// We are not able to pass the context around so it is set here,
	// in the StartContext function. Clear when done with ClearContext.
	Ctx context.Context

	// original standard IO setings, to restore
	OrigStdIO exec.StdIO

	// Hist is the accumulated list of command-line input,
	// which is displayed with the history builtin command,
	// and saved / restored from ~/.goalhist file
	Hist []string

	// transpiling state
	TrState transpile.State
	// contains filtered or unexported fields
}

Goal represents one running Goal language context.

func NewGoal

func NewGoal() *Goal

NewGoal returns a new Goal with default options.

func (*Goal) ActiveSSH

func (gl *Goal) ActiveSSH() *sshclient.Client

ActiveSSH returns the active ssh client

func (*Goal) AddCommand

func (gl *Goal) AddCommand(name string, cmd func(args ...string))

AddCommand adds given command to list of available commands.

func (*Goal) AddError

func (gl *Goal) AddError(err error) error

AddError adds the given error to the error stack if it is non-nil, and calls the Cancel function if set, to stop execution. This is the main way that goal errors are handled. It also prints the error.

func (*Goal) AddHistory

func (gl *Goal) AddHistory(line string)

AddHistory adds given line to the Hist record of commands

func (*Goal) AddPath

func (gl *Goal) AddPath(cmdIO *exec.CmdIO, args ...string) error

AddPath adds the given path(s) to $PATH.

func (*Goal) Args

func (gl *Goal) Args() []any

Args returns the command line arguments.

func (*Goal) CancelExecution

func (gl *Goal) CancelExecution()

CancelExecution calls the Cancel() function if set.

func (*Goal) Cd

func (gl *Goal) Cd(cmdIO *exec.CmdIO, args ...string) error

Cd changes the current directory.

func (*Goal) Close

func (gl *Goal) Close()

Close closes any resources associated with the shell, including terminating any commands that are not running "nohup" in the background.

func (*Goal) CloseSSH

func (gl *Goal) CloseSSH()

CloseSSH closes all open ssh client connections

func (*Goal) CmdArgs

func (gl *Goal) CmdArgs(errOk bool, sargs []string, i int) []string

CmdArgs processes expressions involving "args" for commands

func (*Goal) CompleteEdit

func (gl *Goal) CompleteEdit(data any, text string, cursorPos int, completion complete.Completion, seed string) (ed complete.Edit)

CompleteEdit is the complete.EditFunc for the shell.

func (*Goal) CompleteMatch

func (gl *Goal) CompleteMatch(data any, text string, posLine, posChar int) (md complete.Matches)

CompleteMatch is the complete.MatchFunc for the shell.

func (*Goal) Debug

func (gl *Goal) Debug(cmdIO *exec.CmdIO, args ...string) error

Debug changes log level

func (*Goal) DeleteAllJobs

func (gl *Goal) DeleteAllJobs()

DeleteAllJobs deletes any existing jobs, closing stdio.

func (*Goal) DeleteJob

func (gl *Goal) DeleteJob(job *Job) bool

DeleteJob deletes the given job and returns true if successful,

func (*Goal) EndContext

func (gl *Goal) EndContext()

EndContext ends a processing context, clearing the Ctx and Cancel fields.

func (*Goal) Exec

func (gl *Goal) Exec(errOk, start, output bool, cmd any, args ...any) string

Exec handles command execution for all cases, parameterized by the args. It executes the given command string, waiting for the command to finish, handling the given arguments appropriately. If there is any error, it adds it to the goal, and triggers CancelExecution.

  • errOk = don't call AddError so execution will not stop on error
  • start = calls Start on the command, which then runs asynchronously, with a goroutine forked to Wait for it and close its IO
  • output = return the output of the command as a string (otherwise return is "")

func (*Goal) ExecArgs

func (gl *Goal) ExecArgs(cmdIO *exec.CmdIO, errOk bool, cmd any, args ...any) (*sshclient.Client, string, []string)

ExecArgs processes the args to given exec command, handling all of the input / output redirection and file globbing, homedir expansion, etc.

func (*Goal) Exit

func (gl *Goal) Exit(cmdIO *exec.CmdIO, args ...string) error

Exit exits the shell.

func (*Goal) Fg

func (gl *Goal) Fg(cmdIO *exec.CmdIO, args ...string) error

Fg foregrounds a job by job number

func (*Goal) GoSSH

func (gl *Goal) GoSSH(cmdIO *exec.CmdIO, args ...string) error

GoSSH manages SSH connections, which are referenced by the @name identifier. It handles the following cases:

  • @name -- switches to using given host for all subsequent commands
  • host [name] -- connects to a server specified in first arg and switches to using it, with optional name instead of default sequential number.
  • close -- closes all open connections, or the specified one

func (*Goal) HandleArgErr

func (gl *Goal) HandleArgErr(errok bool, err error) error

func (*Goal) History

func (gl *Goal) History(cmdIO *exec.CmdIO, args ...string) error

History shows history

func (*Goal) Host

func (gl *Goal) Host() string

Host returns the name we're running commands on, which is empty if localhost (default).

func (*Goal) HostAndDir

func (gl *Goal) HostAndDir() string

HostAndDir returns the name we're running commands on, which is empty if localhost (default), and the current directory on that host.

func (*Goal) InstallBuiltins

func (gl *Goal) InstallBuiltins()

InstallBuiltins adds the builtin goal commands to [Goal.Builtins].

func (*Goal) JobIDExpand

func (gl *Goal) JobIDExpand(args []string) int

JobIDExpand expands %n job id values in args with the full PID returns number of PIDs expanded

func (*Goal) JobsCmd

func (gl *Goal) JobsCmd(cmdIO *exec.CmdIO, args ...string) error

JobsCmd is the builtin jobs command

func (*Goal) Kill

func (gl *Goal) Kill(cmdIO *exec.CmdIO, args ...string) error

Kill kills a job by job number or PID. Just expands the job id expressions %n into PIDs and calls system kill.

func (*Goal) OpenHistory

func (gl *Goal) OpenHistory(file string) error

OpenHistory opens Hist history lines from given file, e.g., ~/.goalhist

func (*Goal) OutToFile

func (gl *Goal) OutToFile(cl *sshclient.Client, cmdIO *exec.CmdIO, errOk bool, sargs []string, i int) []string

OutToFile processes the > arg that sends output to a file

func (*Goal) OutToPipe

func (gl *Goal) OutToPipe(cl *sshclient.Client, cmdIO *exec.CmdIO, errOk bool, sargs []string, i int) []string

OutToPipe processes the | arg that sends output to a pipe

func (*Goal) Output

func (gl *Goal) Output(cmd any, args ...any) string

Output executes the given command string, handling the given arguments appropriately. If there is any error, it adds it to the goal. It returns the stdout as a string and forwards stderr to exec.Config.Stderr appropriately.

func (*Goal) OutputErrOK

func (gl *Goal) OutputErrOK(cmd any, args ...any) string

OutputErrOK executes the given command string, handling the given arguments appropriately. If there is any error, it adds it to the goal. It returns the stdout as a string and forwards stderr to exec.Config.Stderr appropriately.

func (*Goal) RestoreOrigStdIO

func (gl *Goal) RestoreOrigStdIO()

RestoreOrigStdIO reverts to using the saved OrigStdIO

func (*Goal) Run

func (gl *Goal) Run(cmd any, args ...any)

Run executes the given command string, waiting for the command to finish, handling the given arguments appropriately. If there is any error, it adds it to the goal, and triggers CancelExecution. It forwards output to exec.Config.Stdout and exec.Config.Stderr appropriately.

func (*Goal) RunBuiltinOrCommand

func (gl *Goal) RunBuiltinOrCommand(cmdIO *exec.CmdIO, errOk, start, output bool, cmd string, args ...string) (bool, string)

RunBuiltinOrCommand runs a builtin or a command, returning true if it ran, and the output string if running in output mode.

func (*Goal) RunCommands

func (gl *Goal) RunCommands(cmds []any) error

RunCommands runs the given command(s). This is typically called from a Makefile-style goal script.

func (*Goal) RunErrOK

func (gl *Goal) RunErrOK(cmd any, args ...any)

RunErrOK executes the given command string, waiting for the command to finish, handling the given arguments appropriately. It does not stop execution if there is an error. If there is any error, it adds it to the goal. It forwards output to exec.Config.Stdout and exec.Config.Stderr appropriately.

func (*Goal) SSHByHost

func (gl *Goal) SSHByHost(host string) (*sshclient.Client, error)

SSHByHost returns the SSH client for given host name, with err if not found

func (*Goal) SaveHistory

func (gl *Goal) SaveHistory(n int, file string) error

SaveHistory saves up to the given number of lines of current history to given file, e.g., ~/.goalhist for the default goal program. If n is <= 0 all lines are saved. n is typically 500 by default.

func (*Goal) SaveOrigStdIO

func (gl *Goal) SaveOrigStdIO()

SaveOrigStdIO saves the current Config.StdIO as the original to revert to after an error, and sets the StdIOWrappers to use them.

func (*Goal) Scp

func (gl *Goal) Scp(cmdIO *exec.CmdIO, args ...string) error

Scp performs file copy over SSH connection, with the remote filename prefixed with the @name: and the local filename un-prefixed. The order is from -> to, as in standard cp. The remote filename is automatically relative to the current working directory on the remote host.

func (*Goal) Set

func (gl *Goal) Set(cmdIO *exec.CmdIO, args ...string) error

Set sets the given environment variable to the given value.

func (*Goal) Source

func (gl *Goal) Source(cmdIO *exec.CmdIO, args ...string) error

Source loads and evaluates the given file(s)

func (*Goal) Start

func (gl *Goal) Start(cmd any, args ...any)

Start starts the given command string for running in the background, handling the given arguments appropriately. If there is any error, it adds it to the goal. It forwards output to exec.Config.Stdout and exec.Config.Stderr appropriately.

func (*Goal) StartContext

func (gl *Goal) StartContext() context.Context

StartContext starts a processing context, setting the Ctx and Cancel Fields. Call EndContext when current operation finishes.

func (*Goal) TranspileCode

func (gl *Goal) TranspileCode(code string)

TranspileCode processes each line of given code, adding the results to the LineStack

func (*Goal) TranspileCodeFromFile

func (gl *Goal) TranspileCodeFromFile(file string) error

TranspileCodeFromFile transpiles the code in given file

func (*Goal) TranspileConfig

func (gl *Goal) TranspileConfig() error

TranspileConfig transpiles the .goal startup config file in the user's home directory if it exists.

func (*Goal) TranspileFile

func (gl *Goal) TranspileFile(in string, out string) error

TranspileFile transpiles the given input goal file to the given output Go file. If no existing package declaration is found, then package main and func main declarations are added. This also affects how functions are interpreted.

func (*Goal) Unset

func (gl *Goal) Unset(cmdIO *exec.CmdIO, args ...string) error

Unset un-sets the given environment variable.

func (*Goal) Which

func (gl *Goal) Which(cmdIO *exec.CmdIO, args ...string) error

Which reports the executable associated with the given command. Processes builtins and commands, and if not found, then passes on to exec which.

type Job

type Job struct {
	*exec.CmdIO
	IsExec  bool
	GotPipe bool
}

Job represents a job that has been started and we're waiting for it to finish.

type ReadlineCompleter

type ReadlineCompleter struct {
	Goal *Goal
}

ReadlineCompleter implements github.com/cogentcore/readline.AutoCompleter.

func (*ReadlineCompleter) Do

func (rc *ReadlineCompleter) Do(line []rune, pos int) (newLine [][]rune, length int)

Directories

Path Synopsis
cmd
goal
Command goal is an interactive cli for running and compiling Goal code.
Command goal is an interactive cli for running and compiling Goal code.
Package goalib defines convenient utility functions for use in the goal shell, available with the goalib prefix.
Package goalib defines convenient utility functions for use in the goal shell, available with the goalib prefix.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL
JackTT - Gopher 🇻🇳