Skip to content

Commit 3d6378f

Browse files
committed
update
1 parent 2c00cf7 commit 3d6378f

File tree

3 files changed

+221
-143
lines changed

3 files changed

+221
-143
lines changed
Lines changed: 71 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Function Overloading
1+
# Ad-Hoc Polymorphism (Overloading)
22

33
<script type="module" src="/javascripts/editor.js"></script>
44
<link rel="stylesheet" href="/static/styles.css">
@@ -156,3 +156,73 @@ Here, the overloaded function `concat`:
156156

157157
- Concatenates numbers when passed two integers using mathematical operations.
158158
- Concatenates strings when passed two strings using built-in string concatenation.
159+
160+
### Currying with Overloaded Functions
161+
162+
Due to the nature of superposition types, overloaded functions are curried as well like regular functions.
163+
This allows for partial application and dynamic resolution of the function's behavior based on the arguments provided.
164+
165+
Consider the following example:
166+
167+
<div class="code-editor" id="code-overloading-curry">
168+
169+
```
170+
def ceilDec(x: Int): Int = if x == 0 then 1 else 10 * ceilDec(x / 10)
171+
def concat(a: Int, b: Int): Int = a * ceilDec(b) + b
172+
def concat(x: String, y: String): String = x ++ y
173+
174+
eval concat 123
175+
eval concat "Hello! "
176+
```
177+
</div>
178+
<div class="button-container">
179+
<button class="md-button button-run" onclick="runCodeInEditor('code-overloading-curry', 'output-overloading-curry')">Run Code</button>
180+
</div>
181+
<div class="output-container" id="output-overloading-curry"></div>
182+
183+
Here, `concat` can be partially applied to an integer or string, with its final behavior determined by the type of the subsequent argument. Specifically, applying an overloaded lambda to an argument invokes a selection process based on the argument’s type, identifying the corresponding function branch. This type-based selection mechanism is termed "**measurement**" of an overloaded lambda, analogous to quantum state measurement, where an indeterminate superposed state collapses into a definite outcome upon observation.
184+
185+
186+
187+
### Why Superposition Types?
188+
189+
Superposition types in **Saki** elegantly combine **function overloading** (ad-hoc polymorphism) with **currying**, overcoming challenges that typically arise when attempting to integrate these concepts in functional programming languages.
190+
191+
In many functional programming languages, **currying** and **function overloading** can clash. Currying, which allows functions to be partially applied, introduces ambiguity when combined with overloading. For instance, in a curried function, the compiler may struggle to determine which version of an overloaded function to apply based on partial arguments, leading to errors.
192+
193+
A typical example of this issue occurs in **Scala**, where the following code results in an **ambiguous overload** error:
194+
195+
<div class="code-editor">
196+
197+
```
198+
def appendToString(s: String)(x: Int): String = s + x
199+
def appendToString(s: String)(x: String): String = s + x
200+
def test = appendToString("foo")
201+
```
202+
</div>
203+
204+
In this case, the compiler cannot decide whether to apply `appendToString("foo")(Int)` or `appendToString("foo")(String)`, because both are valid overloads. The argument `"foo"` is a `String`, but it could be followed by either an `Int` or a `String`, causing ambiguity.
205+
However, **Saki** addresses this problem through **superposition types**. A superposition type, such as `A ⊕ B`, enables a function to handle multiple types dynamically and resolve overloading in a way that is both type-safe and flexible.
206+
When **superposition types** are used in conjunction with **currying**, the function can accept and handle different argument types across different stages of application without ambiguity. In Saki, the curried function `appendToString` would look like this:
207+
208+
<div class="code-editor" id="code-overloading-ambiguous">
209+
210+
```
211+
def appendToString(s: String, x: Int): String = s ++ x.toString
212+
def appendToString(s: String, x: String): String = s ++ x
213+
eval appendToString "foo"
214+
```
215+
</div>
216+
217+
<div class="button-container">
218+
<button class="md-button button-run" onclick="runCodeInEditor('code-overloading-ambiguous', 'output-overloading-ambiguous')">Run Code</button>
219+
</div>
220+
<div class="output-container" id="output-overloading-ambiguous"></div>
221+
222+
Here, the superposition type ensures that the function can adapt its behavior based on the next argument type—whether it’s an `Int` or a `String`. The type of the partial application `appendToString("foo")` would be:
223+
224+
$$
225+
(Int \to String) \oplus (String \to String)
226+
$$
227+
228+
This means that **Saki**’s type system can resolve the correct overload dynamically based on the second argument’s type, thus avoiding the ambiguity.

docs/index.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
!!! warning
88
Saki-lang is currently in a very early stage of design and development, with substantial work required before it reaches a mature state. The prototype interpreter and REPL are still under active development, and many features are not yet implemented or fully supported. The language design and syntax are subject to change based on ongoing research and experimentation.
99

10-
Saki is a dependently-typed, pure functional language with algebraic subtyping and overloaded superposition types. It prioritizes simplicity in design, using a Scala-inspired syntax while leveraging a type system grounded in Martin-Löf Type Theory. Saki introduces novel features like constraint universes and superposition types, serving as a research platform for exploring advanced type systems and verified program synthesis.
10+
**Saki** is a **dependently-typed**, **pure functional** programming language that supports **algebraic subtyping** and **ad-hoc polymorphism**. It prioritizes simplicity in design, using a Scala-inspired syntax while leveraging a type system grounded in Martin-Löf Type Theory. Saki introduces novel features like constraint universes and superposition types, serving as a research platform for exploring advanced type systems and verified program synthesis.
1111

1212
## Playground
1313

docs/javascripts/editor.js

Lines changed: 149 additions & 141 deletions
Original file line numberDiff line numberDiff line change
@@ -1,141 +1,149 @@
1-
import * as shiki from 'https://cdn.jsdelivr.net/npm/[email protected]/+esm'
2-
import { shikiToMonaco } from 'https://cdn.jsdelivr.net/npm/@shikijs/[email protected]/+esm'
3-
import * as monaco from 'https://cdn.jsdelivr.net/npm/[email protected]/+esm'
4-
5-
const colorSchemeMetaTag = document.querySelector('meta[name="color-scheme"]');
6-
if (colorSchemeMetaTag) {
7-
// Create a MutationObserver to watch for attribute changes
8-
const observer = new MutationObserver(() => updateTheme());
9-
// Start observing the `content` attribute on the meta tag
10-
observer.observe(colorSchemeMetaTag, {
11-
attributes: true, // Only observe attribute changes
12-
attributeFilter: ["content"], // Only watch the `content` attribute
13-
});
14-
}
15-
16-
setUpEditor().then(() => {
17-
for (let element of document.getElementsByClassName('code-editor')) {
18-
createEditor(element, null, true, true)
19-
}
20-
21-
for (let element of document.getElementsByClassName('playground-editor')) {
22-
createEditor(element, null, true, false)
23-
}
24-
25-
// for (let element of document.getElementsByClassName('result-editor')) {
26-
// createEditor(element, null, false, true)
27-
// }
28-
})
29-
30-
const DARK_THEME = 'slack-dark';
31-
const LIGHT_THEME = 'slack-ochin';
32-
33-
async function createEditor(element, overrideContent = null, doHighlight = true, editorReadOnly = true) {
34-
let content = element.textContent.trim().replace(/^```|```$/g, '').trim();
35-
element.textContent = undefined;
36-
if (overrideContent) {
37-
content = overrideContent.trim();
38-
}
39-
let language = "text";
40-
if (doHighlight) language = 'saki';
41-
let darkMode = document.querySelector('meta[name="color-scheme"]').getAttribute("content");
42-
43-
let editorConfig = {
44-
value: content,
45-
language: language,
46-
theme: darkMode === 'dark' ? DARK_THEME : LIGHT_THEME,
47-
automaticLayout: true,
48-
minimap: { enabled: false }, // Disable minimap if desired
49-
scrollBeyondLastLine: !editorReadOnly, // Prevent scrolling beyond the last line
50-
scrollbar: {
51-
vertical: editorReadOnly ? "hidden" : "visible", // Disables vertical scroll bar
52-
alwaysConsumeMouseWheel: !editorReadOnly,
53-
},
54-
cursorSmoothCaretAnimation: 'on',
55-
readOnly: editorReadOnly,
56-
cursorBlinking: editorReadOnly ? 'hidden' : 'smooth',
57-
unicodeHighlight: { ambiguousCharacters: false },
58-
lineNumbersMinChars: 4,
59-
lineDecorationsWidth: 0,
60-
lineHeight: 1.5,
61-
padding: { top: 10, bottom: 10 }
62-
}
63-
64-
if (editorReadOnly && content.split('\n').length < 100) {
65-
editorConfig.renderLineHighlight = 'none';
66-
editorConfig.lineNumbers = 'off';
67-
editorConfig.glyphMargin = false;
68-
editorConfig.folding = false;
69-
editorConfig.lineDecorationsWidth = 20;
70-
editorConfig.lineNumbersMinChars = 0;
71-
editorConfig.overviewRulerLanes = 0;
72-
}
73-
74-
if (!doHighlight) {
75-
editorConfig.wordWrap = 'on';
76-
}
77-
78-
let codeEditor = await monaco.editor.create(element, editorConfig);
79-
80-
if (editorReadOnly) {
81-
const resizeEditor = () => {
82-
const editorHeight = codeEditor.getContentHeight(); // minimum height
83-
codeEditor.layout({ width: codeEditor.getLayoutInfo().width, height: editorHeight });
84-
};
85-
codeEditor.onDidContentSizeChange(resizeEditor);
86-
}
87-
88-
element.codeEditor = codeEditor;
89-
updateTheme();
90-
}
91-
92-
async function setUpEditor() {
93-
const grammar = (await fetch('/assets/Saki.tmLanguage.json')).json();
94-
const langConf = (await fetch('/assets/language-configuration.json')).json();
95-
96-
let lightTheme = (await shiki.bundledThemes[LIGHT_THEME]()).default;
97-
lightTheme.colors['editor.background'] = '#F8F8F8';
98-
99-
const highlighter = await shiki.createHighlighter({
100-
themes: [
101-
DARK_THEME,
102-
lightTheme,
103-
],
104-
langs: [ grammar ],
105-
});
106-
monaco.languages.register({ id: 'saki' });
107-
monaco.languages.setLanguageConfiguration('saki', langConf);
108-
await shikiToMonaco(highlighter, monaco);
109-
}
110-
111-
async function updateTheme() {
112-
let darkMode = document.querySelector('meta[name="color-scheme"]').getAttribute("content");
113-
monaco.editor.setTheme(darkMode === 'dark' ? DARK_THEME : LIGHT_THEME);
114-
}
115-
116-
export async function runCodeInEditor(codeEditorElementId, resultEditorElementId, isEval = false) {
117-
let codeEditorElement = document.getElementById(codeEditorElementId);
118-
let resultEditorElement = document.getElementById(resultEditorElementId);
119-
await createEditor(resultEditorElement, null, false, true);
120-
let code = codeEditorElement.codeEditor.getValue();
121-
resultEditorElement.codeEditor.setValue("Running...");
122-
resultEditorElement.style.display = "flex";
123-
fetch("https://api.saki-lang.tech/v1/exec", {
124-
method: "POST",
125-
headers: {
126-
"Content-Type": "application/json"
127-
},
128-
body: JSON.stringify({
129-
sandbox: "saki",
130-
command: "run",
131-
files: { "": isEval ? "eval {\n" + code + "\n}" : code },
132-
}),
133-
signal: AbortSignal.timeout(5000)
134-
}).then(response => response.json()).then(data => {
135-
resultEditorElement.codeEditor.setValue(data.stdout);
136-
}).catch(error => {
137-
resultEditorElement.codeEditor.setValue(error.message);
138-
});
139-
}
140-
141-
window.runCodeInEditor = runCodeInEditor;
1+
import * as shiki from 'https://cdn.jsdelivr.net/npm/[email protected]/+esm'
2+
import { shikiToMonaco } from 'https://cdn.jsdelivr.net/npm/@shikijs/[email protected]/+esm'
3+
import * as monaco from 'https://cdn.jsdelivr.net/npm/[email protected]/+esm'
4+
5+
const colorSchemeMetaTag = document.querySelector('meta[name="color-scheme"]');
6+
if (colorSchemeMetaTag) {
7+
// Create a MutationObserver to watch for attribute changes
8+
const observer = new MutationObserver(() => updateTheme());
9+
// Start observing the `content` attribute on the meta tag
10+
observer.observe(colorSchemeMetaTag, {
11+
attributes: true, // Only observe attribute changes
12+
attributeFilter: ["content"], // Only watch the `content` attribute
13+
});
14+
}
15+
16+
setUpEditor().then(() => {
17+
for (let element of document.getElementsByClassName('code-editor')) {
18+
createEditor(element, null, true, true)
19+
}
20+
21+
for (let element of document.getElementsByClassName('playground-editor')) {
22+
createEditor(element, null, true, false)
23+
}
24+
25+
// for (let element of document.getElementsByClassName('result-editor')) {
26+
// createEditor(element, null, false, true)
27+
// }
28+
})
29+
30+
const DARK_THEME = 'slack-dark';
31+
const LIGHT_THEME = 'slack-ochin';
32+
33+
async function createEditor(element, overrideContent = null, doHighlight = true, editorReadOnly = true) {
34+
let content = element.textContent.trim().replace(/^```|```$/g, '').trim();
35+
element.textContent = undefined;
36+
if (overrideContent) {
37+
content = overrideContent.trim();
38+
}
39+
let language = "text";
40+
if (doHighlight) language = 'saki';
41+
let darkMode = document.querySelector('meta[name="color-scheme"]').getAttribute("content");
42+
43+
let editorConfig = {
44+
value: content,
45+
language: language,
46+
theme: darkMode === 'dark' ? DARK_THEME : LIGHT_THEME,
47+
automaticLayout: true,
48+
minimap: { enabled: false }, // Disable minimap if desired
49+
scrollBeyondLastLine: !editorReadOnly, // Prevent scrolling beyond the last line
50+
scrollbar: {
51+
vertical: editorReadOnly ? "hidden" : "visible", // Disables vertical scroll bar
52+
alwaysConsumeMouseWheel: !editorReadOnly,
53+
},
54+
cursorSmoothCaretAnimation: 'on',
55+
readOnly: editorReadOnly,
56+
cursorBlinking: editorReadOnly ? 'hidden' : 'smooth',
57+
unicodeHighlight: { ambiguousCharacters: false },
58+
lineNumbersMinChars: 4,
59+
lineDecorationsWidth: 0,
60+
lineHeight: 1.5,
61+
padding: { top: 10, bottom: 10 }
62+
}
63+
64+
if (editorReadOnly && content.split('\n').length < 100) {
65+
editorConfig.renderLineHighlight = 'none';
66+
editorConfig.lineNumbers = 'off';
67+
editorConfig.glyphMargin = false;
68+
editorConfig.folding = false;
69+
editorConfig.lineDecorationsWidth = 20;
70+
editorConfig.lineNumbersMinChars = 0;
71+
editorConfig.overviewRulerLanes = 0;
72+
}
73+
74+
if (!doHighlight) {
75+
editorConfig.wordWrap = 'on';
76+
}
77+
78+
let codeEditor = await monaco.editor.create(element, editorConfig);
79+
80+
if (editorReadOnly) {
81+
const resizeEditor = () => {
82+
const editorHeight = codeEditor.getContentHeight(); // minimum height
83+
codeEditor.layout({ width: codeEditor.getLayoutInfo().width, height: editorHeight });
84+
};
85+
codeEditor.onDidContentSizeChange(resizeEditor);
86+
}
87+
88+
element.codeEditor = codeEditor;
89+
updateTheme();
90+
}
91+
92+
async function setUpEditor() {
93+
const grammar = (await fetch('/assets/Saki.tmLanguage.json')).json();
94+
const langConf = (await fetch('/assets/language-configuration.json')).json();
95+
96+
let lightTheme = (await shiki.bundledThemes[LIGHT_THEME]()).default;
97+
lightTheme.colors['editor.background'] = '#F8F8F8';
98+
99+
const highlighter = await shiki.createHighlighter({
100+
themes: [
101+
DARK_THEME,
102+
lightTheme,
103+
],
104+
langs: [ grammar ],
105+
});
106+
monaco.languages.register({ id: 'saki' });
107+
monaco.languages.setLanguageConfiguration('saki', langConf);
108+
await shikiToMonaco(highlighter, monaco);
109+
}
110+
111+
async function updateTheme() {
112+
let darkMode = document.querySelector('meta[name="color-scheme"]').getAttribute("content");
113+
monaco.editor.setTheme(darkMode === 'dark' ? DARK_THEME : LIGHT_THEME);
114+
}
115+
116+
export async function runCodeInEditor(codeEditorElementId, resultEditorElementId, isEval = false) {
117+
let codeEditorElement = document.getElementById(codeEditorElementId);
118+
let resultEditorElement = document.getElementById(resultEditorElementId);
119+
await createEditor(resultEditorElement, null, false, true);
120+
let code = codeEditorElement.codeEditor.getValue();
121+
resultEditorElement.codeEditor.setValue("Running...");
122+
resultEditorElement.style.display = "flex";
123+
await runCode(code, resultEditorElement, 1, isEval);
124+
}
125+
126+
async function runCode(code, resultEditor, retries, isEval = false) {
127+
fetch("https://api.saki-lang.tech/v1/exec", {
128+
method: "POST",
129+
headers: {
130+
"Content-Type": "application/json"
131+
},
132+
body: JSON.stringify({
133+
sandbox: "saki",
134+
command: "run",
135+
files: { "": isEval ? "eval {\n" + code + "\n}" : code },
136+
}),
137+
signal: AbortSignal.timeout(7000)
138+
}).then(response => response.json()).then(data => {
139+
resultEditor.codeEditor.setValue(data.stdout);
140+
}).catch(error => {
141+
if (retries > 0) {
142+
runCode(code, resultEditor, retries - 1, isEval);
143+
} else {
144+
resultEditor.codeEditor.setValue("Failed to send execution request to the server, please retry later: " + error.message);
145+
}
146+
});
147+
}
148+
149+
window.runCodeInEditor = runCodeInEditor;

0 commit comments

Comments
 (0)