Skip to content

Instantly share code, notes, and snippets.

@JSuder-xx
Last active September 7, 2021 12:44
Show Gist options
  • Save JSuder-xx/b5e429143bcf09986d360c34300592f1 to your computer and use it in GitHub Desktop.
Save JSuder-xx/b5e429143bcf09986d360c34300592f1 to your computer and use it in GitHub Desktop.
Small study of partial application of polymorphic functions in F#, OCaml, ReScript, PureScript, Haskell, Elm, and TypeScript.

Overview

Curried functions are important for functional programming ergonomics. Polymorphic functions are import for expressiveness, truthfulness, and consequently re-use.

  • Pure functional language such as Haskell, PureScript, and Elm offer all of the benefits.
  • Languages which admit mutation but which honor soundness (F#, OCaml, and ReScript)
    • F# throws a descriptive error message at the location of the offense.
    • ReScript throws a halfway descriptive message but errors the entire module.
    • OCaml monomorphicizes the application based on the first observed subsequent usage.
  • TypeScript, on the other hand, simply allows an unsound typing (which is likely to work out most of the time but will bite you on edge cases).

F#

F# Online

let tuple a b = (a, b)
let example1 = [1; 2; 3] |> List.map (tuple false)
let example2 = ['a'; 'b'; 'c'] |> List.map (tuple 1)

// Unlike OCaml which will allow deferring the monomorphic type selection
// F# decides to be more truthful with you, since you very likely want polymorphism,
// it will tell you that you need to annotate the type.
let p1 = tuple false // Value Restriction

OCaml

OCaml Online See Weak Type Variables.

let tuple a b = (a, b)
let worksInContext1 = [1; 2; 3] |> List.map (tuple 'a')
let worksInContext2 = ['a'; 'b'; 'c'] |> List.map (tuple false)

(* OCaml defers choosing a type for b, partial1 is NOT polymorphic. *)
let partial1 = tuple 'a'

(* On first usage it decides that b is an int *)
let worksFirstTime = [1; 2; 3] |> List.map partial1 
(* partial1 expects and int so the line below fails *)
let failsBecauseTypeOfPartialGrounded = ['a'; 'b'; 'c'] |> List.map partial1                 

ReScript

ReScript Playground

let tuple = (a, b) => (a, b)
let worksInContext1 = [1, 2, 3] |> Array.map(tuple('a'))
let worksInContext2 = ['a', 'b', 'c'] |> Array.map(tuple(false))

// Interestingly, the designers of Rescript chose the F# route of failing early (which I prefer).
// However, the error message I received in the ReScript Playground was for the **entire** module and
// the message and not terribly clear
// > The type of this module contains type variables that cannot be generalized:
let partial1 = tuple('a')

PureScript

Try PureScript!

tuple a b = Tuple a b
ex1 = [1, 2, 3] # map (tuple 'a')
ex2 = ['a', 'b', 'c'] # map (tuple false)
-- The reasons discussed in Weak Type Variables do not apply to PureScript, which is 
-- pure, and so we can partially apply polymorphic functions all day long without
-- restriction or additional annotations.
partial1 = tuple false
partial2 = tuple 20

Haskell

Obviously Haskell has the same power as PureScript. Online Haskell Compiler

tuple a b = (a, b)
ex1 = map (tuple 'a') [1, 2, 3] 
ex2 = map (tuple 1) ['a', 'b', 'c']
partial1 = tuple 'a'
partial2 = tuple 20

Elm

Try Elm

tuple a b = (a, b)
example1 = [1, 2, 3] |> List.map (tuple False)
example2 = ['a', 'b', 'c'] |> List.map (tuple 1)

-- Elm, though having a much weaker type system, still benefits from purity.
-- Even Elm beats F# and OCaml when it comes to partial application simply because the language is pure. 
partial1 = tuple False
partial2 = tuple 20

TypeScript

TypeScript Playground

const tuple = <A extends unknown>(a: A) => <B extends unknown>(b: B) => [a, b] as const
// TypeScript 'allows' it. However, since TypeScript is working in an impure environment
// it should NOT!!! See the reasons given in
// [Weak Type Variables](https://ocamlverse.github.io/content/weak_type_variables.html)
// for a discussion of how this is an unsound typing. 
const partial1 = tuple(10)
const partial2 = tuple('A')
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment