Skip to content

Extend Immutable typeclass beyond primitives — recursive predicate for FixedArray, Fun, immutable struct #129

Description

@tmteam

Context

NFun's Set/Map currently require the element/key type to be Immutable (renamed from Hashable to capture the underlying property, not the derived capability — see Specs/Collections.md §LINQ-typeclasses).

The initial implementation accepts primitives only:

  • bool, all integers (U8..I64), Real, Char, Text, IPAddress
  • ✗ everything else, including types that are conceptually safe

This issue tracks extending the predicate to its full scope as agreed in the design discussion.

Deferred work

Extend Immutable<T> predicate to be recursive over structurally-immutable composites:

Types to add

  1. Fun(...) — closure is reference-immutable. Identity hash (memory address). Two distinct rule it+1 instances are different keys; the same lambda reference is the same key. Mirrors Python/JS/Java/C# function-key semantics.

  2. FixedArray<T> where Immutable(T) — fixedArray is shallow immutable (slots can't be reassigned). Recursive: fixedArray<list<int>> is NOT immutable because the inner list mutates. fixedArray<int> is immutable. fixedArray<fixedArray<int>> is immutable.

  3. ee-mode T[] where Immutable(T) — same as FixedArray, just the legacy ee-mode container.

  4. Optional<T> where Immutable(T) — None has trivial hash; Some(x) hashes through x.

  5. Future immutable struct / record where all fields are Immutable — when record point = {x:int, y:int} (or frozen struct) lands, it automatically participates.

Always NOT Immutable

  • List<T> (mutable container)
  • Array<T> lang-mode (mutable)
  • Set<T>, Map<K,V> (mutable)
  • Mutable struct (lang-mode default)

The rule is transitive immutability: container immutable AND all type parameters Immutable.

Why deferred

Primitive-only Immutable unblocks the common case (Set/Map keyed by int/text/etc.) with minimal TIC integration. The recursive predicate requires:

  1. Recursive constraint propagation through composite element nodes
  2. Error messages that say which inner type failed Immutable (e.g. set<fixedArray<list<int>>> — fail at the inner list)
  3. Update tests for nested cases

This is a focused 3-5 day task once the primitive Immutable lands.

Convert path users need

When user has a mutable collection and wants to use it as a key:

xs = list(1, 2, 3)
m = map(xs.toFixedArray() -> ""foo"")     # explicit freeze

For nested:

xs = list(list(1,2), list(3,4))
key = xs.map(rule it.toFixedArray()).toFixedArray()    # freeze recursively
m = map(key -> ""foo"")

When the recursive predicate lands, the error message should suggest this conversion at the user level.

Related

  • Design discussion: spec D6 (Hashable → Immutable rename)
  • Spec D7 (kind-deferred literals — separate task, will let map([1,2,3] -> ""foo"") auto-resolve to fixedArray)
  • Initial primitive-only implementation: closes Stage4_Set_RejectsUnhashableElement_Function test

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions