diff --git a/sketching/april_2025/closure.dm b/sketching/april_2025/closure.dm new file mode 100644 index 0000000..595007f --- /dev/null +++ b/sketching/april_2025/closure.dm @@ -0,0 +1,530 @@ +pub enum Closure(P, D = Any, S = Any) -> T { + Read(ReadClosure), // reads self (where Self is Closure) and/or environment + ReadRef(ReadRefClosure), // reads environment/self, return value tied to closure + Mut(MutClosure), // mutates self/environment + MutRef(MutRefClosure), // mutates self/environment, return value tied to closure + Cons(ConsClosure), // consumes self/environment +} + +class Foo(bar: Bar) {} + +class Bar(mut i: Int) {} + +fn readClosure(cl: Closure(Int) -> String) -> String { + cl(42) +} + +fn useReadClosure() { + let s = readClosure { i -> + 'i: ' + i.toString() + } + assertEq('i: 42', s) +} + +fn mutClosure(cl: mut Closure(Int) -> String) -> String { + cl(42) +} + +fn useMutClosure() { + let mut x = 0 + let cl: mut Closure(Int) -> String = mut { i -> + let result = 'Val: ' + (i + x) + x++ + result + } + let s0 = mutClosure cl + assertEq('Val: 42', s0) + let s1 = mutClosure cl + assertEq('Val: 43', s1) +} + +fn consClosure(cl: cons Closure(Int) -> String) -> String { + cl(42) + // cl(43) // Error! cl was already consumed +} + +fn useConsClosure() { + let foo = Foo(Bar(10)) + let cl: cons Closure(Int) -> String = cons { i -> + let fooBarI = foo.bar.i + drop(foo) + 'Val: ' + (i + fooBarI) + } + let s = consClosure(cl) + // let err0 = consClosure(cl) // Error! cl may have been consumed + // let err1 = foo // Error! foo may have been consumed + assertEq('Val: 52', s) +} + +fn readRefClosure(cl: Closure(foo: Foo) -> Bar ref foo) -> Int { + let foo = Foo(Bar(42)) + let bar = cl(foo) + bar.i +} + +fn useReadRefClosure() { + let i = readRefClosure { foo -> foo.bar } + assertEq(42, i) +} + +fn mutRefClosure(cl: mut Closure(foo: Foo) -> Bar ref foo) -> Int { + let foo = Foo(Bar(42)) + let bar = cl(foo) + bar.i +} + +fn useMutRefClosure() { + let i = mutRefClosure mut { foo -> + foo.bar.i = 84 + foo.bar + } + assertEq(84, i) +} + +fn readClosureWithDelegate(cl: Closure(P = Int, D = Foo) -> String) -> String { + cl(42) +} + +fn useReadClosureWithDelegate() { + let foo = Foo(Bar(42)) + let cl = { i: Int -> + 'Val: ' + (i + bar.i) // bar found in delegate + } + cl.delegate = &foo + let s = readClosureWithDelegate(cl) + assertEq('Val: 84', s) +} + +class Baz(foo: Foo) {} + +fn mutRefClosureWithDelegate(baz: &mut Baz, cl: Closure(Int, baz: &mut Baz)(D = Foo) -> &Bar ref baz) -> String { + let foo = Foo(Bar(1)) + cl.delegate = &foo + let bar = cl(2, baz) + 'Val: ' + bar.i +} + +fn useMutRefClosureWithDelegate() { + let cl: Closure(Int, baz: &mut Baz)(D = Foo) -> &Bar ref baz = mut { i, baz -> + let iFromDelegate = bar.i + baz.foo.bar.i = iFromDelegate + i + &baz.foo.bar + } + let baz = Baz(Foo(Bar(20))) + let bar = mutRefClosureWithDelegate(cl) + assertEq(3, bar.i) +} + +fn mutClosureWithDelegate(cl: Closure(P = (Int, Int), D = mut Foo) -> String { + let foo = Foo(Bar(42)) + cl.delegate = &mut foo + cl(1, 2) +} + +fn useMutClosureWithDelegate() { + let cl: Closure(P = (Int, Int), D = mut Foo) -> String = mut { x, y -> + bar.i = 3 + 'Val: ' + (x + y + bar.i) + } + let s = mutClosureWithDelegate(cl) + assertEq('Val: 6', s) +} + +pub int ClosureBase { + params: Array + mut resolutionStrategy: ResolutionStrategy + ref fn getDelegate() -> Option<&Any> + ref fn getSelfObject() -> Option<&Any> +} + +pub enum ResolutionStrategy { + DelegateFirst, + DelegateOnly, + SelfFirst, + SelfOnly, + None +} + +pub int ConsClosure : ClosureBase { + mut fn setDelegate(d: Any) -> Void + mut fn setSelf(s: Any) -> Void + mut fn call(...args: Array) -> T + def mut op () (...args: Array T = call(...args) +} + +pub int MutClosure : ClosureBase { + mut fn setDelegate(d: &mut Any) -> Void + mut fn setSelf(s: &mut Any) -> Void + mut fn call(...args: Array) -> T + def mut op () (...args: Array) -> T = call(...args) +} + +pub int MutRefClosure : ClosureBase { + mut fn setDelegate(d: &mut Any) -> Void + mut fn setSelf(s: &mut Any) -> Void + mut ref fn call(...args: Array) -> T // T ref self + def mut ref op () (...args: Array) -> T = call(...args) +} + +pub int ReadClosure : ClosureBase { + mut fn setDelegate(d: &Any) -> Void + mut fn setSelf(s: &Any) -> Void + fn call(...args: Array) -> T + def op () (...args: Array) -> T = call(...args) +} + +pub int ReadRefClosure : ClosureBase { + mut fn setDelegate(d: &Any) -> Void + mut fn setSelf(s: &Any) -> Void + ref fn call(...args: Array) -> T + def op () (...args) -> T = call(..args) +} + +pub int ClosureParam { + clazz: Class + def op typeof () -> Class = clazz +} + +// Example of how closures are compiled + +fn main() { + let is = [1, 2, 3] + let cl0 = { acc: String, i: Int -> + acc + i + } + println is.reduce(cl0) + + let x = 10 + let cl1 = /* read */ { acc: String, i: Int -> + acc + (i + x) + } + println is.reduce(cl1) + + let mut counter = 0 + let cl2 = mut { acc: String, i: Int -> + let result = acc + (i + counter) + counter++ + result + } + println is.reduce(cl2) + + let mut foo = Foo(Bar(1)) + let cl3 = cons { acc: String, i: Int -> + // do something pointless just for illustration + if foo.bar.y % 2 == 0 { + foo.bar = Bar(1) + } + let result = acc + (i + foo.bar.y) + foo.bar.y++ + result + } + // foo.bar = Bar(42) // Error! foo moved into consuming cl3 + println [1, 2, 3, 4].reduce(cl3) // "11243645" +} + +// Compiled + +@Synthetic +fn closure_main_cl0(acc: String, i: Int) = acc + i + +@Synthetic +class Closure_main_cl1_env(x: &Int) -> String {} + +@Synthetic +class Closure_main_cl1(env: Closure_main_cl1_env) : ReadClosure { + impl fn call(...args: Array) -> String { + let acc: String = args[0] + let i: Int = args[1] + let x: Int = *env.x // because env.getX() -> &&Int + acc + (i + x) + } +} + +@Synthetic +class Closure_main_cl2_env(counter: &mut Int) {} + +@Synthetic +class Closure_main_cl2(env: Closure_main_cl2_env) : MutClosure { + impl mut fn call(...args: Array) -> String { + let acc: String = args[0] + let i: Int = args[1] + let counter = &mut env.counter + let result = acc + (i + counter) + *counter++ + result + } +} + +@Synthetic +class Closure_main_cl3(mut foo: Foo) : ConsClosure { + impl mut fn call(...args: Array) -> String { + let acc: String = args[0] + let i: Int = args[1] + if foo.bar.y % 3 == 0 { + foo.bar = Bar(1) + } + let result = acc + (i + foo.bar.y) + foo.bar.y++ + result + } +} + +fn main() { + let is: List = ArrayList() { + add 1 + add 2 + add 3 + } + let cl0 = &closure_main_cl0 + println is.reduce(cl0) + + let x = 10 + let cl1_env = HashMap() { + put 'x', &x + } + let cl1: Closure = Closure::ReadClosure(Closure_main_cl1(cl1_env)) + println is.reduce(cl1) + + let mut counter = 0 + let cl2_env = Closure_main_cl2_env(&counter) + let cl2: Closure = Closure::MutClosure(Closure_main_cl2(cl2_env)) + println is.reduce(cl2) + + let mut foo = Foo(Bar(1)) + let cl3: Closure = Closure::ConsClosure(Closure_main_cl3(foo)) // foo moved into closure + let tmp0: List = ArrayList() { + addAll 1, 2, 3, 4 + } + println tmp0.reduce(cl3) +} + +// Example + +// original +class Foo { + mut fld current = 0 + + ref fn getClosure(base: Int) -> (mut Closure(Int) -> Int) { + return mut { given -> + let result = base + given + current + current++ + result + } + } +} + +fn main() { + let foo = Foo() + let cl = foo.getClosure(10) + let nums = [cl(1), cl(2), cl(3), cl(4)] + assertEq([11, 13, 15, 17], nums) +} + +// Compiles to + +@Synthetic +class Closure_Foo_getClosure_0(base: &Int, current: &mut Int) : MutClosure(Int) -> Int { + impl mut fn call(given: Int) -> Int { + let result = *base + given + *current + *current++ + result + } +} + +class Foo { + mut fld current = 0 + + ref fn getClosure(base: Int) -> (mut Closure(Int) -> Int) { + return Closure_Foo_getClosure_0(&base, &mut current) + } +} + +fn main() { + /* ... */ +} + +// Library code + +pub int ReadClosure(P : () = ()) : Into[Self(P), Option] { + mut fn setDelegate(d: D) -> Void + ref fn getDelegate() -> Option<&D> + mut fn setSelfObject(s: &S) -> Void + ref fn getSelfObject() -> Option<&S> + fn call(...params: P) -> T + def op () (...params: P) -> T = call(...params) + + def cons fn intoDelegate() -> D { + Into[Self(P), Option].into() + } +} + +pub abstract class AbstractReadClosure(P : () = ()) : ReadClosure(P) { + mut fld delegate: Option + mut fld selfObject: Option<&S> + + impl mut fn setDelegate(d: D) { + delegate = Some(d) + } + + impl ref fn getDelegate() -> Option<&D> { + &delegate is Some(d) ? Some(d) : None + } + + impl mut fn setSelfObject(s: &S) { + selfObject = Some(s) + } + + impl ref fn getSelfObject() -> Option<&S> { + &selfObject is Some(s) ? Some(s) : None + } + + impl Into[Self(P), D] { + cons fn into() -> Option = delegate + } +} + +// User code demonstration + +class Foo(bar: Bar) {} + +class Bar(num: Int) {} + +class Baz(foo: Foo) { + pub ref fn getClosure() -> Closure { + return { foo.bar.num + bar.num } + } +} + +fn main() { + let baz0 = Baz(Foo(Bar(1))) + let cl = baz0.getClosure() + let delegate = Foo(Bar(2)) + cl.delegate = delegate + let result0 = cl() + assertEq(3, result0) + let baz1 = Baz(Foo(Bar(2))) + cl.selfObject = &baz1 + let result1 = cl() + assertEq(4, result1) + let movedDelegate = cl.intoDelegate() + assertEq(2, movedDelegate.bar.num) +} + +// User code compiles to + +class Foo(bar: Bar) {} + +class Bar(num: Int) {} + +@Synthetic +class Closure_Baz_getClosure_0: AbstractReadClosure() { + ctor(selfObject: &Baz) ref selfObject { + self.selfObject = Some(selfObject) + } + + fn call() -> Int { + let t0 = selfObject.unwrap().foo.bar.num + let t1 = self.delegate.unwrap().bar.num + t0 + t1 + } +} + +class Baz(foo: Foo) { + pub ref fn getClosure() -> Closure { + return Closure_Baz_getClosure_0(&self) + } +} + +fn main() { + // ... +} + +// fn vs Closure syntax + +int FnOrClosureTaker { + fn read(f: fn (Int) -> Int) -> Int + fn mut(f: mut fn (Int) -> Int) -> Int + fn cons(f: cons fn (Int) -> Int) -> Int +} + +class FnOrClosureTakerImpl : FnOrClosureTaker { + impl fn read(f: fn (Int) -> Int) { + let i0 = f(0) + let i1 = f(1) + let i2 = f(2) + i0 + i1 + i2 + } + + impl fn mut(f: mut fn (Int) -> Int) { + /* same as read */ + } + + impl fn cons(f: cons fn (Int) -> Int) { + let i0 = f() + // let i1 = f() // Error! f already consumed + i0 + } +} + +mod User { + fn getReader() -> fn (Int) -> Int { + return { it * 2 } + } + + fn useReader() { + let reader = getReader() + let fnOrClosureTaker: FnOrClosureTaker = FnOrClosureTakerImpl() + let result = fnOrClosureTaker.read(reader) + assertEq(6, result) + } +} + +// compiles to + +mod User { + fn getReader_closure_0(it: Int) -> Int { + it * 2 + } + + fn getReader() -> &fn (Int) -> Int { + &getReader_closure_0 + } + + // etc. +} + +// New interfaces? + +pub int ReadClosure(P : () = ()) -> T : fn (P) -> T { + ref fn getDelegate() -> Option<&D> + mut fn setDelegate(d: D) -> Void + fn call(...p: P) -> T + def op () alias call +} + +pub int Iterator { + mut fn next() -> Option +} + +pub int Iterable : Into[Self, Iterator] { + ref fn iterator() -> Iterator<&T> + + def cons fn intoIterator() = Into[Self, Iterator].into(self) +} + +pub class MyIterable : Iterable { + fld items: List = [1, 2, 3] + + impl ref fn iterator() -> Iterator<&Int> { + let mut index = 0 + return mut { + if index >= items.length { + None + } else { + let next = items[index] + index++ + Some(next) + } + } + } +} \ No newline at end of file diff --git a/sketching/april_2025/hkt.dm b/sketching/april_2025/hkt.dm new file mode 100644 index 0000000..c4d2e20 --- /dev/null +++ b/sketching/april_2025/hkt.dm @@ -0,0 +1,260 @@ +pub hkt Functor[Self] { + fn map(f: fn &T -> U) -> Self // &self implied +} + +pub hkt Applicative[Self] : Functor[Self] { + static fn lift(t: T) -> Self // static: no implicit self parameter + fn apply(us: &Self) -> Self where T : fn (u: &U) -> V +} + +pub hkt Monad[Self] : Applicative[Self] { + fn flatMap(f: fn (t: &T) -> Self) -> Self +} + +// Here, T is a separate type parameter from the container's inner type +// The container's inner type is ..., which can be anything, of any number of parameters +pub hkt Into[Self<...>, T] { + cons fn into() -> T // consumes self +} + +pub hkt Zero[Self] { + static fn zero() -> Self +} + +pub int Iterable : Into[Self, Iterator] { + /** + * Iterator for references to contained item(s). + */ + ref fn iter() -> Iterator<&T> // ref indicates return value cannot outlive self + + def cons fn intoIter() = Into[Self, Iterator].into() +} + +pub type Iterator = () -> Option + +pub int Exception { + message: Option +} + +pub enum Option : Monad[Self] { + Some(T), + None; + + class EmptyOptionException(message: Option = Empty) : Exception + + // cons implies that self is consumed (like Rust `self: Self`) + cons fn unwrap() -> T = self is Some(t) ? t : throw EmptyOptionException() + + cons fn expect(message: String) -> T { + if let Some(t) = self { + t + } else { + throw EmptyOptionException(message) + } + } + + cons fn unwrapOr(self, other: T) -> T = self is Some(t) ? t : other + + cons fn unwrapOrElse(self, f: fn () -> T) = self is Some(t) ? t : f() + + impl Monad[Self] { + fn map(f: fn (t: &T) -> U) -> Self = self is Some(t) ? Some(f(t)) : Empty + + static fn lift(t: T) -> Self = Some(t) + + fn apply(us: &Self) -> Self where T : fn (u: U) -> V = + self is Some(f) && us is Some(u) ? Some(f(u)) : Empty + // note in above that `f` is `&fn (u: U) -> V`, but `f()` is sugar for `(*f)()` + + fn flatMap(f: fn (t: &T) -> Self) -> Self = self is Some(t) ? f(t) : Empty + } +} + +pub class OptionT[M](fld o: M>) : Monad[Self[M]] where M : Monad[Option] { + pub fn intoInner() -> M> = Into[Self[M], M>].into(self) + + pub fn flatMapInner(f: fn (t: &T) -> M>) -> Self[M] { + OptionT( + Monad[M].flatMap(o) { ts: &Option -> + ts is Some(t) ? f(t) : None + } + ) + } + + impl Monad[Self[M]] { + fn map(f: fn (t: &T) -> U) -> Self[M] { + OptionT( + Monad[M].map(o) { + // implicit `it` parameter is `&Option` + // When destructured, `t` is `&T` + it is Some(t) ? Some(f(t)) : None + } + ) + } + + static fn lift(t: T) -> Self[M] = OptionT(Monad[M].lift(Some(t))) + + fn apply(us: &Self[M]) -> Self[M] where T : fn (u: &U) -> V { + OptionT( + Monad[M].flatMap(o) { fOpt: &Option V> -> + fOpt is Some(f) + ? Monad[M].map(us.o) { uOpt: &Option -> + uOpt is Some(u) ? Some(f(u)) : None + } + : Monad[M].lift(None) + } + ) + } + + fn flatMap(f: fn (t: &T) -> Self[M]) -> Self[M] { + OptionT( + Monad[M].flatMap(o) { + it is Some(t) ? f(t).o : Monad[M].lift(None) + } + ) + } + } + + impl Into, M>> { + cons fn into() -> M> = o + } +} + +pub int List : Iterable + Zero[Self] + Monad[Self] { + mut fn add(t: T) -> Void // mut implies &mut self + + // def = default + // mut = &mut self + // op = operator overload + // << = left shift + def mut op << (t: T) -> Void = add(t) + + ref fn get(i: Int) -> Option<&T> // ref implies that the returned object cannot outlive self + // implied &self + + // Alternative syntax for get(i) definition: + // fn get(i: Int) -> Option<&T> ref self + + def ref op [] (i: Int) -> Option<&T> = get(i) + + impl Monad[Self] { + fn map(f: fn (t: &T) -> U) -> Self { + let out: List = Zero[Self].zero() + for item in self { // item is &T + out << f(item) + } + out + } + + static fn lift(t: T) -> Self = ArrayList(t) + + fn apply(us: &Self) -> Self where T : fn (u: &U) -> V { + let out: List = Zero[Self].zero() + for f in self { // f is &fn (u: &U) -> V + for u in us { // u is &&u + out << f(*u) // syntax sugar for out.add((*f)(*u)) + } + } + out + } + + fn flatMap(f: fn (t: &T) -> Self) -> Self { + let out: List = Zero[Self].zero() + for t in self { // t is &T + for u in Into[Self, Iterator].into(f(t)) { // u is U + out << u // out take ownership of u + } + } + out + } + } +} + +pub class LinkedList : List { + + // inner classes are static by default + class Node(data: T) { + mut next: Option> + } + + mut fld head: Option> + + impl mut fn add(t: T) -> Void { + match head { + None => { + head = Some(Node(t)) + } + Some => { + let mut current: &Option> = &head + while let Some(next) = ¤t?.next { // get a reference to current.next, or None if either are none + current = next + } + current.next = Some(Node(t)) + } + } + } + + impl ref fn get(index: Int) -> Option<&T> { + let mut current: &Option> = &head + let mut currentIndex = 0 + while currentIndex < index { + if let Some(next) = ¤t?.next { + currentIndex++ + current = next + } else { + break + } + } + current is Some(node) ? Some(&node.data) : None + } + + impl ref fn iter() -> Iterator<&T> { + let mut current: &Option> = &head + () -> { + if let Some(node) = current { + let data: &T = &node.data + current = &node.next + Some(data) + } else { + None + } + } + } + + impl Into[Self, Iterator] { + cons fn into() -> Iterator { + let mut current: Option> = take(head) // take replaces head with None + return () -> { + let result = current + current = current?.next + result is Some(node) ? Some(node.data) : None + } + } + } + + impl Zero[Self] { + static fn zero() -> Self = LinkedList() + } +} + +fn optionTListDemo() { + let oddsOnly: List> = [ + Some(1), + None, + Some(3), + None, + Some(5) + ] + + let liftedOddsOnly: OptionT[]<> = OptionT(oddsOnly) + + let mapped = liftedOddsOnly + .flatMapInner { [Some(it), Some(it + 1)] } + .map { "Value: ${it.toString()}" } + + for o in mapped.intoInner() { + if o is Some(s) { + println s + } + } +} diff --git a/sketching/april_2025/string.dm b/sketching/april_2025/string.dm new file mode 100644 index 0000000..9a8fab1 --- /dev/null +++ b/sketching/april_2025/string.dm @@ -0,0 +1,125 @@ +pub int Copy { + fn copy() -> Self +} + +pub int Display { + fn toString() -> String +} + +pub int LazyDisplay { + ref fn toLazyString() -> DString +} + +decl pub class String : Copy, Display { + decl impl fn toString() -> String + decl impl fn copy() -> Self +} + +pub int DString : Display { + parts: Array +} + +pub enum DStringPart { + Constant(String), + Display(Display), + ReadClosure(ReadClosure), + ReadRefClosure(ReadRefClosure) +} + +#internal +class DStringImpl(parts) : DString { + mut fld cachedEval: Option + mut fld canCache: Option + + fld cachedReads: Map<&ReadClosure ref self, String> = HashMap() + + fn calcCanCache() -> Boolean { + for part in parts { + if part is ReadRefClosure { + return false + } + } + true + } + + impl Display { + fn toString() -> String { + if cachedEval is Some(s) { + return s + } + if canCache is None { + canCache = Some(calcCanCache()) + } + let canCache = canCache.unwrap() + if canCache { + cachedEval = Some(parts.reduce('') { acc, part -> + acc + match part { + Constant(s) => s, + Display(d) => d, + ReadClosure(c) => c(), + ReadRefClosure(c) => throw DumbProgrammerException( + 'Cannot cache when parts contains a ReadRefClosure' + ) + } + }) + return cachedEval.unwrap() + } else { + return parts.reduce('') { acc, part -> + acc + match part { + Constant(s) => s, + Display(d) => d, + ReadClosure(c) => { + if let Some(s) = cachedReads.get(&c) { + s + } else { + let s = c() + cachedReads.put(&c, s) + s + } + }, + ReadRefClosure(c) => c() + } + } + } + } + } +} + +class Foo(mut bar: Bar) {} + +class Bar(mut y: Int) {} + +fn stringDemo() { + let plain: String = 'Hello, World!' + let x = 42 + let withDisplay: String = "Hello! x is $x". + let withReadClosure: String = "Hello! x is ${ x + 42 }". + let mut foo = Foo(Bar(16)) + let withReadRefClosure: String = "Hello! y is ${ foo.bar.y }" // "Hello!, y is 16" + let lazyDString = `Lazy: y is ${ -> foo.bar.y }` // type is DString, not auto String + let eval0: String = lazyDString.toString() // Lazy: y is 16 + foo.bar.y = 64 + let eval1: String = lazyDString.toString() // Lazy: y is 64 +} + +fn badDStringClosures() { + let x = 16 + let c0 = { + x // Int implements copy, so nothing consumed + } // ReadClosure + let foo = Foo(Bar(8)) + let c1 = { + foo.bar.y + } // Read closure + let c2_e0 = { + foo.bar + } // Error! foo.bar cannot be moved out + let c2_e1 = { + &foo.bar + } // Error! reference to foo.bar moved out; closure must declared ref + let c2 = { + &foo.bar + } ref foo // OK, captures foo and return value is tied to lifetime of foo + drop(foo) + c2() // Error! c2 outlives foo +}