Common Utilities
These utility functions are provided in the utils package.
You can access the following functions in the substrate REPL To run the REPL: make repl
caar
Returns the first item of the first list of the outer list.
(caar '('("A" "B" "C") '(3 2 1)))
"A"
cadar
Takes the first inner list and returns the item after the first value.
(cadar '('("A" "B" "C") '(3 2 1)))
"B"
cadr
Returns the first item from the outer list after the first value.
(cadr '('("A" "B" "C") "1" '(1 2 3)))
"1"
cddr
Returns the last item of the last item of a list.
(cddr '('("A" "B" "C") '(3 2 1) "1"))
"1"
Sequence Helpers
(third '(1 2 3 4 5 6 7 8 9 10))
3
(fourth '(1 2 3 4 5 6 7 8 9 10))
4
(fifth '(1 2 3 4 5 6 7 8 9 10))
5
(sixth '(1 2 3 4 5 6 7 8 9 10))
6
(seventh '(1 2 3 4 5 6 7 8 9 10))
7
(last '(1 2 3 4 5 6 7 8 9 10))
10
to-bool
Interprets a truthy value, returning a boolean.
(to-bool ())
false
(to-bool "false")
true
(to-bool "")
true
(to-bool 0)
true
(to-bool (vector))
true
(to-bool false)
false
implies
Perform logical implies on the two arguments:
- If the first argument is truthy, return the boolean value of the second argument
- If the first argument if false, return true
(implies false true)
true
(implies () false)
true
(implies "" false) ;; Note that <""> is truthy
false
(implies true false)
false
(implies true "test")
true
when
The when macro acts as a "one-armed if" expression but also allows multiple consequent expressions which are evaluated as if in a progn.
(when true (debug-print "hello"))
"hello"
()
(when false (debug-print "hello"))
()
unless
The unless macro acts as a "one-armed if not" expression but also allows multiple consequent expressions which are evaluated as if in a progn.
(unless true (debug-print "hello"))
()
(unless false (debug-print "hello"))
"hello"
()
while
The while macro emulates the C "while" block. If predicate-expression evaluates to true, consequent-expressions are evaluated and then the process repeats. If and when the loop terminates, the predicate-expression will have just evaluated to false. The macro always evaluates to ().
(let* ([x 10]
[y 0])
(while (> x 0)
(set! x (- x 1))
(set! y (+ y 1))
(debug-print y)))
1
2
3
4
5
6
7
8
9
10
()
default
Returns x if truthy, and d otherwise.
(default () "fallback")
"fallback"
(default "" "fallback")
""
(default 123 "fallback")
123
default-string
Returns x if the string isn't empty (""), and d otherwise.
(default-string "" "fallback")
"fallback"
(default-string "ABC" "fallback")
"ABC"
curry
Returns a function which will call FUN passing it INITIAL-ARGS and then any other args.
(set 'test (sorted-map "A" 1 "B" 2 "C" 3))
(sorted-map "A" 1 "B" 2 "C" 3)
(map 'list (curry get test) (keys test))
'(1 2 3)
vector-if-nil
Returns a empty vector if x is nil, and x otherwise.
(vector-if-nil "")
""
(vector-if-nil "ABC")
"ABC"
bool-is-substantial
Returns false for non-boolean values or x for booleans.
(bool-is-substantial ())
false
(bool-is-substantial "true")
false
(bool-is-substantial true)
true
(bool-is-substantial false)
false
int-is-substantial
Checks if the value can be interpreted as a int and is non-zero.
(int-is-substantial "123")
false
(int-is-substantial ())
false
(int-is-substantial 123)
true
(int-is-substantial 123.0)
true
(int-is-substantial 123.1)
false
(int-is-substantial 0)
false
float-is-substantial
Checks if the value can be interpreted as a float and is non-zero.
(float-is-substantial "2")
false
(float-is-substantial 2)
false
(float-is-substantial 2.2)
true
(float-is-substantial 2.0)
true
(float-is-substantial 0.1)
true
(float-is-substantial 0.0)
false
string-is-substantial
Checks if the value is a string and isn't empty.
(string-is-substantial "")
false
(string-is-substantial "ABC")
true
(string-is-substantial ())
false
nil-if-empty-string
Returns nil for empty strings, otherwise returns the input string.
(nil-if-empty-string "")
()
(nil-if-empty-string "ABC")
"ABC"
num?
Checks if the value is a number.
(num? 0.0)
true
(num? "0")
false
(num? 123)
true
num-is-substantial
Checks if the value can be interpreted as a number and is non-zero.
(num-is-substantial 0)
false
(num-is-substantial 0.0)
false
(num-is-substantial "100")
false
(num-is-substantial 100)
true
(num-is-substantial -0.1)
true
seq?
Checks if the value is a sequence (list or vector).
(seq? ())
true
(seq? "")
false
(seq? (list 1 2 3))
true
(seq? (vector 1 2 4))
true
seq-is-substantial
Checks if the value is a non-empty sequence.
(seq-is-substantial ())
false
(seq-is-substantial '(1 2 3))
true
map-is-substantial
Checks if the value is a non-empty map.
(map-is-substantial 0)
false
(map-is-substantial (sorted-map))
false
(map-is-substantial (sorted-map "A" 1))
true
generic-is-substantial
Checks the value is non-empty and substantial across types.
(generic-is-substantial ())
false
(generic-is-substantial false)
false
(generic-is-substantial true)
true
(generic-is-substantial 1.1)
true
(generic-is-substantial '())
false
(generic-is-substantial '(1))
true
(generic-is-substantial (sorted-map "A" 1))
true
(generic-is-substantial +)
stdin:1: generic-is-substantial: unknown type in generic-is-substantial
widen-number
Zero pads the number with a width of 20.
(widen-number 11111111)
"00000000000011111111"
widen-number-pennies
Zero pads to penny width.
(widen-number-pennies 1)
"01"
(widen-number-pennies 20)
"20"
string-in?
Checks if the string is in the string sequence.
(string-in? "A" '("A" "B" "C"))
true
(string-in? "A" '("B" "C"))
false
strings-in?
Determines that all strings in seq s are in seq lis.
(strings-in? '("A" "B") '("A" "B" "C"))
true
(strings-in? '("A" "B") '("B" "C"))
false
string-starts-with?
Checks if the string has the given prefix.
(string-starts-with? "ABC" "A")
true
(string-starts-with? "ABC" "B")
false
string-substring
Gets a string substring.
(string-substring "ABC" 0 1)
"A"
(string-substring "ABC" 1 1)
""
(string-substring "ABC" 0 3)
"ABC"
string-is-digits?
Checks if the string contains only numeric digits.
(string-is-digits? "ABC")
false
(string-is-digits? "123")
true
string-is-hexdigits?
Checks if the string contains only hexadecimal digits.
(string-is-hexdigits? "ABC")
true
(string-is-hexdigits? "ABC123")
true
(string-is-hexdigits? "GHI")
false
string-is-guid?
Checks if the string is a universally unique identifier.
(string-is-guid? "ABC")
false
(string-is-guid? "5eb24518-8604-4998-805a-7d7097d02b3b")
true
basic-match
Checks if the strings match, supports ? to mach any character.
elps> (basic-match "ABC" "AB")
false
elps> (basic-match "ABC" "ABC")
true
elps> (basic-match "ABC" "?B?")
true
count
Counts the items matched by the predicate function.
elps> (count #^(<= % 5) '(1 2 3 4 5 6 7 8 9 10))
5
elps> (count #^(> % 5) '(1 2 3 4 5 6 7 8 9 10))
5
elps> (count #^(> % 10) '(1 2 3 4 5 6 7 8 9 10))
0
seq-with-index
Returns the sequence values with indexes.
elps> (seq-with-index '("A" "B" "C"))
'('("A" 0) '("B" 1) '("C" 2))
foldlpairs
foldl sequence pairs.
elps> (foldlpairs (lambda (acc elma elmb) (append! acc (vector elma elmb))) (vector) '(1))
(vector)
elps> (foldlpairs (lambda (acc elma elmb) (append! acc (vector elma elmb))) (vector) '(1 2 3))
(vector (vector 1 2) (vector 2 3))
elps> (foldlpairs (lambda (acc elma elmb) (append! acc (vector elma elmb))) (vector) '(1 2 3 4))
(vector (vector 1 2) (vector 2 3) (vector 3 4))
deduplicate
Deduplicate a sequence of hashable values.
elps> (deduplicate '("A" "B" "C" "D" "C" "B" "A"))
'("A" "B" "C" "D")
deduplicate-list
Same as deduplicate, but only for list sequences.
deduplicate-vector
Same as deduplicate, but only for vector sequences.
make-string-set
Creates a sorted-map with string keys mapping to true values.
elps> (make-string-set '("A" "B" "C" "D" "C" "B" "A"))
(sorted-map "A" true "B" true "C" true "D" true)
make-mergemap
Constructs a new map by taking the union of keys, vals of maps m1 and m2, where m2 value override m1 values where both maps share a common key.
elps> (make-mergemap (sorted-map "A" 1) (sorted-map "A" 123 "B" 99))
(sorted-map "A" 123 "B" 99)
make-submap
Create a sorted-map containing the subset of m's values associated with keys in the sequence, map-keys. Any elements of map-keys that are not keys of m will not be keys of the returned submap either.
elps> (make-submap (sorted-map "A" 123 "B" 99) '("A"))
(sorted-map "A" 123)
strings-unique?
Returns true if all strings in sequence s1 are unique.
elps> (strings-unique? '("A" "B"))
true
elps> (strings-unique? '("A" "A"))
false
strings-union
Performs a union of two string sequences.
elps> (strings-union 'list '("A" "B") '("C" "B"))
'("A" "B" "C")
strings-intersect
Returns unique elements present in both sequences.
elps> (strings-intersect 'list '("A" "B") '("C" "B"))
'("B")
strings-intersect-seqs
Returns unique elements present in both sequences.
elps> (strings-intersect 'list '("A" "B") '("C" "B"))
'("B")
strings-subtract
Returns unique elements of sequence minuend which are not present in sequence subtrahend.
elps> (strings-subtract 'list '("A" "B" "C") '("A" "B"))
'("C")
strings-xor
Returns unique strings that are in seqa or seqb, but are not in both.
elps> (strings-xor 'list '("A" "B" "C") '("A" "B"))
'("C")
strings-equal?
Returns true if the unique strings in seqa are exactly same in seqb.
elps> (strings-equal? '("A" "B" "C") '("A" "B"))
false
elps> (strings-equal? '("A" "B" "C") '("C" "A" "B"))
true
clear-map!
Clears map values, mutating the input map.
elps> (set 'test (sorted-map "A" 1))
(sorted-map "A" 1)
elps> (clear-map! test)
(sorted-map)
elps> test
(sorted-map)
deep-equal?
Works closer to the way that equal? is supposed to for structured data.
elps> (deep-equal? true true)
true
elps> (deep-equal? (sorted-map "B" 1 "A" 1) (sorted-map "A" 1 "B" 1))
true
elps> (deep-equal? true "HELLO")
false
denil
Filters out nil values from a sequence.
elps> (denil 'list '(1 2 3))
'(1 2 3)
elps> (denil 'list '(1 2 3 ()))
'(1 2 3)
human-join-strings
Joins a list of strings with commas, and adds an "and" before the last element. It uses oxford comma style.
elps> (human-join-strings '("This" "that" "other"))
"This, that, and other"
triml
Remove leading spaces from a string.
Deprecated: use (string:trim-left str " "), which is the same.
elps> (triml " Hello World ")
"Hello World "
trimr
Remove trailing spaces from a string.
Deprecated: use (string:trim-right str " "), which is the same.
elps> (trimr " Hello World ")
" Hello World"
trim
Removes leading and trailing spaces from a string.
Deprecated: use (string:trim str " "), which is the same. string:trim-space also removes tabs and newlines.
elps> (trim " Hello World ")
"Hello World"
human-full-name
Tries to construct a single full name string based on optional name fields.
elps> (human-full-name "Charles " " Robert" " Darwin ")
"Charles Robert Darwin"
encode-map-storage-key
Encode a map to be used as part of a storage key delimited by ":". The encoding doesn't use ":" so that two maps may be used side-by-side in a storage key without ambiguity.
shiro> (encode-map-storage-key (sorted-map "testkey" "testval" "fnord" "eris"))
"fnord=eris/testkey=testval"
map-with-index
Map a function on a sequence, where the first argument to the function is i and the second argument to the function is the i-th element. Returns a list.
elps> (trim " Hello World ")
"Hello World"
strings-intersect-seqs
Performs intersection across a sequence of string sequences.
shiro> (strings-intersect-seqs 'list (list (list "1" "2" "3") (list "2" "3") (list "2")))
'("2")
find-pos
find-pos iterates across seq and returns the last position that fn returned true.
If no element is found, 0 is returned, which requires special handling when the element found is the first element.
shiro> (find-pos '(1 2 3 2) #^(equal? % 2))
3
shiro> (find-pos '(1 2 3 2) #^(equal? % 4))
0
decimal-money-add
Add 2 decimal strings, and return a decimal string. DEPRECATED: Please use libdecimal instead of these decimal-money functions.
shiro> (decimal-money-add "0.11" "1.99")
"2.10"
memoize
An optimization technique by storing the results of expensive function calls and returning the cached result when the same inputs occur again.
shiro> (let* ([t (lambda (x) (+ x 1))]
[m (memoize-wrap t)])
(m 1) ; system caches result.
(m 2)
(m 1)) ; system returns cached result.
2
now
Returns current UTC time.
shiro> now
"2020-11-25T19:26:38Z"
now-unix
Returns current time as hexidecimal of the unix epoch time in seconds.
shiro> now-unix
"5FBEB281"
cdar
Returns everything but the first item of the first list, of the outer list.
shiro> (cdar '('("A" "B" "C") '(1 2 3)))
'("B" "C")
widen-number-range
Returns number range.
shiro> widen-number-range
20
widen-number-pennies-range
Returns pennies range.
shiro> widen-number-pennies-range
2
widen-number-pennies-prefix
Returns pennies prefix.
shiro> widen-number-pennies-prefix
'("00" "0" "")
String-trailer
Returns corpus without its first (length prefix) characters. It does not check that corpus starts with prefix: (string-trailer "abcdef" "xy") returns "cdef".
Deprecated: use string:trim-prefix (substrate with elps v1.69.0 or later), which removes prefix only when corpus starts with it.
shiro> (string-trailer "qwerty" "qwe")
"rty"
now-epoch
Return current unix epoch time in seconds.
shiro> (now-epoch)
1606333057
concat!
Mutates the first vector to include the second vector as the last elements.
shiro> (let* ([v1 (vector 1 2 3)]
[v2 (vector 5 6 7)])
(concat! v1 v2)
v1)
(vector 1 2 3 5 6 7)
get-tx-metadata
Get metadata (transaction metadata is stored in the state database under the transaction ID) with string key from the transaction context.
shiro> (cc:set-tx-metadata "test-key" "test-value")
()
shiro> (cc:get-tx-metadata "test-key")
"test-value"
entity-metadata
Returns a specified entry in the metadata field of an entity.
shiro> (let* ([entity (sorted-map "metadata" (vector (sorted-map "name" "test"
"bool_value" true)))])
(entity-metadata entity "test"))
(sorted-map "bool_value" true "name" "test")
get-metadata-str
Returns the string value for a specific entry in the metadata field of an entity.
shiro> (let* ([entity (sorted-map "metadata" (vector (sorted-map "name" "test"
"string_value" "data")))])
(get-metadata-str entity "test"))
"data"
get-metadata-int
Returns the integer value for a specific entry in the metadata field of an entity.
shiro> (let* ([entity (sorted-map "metadata" (vector (sorted-map "name" "test"
"int_value" 250)))])
(get-metadata-int entity "test"))
250
assert-deep-equal
Ensures the 2 arguments are deeply equal. Used in testing.
shiro> (assert-deep-equal (sorted-map "1" 2) (sorted-map "1" 2))
()
shiro> (assert-deep-equal (sorted-map "1" 2) (sorted-map "1" 1))
<native code>: lisp:assert: the values are not ``deep-equal?''
expression: (sorted-map "1" 1)
result: (sorted-map "1" 1)
expected: (sorted-map "1" 2)
Stack Trace [2 frames -- entrypoint last]:
height 1: <native code>: lisp:assert
height 0: <native code>: lisp:let* [terminal]
asset-not-deep-equal
Ensures the 2 arguments are NOT deeply equal. Used in testing.
shiro> (assert-not-deep-equal (sorted-map "1" 2) (sorted-map "1" 1))
()
shiro> (assert-not-deep-equal (sorted-map "1" 2) (sorted-map "1" 2))
<native code>: lisp:assert: the values are ``deep-equal?''
expression: (sorted-map "1" 2)
result: (sorted-map "1" 2)
Stack Trace [2 frames -- entrypoint last]:
height 1: <native code>: lisp:assert
height 0: <native code>: lisp:let* [terminal]
assert-condition
Expects an exception or error to be returned and handles accordingly.
shiro> (assert-condition set-exception-error (validate-nonempty-string (sorted-map "test" 123) "test"))
INFO[305120] generating exception description="test must be a non-empty string" id=shirotester-1 op=TestUtils req_time="2020-11-18T21:52:56Z" test-key-1=test-value-1 test-key-2=test-value-2 test-key-3=test-value-3 type=BUSINESS
()
shiro> (assert-condition set-exception-error (validate-nonempty-string (sorted-map "test" "123") "test"))
()
timestamp-to-date
Converts a timestamp to a date.
shiro> (timestamp-to-date (cc:timestamp (cc:now)))
"2020-11-18"
date-now
Returns today's date.
shiro> (date-now)
"2020-11-18"
ymd-between-dates
Returns the number of years, months, and days in between 2 dates as a vector.
shiro> (ymd-between-dates "1997-08-05" "2020-11-18")
(vector 23 3 13)
ymd-extract
Extracts the year, month, and date and returns that as a vector.
shiro> (ymd-extract "1997-08-05")
(vector 1997 8 5)
decimal-money-from-int
Returns the integer input in money format.
shiro> (decimal-money-from-int 250)
"250.00"
decimal-money-from-vector
Takes a vector of the dollar amount and cent amount as input and returns in money format.
shiro> (decimal-money-from-vector (vector 250 25))
"250.25"
decimal-money-to-vector
Takes a money amount as input and returns a vector of the dollar amount and the cent amount.
shiro> (decimal-money-to-vector "250.25")
(vector 250 25)
decimal-money-cmp
Whether the first money amount is larger than the second.
shiro> (decimal-money-cmp "100.49" "100.50")
-1
shiro> (decimal-money-cmp "100.49" "100.49")
0
shiro> (decimal-money-cmp "100.49" "100.48")
1