Zulip Chat Archive

Stream: Is there code for X?

Topic: Purescript-style Variants (dual of records)?


André Muricy Santos (Aug 15 2024 at 15:55):

Purescript has the notion of variants, which are basically the dual of row-type based anonymous records. If records allow for things like

modify :: forall r. { field :: Int | r } -> { field :: String | r }
modify x = let newVal = show (add 10 x.field) in x { field = newVal }

then variants allow for things like:

allToString :: forall r. Variant (foo :: Int, bar :: Boolean | r ) -> String
allToString =
  default "unknown"
    # on (Proxy :: Proxy "foo") show
    # on (Proxy :: Proxy "bar") (\x -> if x then "true" else "false")

This is all thanks to the fact that the compiler has efficient ordered rows, which are basically type-level lists of string (name of field) and type (type of field) tuples.

Since Lean has dependent types I'm sure it has sufficient expressive power for both variants and anonymous records, and with metaprogramming even some convenient syntax could be built?

Kyle Miller (Aug 15 2024 at 16:44):

Here's a typeclass-based experiment with row types, though it doesn't handle reordering rows. Something similar should be possible for variants and .

import Lean

open Lean

def WithField (name : Name) (α : Type _) (r : Type _) := α × r

macro "field(" name:ident " : " α:term ")" " × " r:term : term =>
  `(WithField $(quote name.getId) $α $r)

class GetField (name : Name) (ty : Type _) (α : outParam <| Type _) where
  proj : ty  α

instance (priority := low) [GetField name r α] : GetField name (WithField name' α' r) α where
  proj x := GetField.proj name x.2

instance : GetField name (WithField name α r) α where
  proj x := x.1

class SetField (name : Name) (ty : Type _) (α : Type _) (ty' : outParam <| Type _) where
  set : ty  α  ty'

instance (priority := low) [SetField name r α r'] :
    SetField name (WithField name' α' r) α (WithField name' α' r') where
  set x v := (x.1, SetField.set name x.2 v)

instance : SetField name (WithField name α r) α' (WithField name α' r) where
  set x v := (v, x.2)

def modify1 {r} (x : field(f : Int) × r) : field(f : String) × r :=
  let newVal := 10 + GetField.proj `f x
  SetField.set `f x (toString newVal)

def modify2 {r} (x : field(fst : String) × field(snd : String) × r) :
    field(fst : String) × field(snd : String) × r :=
  SetField.set `fst (SetField.set `snd x (GetField.proj `fst x)) (GetField.proj `snd x)

#eval modify1 (22, "a", ())
-- ("32", "a", ())

#eval modify2 ("a", "b", ())
-- ("b", "a", ())

Last updated: May 02 2025 at 03:31 UTC