Home

Awesome

Enum proposal

Champion group & Authors

Motivation

There is a common need today to create related but distinguishable values, and there are many different ways to do it, and you can easily make mistakes.

Let's take an example: https://nodejs.org/dist/latest-v17.x/docs/api/fs.html#fs_file_open_constants

import { open, constants } from 'node:fs'

const { O_RDWR, S_IFDIR } = constants
export const MESSAGE_TYPE = Object.freeze({
    CONNECT: 'connect',
    DISCONNECT: 'disconnect',
    MESSAGE: 'message',
    ERROR: 'error',
})

export function createMessage(type, ...args) {
    if (type === MESSAGE_TYPE.CONNECT) return { type: MESSAGE_TYPE.CONNECT }
    if (type === MESSAGE_TYPE.DISCONNECT) return { type: MESSAGE_TYPE.DISCONNECT }
    if (type === MESSAGE_TYPE.MESSAGE) return { type: MESSAGE_TYPE.MESSAGE, message: args[0] }
    if (type === MESSAGE_TYPE.ERROR) return { type: MESSAGE_TYPE.ERROR, error: args[0] }
    throw new TypeError('Invalid message type')
}

We want to provide a better way to create those values.

Enum can help:

enum MessageType {
    CONNECT,
    DISCONNECT,
    MESSAGE,
    ERROR,
}
match (val) {
// ~~~~~~~~ Error: Missing case unhandled: val.type might be MessageType.ERROR
    when ({type: ${MessageType.CONNECT}}) -> startListening()
    when ({type: ${MessageType.DISCONNECT}}) -> stopListening()
    when ({type: ${MessageType.MESSAGE}, message}) -> event.emit('message', message)
}

Goal

Enum.format(FileOpen, mode) // ["CREAT", "EXCL"]

Why a frozen object is not enough?

Even you can create an enum today like:

export const MESSAGE_TYPE = Object.freeze({
    CONNECT: 'connect',
    DISCONNECT: 'disconnect',
    MESSAGE: 'message',
    ERROR: 'error',
})

You will still need to create helper functions to

Engine will still need to do a runtime lookup when accessing those values.

Future steps, ADT enum

What is ADT enum?

Plain enum is "related distinguishable primitive values", and ADT enum is "related distinguishable rich data structures".

With ADT enum, the example above can be rewritten as:

enum MessageType {
    CONNECT,
    DISCONNECT,
    MESSAGE(message),
    ERROR(error),
}
match (val) {
// ~~~~~~~~ Error: Missing case in the match clause: MessageType.ERROR
    when (${MessageType.CONNECT}) -> startListening()
    when (${MessageType.DISCONNECT}) -> stopListening()
    when (${MessageType.MESSAGE} with message) -> event.emit('message', message)
}

This part is more complicated, but also much more useful. It'll be a challenge to work on this, therefore we want to exclude it from the current proposal.

We're interested to develop the idea of ADT enum and make sure that plain enum will not block the possibility that we adding this feature in the future.