Owen [ocs-0001]
Owen [ocs-0001]
I am a Sr. Scientific Software Developer for NASA Goddard's Earth Sciences Data Information Services Center and a Ph.D. Candidate in Geospatial Analytics @ NCSU. You can also find me elsewhere at bsky, sourcehut, github.
This website is built using Forester and is used to collect various notes. You can search the site using ctrl+k
1. Notes [ocs-0002]
1. Notes [ocs-0002]
This is a notebook in which I keep notes of a variety of subjects at varying degrees of disjointedness.
Note. Double Logistic Greendown Model [ocs-0006]
- March 20, 2025
Note. Double Logistic Greendown Model [ocs-0006]
- March 20, 2025
Logistic sigmoid function for modeling Phenology detailed in Elmore et. al 2011 & Gao et al. 2011. The function combines spring and autumn seasons into a single equation to account for the greendown phenomena by allowing for a gradual reduction of VI values in mid-summer. Typically the model is fit to all VI observations within a single year using maximum likelihood methods.
\[ v(t, M) = m_1 + (m_2 - m_7t)\left (\frac {1}{1+e^{(m_3-t)/m_4}}-\frac {1}{1 + e^{(m_5-t)/m_6}}\right ) \]where
- \(v(t,m)\) is the modeled VI value at time \(t\) in terms of day of year (DOY)
- \(M = [m_1, \dots , m_7]^T\) is a parameter vector controlling the shape of the double-logistic function.
- \(m_1\) represents the mean VI value in the dormant period;
- \((m_2 − m_7t)\) represents the difference between VI value at time \(t\) and the dormant mean;
- \(m_3\) and \(m_5\) are the inflection points that are commonly used to represent start-of-season (SOS) and end-of-season (EOS) dates in terms of DOY in the spring and autumn;
- \(m_4\) and \(m_6\) are the corresponding slopes of the VI trajectories in spring and autumn;
- \(m_7\) is the “greendown” parameter that accounts for the VI greendown phenomenon in the mid-summer time.
Given the set \(t \in [1, \dots , 366]\) and \(M = \{0.05, 1, 120, 8, 290, 6, 0.0011\}\), a single years phenology would be estimated by the following curve.
Note. Arena Allocator [ocs-0003]
- May 29, 2024
Note. Arena Allocator [ocs-0003]
- May 29, 2024
Logic behind the allocator [ocs-0004]
Logic behind the allocator [ocs-0004]
Only requires an offset or pointer to state the end of the last allocation. To allocate some memory from the arena it is as simple as moving the offset (or pointer) forward. The allocation has the complexity of \(O(1)\). Due to being the simplest allocator possible, the arena allocator does nto allow the user to free certain blocks of memory. The memory is usually freed all at once.
Basic arena interface [ocs-0005]
Basic arena interface [ocs-0005]
An arena is like a stack with extensions, e.g. it can be named.
Arena *arena_alloc(void);
void arena_release(Arena *arena);
void arena_set_auto_align(Arena *arena, u64 align);
u64 arena_pos(Arena *arena);
void *arena_push_no_zero(Arena *arena, u64 size);
void *arena_push_aligner(Arena *arena, u64 alignment);
void *arena_push(Arena *arena, u64 size);
void arena_pop_to(Arena *arena, u64 pos);
void arena_pop(Arena *arena, u64 size);
void arena_clear(Arena *arena);
#define PushArrayNoZero(arena, type, count) (type *) \
arena_push_no_zero((arena), sizeof(type) * (count))
#define PushArray(arena, type, count) (type *) \
arena_push((arena), sizeof(type) * (count))
the simplest arena allocator will look something like
static unsigned char *arena_buffer;
static size_t arena_buffer_length;
static size_t arena_offset;
void *arena_alloc(size_t size) {
// Check to see if the backing memory has space left
if (arena_offset+size <= arena_buffer_length) {
void *ptr = &arena_buffer[arena_offset];
arena_offset += size;
// Zero memory
memset(ptr, 0, size);
return ptr;
}
return NULL;
}
While this is as simple as it gets, there are two issues with the basic approach * You cannot reuse this procedure for different arenas. * The pointer returned may not be aligned correctly for the data you need. The first issue can be solved by coupling the data into a structure and passing that to the procedure =arena_alloc=.
Snippet. Bash color codes [ocs-0008]
Snippet. Bash color codes [ocs-0008]
# Reset Color_Off='\033[0m' # Text Reset # Regular Colors Black='\033[0;30m' # Black Red='\033[0;31m' # Red Green='\033[0;32m' # Green Yellow='\033[0;33m' # Yellow Blue='\033[0;34m' # Blue Purple='\033[0;35m' # Purple Cyan='\033[0;36m' # Cyan White='\033[0;37m' # White # Bold BBlack='\033[1;30m' # Black BRed='\033[1;31m' # Red BGreen='\033[1;32m' # Green BYellow='\033[1;33m' # Yellow BBlue='\033[1;34m' # Blue BPurple='\033[1;35m' # Purple BCyan='\033[1;36m' # Cyan BWhite='\033[1;37m' # White # Underline UBlack='\033[4;30m' # Black URed='\033[4;31m' # Red UGreen='\033[4;32m' # Green UYellow='\033[4;33m' # Yellow UBlue='\033[4;34m' # Blue UPurple='\033[4;35m' # Purple UCyan='\033[4;36m' # Cyan UWhite='\033[4;37m' # White # Background On_Black='\033[40m' # Black On_Red='\033[41m' # Red On_Green='\033[42m' # Green On_Yellow='\033[43m' # Yellow On_Blue='\033[44m' # Blue On_Purple='\033[45m' # Purple On_Cyan='\033[46m' # Cyan On_White='\033[47m' # White # High Intensity IBlack='\033[0;90m' # Black IRed='\033[0;91m' # Red IGreen='\033[0;92m' # Green IYellow='\033[0;93m' # Yellow IBlue='\033[0;94m' # Blue IPurple='\033[0;95m' # Purple ICyan='\033[0;96m' # Cyan IWhite='\033[0;97m' # White # Bold High Intensity BIBlack='\033[1;90m' # Black BIRed='\033[1;91m' # Red BIGreen='\033[1;92m' # Green BIYellow='\033[1;93m' # Yellow BIBlue='\033[1;94m' # Blue BIPurple='\033[1;95m' # Purple BICyan='\033[1;96m' # Cyan BIWhite='\033[1;97m' # White # High Intensity backgrounds On_IBlack='\033[0;100m' # Black On_IRed='\033[0;101m' # Red On_IGreen='\033[0;102m' # Green On_IYellow='\033[0;103m' # Yellow On_IBlue='\033[0;104m' # Blue On_IPurple='\033[0;105m' # Purple On_ICyan='\033[0;106m' # Cyan On_IWhite='\033[0;107m' # White
Note. Single Instruction Multiple Vector (SIMD) [ocs-0009]
- March 20, 2025
Note. Single Instruction Multiple Vector (SIMD) [ocs-0009]
- March 20, 2025
Single vector instruction sets are extensions to the original architecture instruction sets, and thus you have to invest extra effort to use and support them. With compiled code, you need to write inline assembly, or use vector intrinsics.
Vector intrinsics [ocs-000J]
Vector intrinsics [ocs-000J]
Intrinsics appear like regular libary functions. You include the relevant header, and then you can use it just like any other lib. E.g. assuming a visual c++ compiler, if you wanted to add four float numbers to another four numbers, you would include the xmmintrin.h header which contains the following declaration for the _mm_add_ps intrinsic:
extern _m128 _mm_add_ps( _m128 _A, _m128 _B);
However unlike lib functions, intrinsics are implemented directly in compilers. The example SSE intrinsic typically compiles to a single instruction, x86 and amd64 instructions. For the time it takes CPU to call a library function, it might have completed dozen of these functions.
For all x86 the current best practice is to use <immintrin.h> which loads in the the correct compatible instruction set.
- __MMX__ -- X86 MMX ::
mmintrin.h - __SSE__ -- X86 SSE ::
xmmintrin.h - __SSE2__ -- X86 SSE2 ::
emmintrin.h - __VEC__ -- altivec functions ::
altivec.h - __ARM_NEON__ -- ARM Neon functions ::
arm_neon.h
| Operation | Altivec | Neon | MMX/SSE/SSE2 |
|
|
|
|
|
| loading | vec_ld | vld1q_f32 | _mm_set_epi16 |
| vector | vec_splat | vld1q_s16 | _mm_set1_epi16 |
| vec_splat_s16 | vsetq_lane_f32 | _mm_set1_pi16 | |
| vec_splat_s32 | vld1_u8 | _mm_set_pi16 | |
| vec_splat_s8 | vdupq_lane_s16 | _mm_load_ps | |
| vec_splat_u16 | vdupq_n_s16 | _mm_set1_ps | |
| vec_splat_u32 | vmovq_n_f32 | _mm_loadh_pi | |
| vec_splat_u8 | vset_lane_u8 | _mm_loadl_pi | |
| storing | vec_st | vst1_u8 | |
| vector | vst1q_s16 | _mm_store_ps | |
| vst1q_f32 | |||
| vst1_s16 | |||
| add | vec_madd | vaddq_s16 | _mm_add_epi16 |
| vec_mladd | vaddq_f32 | _mm_add_pi16 | |
| vec_adds | vmlaq_n_f32 | _mm_add_ps | |
| subtract | vec_sub | vsubq_s16 | |
| multiply | vec_madd | vmulq_n_s16 | _mm_mullo_epi16 |
| vec_mladd | vmulq_s16 | _mm_mullo_pi16 | |
| vmulq_f32 | _mm_mul_ps | ||
| vmlaq_n_f32 | |||
| arithmetic | vec_sra | vshrq_n_s16 | _mm_srai_epi16 |
| shift | vec_srl | _mm_srai_pi16 | |
| vec_sr | |||
| byte | vec_perm | vtbl1_u8 | _mm_shuffle_pi16 |
| permutation | vec_sel | vtbx1_u8 | _mm_shuffle_ps |
| vec_mergeh | vget_high_s16 | ||
| vec_mergel | vget_low_s16 | ||
| vdupq_lane_s16 | |||
| vdupq_n_s16 | |||
| vmovq_n_f32 | |||
| vbsl_u8 | |||
| type | vec_cts | vmovl_u8 | _mm_packs_pu16 |
| conversion | vec_unpackh | vreinterpretq_s16_u16 | |
| vec_unpackl | vcvtq_u32_f32 | ||
| vec_cts | vqmovn_s32 | _mm_cvtps_pi16 | |
| vec_ctu | vqmovun_s16 | _mm_packus_epi16 | |
| vqmovn_u16 | |||
| vcvtq_f32_s32 | |||
| vmovl_s16 | |||
| vmovq_n_f32 | |||
| vector | vec_pack | vcombine_u16 | |
| combination | vec_packsu | vcombine_u8 | |
| vcombine_s16 | |||
| maximum | _mm_max_ps | ||
| minimum | _mm_min_ps | ||
| vector | _mm_andnot_ps | ||
| logic | _mm_and_ps | ||
| _mm_or_ps | |||
| rounding | vec_trunc | ||
| misc | _mm_empty | ||
|
|
|
|
|
Links
- Utilizing the other 80% of your system's performance: Starting with Vectorization
- Improving perf with simd intrinsics in three use cases
- Basics of SIMD programming
- SIMD Keyword OpenMP, R, Parallelism
- SIMD a practical guide
- Organizing SIMD code - stackoverflow
- SSE SSE2 and SSE3 for GNU C++ [closed] - stackoverflow
- SIMD, memory locality, vectorization, and branch point prediction
- Code in ARM assembly: Rounding and arithmetic (scalar)
- Comparing SIMD on x86-64 and arm64
Note. Makefiles [ocs-000B]
- September 13, 2024
Note. Makefiles [ocs-000B]
- September 13, 2024
A well written makefile describes all the files and settings used to compile a project and link it with the appropriate libraries
Managing a build with a makefile is less error-prone and far simpler after a _one-time setup cost_. A makefile with a proper dependency graph will only rebuild a target when one or more of the components it depends on has changed. This allows seperating the components of a project whcih can be built independently.
Makefile syntax [ocs-000D]
Makefile syntax [ocs-000D]
It is common practice to copy and modify old makefules rather than generating new ones from scratch.
#
# A simple makefile for managing build of project composed of C source files.
#
# It is likely that default C compiler is already gcc, but explicitly
# set, just to be sure
CC = gcc
# The CFLAGS variable sets compile flags for gcc:
# -g compile with debug information
# -Wall give verbose compiler warnings
# -O0 do not optimize generated code
# -std=c99 use the C99 standard language definition
CFLAGS = -g -Wall -O0 -std=c99
# The LDFLAGS variable sets flags for linker
# -lm says to link in libm (the math library)
LDFLAGS = -lm
# In this section, you list the files that are part of the project.
# If you add/change names of source files, here is where you
# edit the Makefile.
SOURCES = demo.c vector.c map.c
OBJECTS = $(SOURCES:.c=.o)
TARGET = demo
# The first target defined in the makefile is the one
# used when make is invoked with no argument. Given the definitions
# above, this Makefile file will build the one named TARGET and
# assume that it depends on all the named OBJECTS files.
$(TARGET) : $(OBJECTS)
$(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS)
# Phony means not a "real" target, it doesn't build anything
# The phony target "clean" is used to remove all compiled object files.
.PHONY: clean
clean:
@rm -f $(TARGET) $(OBJECTS) core
Makefile macros [ocs-000E]
Makefile macros [ocs-000E]
Subsitutions defined toward top of the file, e.g. (CFLAGS = -g -Wall), and are similar to #define in C . It can be referenced later with $(CFLAGS).
Example macros: - OBJECTS = $(SOURCES:.c=o) - Defines the OBJECTS macro to be the same as the SOURCES macro except that every instance of .c is replaced with .o. - $@: - built-in, name of the current target - $^: - built-in, current targets list of dependencies
Targets [ocs-000F]
Targets [ocs-000F]
Written in the following form
target-name : dependencies
action
where the target name is the name of the file that will be produced when this target is built. The first target listed in a make file is the default target, meaning it is invoked by make with no args.
Phony targets [ocs-000C]
Phony targets [ocs-000C]
The clean target in the example does not fit the pattern which is previously described of targets. It does not create a file named clean, rather it is a shortcut for running a command whcih cclears out the projects build files. These commands are listed as dependencies of .PHONY which allows Make to run the target without any dependency checks.
links
Language. Golang [go-00rs]
Language. Golang [go-00rs]
Initialize module [go-00rv]
Initialize module [go-00rv]
Within a new directory, initialize a module with
go mod init your/module
This will create a new file `go.mod`
Types [go-00rx]
Types [go-00rx]
| Zero: | Predeclared | Default value assigned to declared but unassigned variables |
| Literals: | Predeclared | Explicitly specified values |
| Integer: | Literals | Base 10 by default |
| Floating-point: | Literals | Uses decimal point for fractions |
| Rune: | Literals | Single character representation |
| String: | Literals | Two creation methods |
| Booleans: | Predeclared | Binary true/false values |
| byte: | Integer | Alias for uint8 |
| int: | Integer | Signed integer sized to architecture's word size |
| uint: | Integer | Unsigned integer sized to architecture's word size |
| rune: | Integer | Alias for int32 |
| uintptr: | Integer | Integer type for storing pointer values |
| String: | Predeclared | Immutable sequence of bytes, zero value is empty string |
Type conversion [go-00ry]
Type conversion [go-00ry]
Go does not support automatic type conversion. All variable types need to be converted to interact.
var x int = 10 var y float64 = 30.2 var sum1 float64 = float64(x) + y var sum2 int = x + int(y) fmt.Println(sum1, sum2) // 40.2 40
Quotes [go-00rz]
Quotes [go-00rz]
Different qoutes are used for different contexts.
The double qoute " will be used to create string literals, while the backtick is used to create raw string literals. The single qoute ' is used to create runes, a single unicode point. E.g. r := '學'.
Slices [go-00s0]
Slices [go-00s0]
- Slices will have a zero value of
niland is only comprable withniland==. - Modifying shared slices will affect all references.
- Length can differ from capacity
Creation [go-00s3]
Creation [go-00s3]
var s []int // nil slice
s := []int{1, 2, 3} // slice literal
s := make([]int, 5) // length 5, capacity 5
s := make([]int, 0, 10) // length 0, capacity 10
Modification [go-00s4]
Modification [go-00s4]
s = append(s, 10) // single element s = append(s, 1, 2, 3) // multiple elements s = append(s, other...) // append slice clear(s) // zero all elements (Go 1.21+)
Slicing [go-00s5]
Slicing [go-00s5]
s[low:high] // items low through high-1 s[low:] // items low through end s[:high] // items beginning through high-1 s[:] // copy of entire slice s[low:high:max] // control capacity of new slice
Copying [go-00s6]
Copying [go-00s6]
copy(dest, src) // copies min(len(dest), len(src)) elements copy(dest, src[1:]) // copy from middle of src
Array Conversion [go-00s7]
Array Conversion [go-00s7]
array := [4]int{1, 2, 3, 4}
slice := array[:] // array to slice
array2 := [4]int(slice) // slice to array (new memory)
arrayPtr := (*[4]int)(slice) // slice to array pointer (shared memory)
When to Use What [go-00s9]
When to Use What [go-00s9]
1. `var` declaration (nil slice): When size unknown, no initial data 2. Slice literal: When starting values are known 3. `make`: - With length: For buffers or exact size known - Zero length + capacity: For growing slices
Memory Management [go-00sa]
Memory Management [go-00sa]
- Capacity doubles when < 256 - Growth slows to ~25/for larger slices - Use full slice expression `s[low:high:max]` to prevent capacity issues - Be careful with shared memory when slicing
Standard Library [go-00sb]
Standard Library [go-00sb]
- `slices.Equal(a, b)`: Compare if length and elements match - `slices.EqualFunc(a, b, fn)`: Custom comparison function
Maps [go-00sc]
Maps [go-00sc]
Maps are declared as `map[keyType]valueType`. A `nil` map (e.g., `var m map[string]int`) cannot be written to. Use `make` or a map literal for writable maps.
// Map literal
m := map[string]int{"Alice": 10, "Bob": 20}
// Using make
m := make(map[string]int, 10) // Capacity 10
m["Charlie"] = 30
Structs [go-00sd]
Structs [go-00sd]
Structs are user-defined types with fields. Define them with `type` and access fields using dot notation.
type Person struct {
Name string
Age int
}
p := Person{Name: "Alice", Age: 30}
fmt.Println(p.Name)
Anonymous structs are inline struct definitions:
pet := struct {
Name string
Kind string
}{Name: "Fido", Kind: "dog"}
Pointers [go-00se]
Pointers [go-00se]
Use `&` to get a pointer and `*` to dereference. `nil` pointers cannot be dereferenced.
x := 10 p := &x fmt.Println(*p) // 10
Shadowing [go-00sf]
Shadowing [go-00sf]
Inner-scope variables can shadow outer-scope ones:
x := 10
if x > 5 {
x := 5
fmt.Println(x) // 5
}
fmt.Println(x) // 10
Control Flow [go-00sg]
Control Flow [go-00sg]
If [go-00sh]
If [go-00sh]
Go's `if` statements do not require parentheses. You can declare variables scoped to the condition:
if n := rand.Intn(10); n > 5 {
fmt.Println("Big:", n)
} else {
fmt.Println("Small:", n)
}
For [go-00si]
For [go-00si]
Loops in go support complete (C-style), condition only, for-range styles.
// C-style
for i := 0; i < 10; i++ {
fmt.Println(i)
}
// condition only (while)
i := 1
for i < 10 {
fmt.Println(i)
i++
}
// for-range
nums := []int{1, 2, 3}
for i, v := range nums {
fmt.Println(i, v)
}
If you don't need the key/index in, the underscore _ can be used, telling go to ignore the value.
evenVals := []int{2, 4, 6, 8, 10, 12}
for _, v := range evenVals {
fmt.Printf("%d ", v)
}
If you just need the key/index, you can leave off the second variable:
uniqueNames := map[string]bool{"Fred": true, "Raul": true, "Wilma": true}
for k := range uniqueNames {
fmt.Printf("%s ", k)
}
Switch [go-00sj]
Switch [go-00sj]
words := []string{"a", "cow", "smile", "gopher",
"octopus", "anthropologist"}
for _, word := range words {
switch size := len(word); size {
case 1, 2, 3, 4:
fmt.Println(word, "is a short word!")
case 5:
wordLen := len(word)
fmt.Println(word, "is exactly the right length:", wordLen)
case 6, 7, 8, 9:
default:
fmt.Println(word, "is a long word!")
}
}
Functions [go-00sk]
Functions [go-00sk]
Variadic Parameters [go-00sl]
Variadic Parameters [go-00sl]
Variadic functions accept a variable number of arguments as a slice:
func addTo(base int, vals ...int) []int {
out := make([]int, 0, len(vals))
for _, v := range vals {
out = append(out, base+v)
}
return out
}
Multiple Return Values [go-00sm]
Multiple Return Values [go-00sm]
Functions can return multiple values, often used with errors:
func divAndRemainder(num, denom int) (int, int, error) {
if denom == 0 {
return 0, 0, errors.New("cannot divide by zero")
}
return num / denom, num % denom, nil
}
Named return values [go-00sn]
Named return values [go-00sn]
Functions can have names specified for the return values, which predeclares variables that are used within the function to hold the return values. These values are initialized to their zero values allowing you to return them before any explicit use or assignment.
func divAndRemainder(num, denom int) (result int, remainder int, err error) {
if denom == 0 {
err = errors.New("cannot divide by zero")
return result, remainder, err
}
result, remainder = num / denom, num % denom
return result, remainder, err
}
Closures [go-00sp]
Closures [go-00sp]
These are functions defined _within_ a function, and are able to access and modify variables declared in the outer function. Closures allow you to pass fome function state to another function. These can also be returned from a function.
package main
import "fmt"
func main() {
a := 20
f := func() {
fmt.Println(a)
a = 30
}
f()
fmt.Println(a)
}
Anonymous function `f` can read and write `a` even though `a` is not passed in to the function. Using `:=` instead of `=` inside the closure creates a new `a` that ceases to exist when the closure exits.
Why use closures? [go-00sq]
Why use closures? [go-00sq]
- Limit function scope
- Removing logic repetetion
- Passing to funcs/returning from funcs
If a function is going to be called from only one other function, but it's called multiple times, you can use an inner function to "hide" the called function. This reduces the number of declarations at a package level, making it easier to find an unused name.
Given a piece of logic that is repeated multiple times within a function, a closure can be used to remove that repetition.
These become really interesting, allowing you to take variables within your function and use those variables outside of the function
Pointers [go-00sv]
Pointers [go-00sv]
Standard address (`&`) and indirection/dereferencing (`*`) operators. Pointer tricks such as _pointer arithmetic_ that you can do in C are not allowed in go.
Before dereferencing a pointer, you must make sure it is non-nil, otherwise the program will panic if you attempt to dereference a `nil` pointer:
var * int fmt.Println(x == nil) // prints true fmt.Println(*x) // panics
The pointer type can be based on any type, and the built in function `new` creates a pointer variable which returns a pointer to a zero-value instance of the type.
var x = new(int) fmt.Println(x == nil) // prints false fmt.Println(*x) // prints 0
However, the `new` function is rarely used.
For structs use `&` before a struct literal to create a pointer instance. When you need a pointer to a primitive type, declare a variable and then point to it.
Pointers of constants / literals [go-00sw]
Pointers of constants / literals [go-00sw]
Constant literals cannot use `&` because they don't have a memory address, e.g.
type person struct {
FirstName string
MiddleName *string
LastName string
}
p := person {
FirstName: "Pat",
MiddleName: "Perry", // This line will not compile
LastName: "Peterson",
}
There are two ways around the problem. The first is to introduce a variable to hold the constant. The second is to write a generic helper function that takes a parameter of any type and returns a pointer to that type:
func makePointer[T any](t T) *T {
return &t
}
which allows one to write the previous code as
p := person {
FirstName: "Pat",
MiddleName: makePointer("Perry"),
LastName: "Peterson",
}
**NOTE**: The only time you should use pointer parameters to modify a variable is when the function expects an interface, e.g. JSON.
Difference between maps and slices [go-00sx]
Difference between maps and slices [go-00sx]
Within the go runtime, a map is implemented as a pointer to a struct, so passing a map to a function means that you are copying a pointer. Thus changes made to a map within the functions are reflected in the original.
**NOTE**: maps for input parameters or return values should be carefully considered. On an API design level, they are a bad choice because they say nothing about the values contained within. In general one should prefer a struct for these cases.
A slice is more complicated. Any modification to a slices contents within a function is reflected in the original, but using append to change the length of the array is not, even if the slice has a capacity greater than its length. This is due to slice being implemented a struct with three fields:
-
intfor length -
intfor capcity - a pointer to a block of memory
Note. Enums in Go [ocs-000N]
- March 2, 2026
Note. Enums in Go [ocs-000N]
- March 2, 2026
Go does not have enums like other languages e.g. with an `enum` keyword, but it's fairly straight forward to implement and extend the same functionality.
The following defines the type that will enumerate our variables, and then we construct the enumeration as constants.
type Environment int
const (
OPS Environment = iota
UAT
SITS
)
That we can create a new type to alias each of these constants, instead of just doing something like `OPS int = iota`, makes it so that we can actually reliably use these enumerated values in a type safe way.
This way we can define methods that only operate on `Environment` types, instead of just `int` types. E.g. the following methods take a `Environment` and return a corresponding endpoint/url.
func (e Environment) URS_URL() string {
switch e {
case OPS:
return "https://urs.earthdata.nasa.gov"
case UAT:
return "https://uat.urs.earthdata.nasa.gov"
case SITs:
return "https://sit.urs.earthdata.nasa.gov"
default:
return "https://uat.urs.earthdata.nasa.gov"
}
func (e Environment) CMR_Search_URL() string {
switch e {
case OPS:
return "https://cmr.earthdata.nasa.gov/search/"
case UAT:
return "https://cmr.uat.earthdata.nasa.gov/search/"
case OPS:
return "https://cmr.sit.earthdata.nasa.gov/search/"
default:
return "https://cmr.uat.earthdata.nasa.gov/search/"
}
Since the underlying type of the enums values are just `int`s, we can also use them just as we would `int`s.
func (e Environment) String() string {
names := [...]string{"OPS", "UAT", "SITS"}
if e < OPS || e > SITS {
return fmt.Sprintf("Unkown(%d)", e)
}
return names[e]
}