How do I use UnaryOperator for same-type transformations?
← Generics · Ref: Q715
UnaryOperator of T is a pure function type that takes 't' of type T and returns 'r' of the SAME type T.
UNARYOPERATOR SIGNATURE
UnaryOperator of T takes one parameter and returns the same type:
-> t as T <- r as T?
The input type T and return type T are always identical.
ALWAYS PURE
UnaryOperator is always pure — there is no impure variant.
VS FUNCTION
Function of (T, R) allows different input and output types. UnaryOperator constrains both to T:
Function of (String, Integer): String -> Integer (different types) UnaryOperator of String: String -> String (same type)
USING AS PARAMETER
Pass UnaryOperator as a parameter to constrain same-type transforms:
applyOp() as pure -> item as String, op as UnaryOperator of String <- result as String: op(item)
USE CASES
- String transformations (trim, case conversion)
- Numeric adjustments (doubling, negation)
- Same-type transforms in stream map operations
See Q54 for pure function concepts. See Q710 for Function/Routine (different types). See Q89 for stream map operations.
Example
defines module qa.genericsdeep.unaryoperator defines function <?- A user-defined abstract function with same-type signature. Mirrors UnaryOperator's pattern but user-defined for demonstration. -?> stringTransform() as pure abstract -> item as String <- result as String? <?- Named implementation: converts to uppercase. -?> toUpper() is stringTransform as pure -> item as String <- result as String: item.upperCase() <?- Named implementation: trims whitespace. -?> toTrimmed() is stringTransform as pure -> item as String <- result as String: item.trim() <?- Helper that accepts a UnaryOperator as parameter. Pure context — UnaryOperator is always pure. -?> applyOp() as pure -> item as String op as UnaryOperator of String <- result as String: op(item) <?- Helper that accepts the user-defined stringTransform. -?> applyTransform() as pure -> item as String op as stringTransform <- result as String: op(item) defines program UnaryOperatorDemo() stdout <- Stdout() //User-defined functions mirroring UnaryOperator pattern stdout.println(`Upper: ${applyTransform("hello world", toUpper)}`) stdout.println(`Trimmed: '${applyTransform(" spaced ", toTrimmed)}'`) //Chained application result <- applyTransform(applyTransform(" hello ", toTrimmed), toUpper) stdout.println(`Trimmed and uppercased: ${result}`)
Common mistakes
E05150 — When extending a pure abstract function (like a user-defined UnaryOperator pattern), the implementation must be marked 'as pure'. The compiler requires purity to match the super function. See ek9 -h E05150 for details.
Incorrect:
toUpper() is stringTransform -> item as String <- result as String: item.upperCase()
Correct:
toUpper() is stringTransform as pure -> item as String <- result as String: item.upperCase()
Other ways to ask this
- What is UnaryOperator of T and how does it differ from Function?
- How do I create a UnaryOperator of String?
- When should I use UnaryOperator instead of Function?
Coming from another language?
Java: java.util.function.UnaryOperator<T> extends Function<T,T> — no purity distinction, can have side effects. Kotlin: (T) -> T — no named type, no purity. Rust: Fn(T) -> T — structural, no named type. Go: func(T) T — no purity. C#: Func<T, T> — no named type, no purity. EK9: UnaryOperator is always pure, constrains input=output type, separate from Function — compile-time enforced.
Keywords: map, pure, unaryoperator, same-type, function-type, string, stream, transform, generic, identity