Tenecs is a language for building software that stays understandable as it grows. You can start by learning the syntax, then try snippets in the playground.
This page gives a quick overview of the language direction, with a short philosophy section that explains one core design choice.
What's different
A central Tenecs idea is: side-effects and mutability are provided through function arguments. As an example, if you'd like to write to the terminal, you need the Console instance. You can't use a function already implicitly in scope or an imported one to do it directly. Functions receive the capabilities they need explicitly.
Why this helps
Better testability: all your code is testable in deterministic unit tests.
Pass a fake dependency directly and assert behavior without patching globals.
package home.advantages
import tenecs.list.append
import tenecs.ref.Ref
import tenecs.string.join
import tenecs.test.UnitTest
notifyUser := (
send: (message: String) ~> Void,
userId: String
): Void => {
send(join("welcome ", userId))
}
_ := UnitTest("notifyUser sends deterministic message", (testkit) => {
sent := testkit.ref.new(<String>[])
fakeSend := (message: String): Void => {
sent.set(append(sent.get(), message))
}
notifyUser(fakeSend, "u-1")
testkit.assert.equal(sent.get(), ["welcome u-1"])
})
Easier code reviews: function arguments document effectful dependencies directly.
Reviewers can see architecture from signatures: pure flow vs effectful boundary.
package home.advantages
import tenecs.string.join
struct Deps(
readUser: (id: String) ~> String,
log: (msg: String) ~> Void
)
buildGreeting := (name: String): String => {
join("hello ", name)
}
handleRequest := (deps: Deps, userId: String): String => {
name := deps.readUser(userId)
greeting := buildGreeting(name)
deps.log(join("served ", userId))
greeting
}
Re-use code between programs: you can have your web frontend and backend share the same codebase.
Keep domain rules pure and plug different runtimes around the same logic.
package home.advantages
import tenecs.int.minus
// Shared in frontend and backend.
discountedTotal := (price: Int, isVip: Boolean): Int => {
if isVip {
price->minus(20)
} else {
price
}
}
// Backend usage: write to database.
storeInvoice := (save: (amount: Int) ~> Void, amount: Int): Void => {
save(amount)
}
// Frontend usage: render amount.
renderInvoice := (render: (amount: Int) ~> Void, amount: Int): Void => {
render(amount)
}
Improved security: functions can't act outside of their provided capabilities.
Limit blast radius by passing minimal capabilities instead of wide global access.
package home.advantages
struct BillingWrite(
writeInvoice: (invoiceId: String) ~> Void
)
// This function can only write invoices.
// It cannot read secrets, call random endpoints, or access unrelated storage.
createInvoice := (cap: BillingWrite, invoiceId: String): Void => {
cap.writeInvoice(invoiceId)
}
In practice
Tenecs does not avoid effects; it makes them explicit. Real programs still perform I/O, call services, and update state, but those capabilities are passed intentionally so architecture is legible instead of a hidden convention.