1

I find the following getter function very useful and reusable since it can get a value of the property key of any object with such property.

export function keyOf<a>(value: { key: a; }) : a {
   return value.key;
}

Similarly I can define a universal setter function:

export function withKey<a>(value: { key: a; }, key: a) : void {
    value.key = key;
}

The only problem is that instead of returning void I need it to return the original object value with the modified key property. Something like this:

export function withKey<b, a extends { key: b }>(value: a, key: b) : a {
   value.key = key;
   return value;
}

But this is not a valid piece of TypeScript code.

Question: How else can I get a typesafe universal setter function that returns the original object with its property set?

UPDATE:

In the current TypeScript dependencies between type parameters are prohibited. I believe it was done for making the type system simplier and faster. However this limitation prevents certain useful scenarios like the one in question. There is a hack that can turn a dependency between type parameters into a functional dependency:

export function withKey<a, b>(
    value: a,
    key: b,
    toAssignable: (value: a) => { key: b } = function<c>(anything: c) : c {
        return anything;
    }
) : a {
    toAssignable(value).key = key;
    return value;
}

This looks ugly as hell and alters the original signature, but it compiles and works.

Does anyone know a better way?

Trident D'Gao
  • 18,973
  • 19
  • 95
  • 159
  • Your universal setter function looks like an anti-pattern imported from different language. See [Stack Overflow: get and set in TypeScript](http://stackoverflow.com/questions/12827266/get-and-set-in-typescript), but if you insist on the generic setter concept then explore the language keywords `interface` and `implements`, e.g. in [Stack Overflow: Whats the difference between “declare class” and “interface” in TypeScript](http://stackoverflow.com/questions/14345485/whats-the-difference-between-declare-class-and-interface-in-typescript) – xmojmr Jan 18 '15 at 17:37
  • Since when generic functions became an antipattern? Classe are too clumsy and require being instantiated as objects which introduce unwanted overhead. – Trident D'Gao Jan 18 '15 at 18:03
  • I'm not gonna argue whether or not it's an antipattern, but: How exactly would you use your method *without* creating an object for it to work on, though? – Ingo Bürk Jan 18 '15 at 22:14
  • First of all it's not a method, it's a function. [Difference between a method and a function](http://stackoverflow.com/questions/155609/what-is-the-difference-between-a-method-and-a-function). There are plenty useful scenarios for functions like these. In my particular case I need it for implementing [lenses](http://stackoverflow.com/questions/8307370/functional-lenses) in TypeScript. – Trident D'Gao Jan 18 '15 at 22:41

1 Answers1

0

I played around a bit, and I could only come up with this:

// === unsave approach ===  

/**
 * Test class
 */ 
class MyClassWithoutKey {
    public youCanCallThis() { }
}

/**
 * The setter function
 */
function withKeyUnsafe<T>(value: T, key: any) {
    (<any>value).key = key;
    return value;
}

// compile test
var w = withKeyUnsafe(new MyClassWithoutKey(), 2).youCanCallThis();

and this:

// === typesave approach ===

/**
 * Test class
 */     
class MyClassWithKey {
    key: string;
    public youCanCallThis() { }
}

/**
 * Helper interface
 */ 
interface WithKey<T> {
    key: T;
}

/**
 * Factory function that adds type constraint
 */
function withKeyTypeSave<T>() {
    /**
     * The actual setter function
     */
    return function <WithKeyT extends WithKey<T>>(value: WithKeyT, key: T) {
        value.key = key;
        return value;
    }
}


// compile test -- works
withKeyTypeSave<string>()(new MyClassWithKey(), "new key").youCanCallThis();

// compile test -- fails
withKeyTypeSave<number>()(new MyClassWithKey(), "new key").youCanCallThis();

// compile test -- fails
withKeyTypeSave<string>()(new MyClassWithKey(), 3).youCanCallThis();
Benjamin
  • 1,165
  • 7
  • 19