11

I have an es6 class User and a global function map() given below:

class  User {
  constructor(public name: string) {}
}

const map = <T, R>(project: (value: T) => R) => {}

Instead of writing the following:

map((value) => new User(value))

I want to (somehow) write something like:

map(new User)

I am not sure if this is possible or not.

6
  • new calls the class. in the second one, you get the same instance for every call. Commented Dec 28, 2018 at 10:09
  • How can I otherwise pass the constructor? Commented Dec 28, 2018 at 10:09
  • Possible duplicate of Passing a constructor to Array.map? Commented Dec 28, 2018 at 10:18
  • @MateuszWitkowski not exact duplicate, because in that question Date object has static constructor function as I understand and in my case it is different. I want to understand how new User will work without passing argument Commented Dec 28, 2018 at 10:27
  • 2
    All that to save twelve characters? Commented Dec 29, 2018 at 13:42

6 Answers 6

6
+50

You can create a static function in your class that takes the value param and returns a new User:

class User {
  static createUser(value) {
    return new User(value)
  }
}

And then use:

map(User.createUser)
Sign up to request clarification or add additional context in comments.

3 Comments

To allow for subclassing, I'd recommend to do return new this(value); though - and then you'd have to User.createUser.bind(User)
Thanks, that looks great, but for now I am trying to understand how the class constructor is going to work
Good point to support subclassing. Binding context to functions is slow though. A good compromise would be to override the function in the subclass
5

You can't do it directly. If you control the target function (ie it's not the stock map function) you can have it take a constructor instead of the function:

class User { constructor(private id: number) { }}
function map<TIn, T>(value: TIn, ctor: new (a: TIn) => T): T{
    return new ctor(value)
}
map(10, User)

Another more flexible solution is to use a helper function that transform the constructor into the desired function, although it's not much shorter then the original version:

class User { constructor(private id: number) { }}
function ctor<TIn, T>(ctor: new (a: TIn) => T): (value: TIn) => T{
    return value => new ctor(value)
}
[10, 11].map(ctor(User));

1 Comment

Yes I can actually control the map by writing my own high-order map function. I will try your version
2

You could add a check with new.target if the function is called without new and call then function with new.

function Person(name) {
    if (!new.target) return new Person(...arguments);
    this.name = name;
}

var names = ['Jane', 'Dan', 'Grace', 'Paul'],
    instances = names.map(Person);

console.log(instances);

2 Comments

Could this work for es6 classes as well ? (If I convert Person to a class I get Class constructor cannot be called without the new keyword at runtime)
no, because you would need a static constructor, which does not work.
2

The pattern you are describing is called a scope-safe constructor. It can be implemented by overloading a constructor so it works with and without the new keyword.

interface User {
  name: string;
}

interface UserConstructor {
  new (name: string): User;
  (name: string): User;
}

The same trick is used for global objects like Array or Date.

We need to recognize whether the new keyword was used:

const User = function (this: User | void, name: string): User {
  if (!(this instanceof User)) {
    return new User(name);
  }

  this.name = name;
  return this;
} as UserConstructor;

Your class has just become new-agnostic.

console.log(
  new User('Bob'),
  User('Alice'),
);

Which enabled us to write:

['Alice', 'Bob'].map(User); // $ExpectType User[]

Comments

2

This is a well known "problem" in the oop vs fp debate in JS - class constructors vs function constructors

since es6 classes need the new operator, it is impossible to write something like map(new User)

you need a wrapper around the class constructor that creates instances via a function call. IMO, @baboo's approach is way to go

class MyClass {
  // ...

  static create(...args) {
    return new MyClass(...args)
  }
}
const a = new MyClass('hello', [])
const b = MyClass.create('world', 123])

you can read more about the problems of new here.

also, checkout daggy - Library for creating tagged constructors

Comments

1

If you want a generic solution, and can't modify the classes, you could use a higher-order function:

function make(klass) {
    return (...params) => new klass(...params);
}

// this:
map((value) => new User(value));

// becomes this:
map(make(User));

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.