最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

Explain generics using Javascript's Flowtype - Stack Overflow

programmeradmin1浏览0评论

I have never written in statically typed language before. I'm mostly developing in Javascript and lately I've been interested in learning more about FB's Flowtype.

I find the documentation nicely written and I understand most of it. However I don't quite get the concept of generics. I've tried googling some examples / explanations but with no luck.

Could someone please explain what generics are, what are they mostly used for and perhaps provide an example?

I have never written in statically typed language before. I'm mostly developing in Javascript and lately I've been interested in learning more about FB's Flowtype.

I find the documentation nicely written and I understand most of it. However I don't quite get the concept of generics. I've tried googling some examples / explanations but with no luck.

Could someone please explain what generics are, what are they mostly used for and perhaps provide an example?

Share Improve this question asked Jun 28, 2017 at 18:07 user3056783user3056783 2,6443 gold badges36 silver badges63 bronze badges
Add a ment  | 

3 Answers 3

Reset to default 6

Let's say I want to write a class that just stores a single value. Obviously this is contrived; I'm keeping it simple. In reality this might be some collection, like an Array, that can store more than one value.

Let's say I need to wrap a number:

class Wrap {
  value: number;
  constructor(v: number) {
    this.value = v;
  }
}

Now I can create an instance that stores a number, and I can get that number out:

const w = new Wrap(5);
console.log(w.value);

So far so good. But wait, now I also want to wrap a string! If I naively just try to wrap a string, I get an error:

const w = new Wrap("foo");

Gives the error:

const w = new Wrap("foo");
                       ^ string. This type is inpatible with the expected param type of
constructor(v: number) {
                    ^ number

This doesn't work because I told Flow that Wrap just takes numbers. I could rename Wrap to WrapNumber, then copy it, call the copy WrapString, and change number to string inside the body. But that is tedious and now I have two copies of the same thing to maintain. If I keep copying every time I want to wrap a new type, this will quickly get out of hand.

But notice that Wrap doesn't actually operate on the value. It doesn't care whether it is number or string, or something else. It only exists to store it and give it back later. The only important invariant here is that the value you give it and the value you get back are the same type. It doesn't matter what specific type is used, just that those two values have the same one.

So, with that in mind we can add a type parameter:

class Wrap<T> {
  value: T;
  constructor(v: T) {
    this.value = v;
  }
}

T here is just a placeholder. It means "I don't care what type you put here, but it's important that everywhere T is used, it is the same type." If I pass you a Wrap<number> you can access the value property and know that it is a number. Similarly, if I pass you a Wrap<string> you know that the value for that instance is a string. With this new definition for Wrap, let's try again to wrap both a number and a string:

function needsNumber(x: number): void {}
function needsString(x: string): void {}

const wNum = new Wrap(5);
const wStr = new Wrap("foo");

needsNumber(wNum.value);
needsString(wStr.value);

Flow infers the type parameter and is able to understand that everything here will work at runtime. We also get an error, as expected, if we try to do this:

needsString(wNum.value);

Error:

20: needsString(wNum.value);
                ^ number. This type is inpatible with the expected param type of
11: function needsString(x: string): void {}
                            ^ string

(tryflow for the full example)

Generics among statically typed languages are a method of defining a single function or class that can be applied to any type dependency instead of writing a separate function/class for each possible data type. They ensure that the type of one value will always be the same at the type of another that are assigned to the same generic value.

For example, if you wanted to write a function that added two parameters together, that operation (depending on the language) could be entirely different. In JavaScript, since it is not a statically typed language to begin with, you can do this anyway and type check within the function, however Facebook's Flow allows for type consistency and validation in addition to single definitions.

function add<T>(v1: T, v2: T): T {
    if (typeof v1 == 'string')
        return `${v1} ${v2}`
    else if (typeof v1 == 'object')
        return { ...v1, ...v2 }
    else
        return v1 + v2
}

In this example we define a function with a generic type T and say that all parameters will be of the same type T and the function will always return the same type T. Inside of the function since we know that the parameters will always be of the same type, we can test the type of one of them using standard JavaScript and return what we perceive and "addition" for that type to be.

When in use later in our code, this function can then be called as:

add(2, 3) // 5
add('two', 'three') // 'two three'
add({ two: 2 }, { three: 3 }) // { two: 2, three: 3 }

But will throw typing errors if we attempt:

add(2, 'three')
add({ two: 2 }, 3)
// etc.

Basically, it's just a placeholder for a type.

When using a generic type, we are saying that any Flow type can be used here instead.

By putting <T> before the function arguments, we're saying that this function can (but doesn't have to) use a generic type T anywhere within its arguments list, its body, and as its return type.

Let's look at their basic example:

function identity<T>(value: T): T {
  return value;
}

This means that the parameter value within identity will have some type, which isn't known in advance. Whatever that type is, the return value of identity must match that type as well.

const x: string = identity("foo"); // x === "foo"
const y: string = identity(123);   // Error

An easy way to think about generics is to imagine one of the primitive types instead of T and see how that would work, then understand that this primitive type can be substituted for any other.

In terms of identity: think of it as a function that accepts a [string] and returns a [string]. Then understand that [string] can be any other valid flow type as well. This means identity is a function that accepts T and returns a T, where T is any flow type.

The docs also have this helpful analogy:

Generic types work a lot like variables or function parameters except that they are used for types.

Note: Another word for this concept is polymorphism.

发布评论

评论列表(0)

  1. 暂无评论