Tenecs

The programming language to run in production

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.