5 min read

This post explores the functional aspect of both Elm and TypeScript, providing a better understanding of both programming languages.

Elm and TypeScript both use JavaScript as a compile target, but they do have major differences, which we’ll examine here.

Elm

Elm is a functional language with a static type analysis and strong inferred type system. In other words, the Elm compiler only runs type checks during compilation and can predict types for all expressions without having explicit type annotations. This guarantees the absence of runtime exceptions due to type mismatch. Elm supports generic types and structural type definitions.

TypeScript

TypeScript is a superset of JavaScript, but unlike Elm it is a multi-paradigm language with a strong imperative part and a significantly weaker functional part. It also has static type checker and supports generic types and structural typing.

TypeScript also has type inference, and although it is not as reliable as Elm’s, it’s still quite useful.

Functions

Let’s see how both languages handle functions.

Elm

Functions is where Elm shines. Function declarations and lambda expressions are supported. Type definitions are simple and robust.

It is worth mentioning that lambda expressions can only have a type definition when used as a return value from a function declaration.

-- Function declaration
add : Int -> Int -> Int
add x y =
    x + y
-- Function expression
x y -> x + y
-- Type definition for lambda expression
add : Int -> Int -> Int
add =
    x y -> x + y

TypeScript

TypeScript can offer the standard set from JavaScript: function declaration, lambda expression and arrow functions.

// Function declaration
function add(x: number, y: number): number {
return x + y;
}

// Function expression 
const add = function (x: number, y: number): number {
return x + y;
}

// Arrow function expresion
const add = (x: number, y: number): number => {
return x + y;
}

Structural typings

Let’s see how they both stack up.

Elm

The structural type annotation definition is available for Tuples and Records.

Records are primarily used for modelling abstract data types. Type aliases can be used as value constructors of the said data type.

type alias User =
  { name: String
  , surname: Maybe String
  }
displayName : User -> String
displayName { name, surname } =
  case surname of
    Just value ->
      name ++ " " ++ value

    Nothing ->
      name
displayName (User "John" (Just "Doe")) -- John Doe

TypeScript

Structural typing is done with interfaces, so it is possible to deconstruct values.

It is also worth mentioning that classes can extend interfaces.

interface User {
    name: string;
    surname?: string;
}

function displayName ({ name, surname="" }:User ):string {
  return name + ' ' + surname
}
console.log(displayName({ name: 'John', surname: 'Doe' }))

Union Types

Elm and TypeScript both handle union types.

Elm

Union Types in Elm are the essential tool for defining abstract data structures with dynamic nature.

Let’s have a look at the Maybe type. The union describes two possible states—the closest you can get to this in TypeScript is an optional value. 

Type variable points out that a stored value might belong to any data type.

type Maybe a
    = Just a
    | Nothing

This might be useful and make a lot of sense in a functional language. Here is an example of how you might use it. If you want to crash the program explicitly, then there is a logical error in the state of the application.

displayName userName =
  case userName of
    Just name ->
      String.toUpper name

    Nothing ->
      Debug.crash "Name is missing"
displayName (Just "Bond") -- BOND
displayName Nothing -- Cause run-time error explicitly

TypeScript

Union types in TypeScript are currently referred to as Discriminated Union.

Here is an example of using union todefine a list of available actions for a dispatcher.

interface ActionSendId {
  name: "ID",
  data: number
}
interface ActionSendName {
  name: "NAME",
  data: string
}
function dispatch(action:ActionSendId | ActionSendName):void {

    switch (action.name) {
      case "ID":
        sendId(action.data)
        break;
      case "NAME":
        sendName(action.data)
        break;
        default:
          break;
    }
}

Interoperation with JavaScript

I will only focus on aspects that are affected by the implementation of a type system.

Elm

You can pass values once from JavaScript to Elm during the initialization process. There is a special type of program for that called programWithFlags.

The Elm application can inter-operate with JavaScript directly using special interfaces, called ports. It implementsa Signal pattern. Sending a value of an unexpected type will cause an error.

During HTTP communication, you have to decode and encode values using Json.Decode and Json.Encode.

During DOM Events, you can use Json.Decode to retrieve values from an Event object.

TypeScript

Using JavaScript with TypeScript is quite possible, but you will have to specify type definitions for the code. As an option, you can use a special type: any.

The any type is a powerful way to work with existing JavaScript, allowing you to gradually optin and optout of typechecking during compilation.

As an alternative, you might have to provide typing files.

Conclusion

Both Elm and TypeScript have their strengths and weaknesses, but despite all of the differences, both type systems give you similar benefits.

Elm has the upper hand with type inference, thanks to the purely functional nature of the language and strict inter-operation with the outside world.

TypeScript does not guarantee type-error-free runtime, but it’s easier pickup and very intuitive if you have JavaScript or a C# background.

About the author

Eduard Kyvenko is a frontend lead at Dinero. He has been working with Elm for over half a year and has built a tax return and financial statements app for Dinero. You can find him on GitHub at @halfzebra.

LEAVE A REPLY

Please enter your comment!
Please enter your name here