From 01330447ef8623a582d4fc0fd56860ccf69ea1f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonathan=20Brachtha=CC=88user?= Date: Thu, 12 Sep 2024 11:24:01 +0200 Subject: [PATCH 1/2] Type parameters bound by singleton operations are now universal, not existential --- .../shared/src/main/scala/effekt/Namer.scala | 7 +- .../main/scala/effekt/RecursiveDescent.scala | 12 ++-- .../shared/src/main/scala/effekt/Typer.scala | 2 +- .../main/scala/effekt/core/Transformer.scala | 2 +- .../src/main/scala/effekt/source/Tree.scala | 2 +- .../src/main/scala/effekt/symbols/Scope.scala | 18 ++++- examples/casestudies/buildsystem.effekt.md | 56 ++++++++-------- examples/casestudies/lexer.effekt.md | 4 +- examples/casestudies/parser.effekt.md | 2 +- examples/llvm/choice.effekt | 16 ++--- examples/llvm/triples.effekt | 19 +++--- examples/neg/effect_not_part.effekt | 2 +- examples/neg/namer/issue281/issue352.effekt | 3 - examples/neg/namer/shadowing-types.effekt | 10 +-- examples/neg/unbound_type.check | 6 +- examples/neg/unbound_type.effekt | 4 +- examples/pos/build.effekt | 44 ++++++------- examples/pos/issue262.check | 1 + examples/pos/issue262.effekt | 6 ++ examples/pos/multieffects.effekt | 6 +- examples/pos/nim.effekt | 14 ++-- examples/pos/parser.effekt | 48 +++++++------- examples/pos/probabilistic.effekt | 66 +++++++++---------- examples/pos/simpleparser.effekt | 48 +++++++------- examples/pos/triples.effekt | 16 ++--- examples/pos/withstatement.effekt | 6 +- 26 files changed, 217 insertions(+), 203 deletions(-) delete mode 100644 examples/neg/namer/issue281/issue352.effekt create mode 100644 examples/pos/issue262.check create mode 100644 examples/pos/issue262.effekt diff --git a/effekt/shared/src/main/scala/effekt/Namer.scala b/effekt/shared/src/main/scala/effekt/Namer.scala index 65d6335ec..3c1873d76 100644 --- a/effekt/shared/src/main/scala/effekt/Namer.scala +++ b/effekt/shared/src/main/scala/effekt/Namer.scala @@ -132,7 +132,7 @@ object Namer extends Phase[Parsed, NameResolved] { } Context.define(id, sym) - case source.InterfaceDef(id, tparams, ops, isEffect) => + case source.InterfaceDef(id, tparams, ops) => val effectName = Context.nameFor(id) // we use the localName for effects, since they will be bound as capabilities val effectSym = Context scoped { @@ -315,7 +315,7 @@ object Namer extends Phase[Parsed, NameResolved] { } } - case source.InterfaceDef(id, tparams, operations, isEffect) => + case source.InterfaceDef(id, tparams, operations) => // symbol has already been introduced by the previous traversal val interface = Context.symbolOf(id).asInterface interface.operations = operations.map { @@ -345,7 +345,6 @@ object Namer extends Phase[Parsed, NameResolved] { } } } - if (isEffect) interface.operations.foreach { op => Context.bind(op) } case source.NamespaceDef(id, definitions) => Context.namespace(id.name) { @@ -840,7 +839,7 @@ trait NamerOps extends ContextOps { Context: Context => */ private[namer] def resolveMethodCalltarget(id: IdRef): Unit = at(id) { - val syms = scope.lookupOverloaded(id, term => term.isInstanceOf[BlockSymbol]) + val syms = scope.lookupOverloadedMethod(id, term => term.isInstanceOf[BlockSymbol]) if (syms.isEmpty) { abort(pretty"Cannot resolve function ${id.name}") diff --git a/effekt/shared/src/main/scala/effekt/RecursiveDescent.scala b/effekt/shared/src/main/scala/effekt/RecursiveDescent.scala index 6a56ab7d9..702a3c031 100644 --- a/effekt/shared/src/main/scala/effekt/RecursiveDescent.scala +++ b/effekt/shared/src/main/scala/effekt/RecursiveDescent.scala @@ -452,9 +452,9 @@ class RecursiveDescent(positions: Positions, tokens: Seq[Token], source: Source) def operationDef(): Def = nonterminal: `effect` ~> operation() match { - case op => + case op @ Operation(id, tps, vps, bps, ret) => // TODO is the `true` flag used at all anymore??? - InterfaceDef(IdDef(op.id.name), Nil, List(op), true) + InterfaceDef(IdDef(id.name), tps, List(Operation(id, Nil, vps, bps, ret) withPositionOf op)) } def operation(): Operation = @@ -465,7 +465,7 @@ class RecursiveDescent(positions: Positions, tokens: Seq[Token], source: Source) def interfaceDef(): InterfaceDef = nonterminal: - InterfaceDef(`interface` ~> idDef(), maybeTypeParams(), `{` ~> manyWhile(`def` ~> operation(), `def`) <~ `}`, true) + InterfaceDef(`interface` ~> idDef(), maybeTypeParams(), `{` ~> manyWhile(`def` ~> operation(), `def`) <~ `}`) def namespaceDef(): Def = nonterminal: @@ -697,11 +697,11 @@ class RecursiveDescent(positions: Positions, tokens: Seq[Token], source: Source) // Interface[...] { () => ... } // Interface[...] { case ... => ... } - def operationImplementation() = idRef() ~ maybeTypeParams() ~ implicitResume ~ functionArg() match { + def operationImplementation() = idRef() ~ maybeTypeArgs() ~ implicitResume ~ functionArg() match { case (id ~ tps ~ k ~ BlockLiteral(_, vps, bps, body)) => val synthesizedId = IdRef(Nil, id.name).withPositionOf(id) - val interface = BlockTypeRef(id, Nil).withPositionOf(id): BlockTypeRef - val operation = OpClause(synthesizedId, tps, vps, bps, None, body, k).withRangeOf(id, body) + val interface = BlockTypeRef(id, tps).withPositionOf(id): BlockTypeRef + val operation = OpClause(synthesizedId, Nil, vps, bps, None, body, k).withRangeOf(id, body) Implementation(interface, List(operation)) } diff --git a/effekt/shared/src/main/scala/effekt/Typer.scala b/effekt/shared/src/main/scala/effekt/Typer.scala index 207922c5f..653287a04 100644 --- a/effekt/shared/src/main/scala/effekt/Typer.scala +++ b/effekt/shared/src/main/scala/effekt/Typer.scala @@ -679,7 +679,7 @@ object Typer extends Phase[NameResolved, Typechecked] { case d @ source.ExternResource(id, tpe) => Context.bind(d.symbol) - case d @ source.InterfaceDef(id, tparams, ops, isEffect) => + case d @ source.InterfaceDef(id, tparams, ops) => d.symbol.operations.foreach { op => if (op.effects.toList contains op.appliedInterface) { Context.error("Bidirectional effects that mention the same effect recursively are not (yet) supported.") diff --git a/effekt/shared/src/main/scala/effekt/core/Transformer.scala b/effekt/shared/src/main/scala/effekt/core/Transformer.scala index ba8dbbc7d..773edfa77 100644 --- a/effekt/shared/src/main/scala/effekt/core/Transformer.scala +++ b/effekt/shared/src/main/scala/effekt/core/Transformer.scala @@ -106,7 +106,7 @@ object Transformer extends Phase[Typechecked, CoreTransformed] { case _: source.VarDef | _: source.RegDef => Context.at(d) { Context.abort("Mutable variable bindings not allowed on the toplevel") } - case d @ source.InterfaceDef(id, tparamsInterface, ops, isEffect) => + case d @ source.InterfaceDef(id, tparamsInterface, ops) => val interface = d.symbol List(core.Interface(interface, interface.tparams, interface.operations.map { case op @ symbols.Operation(name, tps, vps, bps, resultType, effects, interface) => diff --git a/effekt/shared/src/main/scala/effekt/source/Tree.scala b/effekt/shared/src/main/scala/effekt/source/Tree.scala index 9e15886e0..dacffce9d 100644 --- a/effekt/shared/src/main/scala/effekt/source/Tree.scala +++ b/effekt/shared/src/main/scala/effekt/source/Tree.scala @@ -228,7 +228,7 @@ enum Def extends Definition { case NamespaceDef(id: IdDef, definitions: List[Def]) - case InterfaceDef(id: IdDef, tparams: List[Id], ops: List[Operation], isEffect: Boolean = true) + case InterfaceDef(id: IdDef, tparams: List[Id], ops: List[Operation]) case DataDef(id: IdDef, tparams: List[Id], ctors: List[Constructor]) case RecordDef(id: IdDef, tparams: List[Id], fields: List[ValueParam]) diff --git a/effekt/shared/src/main/scala/effekt/symbols/Scope.scala b/effekt/shared/src/main/scala/effekt/symbols/Scope.scala index 764357be8..6bc225ab7 100644 --- a/effekt/shared/src/main/scala/effekt/symbols/Scope.scala +++ b/effekt/shared/src/main/scala/effekt/symbols/Scope.scala @@ -48,6 +48,12 @@ class Namespace( def getNamespace(name: String): Namespace = namespaces.getOrElseUpdate(name, Namespace.empty) + def operations: Map[String, Set[Operation]] = + types.values.toSet.flatMap { + case BlockTypeConstructor.Interface(_, _, operations) => operations.toSet + case _ => Set.empty + }.groupMap(_.name.name)(op => op) + /** * Convert to immutable bindings */ @@ -171,10 +177,16 @@ object scopes { def lookupOverloaded(id: IdRef, filter: TermSymbol => Boolean)(using ErrorReporter): List[Set[TermSymbol]] = all(id.path, scope) { _.terms.getOrElse(id.name, Set.empty).filter(filter) } + def lookupOverloadedMethod(id: IdRef, filter: TermSymbol => Boolean)(using ErrorReporter): List[Set[TermSymbol]] = + all(id.path, scope) { namespace => + // an overloaded method can be a term... + namespace.terms.getOrElse(id.name, Set.empty).filter(filter) ++ + // or an operation + namespace.operations.getOrElse(id.name, Set.empty).filter(filter) + } + def lookupOperation(path: List[String], name: String)(using ErrorReporter): List[Set[Operation]] = - all(path, scope) { _.terms.getOrElse(name, Set.empty).collect { - case o: Operation => o - }} + all(path, scope) { _.operations.getOrElse(name, Set.empty) } // can be a term OR a type symbol def lookupFirst(path: List[String], name: String)(using E: ErrorReporter): Symbol = diff --git a/examples/casestudies/buildsystem.effekt.md b/examples/casestudies/buildsystem.effekt.md index 62f2e05a1..ac2a952b7 100644 --- a/examples/casestudies/buildsystem.effekt.md +++ b/examples/casestudies/buildsystem.effekt.md @@ -24,15 +24,15 @@ This example revolves around a single effect: `Need`. type Key = String type Val = Int -effect Need(key: Key): Val +effect need(key: Key): Val ``` -The `Need` effect operation requests the value for a specific key. In this example keys are strings and values are integers. +The `need` effect operation requests the value for a specific key. In this example keys are strings and values are integers. -A build system defines rules that specify how to build the values for each key. The values for some keys are inputs. For those we have a second effect, `NeedInput`. +A build system defines rules that specify how to build the values for each key. The values for some keys are inputs. For those we have a second effect, `needInput`. ``` -effect NeedInput(key: Key): Val +effect needInput(key: Key): Val ``` With these two effect operations, we can express the rules for the spreadsheet example from the paper. We define the rules for cells `"B1"` and `"B2"` and treats the other cells as inputs. @@ -46,22 +46,22 @@ spreadsheet: | **2** | 20 | B1 * 2 | ``` -def example1(key: Key): Val / { Need, NeedInput } = { +def example1(key: Key): Val / { need, needInput } = { println(key); - if (key == "B1") do Need("A1") + do Need("A2") - else if (key == "B2") do Need("B1") * 2 - else do NeedInput(key) + if (key == "B1") do need("A1") + do need("A2") + else if (key == "B2") do need("B1") * 2 + else do needInput(key) } ``` -This example explains how to get the value for a given key. It uses the `Need` operation when it needs the result for another key and it uses the `NeedInput` operation when it needs an input. +This example explains how to get the value for a given key. It uses the `need` operation when it needs the result for another key and it uses the `needInput` operation when it needs an input. -A build system is a handler for the `Need` effect. +A build system is a handler for the `need` effect. ``` -def build(target: Key) { tasks: Key => Val / { Need } }: Val / {} = +def build(target: Key) { tasks: Key => Val / { need } }: Val / {} = try { tasks(target) } - with Need { requestedKey => + with need { requestedKey => resume(build(requestedKey) { k => tasks(k) }) } ``` @@ -71,13 +71,13 @@ This handler function recursively calls itself when a key is needed. It duplicat Another handler would memoize keys once they are built to avoid duplication. ``` -effect KeyNotFound[A](key: Key): A +effect keyNotFound(key: Key): Nothing type Store = List[(Key,Val)] -def find(store: Store, key: Key): Val / KeyNotFound = { +def find(store: Store, key: Key): Val / keyNotFound = { store match { - case Nil() => do KeyNotFound(key) + case Nil() => do keyNotFound(key) case Cons((k, v), xs) => if (k == key) { v } else { find(xs, key) } } } @@ -85,18 +85,18 @@ def find(store: Store, key: Key): Val / KeyNotFound = { For this we need a store, which we represent as a list of pairs of keys and values. -The `memo` handler function tries to look up the needed key in the store. If the key is found it returns the associated value. Otherwise it itself uses `Need`, stores the result, and returns the value. +The `memo` handler function tries to look up the needed key in the store. If the key is found it returns the associated value. Otherwise it itself uses `need`, stores the result, and returns the value. ``` -def memo[R] { prog: => R / { Need } }: R / { Need } = { +def memo[R] { prog: => R / { need } }: R / { need } = { var store: Store = Nil(); try { prog() - } with Need { (key) => + } with need { (key) => try { resume(find(store, key)) - } with KeyNotFound[A] { (k) => - val v = do Need(k); + } with keyNotFound { (k) => + val v = do need(k); store = Cons((k, v), store); resume(v) } @@ -107,22 +107,22 @@ def memo[R] { prog: => R / { Need } }: R / { Need } = { A second example needs the same key twice. ``` -// Needing the same key twice +// needing the same key twice def example2(key: Key) = { println(key); - if (key == "B1") do Need("A1") + do Need("A2") - else if (key == "B2") do Need("B1") * do Need("B1") - else do NeedInput(key) + if (key == "B1") do need("A1") + do need("A2") + else if (key == "B2") do need("B1") * do need("B1") + else do needInput(key) } ``` When we run this example without memoization we will see `"B1"`, `"A1"`, and `"A2"` printed twice. When we add the `memo` handler function we do not as the result is reused. -Finally, to supply the inputs, we have a handler for the `NeedInput` effect. +Finally, to supply the inputs, we have a handler for the `needInput` effect. ``` -def supplyInput[R](store: Store) { prog: => R / { NeedInput } }: R / { KeyNotFound } = { - try { prog() } with NeedInput { (key) => resume(find(store, key)) } +def supplyInput[R](store: Store) { prog: => R / { needInput } }: R / { keyNotFound } = { + try { prog() } with needInput { (key) => resume(find(store, key)) } } ``` @@ -140,6 +140,6 @@ def main() = { println(""); val result3 = supplyInput(inputs) { build ("B2") { (key) => memo { example2(key) } } }; println(result3) - } with KeyNotFound[A] { (key) => println("Key not found: " ++ key) } + } with keyNotFound { (key) => println("Key not found: " ++ key) } } ``` diff --git a/examples/casestudies/lexer.effekt.md b/examples/casestudies/lexer.effekt.md index 6cd66c76b..2b67cc624 100644 --- a/examples/casestudies/lexer.effekt.md +++ b/examples/casestudies/lexer.effekt.md @@ -73,7 +73,7 @@ def example1() = { ## Handling the Lexer Effect with a given List A dummy lexer reading lexemes from a given list can be implemented as a handler for the `Lexer` effect. The definition uses the effect `LexerError` to signal the end of the input stream: ``` -effect LexerError[A](msg: String, pos: Position): A +effect LexerError(msg: String, pos: Position): Nothing val dummyPosition = Position(0, 0, 0) def lexerFromList[R](l: List[Token]) { program: => R / Lexer }: R / LexerError = { @@ -93,7 +93,7 @@ def lexerFromList[R](l: List[Token]) { program: => R / Lexer }: R / LexerError = We define a separate handler to report lexer errors to the console: ``` def report { prog: => Unit / LexerError }: Unit = - try { prog() } with LexerError[A] { (msg, pos) => + try { prog() } with LexerError { (msg, pos) => println(pos.line.show ++ ":" ++ pos.col.show ++ " " ++ msg) } ``` diff --git a/examples/casestudies/parser.effekt.md b/examples/casestudies/parser.effekt.md index 8a3a37b7a..08d857ae4 100644 --- a/examples/casestudies/parser.effekt.md +++ b/examples/casestudies/parser.effekt.md @@ -206,7 +206,7 @@ def parse[R](input: String) { p: => R / Parser }: ParseResult[R] = try { case Success(res) => Success(res) } def fail[A](msg) = Failure(msg) -} with LexerError[A] { (msg, pos) => +} with LexerError { (msg, pos) => Failure(msg) } ``` diff --git a/examples/llvm/choice.effekt b/examples/llvm/choice.effekt index 8cb9ceaf4..f7172df2e 100644 --- a/examples/llvm/choice.effekt +++ b/examples/llvm/choice.effekt @@ -1,22 +1,22 @@ -effect Flip(): Bool -effect Fail[A](): A +effect flip(): Bool +effect fail(): Nothing -def choice(n : Int): Int / { Flip, Fail } = +def choice(n: Int): Int / { flip, fail } = if (n < 1) { - do Fail() - } else if (do Flip()) { + do fail() + } else if (do flip()) { n } else { choice(n - 1) } -def handledTriples(n : Int, s : Int) = +def handledTriples(n: Int, s: Int) = try { try { println(choice(1)) - } with Fail[A] { () } - } with Flip { resume(true); resume(false) } + } with fail { () } + } with flip { resume(true); resume(false) } def main() = handledTriples(6, 6) diff --git a/examples/llvm/triples.effekt b/examples/llvm/triples.effekt index 931c82d99..40aeebe00 100644 --- a/examples/llvm/triples.effekt +++ b/examples/llvm/triples.effekt @@ -1,13 +1,13 @@ -effect Flip(): Bool -effect Fail[A](): A +effect flip(): Bool +effect fail(): Nothing record Triple(a: Int, b: Int, c: Int) -def choice(n : Int): Int / { Flip, Fail } = +def choice(n: Int): Int / { flip, fail } = if (n < 1) { - do Fail() - } else if (do Flip()) { + do fail() + } else if (do flip()) { n } else { choice(n - 1) @@ -20,17 +20,16 @@ def triples(n: Int, s: Int) = { if ((i + j + k) == s) { Triple(i, j, k) } else { - do Fail() + do fail() } } -def handledTriples(n : Int, s : Int) = +def handledTriples(n: Int, s: Int) = try { try { val t = triples(n, s); println(t.a); println(t.b); println(t.c) - } with Fail[A] { () } - } with Flip { resume(true); resume(false) } + } with fail { () } + } with flip { resume(true); resume(false) } def main() = handledTriples(100, 15) - diff --git a/examples/neg/effect_not_part.effekt b/examples/neg/effect_not_part.effekt index 46e3b91aa..fdeca84f9 100644 --- a/examples/neg/effect_not_part.effekt +++ b/examples/neg/effect_not_part.effekt @@ -1,6 +1,6 @@ module examples/pos/effect_not_part -effect Exception[A](msg: String): A +effect Exception(msg: String): Nothing // TODO If we move `State` below `state` then it is not resolved interface State { diff --git a/examples/neg/namer/issue281/issue352.effekt b/examples/neg/namer/issue281/issue352.effekt deleted file mode 100644 index 1091c71f6..000000000 --- a/examples/neg/namer/issue281/issue352.effekt +++ /dev/null @@ -1,3 +0,0 @@ - -effect Emit[A](x: A): Unit -def emit[A](x: A): Unit / Emit[A] = do Emit[A](x) // ERROR Wrong number of type arguments diff --git a/examples/neg/namer/shadowing-types.effekt b/examples/neg/namer/shadowing-types.effekt index 06f3a43f6..0a648bb2a 100644 --- a/examples/neg/namer/shadowing-types.effekt +++ b/examples/neg/namer/shadowing-types.effekt @@ -8,10 +8,10 @@ def bar = new Bar { def op[Int](n) = () // WARN shadows } -def main(): Int = - try { +def main(): Unit = + try { // WARN not to be used //... - 0 - } with Foo[Int] { // WARN shadows - (x: Int) => 12 + () + } with Foo[Int] { + (x: Int) => () } diff --git a/examples/neg/unbound_type.check b/examples/neg/unbound_type.check index 777afa0b7..a7d724b24 100644 --- a/examples/neg/unbound_type.check +++ b/examples/neg/unbound_type.check @@ -1,3 +1,3 @@ -[error] examples/neg/unbound_type.effekt:7:40: Could not resolve type Position -effect LexerError[A](msg: String, pos: Position): A - ^^^^^^^^ +[error] examples/neg/unbound_type.effekt:7:37: Could not resolve type Position +effect LexerError(msg: String, pos: Position): Nothing + ^^^^^^^^ diff --git a/examples/neg/unbound_type.effekt b/examples/neg/unbound_type.effekt index f6252fc65..160767fb5 100644 --- a/examples/neg/unbound_type.effekt +++ b/examples/neg/unbound_type.effekt @@ -3,8 +3,8 @@ module examples/pos/lexer import string import text/regex -effect EOS[A](): A -effect LexerError[A](msg: String, pos: Position): A +effect EOS(): Nothing +effect LexerError(msg: String, pos: Position): Nothing interface Lexer { def peek(): Option[Token] diff --git a/examples/pos/build.effekt b/examples/pos/build.effekt index 7de6d4180..84dcac7f7 100644 --- a/examples/pos/build.effekt +++ b/examples/pos/build.effekt @@ -3,43 +3,43 @@ module examples/pos/build type Key = String type Val = Int -effect Need(key: Key): Val -effect NeedInput(key: Key): Val -effect KeyNotFound[A](key: Key): A +effect need(key: Key): Val +effect needInput(key: Key): Val +effect keyNotFound(key: Key): Nothing type Store = List[(Key,Val)] -def find(store: Store, key: Key): Val / KeyNotFound = { +def find(store: Store, key: Key): Val / keyNotFound = { store match { - case Nil() => do KeyNotFound(key) + case Nil() => do keyNotFound(key) case Cons((k, v), xs) => if (k == key) { v } else { find(xs, key) } } } -def build(target: Key) { tasks: Key => Val / { Need } }: Val / {} = +def build(target: Key) { tasks: Key => Val / { need } }: Val / {} = try { tasks(target) } - with Need { requestedKey => + with need { requestedKey => resume(build(requestedKey) { k => tasks(k) }) } // effect transformer "memo" -def memo[R] { prog: => R / { Need } } = { +def memo[R] { prog: => R / { need } } = { var store: Store = Nil(); try { prog() - } with Need { (key) => + } with need { (key) => try { resume(find(store, key)) - } with KeyNotFound[A] { (k) => - val v = do Need(k); + } with keyNotFound { (k) => + val v = do need(k); store = Cons((k, v), store); resume(v) } } } -def supplyInput[R](store: Store) { prog: => R / { NeedInput } } = { - try { prog() } with NeedInput { (key) => resume(find(store, key)) } +def supplyInput[R](store: Store) { prog: => R / { needInput } } = { + try { prog() } with needInput { (key) => resume(find(store, key)) } } @@ -48,19 +48,19 @@ def supplyInput[R](store: Store) { prog: => R / { NeedInput } } = { // | A | B // 1| 10 | A1 + A2 // 2| 20 | B1 * 2 -def example1(key: Key): Val / { Need, NeedInput } = { +def example1(key: Key): Val / { need, needInput } = { println(key); - if (key == "B1") do Need("A1") + do Need("A2") - else if (key == "B2") do Need("B1") * 2 - else do NeedInput(key) + if (key == "B1") do need("A1") + do need("A2") + else if (key == "B2") do need("B1") * 2 + else do needInput(key) } -// Needing the same key twice +// needing the same key twice def example2(key: Key) = { println(key); - if (key == "B1") do Need("A1") + do Need("A2") - else if (key == "B2") do Need("B1") * do Need("B1") - else do NeedInput(key) + if (key == "B1") do need("A1") + do need("A2") + else if (key == "B2") do need("B1") * do need("B1") + else do needInput(key) } def main() = { @@ -74,5 +74,5 @@ def main() = { println(""); val result3 = supplyInput(inputs) { build ("B2") { (key) => memo { example2(key) } } }; println(result3) - } with KeyNotFound[A] { (key) => println("Key not found: " ++ key) } + } with keyNotFound { (key) => println("Key not found: " ++ key) } } diff --git a/examples/pos/issue262.check b/examples/pos/issue262.check new file mode 100644 index 000000000..920a13966 --- /dev/null +++ b/examples/pos/issue262.check @@ -0,0 +1 @@ +43 diff --git a/examples/pos/issue262.effekt b/examples/pos/issue262.effekt new file mode 100644 index 000000000..2af455826 --- /dev/null +++ b/examples/pos/issue262.effekt @@ -0,0 +1,6 @@ +effect apply[A, B](a: A): B + +def main() = { + def f = new apply[Int, Int] { a => a + 1 }; + println(f.apply(42)) +} \ No newline at end of file diff --git a/examples/pos/multieffects.effekt b/examples/pos/multieffects.effekt index b4a2e61a6..5293033b6 100644 --- a/examples/pos/multieffects.effekt +++ b/examples/pos/multieffects.effekt @@ -12,7 +12,7 @@ interface Exc { def raise[A](): A } -effect Exception[A](msg: String): A +effect raise(msg: String): Nothing // TODO If we move `State` below `state` then it is not resolved interface State { @@ -20,12 +20,12 @@ interface State { def set(n: Int): Unit } -def state[R](init: Int) { f: => R / { State, Exception } }: Unit = { +def state[R](init: Int) { f: => R / { State, raise } }: Unit = { var s = init; try { f(); () } with State { def get() = resume(s) def set(n) = { s = n; resume(()) } - } with Exception[A] { msg => println(msg) } + } with raise { msg => println(msg) } } diff --git a/examples/pos/nim.effekt b/examples/pos/nim.effekt index 1736cb4ef..fd239fb1e 100644 --- a/examples/pos/nim.effekt +++ b/examples/pos/nim.effekt @@ -65,21 +65,21 @@ def gametree { prog: => Player / Move } = try { Winner(prog()) } with Move { (p, // ---------------- // Cheating // ---------------- -effect Cheat[A](p: Player): A -effect Error[A](msg: String): A +effect cheat(p: Player): Nothing +effect error(msg: String): Nothing -def printError { prog: => Unit / Error } = try { prog() } with Error[A] { (msg) => +def printError { prog: => Unit / error } = try { prog() } with error { (msg) => println(msg) } -def cheatReport[R] { prog: => R / Cheat }: R / Error = - try { prog() } with Cheat[A] { (p) => - do Error(genericShow(p) ++ " cheated!") +def cheatReport[R] { prog: => R / cheat }: R / error = + try { prog() } with cheat { (p) => + do error(genericShow(p) ++ " cheated!") } def check { prog: => Player / Move } = try { prog() } with Move { (p, n) => val m = do Move(p, n); - if (m <= 3) { resume(m) } else { do Cheat(p) } + if (m <= 3) { resume(m) } else { do cheat(p) } } def pc { prog: => Player / Move } = try { prog() } with Move { (p, n) => diff --git a/examples/pos/parser.effekt b/examples/pos/parser.effekt index 9df3f3b93..2d7dc1ec5 100644 --- a/examples/pos/parser.effekt +++ b/examples/pos/parser.effekt @@ -1,9 +1,9 @@ module parser -effect Flip(): Bool -effect Next(): String -effect Fail[A](msg: String): A -effect Error[A](msg: String): A +effect flip(): Bool +effect next(): String +effect fail(msg: String): Nothing +effect error(msg: String): Nothing type Stream { Cons(el: String, rest: Stream); @@ -22,32 +22,32 @@ type ParseResult[R] { } def accept(exp: String) = { - val got = do Next(); + val got = do next(); if (got == exp) { got } else { - do Fail("Expected " ++ exp ++ " but got " ++ got) + do fail("Expected " ++ exp ++ " but got " ++ got) } } def or[R] { p: => R } { q: => R } = - if (do Flip()) { p() } else { q() } + if (do flip()) { p() } else { q() } -def asOrB(): Int / { Flip, Next, Fail } = +def asOrB(): Int / { flip, next, fail } = or { accept("a"); asOrB() + 1 } { accept("b"); 0 } -def parens(): Int / { Flip, Next, Fail } = +def parens(): Int / { flip, next, fail } = or { accept("()"); 0 } { accept("("); val res = parens(); accept(")"); res + 1 } // Int = Result -def reader[R](s: Stream) { p : => R / Next } : R / Fail = { +def reader[R](s: Stream) { p : => R / next } : R / fail = { var inn = s; try{ p() - } with Next { () => inn match { + } with next { () => inn match { case Nil() => - do Fail("Unexpected Nil") + do fail("Unexpected Nil") case Cons(el, rest) => inn = rest; resume(el) @@ -55,34 +55,34 @@ def reader[R](s: Stream) { p : => R / Next } : R / Fail = { } } -def eager[R] { p: => R / { Flip, Fail, Error } }: ParseResult[R] = try { +def eager[R] { p: => R / { flip, fail, error } }: ParseResult[R] = try { Success(p()) -} with Flip { () => +} with flip { () => resume(true) match { case Failure(msg) => resume(false) case Success(res) => Success(res) case ParseError(msg) => ParseError(msg) } -} with Fail[A] { (msg) => +} with fail { (msg) => Failure(msg) -} with Error[A] { (msg) => +} with error { (msg) => ParseError(msg) } -def commit[R] { p : => R / Fail } : R / Error = - try { p() } with Fail[A] { (msg) => - do Error(msg) +def commit[R] { p : => R / fail } : R / error = + try { p() } with fail { (msg) => + do error(msg) } -def nocut[R] { p: => R / Error } : R / Fail = - try { p() } with Error[A] { (msg) => - do Fail(msg) +def nocut[R] { p: => R / error } : R / fail = + try { p() } with error { (msg) => + do fail(msg) } -def opt[R] { p: => R }: Option[R] / Flip = +def opt[R] { p: => R }: Option[R] / flip = or { Some(p()) } { None() } -def parse[R](s: Stream) { p : => R / { Flip, Next, Fail, Error } } = +def parse[R](s: Stream) { p : => R / { flip, next, fail, error } } = eager { reader(s) { p() diff --git a/examples/pos/probabilistic.effekt b/examples/pos/probabilistic.effekt index 40b784a39..63207005a 100644 --- a/examples/pos/probabilistic.effekt +++ b/examples/pos/probabilistic.effekt @@ -1,6 +1,6 @@ module probabilistic -effect IndexOutOfBounds[A](): A +effect indexOutOfBounds(): Nothing type List[A] { Nil(); @@ -12,13 +12,13 @@ def concat[A](l1: List[A], l2: List[A]): List[A] = l1 match { case Cons(a, rest) => concat(rest, Cons(a, l2)) } -def lookup[A](l: List[A], idx: Int): A / IndexOutOfBounds = l match { - case Nil() => do IndexOutOfBounds() +def lookup[A](l: List[A], idx: Int): A / indexOutOfBounds = l match { + case Nil() => do indexOutOfBounds() case Cons(n, rest) => if (idx == 0) n else lookup(rest, idx - 1) } -def updateAt[A](l: List[A], idx: Int, el: A): List[A] / IndexOutOfBounds = l match { - case Nil() => do IndexOutOfBounds() +def updateAt[A](l: List[A], idx: Int, el: A): List[A] / indexOutOfBounds = l match { + case Nil() => do indexOutOfBounds() case Cons(n, rest) => if (idx == 0) { Cons(el, rest) } else Cons(n, updateAt(rest, idx - 1, el)) } @@ -61,50 +61,50 @@ def heap[R] { prog: => R / Heap } = { } } -effect Flip(prob: Double): Ref -effect Disj(x: Ref, y: Ref): Ref -effect Prior(x: Ref, prob: Double): Unit +effect flip(prob: Double): Ref +effect disj(x: Ref, y: Ref): Ref +effect prior(x: Ref, prob: Double): Unit -effect Score(prob: Double): Unit -effect Fork(): Bool -effect Fail[A](): A +effect score(prob: Double): Unit +effect fork(): Bool +effect fail(): Nothing def obs(r: Ref, a: Bool) = do get(r) match { case Unobserved() => do put(r, Observed(a)) - case Observed(y) => if (a == y) { () } else { do Fail() } + case Observed(y) => if (a == y) { () } else { do fail() } } -def choose3[R] { x: => R } { y: => R } { z: => R } : R / Fork = - if (do Fork()) { x() } else if (do Fork()) { y() } else { z() } +def choose3[R] { x: => R } { y: => R } { z: => R } : R / fork = + if (do fork()) { x() } else if (do fork()) { y() } else { z() } type Weighted[R] { MkWeighted(weight: Double, value: R) } -def handleProb[R] { prog: => R / { Score, Fork, Fail } } = { +def handleProb[R] { prog: => R / { score, fork, fail } } = { val empty: List[Weighted[R]] = Nil(); try { var current = 1.0; try { val res = prog(); Cons(MkWeighted(current, res), empty) - } with Score { p => current = current * p; resume(()) } - } with Fork { () => concat(resume(true), resume(false)) } - with Fail[A] { () => empty } + } with score { p => current = current * p; resume(()) } + } with fork { () => concat(resume(true), resume(false)) } + with fail { () => empty } } def fresh(): Ref / Heap = do empty(Unobserved()) -def handleLang(expected: Bool) { prog: Ref => Ref / { Flip, Disj, Prior }}: Var / {IndexOutOfBounds, Score, Fail, Fork} = heap { +def handleLang(expected: Bool) { prog: Ref => Ref / { flip, disj, prior }}: Var / {indexOutOfBounds, score, fail, fork} = heap { val input = fresh(); try { do put(prog(input), Observed(expected)) } - with Flip { p => + with flip { p => val res: Ref = fresh(); resume(res); do get(res) match { case Unobserved() => () - case Observed(b) => if (b) { do Score(p) } else { do Score(1.0 - p) } + case Observed(b) => if (b) { do score(p) } else { do score(1.0 - p) } } - } with Disj { (x, y) => + } with disj { (x, y) => val res: Ref = fresh(); resume(res); do get(res) match { @@ -117,27 +117,27 @@ def handleLang(expected: Bool) { prog: Ref => Ref / { Flip, Disj, Prior }}: Var obs(x, true); obs(y, true) } } else { obs(x, false); obs(y, false) } } - } with Prior { (x, p) => + } with prior { (x, p) => resume(()); do get(x) match { case Unobserved() => () - case Observed(b) => if (b) { do Score(p) } else { do Score(1.0 - p) } + case Observed(b) => if (b) { do score(p) } else { do score(1.0 - p) } } }; do get(input) } def test() = { - if (do Fork()) { - do Score(0.2); + if (do fork()) { + do score(0.2); false } else { - do Score(0.8); - if (do Fork()) { - do Score(0.6); + do score(0.8); + if (do fork()) { + do score(0.6); true } else { - do Score(0.4); + do score(0.4); false } } @@ -159,9 +159,9 @@ def heapTest() = heap { println(show(do get(r1)) ++ show(do get(r2)) ++ show(do get(r3))) } -def catch[R] { p: => R / IndexOutOfBounds }: Unit = +def catch[R] { p: => R / indexOutOfBounds }: Unit = try { p(); () } - with IndexOutOfBounds[A] { () => println("Index out of bounds!") } + with indexOutOfBounds { () => println("Index out of bounds!") } def main() = { val res = handleProb { @@ -176,7 +176,7 @@ def main() = { catch { val res = handleProb { handleLang(true) { r => - do Disj(do Flip(0.2), r) + do disj(do flip(0.2), r) } }; inspect(res) diff --git a/examples/pos/simpleparser.effekt b/examples/pos/simpleparser.effekt index 79eb1380e..09d7ef5c7 100644 --- a/examples/pos/simpleparser.effekt +++ b/examples/pos/simpleparser.effekt @@ -3,48 +3,48 @@ module examples/pos/simpleparser import string import text/regex -effect Fail[A](msg: String) : A +effect fail(msg: String): Nothing -def stringToInt(str: String): Int / { Fail } = { - with default[WrongFormat, Int] { do Fail("cannot convert input to integer") }; +def stringToInt(str: String): Int / { fail } = { + with default[WrongFormat, Int] { do fail("cannot convert input to integer") }; str.toInt } -def perhapsAdd(): Int / { Fail } = stringToInt("1") + stringToInt("2") +def perhapsAdd(): Int / { fail } = stringToInt("1") + stringToInt("2") -def handledExample() = try { perhapsAdd() } with Fail[A] { (msg) => println(msg); 0 } +def handledExample() = try { perhapsAdd() } with fail { (msg) => println(msg); 0 } -effect Next(): String +effect next(): String -def print3(): Unit / { Next } = { - println(do Next()); - println(do Next()); - println(do Next()) +def print3(): Unit / { next } = { + println(do next()); + println(do next()); + println(do next()) } -def alwaysHello[R] { prog: () => R / Next }: R / {} = - try { prog() } with Next { () => resume("hello") } +def alwaysHello[R] { prog: () => R / next }: R / {} = + try { prog() } with next { () => resume("hello") } -def number() : Int / { Next, Fail } = - stringToInt(do Next()) +def number() : Int / { next, fail } = + stringToInt(do next()) -def feed[R](input: List[String]) { prog: () => R / Next } : R / Fail = { +def feed[R](input: List[String]) { prog: () => R / next } : R / fail = { var remaining = input; - try { prog() } with Next { () => + try { prog() } with next { () => remaining match { - case Nil() => do Fail("End of input") + case Nil() => do fail("End of input") case Cons(element, rest) => remaining = rest; resume(element) } } } -effect Flip(): Bool +effect flip(): Bool -def many[R] { prog: () => R }: List[R] / Flip = { +def many[R] { prog: () => R }: List[R] / flip = { var result: List[R] = Nil(); - while (do Flip()) { + while (do flip()) { result = Cons(prog(), result) }; reverse(result) @@ -55,12 +55,12 @@ type Result[R] { Failure(msg: String) } -def backtrack[R] { prog: () => R / { Fail, Flip } }: Result[R] / {} = +def backtrack[R] { prog: () => R / { fail, flip } }: Result[R] / {} = try { Success(prog()) - } with Fail[A] { (msg) => + } with fail { (msg) => Failure(msg) - } with Flip { () => + } with flip { () => resume(true) match { case Failure(msg) => resume(false) case Success(res) => Success(res) @@ -68,7 +68,7 @@ def backtrack[R] { prog: () => R / { Fail, Flip } }: Result[R] / {} = } -effect Parser = { Fail, Flip, Next } +effect Parser = { fail, flip, next } def manyNumbers() : List[Int] / Parser = many { number() } diff --git a/examples/pos/triples.effekt b/examples/pos/triples.effekt index d9ad8b320..13b63d6ca 100644 --- a/examples/pos/triples.effekt +++ b/examples/pos/triples.effekt @@ -1,7 +1,7 @@ module triples -effect Flip(): Bool -effect Fail[A](): A +effect flip(): Bool +effect fail(): Nothing type Triple { MkTriple(x: Int, y: Int, z: Int) @@ -16,10 +16,10 @@ def concat[A](l1: List[A], l2: List[A]): List[A] = l1 match { case Cons(a, rest) => Cons(a, concat(rest, l2)) } -def choice(n : Int): Int / { Flip, Fail } = +def choice(n : Int): Int / { flip, fail } = if (n < 1) { - do Fail() - } else if (do Flip()) { + do fail() + } else if (do flip()) { n } else { choice(n - 1) @@ -32,7 +32,7 @@ def triple(n: Int, s: Int) = { if ((i + j + k) == s) { MkTriple(i, j ,k) } else { - do Fail[Triple]() + do fail() } } @@ -43,7 +43,7 @@ def handledTriple(n : Int, s : Int) = // what if a local function closes over some capabilities and receives a few others? // what if that function is used under a handler? try { Cons(triple(n, s), Nil[Triple]()) } - with Fail[A] { () => Nil[Triple]() } - } with Flip { () => concat(resume(true), resume(false)) } + with fail { () => Nil[Triple]() } + } with flip { () => concat(resume(true), resume(false)) } def main() = inspect(handledTriple(10, 15)) diff --git a/examples/pos/withstatement.effekt b/examples/pos/withstatement.effekt index c2cbdd2fa..bf164cd6d 100644 --- a/examples/pos/withstatement.effekt +++ b/examples/pos/withstatement.effekt @@ -1,8 +1,8 @@ module examples/pos/withstatement -effect Exc[A](msg: String): A +effect raise(msg: String): Nothing -def printer { p: => Unit / Exc }: Unit = try { p() } with Exc[A] { (msg) => +def printer { p: => Unit / raise }: Unit = try { p() } with raise { (msg) => println(msg) } @@ -18,7 +18,7 @@ def user(): Unit = { val x = 2; println("hello " ++ show(x)) }; - do Exc[Unit]("raised exception"); + do raise("raised exception"); println("world") } From fcf3f510f51a2b0e6b617f4d2bf2ebe468e9e9b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonathan=20Brachtha=CC=88user?= Date: Thu, 12 Sep 2024 12:11:29 +0200 Subject: [PATCH 2/2] Do not export the internal state type --- effekt/shared/src/main/scala/effekt/symbols/builtins.scala | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/effekt/shared/src/main/scala/effekt/symbols/builtins.scala b/effekt/shared/src/main/scala/effekt/symbols/builtins.scala index 6cb86e28e..6c7846b91 100644 --- a/effekt/shared/src/main/scala/effekt/symbols/builtins.scala +++ b/effekt/shared/src/main/scala/effekt/symbols/builtins.scala @@ -83,8 +83,7 @@ object builtins { "Any" -> TopSymbol, "Nothing" -> BottomSymbol, "IO" -> IOSymbol, - "Region" -> RegionSymbol, - "Ref" -> TState.interface + "Region" -> RegionSymbol ) lazy val globalRegion = ExternResource(name("global"), TRegion)