Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .typos.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ Hashi = "Hashi"
# CLI help abbreviation syntax: b[reakpoint], l[ist]
reakpoint = "reakpoint"
ist = "ist"
juxt = "juxt"

[default.extend-identifiers]
# bimap is a crate name (bijective map)
Expand All @@ -15,3 +16,4 @@ caf = "caf"
abd = "abd"
hel = "hel"
fo = "fo"

28 changes: 28 additions & 0 deletions crates/mq-lang/builtin.mq
Original file line number Diff line number Diff line change
Expand Up @@ -367,6 +367,23 @@ def group_by(arr, f):
end
end

# Counts occurrences of each key extracted from the elements of an array,
# returning a dict of `{key: count}`.
def frequencies_by(arr, f):
if (not(is_array(arr))):
error("first argument must be an array")
else:
fold(arr, dict(), fn(acc, x):
let key = to_string(f(x))
| let existing = get(acc, key)
| let count = if (is_none(existing)): 1 else: existing + 1
| set(acc, key, count);
)
end

# Counts occurrences of each element in an array, returning a dict of `{value: count}`.
def tally(arr): frequencies_by(arr, identity);

# Returns true if any element in the array satisfies the provided function.
def any(v, f): if (is_empty(v)): false else: len(filter(v, f)) != 0;

Expand Down Expand Up @@ -792,6 +809,17 @@ end
# Returns a new function that takes the same arguments as the original function but with the first two arguments flipped.
def flip(f, a, b): f(b, a);

# Returns a new predicate function that negates the result of the given function.
def complement(f): fn(x): !f(x);;

# Composes functions into one function, applying them right-to-left.
# `comp(f, g, h)(x)` is equivalent to `f(g(h(x)))`.
def comp(*fns): fn(x): fold(reverse(fns), x, fn(acc, f): f(acc););;

# Returns a function that applies each given function to its argument and collects the results into an array.
# `juxt(f, g, h)(x)` is equivalent to `[f(x), g(x), h(x)]`.
def juxt(*fns): fn(x): map(fns, fn(f): f(x););;

# Returns the sum of the elements in an array after applying a transformation function to each element.
def sum(arr): sum_by(arr, identity);

Expand Down
50 changes: 50 additions & 0 deletions crates/mq-lang/builtin_tests.mq
Original file line number Diff line number Diff line change
Expand Up @@ -384,6 +384,25 @@ def test_group_by():
| assert_eq(result2, dict())
end

def test_frequencies_by():
let result1 = frequencies_by([1, 2, 3, 4, 5, 6], fn(x): x % 2;)
| assert_eq(result1["0"], 3)
| assert_eq(result1["1"], 3)

| let result2 = frequencies_by([], fn(x): x;)
| assert_eq(result2, dict())
end

def test_tally():
let result1 = tally(["a", "b", "a", "c", "b", "a"])
| assert_eq(result1["a"], 3)
| assert_eq(result1["b"], 2)
| assert_eq(result1["c"], 1)

| let result2 = tally([])
| assert_eq(result2, dict())
end

def test_count_by():
let result1 = count_by([1, 2, 3, 4, 5], fn(x): x > 2;)
| assert_eq(result1, 3)
Expand Down Expand Up @@ -767,6 +786,37 @@ def test_flip():
| assert_eq(result2, -3)
end

def test_complement():
let is_even = fn(x): x % 2 == 0;
| let is_odd = complement(is_even)
| assert_eq(is_odd(3), true)
| assert_eq(is_odd(4), false)
| assert_eq(filter([1, 2, 3, 4, 5], complement(is_even)), [1, 3, 5])
end

def test_comp():
let inc = fn(x): x + 1;
| let double = fn(x): x * 2;
| let f = comp(inc, double)
| assert_eq(f(5), 11)

| let g = comp(double, inc)
| assert_eq(g(5), 12)

| let h = comp()
| assert_eq(h(5), 5)
end

def test_juxt():
let inc = fn(x): x + 1;
| let double = fn(x): x * 2;
| let f = juxt(inc, double)
| assert_eq(f(5), [6, 10])

| let g = juxt()
| assert_eq(g(5), [])
end

def test_sum():
let result1 = sum([1, 2, 3, 4, 5])
| assert_eq(result1, 15)
Expand Down