Home > Software design >  making F# discriminated union types like Typescript literal types
making F# discriminated union types like Typescript literal types

Time:03-19

i had go at making an F# equivalent of the following Typescript types:

/**
 * styling variables for this App
 */
export type YouTubeCssVariables =

    /* html anchor */
    '--thumbs-header-link-color' |
    '--thumbs-header-link-text-decoration' |

    /* thumbs set */
    '--thumbs-set-header-color' |
    '--thumbs-set-background-color' |
    '--thumbs-set-header-position' |
    '--thumbs-set-padding-top'
    ;

/**
 * defines a name-value pair for a CSS variable
 */
export interface YouTubeCssOption {
    /**
     * CSS variable name
     */
    variableName: YouTubeCssVariables;

    /**
     * CSS variable value
     */
    variableValue: string;
}

we see that YouTubeCssVariables is a literal type and my assertion is that there is no concept of this kind of type in F# (is this assertion correct?); so, here is my rendition of the F# ‘equivalent’:

type YouTubeCssVariable =
    | ThumbsHeaderLinkColor of string
    | ThumbsHeaderLinkTextDecoration of string
    | ThumbsSetHeaderColor of string
    | ThumbsSetBackgroundColor of string
    | ThumbsSetHeaderPosition of string
    | ThumbsSetPaddingTop of string

    member this.Name =
        match this with
        | ThumbsHeaderLinkColor _ -> "--thumbs-header-link-color"
        | ThumbsHeaderLinkTextDecoration _ -> "--thumbs-header-link-text-decoration"
        | ThumbsSetHeaderColor _ -> "--thumbs-set-header-color"
        | ThumbsSetBackgroundColor _ -> "--thumbs-set-background-color"
        | ThumbsSetHeaderPosition _ -> "--thumbs-set-header-position"
        | ThumbsSetPaddingTop _ -> "--thumbs-set-padding-top"

    member this.Value =
        match this with
        | ThumbsHeaderLinkColor v -> v
        | ThumbsHeaderLinkTextDecoration v -> v
        | ThumbsSetHeaderColor v -> v
        | ThumbsSetBackgroundColor v -> v
        | ThumbsSetHeaderPosition v -> v
        | ThumbsSetPaddingTop v -> v

    member this.Pair =
        let n = this.Name
        let v = this.Value
        n, v

is this the right direction? …when, yes, then are these match expressions too verbose? …is there some more terse syntax out there i am missing?

CodePudding user response:

What's really your goal here? Surely it's not making F# types more like Typescript types. That article strikes me as trying to make Javascript more like F#. I suggest forgetting about Typescript here and just use F# types to best solve the problems you have.

If your payload is always a string (which it seems to be) I would consider splitting that type into two:

open System

type CssVariableType =
    | ThumbsHeaderLinkColor
    | ThumbsHeaderLinkTextDecoration
    | ThumbsSetHeaderColor
    | ThumbsSetBackgroundColor
    | ThumbsSetHeaderPosition
    | ThumbsSetPaddingTop

type CssVariable = {
    Type: CssVariableType
    Value: String
}

I would drop the Name, Value, and Pair type additions and just write a module containing the functions you need. Pattern match as needed inside the functions. Maybe a print function would look like this:

module CssVariable =
    let print var =
        let name =
            match var.Type with
            | ThumbsHeaderLinkColor -> "--thumbs-header-link-color"
            | ThumbsHeaderLinkTextDecoration -> "--thumbs-header-link-text-decoration"
            | ThumbsSetHeaderColor -> "--thumbs-set-header-color"
            | ThumbsSetBackgroundColor -> "--thumbs-set-background-color"
            | ThumbsSetHeaderPosition -> "--thumbs-set-header-position"
            | ThumbsSetPaddingTop -> "--thumbs-set-padding-top"
        $"{name}={var.Value}"

Example:

let v = { Type = ThumbsHeaderLinkColor; Value = "blue" }

CssVariable.print v // "--thumbs-header-link-color=blue"

CodePudding user response:

I think the main reason why TypeScript has type definitions with string values as cases is to allow type checking of popular programming patterns in JavaScript. If you do not need to type-check code where "magic strings" are used to represent cases of a union, then it makes much more sense to use an explicit union type like the one used in F#.

The question then really becomes about assigning a nice textual representation to each case of a union, perhaps because they represent some CSS classes that also can be represented as strings. I think the solution with just having a format function that takes the union value and returns a corresponding string is perfectly reasonable - you only have to write this once (and it is also more future proof in case some names change).

If you needed this for a large number of union cases, it may make sense to use some reflection magic to generate the CSS names from your F# DU case names automatically. I do not think it is worthe the extra complexity for a few cases, but it can be done. Something like the following would work (but it does not handle possible errors):

type CssVariableType =
  | ThumbsHeaderLinkColor
  | ThumbsHeaderLinkTextDecoration
  | ThumbsSetHeaderColor
  | ThumbsSetBackgroundColor
  | ThumbsSetHeaderPosition
  | ThumbsSetPaddingTop

open Microsoft.FSharp.Reflection

let formatCase<'T> () = 
  let formatName (s:string) = 
    [ yield "-"
      for c in s do
        if System.Char.IsUpper(c) then yield "-"
        yield c.ToString().ToLower() ]
    |> String.concat ""
  let lookup = 
    FSharpType.GetUnionCases(typeof<'T>)
    |> Seq.map (fun c -> FSharpValue.MakeUnion(c, [||]), formatName c.Name)
    |> dict
  fun (c:'T) -> lookup.[c]

let f = formatCase<CssVariableType>()
f ThumbsHeaderLinkColor // "--thumbs-header-link-color"
f ThumbsSetHeaderColor // "--thumbs-set-header-color"
  • Related