Get name of type constructor as a string


#1

Description

Is there a way to get the name of a custom type constructor as a string? For example, suppose I have this custom type:

type User
  = Regular String Int
  | Visitor String

I want a function toString such that:

toString Regular == "Regular"
toString Visitor == "Visitor"

Use Case

I’m interested in using this to build an API for a port that is similar to the API for Elm’s builtin commands. Usually, they take a parameter that is a function which takes the result and turns it into a message. The first parameter of the Http.send function is an example of this:

Http.send NewBook bookRequest

The closest I’ve gotten with my port API is to have the caller provide the name of the constructor as a string rather than passing the constructor itself. Here’s an example where getParsedDate is the function my application uses to send a message to JavaScript via a port and SetDate is the message it wants to get back when the response is ready:

getParsedDate : String -> String -> Cmd msg

type DateMsg
    = SetDate Int

-- Example invocation of `getParsedDate`
update =
  ...
  (model, getParsedDate "SetDate" someDateString)
  ...

If you want more details, you can see a full example at https://ellie-app.com/44hQBb9gqK2a1.

My goal is to be able to write the invocation like this instead:

update =
  ...
  (model, getParsedDate SetDate someDateString)
  ...

#2

You should be able to use a case to return your required string:

type Beatle = John
            | Paul
            | George
            | Ringo

beatleString : Beatle -> String
beatleString beatle =
    case beatle of
        John ->
            "John"
        Paul ->
            "Paul"
        George ->
            "George"
        Ringo ->
            "Ringo"

But I’m not too sure if this is ultimately the best solution to your actual problem. I’ll take a closer look later on and see if I can offer a better refactor.


#3

Thanks for the response. Can an approach like that handle type constructors that have parameters? For example, can you write a function like that that returns the constructor name when given one of these type constructors?:

type User
  = Regular String Int
  | Visitor String

-- How do you implement `userString` such that:
userString Regular == "Regular"
userString Visitor == "Visitor"

#4

Yes, you can have:

userString : User -> String
userString user =
    case user of
         Regular _ _ ->
              "Regular"
         Visitor _ ->
              "Visitor"

#5

If you really mean matching actual constructors (and not User values created with those constructors as in @allanderek:s example), then that is not possible.

First of all Regular and Visitor have different types as constructor functions. Type of Regular is String -> Int -> User while Visitor is String -> User, so you can’t create a function which accepts either as argument.

Now if you change them to have same type, then it is possible to create a function which accepts either as argument, and you might think that something like following would work, BUT == doesn’t work on functions in Elm so even this is not possible:

type User
    = Regular String Int
    | Visitor String Int

userString : (String -> Int -> User) -> Maybe String
userString constructor =
    -- this does NOT work, comparing functions is not possible
    if constructor == Regular then
        Just "Regular"
    else if constructor == Visitor then
        Just "Visitor"
    else
        Nothing

#6

Exactly, but this isn’t the entire picture.

You can’t call userString Regular since Regular is not of type User. You would have to pass the entire sub-type userString (Regular "string" 1) or userString (Visitor "string").


#7

Ah yes you and @malaire are correct, I answered the wrong question.


#8

To answer your use case question the short answer is; Elm internals may do this because Elm internals are allowed to write commands. Userland is not allowed to write commands.

To receive information from non Elm internal javascript you must subscribe to a port function, and call that port function from javascript. There is no other way.

Since functions are not serializeable, you cannot pass or receive a function over the border even if you simply want to receive the same function you sent.


#9

Thanks for all of the responses.

I’m going to mark this thread as resolved. Based on the responses, it seems that it’s not possible to get the name of a type constructor in the general case. Also, it turns out getting the name of the type constructor isn’t actually helpful for solving the use case described in my original post (see below if you want more details about this).

Extra Details

Thinking more about this, Elm’s builtins that generate commands take more than just type constructors. They take functions. For example, NewBook works as the first parameter of Http.send NewBook bookRequest not because it’s a type constructor but because it’s a function that matches the expected signature.

So to solve the use case described in my original post, I should be thinking about functions rather than type constructors. This means that my original question about getting the name of a type constructor as a string is not actually helpful in solving my use case.

To solve my use case, I need to share the function between the sending and receiving sides of the port in Elm. I don’t see any nice way of doing this.