unison/unison-src/transcripts-manual/rewrites.md
2024-06-25 11:11:07 -07:00

3.5 KiB

scratch/main> builtins.mergeio
scratch/main> load unison-src/transcripts-using-base/base.u
scratch/main> add

Structural find and replace

Here's a scratch file with some rewrite rules:

ex1 = List.map (x -> x + 1) [1,2,3,4,5,6,7]

eitherToOptional e a =
  @rewrite
    term Left e ==> None
    term Right a ==> Some a
    case Left e ==> None
    case Right a ==> Some a
    signature e a . Either e a ==> Optional a

Either.mapRight : (a ->{g} b) -> Either e a ->{g} Either e b
Either.mapRight f = cases
  Left e -> Left e
  Right a -> Right (f a)

rule1 f x = @rewrite
  term x + 1 ==> Nat.increment x
  term (a -> f a) ==> f -- eta reduction

unique type Optional2 a = Some2 a | None2

rule2 x = @rewrite signature Optional ==> Optional2

Let's rewrite these:

scratch/main> rewrite rule1
scratch/main> rewrite eitherToOptional
scratch/main> load
scratch/main> add

After adding to the codebase, here's the rewritten source:

scratch/main> view ex1 Either.mapRight rule1

Another example, showing that we can rewrite to definitions that only exist in the file:

unique ability Woot1 where woot1 : () -> Nat
unique ability Woot2 where woot2 : () -> Nat

woot1to2 x = @rewrite
  term Woot1.woot1 ==> Woot2.woot2
  term blah ==> blah2
  signature _ . Woot1 ==> Woot2

wootEx : Nat ->{Woot1} Nat
wootEx a =
  _ = !woot1
  blah

blah = 123
blah2 = 456

Let's apply the rewrite woot1to2:

scratch/main> rewrite woot1to2
scratch/main> load
scratch/main> add

After adding the rewritten form to the codebase, here's the rewritten Woot1 to Woot2:

scratch/main> view wootEx

This example shows that rewrite rules can to refer to term definitions that only exist in the file:

foo1 =
  b = "b"
  123

foo2 =
  a = "a"
  233

rule = @rewrite
  case None ==> Left "oh no"
  term foo1 ==> foo2
  case None ==> Left "89899"

sameFileEx =
  _ = "ex"
  foo1
scratch/main> rewrite rule
scratch/main> load
scratch/main> add

After adding the rewritten form to the codebase, here's the rewritten definitions:

scratch/main> view foo1 foo2 sameFileEx

Capture avoidance

bar1 =
  b = "bar"
  123

bar2 =
  a = 39494
  233

rule bar2 = @rewrite
  case None ==> Left "oh no"
  term bar1 ==> bar2

sameFileEx =
  _ = "ex"
  bar1

In the above example, bar2 is locally bound by the rule, so when applied, it should not refer to the bar2 top level binding.

scratch/main> rewrite rule

Instead, it should be an unbound free variable, which doesn't typecheck:

scratch/main> load

In this example, the a is locally bound by the rule, so it shouldn't capture the a = 39494 binding which is in scope at the point of the replacement:

bar2 =
  a = 39494
  233

rule a = @rewrite
  case None ==> Left "oh no"
  term 233 ==> a
scratch/main> rewrite rule

The a introduced will be freshened to not capture the a in scope, so it remains as an unbound variable and is a type error:

scratch/main> load

Structural find

eitherEx = Left ("hello", "there")
scratch/main> add
findEitherEx x = @rewrite term Left ("hello", x) ==> Left ("hello" Text.++ x)
findEitherFailure = @rewrite signature a . Either Failure a ==> ()
scratch/main> sfind findEitherEx
scratch/main> sfind findEitherFailure
scratch/main> find 1-5