diff --git a/.github/workflows/code-checks.yml b/.github/workflows/code-checks.yml new file mode 100644 index 0000000..44d88d9 --- /dev/null +++ b/.github/workflows/code-checks.yml @@ -0,0 +1,15 @@ +name: Code Style + +on: + push: + branches: [master] + pull_request: + branches: [master] + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Check code formatting + run: npx prettier . --check diff --git a/.github/workflows/node.js.yml b/.github/workflows/node.js.yml index 226cd3f..077ab46 100644 --- a/.github/workflows/node.js.yml +++ b/.github/workflows/node.js.yml @@ -5,13 +5,12 @@ name: Node.js CI on: push: - branches: [ master ] + branches: [master] pull_request: - branches: [ master ] + branches: [master] jobs: build: - runs-on: ubuntu-latest strategy: @@ -26,4 +25,4 @@ jobs: with: node-version: ${{ matrix.node-version }} - run: npm ci - - run: npm run build --if-present \ No newline at end of file + - run: npm run build --if-present diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000..d8ffa7d --- /dev/null +++ b/.prettierignore @@ -0,0 +1,3 @@ +node_modules/** +build/** +src/data/** diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..67ac7dc --- /dev/null +++ b/.prettierrc @@ -0,0 +1,11 @@ +{ + "printWidth": 120, + "tabWidth": 2, + "semi": false, + "singleQuote": true, + "quoteProps": "as-needed", + "trailingComma": "all", + "bracketSpacing": true, + "arrowParens": "avoid", + "endOfLine": "auto" +} diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 0000000..b1eeca3 --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,7 @@ +{ + // See https://go.microsoft.com/fwlink/?LinkId=827846 to learn about workspace recommendations. + // Extension identifier format: ${publisher}.${name}. Example: vscode.csharp + + // List of extensions which should be recommended for users of this workspace. + "recommendations": ["esbenp.prettier-vscode"] +} diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..f25bd2e --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,11 @@ +{ + "json.schemaDownload.enable": true, + "eslint.enable": true, + "editor.codeActionsOnSave": { + "source.fixAll.eslint": true + }, + "editor.detectIndentation": false, + "editor.formatOnSave": true, + "editor.defaultFormatter": "esbenp.prettier-vscode", + "editor.tabSize": 2 +} diff --git a/AUTHORS.md b/AUTHORS.md index f9ca241..3984e90 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -1,5 +1,5 @@ # Contributors By adding your name to this document, you agree to release all your contributions to Flix under the [Apache 2.0 License](LICENSE.md). - + - [Magnus Madsen](https://github.com/magnus-madsen) diff --git a/LICENSE.md b/LICENSE.md index 1cf109f..5312f82 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -1,5 +1,4 @@ -Apache License -============== +# Apache License _Version 2.0, January 2004_ _<>_ @@ -89,15 +88,15 @@ You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: -* **(a)** You must give any other recipients of the Work or Derivative Works a copy of +- **(a)** You must give any other recipients of the Work or Derivative Works a copy of this License; and -* **(b)** You must cause any modified files to carry prominent notices stating that You +- **(b)** You must cause any modified files to carry prominent notices stating that You changed the files; and -* **(c)** You must retain, in the Source form of any Derivative Works that You distribute, +- **(c)** You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and -* **(d)** If the Work includes a “NOTICE” text file as part of its distribution, then any +- **(d)** If the Work includes a “NOTICE” text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the diff --git a/build-scripts/main.mjs b/build-scripts/main.mjs index cfc2751..0de5ad8 100644 --- a/build-scripts/main.mjs +++ b/build-scripts/main.mjs @@ -1,29 +1,29 @@ -import fs from "fs"; +import fs from 'fs' -import { sampleFiles, baseUrl } from "./sampleFiles.mjs"; +import { sampleFiles, baseUrl } from './sampleFiles.mjs' async function fetchSamples(baseUrl, files) { - return await Promise.all( - files.map(async sample => { - const response = await fetch(baseUrl + sample.file); - const code = await response.text(); + return await Promise.all( + files.map(async sample => { + const response = await fetch(baseUrl + sample.file) + const code = await response.text() - return { - name: sample.name, - code, - }; - }) - ); + return { + name: sample.name, + code, + } + }), + ) } -const samples = await fetchSamples(baseUrl, sampleFiles); -const objString = JSON.stringify(samples); +const samples = await fetchSamples(baseUrl, sampleFiles) +const objString = JSON.stringify(samples) const fileString = ` // This file is generated by build-scripts/main.mjs /* eslint-disable */ export default ${objString}; -`; +` // The file is placed in the src directory to include it in the bundle -fs.mkdirSync("./src/data", { recursive: true }); -fs.writeFileSync("./src/data/Samples.js", fileString); +fs.mkdirSync('./src/data', { recursive: true }) +fs.writeFileSync('./src/data/Samples.js', fileString) diff --git a/build-scripts/sampleFiles.mjs b/build-scripts/sampleFiles.mjs index d851765..ea9a48d 100644 --- a/build-scripts/sampleFiles.mjs +++ b/build-scripts/sampleFiles.mjs @@ -1,116 +1,116 @@ -const version = "0.38.0"; -export const baseUrl = `https://raw.githubusercontent.com/flix/flix/v${version}/examples/`; +const version = '0.38.0' +export const baseUrl = `https://raw.githubusercontent.com/flix/flix/v${version}/examples/` export const sampleFiles = [ - { - name: "Algebraic Data Types and Pattern Matching", - file: "algebraic-data-types-and-pattern-matching.flix", - }, - { - name: "Lists and List Processing", - file: "lists-and-list-processing.flix", - }, - { - name: "Higher-Order Functions", - file: "higher-order-functions.flix", - }, - { - name: "Enums and Parametric Polymorphism", - file: "enums-and-parametric-polymorphism.flix", - }, - { - name: "Record Construction and Use", - file: "record-construction-and-use.flix", - }, - { - name: "Polymorphic Record Update", - file: "polymorphic-record-update.flix", - }, - { - name: "Polymorphic Record Extension and Restriction", - file: "polymorphic-record-extension-and-restriction.flix", - }, - { - name: "Function Composition, Pipelines, and Currying", - file: "function-composition-pipelines-and-currying.flix", - }, - { - name: "Pure and Impure Functions", - file: "pure-and-impure-functions.flix", - }, - { - name: "Effect Polymorphic Functions", - file: "effect-polymorphic-functions.flix", - }, - { - name: "Type Aliases", - file: "type-aliases.flix", - }, - { - name: "Mutual Recursion with Full Tail-Call Elimination", - file: "mutual-recursion-with-full-tail-call-elimination.flix", - }, - { - name: "Sending and Receiving on Channels", - file: "sending-and-receiving-on-channels.flix", - }, - { - name: "Using Channels and Select", - file: "using-channels-and-select.flix", - }, - { - name: "Select with Defaults and Timers", - file: "select-with-defaults-and-timers.flix", - }, - { - name: "First-Class Constraints and Fixpoints", - file: "first-class-constraints-and-fixpoints.flix", - }, - { - name: "Polymorphic First-Class Constraints", - file: "polymorphic-first-class-constraints.flix", - }, - { - name: "Pipelines of Fixpoint Computations", - file: "pipelines-of-fixpoint-computations.flix", - }, - { - name: "Using Datalog to Solve a Compiler Puzzle", - file: "compiler-puzzle.flix", - }, - { - name: "An Interpreter for a Trivial Expression Language", - file: "an-interpreter-for-a-trivial-expression-language.flix", - }, - { - name: "A Simple Card Game Simulation", - file: "simple-card-game.flix", - }, - { - name: "Deriving Type Classes", - file: "deriving-type-classes.flix", - }, - { - name: "Internal Mutability with Regions", - file: "internal-mutability-with-regions.flix", - }, - { - name: "File Information", - file: "file-information.flix", - }, - { - name: "Working with Files and Directories", - file: "working-with-files-and-directories.flix", - }, - { - name: "Using Laziness for Infinite Streams", - file: "using-laziness-for-infinite-streams.flix", - }, - { - name: "Using Laziness for Logging", - file: "using-laziness-for-logging.flix", - }, - { - name: "Using Laziness to Compute Fibonacci", - file: "using-laziness-to-compute-fibonacci.flix", - } -]; \ No newline at end of file + { + name: 'Algebraic Data Types and Pattern Matching', + file: 'algebraic-data-types-and-pattern-matching.flix', + }, + { + name: 'Lists and List Processing', + file: 'lists-and-list-processing.flix', + }, + { + name: 'Higher-Order Functions', + file: 'higher-order-functions.flix', + }, + { + name: 'Enums and Parametric Polymorphism', + file: 'enums-and-parametric-polymorphism.flix', + }, + { + name: 'Record Construction and Use', + file: 'record-construction-and-use.flix', + }, + { + name: 'Polymorphic Record Update', + file: 'polymorphic-record-update.flix', + }, + { + name: 'Polymorphic Record Extension and Restriction', + file: 'polymorphic-record-extension-and-restriction.flix', + }, + { + name: 'Function Composition, Pipelines, and Currying', + file: 'function-composition-pipelines-and-currying.flix', + }, + { + name: 'Pure and Impure Functions', + file: 'pure-and-impure-functions.flix', + }, + { + name: 'Effect Polymorphic Functions', + file: 'effect-polymorphic-functions.flix', + }, + { + name: 'Type Aliases', + file: 'type-aliases.flix', + }, + { + name: 'Mutual Recursion with Full Tail-Call Elimination', + file: 'mutual-recursion-with-full-tail-call-elimination.flix', + }, + { + name: 'Sending and Receiving on Channels', + file: 'sending-and-receiving-on-channels.flix', + }, + { + name: 'Using Channels and Select', + file: 'using-channels-and-select.flix', + }, + { + name: 'Select with Defaults and Timers', + file: 'select-with-defaults-and-timers.flix', + }, + { + name: 'First-Class Constraints and Fixpoints', + file: 'first-class-constraints-and-fixpoints.flix', + }, + { + name: 'Polymorphic First-Class Constraints', + file: 'polymorphic-first-class-constraints.flix', + }, + { + name: 'Pipelines of Fixpoint Computations', + file: 'pipelines-of-fixpoint-computations.flix', + }, + { + name: 'Using Datalog to Solve a Compiler Puzzle', + file: 'compiler-puzzle.flix', + }, + { + name: 'An Interpreter for a Trivial Expression Language', + file: 'an-interpreter-for-a-trivial-expression-language.flix', + }, + { + name: 'A Simple Card Game Simulation', + file: 'simple-card-game.flix', + }, + { + name: 'Deriving Type Classes', + file: 'deriving-type-classes.flix', + }, + { + name: 'Internal Mutability with Regions', + file: 'internal-mutability-with-regions.flix', + }, + { + name: 'File Information', + file: 'file-information.flix', + }, + { + name: 'Working with Files and Directories', + file: 'working-with-files-and-directories.flix', + }, + { + name: 'Using Laziness for Infinite Streams', + file: 'using-laziness-for-infinite-streams.flix', + }, + { + name: 'Using Laziness for Logging', + file: 'using-laziness-for-logging.flix', + }, + { + name: 'Using Laziness to Compute Fibonacci', + file: 'using-laziness-to-compute-fibonacci.flix', + }, +] diff --git a/public/index.html b/public/index.html index 9348685..06085b7 100644 --- a/public/index.html +++ b/public/index.html @@ -1,26 +1,29 @@ - + + + + - - - - - + + + + + Flix | The Flix Programming Language - - - + + + - - Fork me on GitHub - + + Fork me on GitHub + -
- - + diff --git a/src/App.css b/src/App.css index ae387ec..cb25a79 100644 --- a/src/App.css +++ b/src/App.css @@ -1,96 +1,97 @@ body { - font-family: 'Open Sans', sans-serif; - overflow-y: scroll; + font-family: 'Open Sans', sans-serif; + overflow-y: scroll; - background-color: #efefef; + background-color: #efefef; } h1 { - font-size: 2rem; - margin-bottom: 0.75rem; - text-align: left; + font-size: 2rem; + margin-bottom: 0.75rem; + text-align: left; } h2 { - font-size: 1.7rem; - margin-bottom: 0.75rem; - text-align: left; + font-size: 1.7rem; + margin-bottom: 0.75rem; + text-align: left; } h3 { - font-size: 1.6rem; - margin-bottom: 0.75rem; - text-align: left; + font-size: 1.6rem; + margin-bottom: 0.75rem; + text-align: left; } p:last-child { - margin-bottom: 0; + margin-bottom: 0; } .menu { - margin-left: -15px; - margin-right: -15px; + margin-left: -15px; + margin-right: -15px; } .page { - background-color: white; - min-height: 95vh; + background-color: white; + min-height: 95vh; } .motto { - font-size: 1.35rem; - color: #17a2b8; + font-size: 1.35rem; + color: #17a2b8; } .editor-output h5 { - font-size: 1.1rem; - color: gray; - text-align: center; - padding-bottom: 0.5rem; - border-bottom: 1px solid #b7b7b7; + font-size: 1.1rem; + color: gray; + text-align: center; + padding-bottom: 0.5rem; + border-bottom: 1px solid #b7b7b7; } .editor-output pre { - font-family: monospace; + font-family: monospace; } .inline-editor-frame { - margin-top: 1em; - margin-bottom: 1.5em; + margin-top: 1em; + margin-bottom: 1.5em; } .inline-editor-code { - padding: 1em 1.5em; - background-color: #f7f7f7; + padding: 1em 1.5em; + background-color: #f7f7f7; } .nav-item { - cursor: pointer; + cursor: pointer; } .navbar-nav .nav-item .nav-link { - color: rgba(255, 255, 255, .85) + color: rgba(255, 255, 255, 0.85); } .navbar-nav .nav-item .nav-link:hover { - color: rgba(255, 255, 255, 1.00) + color: rgba(255, 255, 255, 1); } -.btn-group .btn, .btn-xs { - padding: .1rem .5rem; - font-size: .875rem; - line-height: 1.5; - border-radius: .2rem; +.btn-group .btn, +.btn-xs { + padding: 0.1rem 0.5rem; + font-size: 0.875rem; + line-height: 1.5; + border-radius: 0.2rem; } .card-title { - font-size: 1.2rem; + font-size: 1.2rem; } .ace_hidden-cursors { - opacity: 0 + opacity: 0; } .w-33 { - width: 33%; + width: 33%; } diff --git a/src/App.js b/src/App.js index b7f9453..cf5496b 100644 --- a/src/App.js +++ b/src/App.js @@ -1,152 +1,168 @@ -import React, {Component} from 'react'; -import './App.css'; - -import Home from "./page/Home"; -import GetStarted from "./page/GetStarted"; -import Documentation from "./page/Documentation"; -import Principles from "./page/Principles"; -import Contribute from "./page/Contribute"; -import Research from "./page/Research"; -import Faq from "./page/Faq"; -import Checklist from "./page/misc/Checklist"; -import {Container, Navbar, Nav, NavItem, NavLink, Row} from 'reactstrap'; -import {Route} from "react-router"; -import {Link} from "react-router-dom"; - -import ReconnectingWebSocket from 'reconnecting-websocket'; -import About from "./page/About"; -import Blog from "./page/Blog"; -import Koans from "./page/Koans"; -import Internships from "./page/Internships"; - -const SocketAddress = 'wss://evaluator.flix.dev/ws'; +import React, { Component } from 'react' +import './App.css' + +import Home from './page/Home' +import GetStarted from './page/GetStarted' +import Documentation from './page/Documentation' +import Principles from './page/Principles' +import Contribute from './page/Contribute' +import Research from './page/Research' +import Faq from './page/Faq' +import Checklist from './page/misc/Checklist' +import { Container, Navbar, Nav, NavItem, NavLink, Row } from 'reactstrap' +import { Route } from 'react-router' +import { Link } from 'react-router-dom' + +import ReconnectingWebSocket from 'reconnecting-websocket' +import About from './page/About' +import Blog from './page/Blog' +import Koans from './page/Koans' +import Internships from './page/Internships' + +const SocketAddress = 'wss://evaluator.flix.dev/ws' class App extends Component { - constructor(props) { - super(props); - - this.state = { - connected: false - }; - - console.log("Connecting to: " + SocketAddress); - - let options = { - connectionTimeout: 2500 - }; - - this.websocket = new ReconnectingWebSocket(SocketAddress, [], options); - - this.websocket.addEventListener("open", event => { - console.log("Connected to: " + SocketAddress); - this.setState({connected: true}); - }); - this.websocket.addEventListener("close", event => { - console.log("Disconnected from: " + SocketAddress); - console.log(event); - this.setState({connected: false}); - }); - this.websocket.addEventListener("error", event => { - console.log("Disconnected from: " + SocketAddress); - console.log(event); - this.setState({connected: false}); - }); - } + constructor(props) { + super(props) - runProgram = (src, callback) => { - if (!this.state.connected) { - console.log("Not connected yet"); - return; - } + this.state = { + connected: false, + } - this.websocket.onmessage = event => { - console.log("Received reply from: " + SocketAddress); - const data = JSON.parse(event.data); + console.log('Connecting to: ' + SocketAddress) - console.log(data); - callback(data); - }; + let options = { + connectionTimeout: 2500, + } - let data = { - src: src - }; + this.websocket = new ReconnectingWebSocket(SocketAddress, [], options) + + this.websocket.addEventListener('open', event => { + console.log('Connected to: ' + SocketAddress) + this.setState({ connected: true }) + }) + this.websocket.addEventListener('close', event => { + console.log('Disconnected from: ' + SocketAddress) + console.log(event) + this.setState({ connected: false }) + }) + this.websocket.addEventListener('error', event => { + console.log('Disconnected from: ' + SocketAddress) + console.log(event) + this.setState({ connected: false }) + }) + } + + runProgram = (src, callback) => { + if (!this.state.connected) { + console.log('Not connected yet') + return + } - this.websocket.send(JSON.stringify(data)); - }; + this.websocket.onmessage = event => { + console.log('Received reply from: ' + SocketAddress) + const data = JSON.parse(event.data) - getHome() { - return + console.log(data) + callback(data) } - render() { - return ( - - - + + + this.getHome()} /> + + + + + + + + + + + + + + + + ) + } } -export default App; +export default App diff --git a/src/index.js b/src/index.js index dd109f3..9c6a031 100644 --- a/src/index.js +++ b/src/index.js @@ -1,12 +1,12 @@ -import React from 'react'; -import ReactDOM from 'react-dom'; -import 'bootstrap/dist/css/bootstrap.min.css'; -import App from './App'; -import {BrowserRouter} from "react-router-dom"; +import React from 'react' +import ReactDOM from 'react-dom' +import 'bootstrap/dist/css/bootstrap.min.css' +import App from './App' +import { BrowserRouter } from 'react-router-dom' ReactDOM.render( - - - , - document.getElementById('root') -); + + + , + document.getElementById('root'), +) diff --git a/src/page/About.js b/src/page/About.js index 4ae07c7..93571ab 100644 --- a/src/page/About.js +++ b/src/page/About.js @@ -1,66 +1,58 @@ -import React, {Component} from "react"; -import {Col, Container, Row} from "reactstrap"; -import InlineEditor from "../util/InlineEditor"; +import React, { Component } from 'react' +import { Col, Container, Row } from 'reactstrap' +import InlineEditor from '../util/InlineEditor' class About extends Component { + componentDidMount() { + document.title = 'Flix | About' + } - componentDidMount() { - document.title = "Flix | About"; - } - - render() { - return ( - - - - - -

About

- -

- Flix is a functional-, logic-, and imperative- programming language focusing on making it - simple to write safe, robust, and elegant programs. It tries to achieve this through a - combination of a strong static type system with type inference, an extensive collection of - compile-time checks, and by - avoiding many of the mistakes of programming languages past. Flix aims to have a principled - design that does not sacrifice safety nor elegance at the altar of expediency. Flix - is a language for programmers that care about programs. -

- -

- Flix draws inspiration from many other excellent languages, including Elm, F#, OCaml, - Haskell, Rust, and Scala. Flix visually resembles Scala with an emphasis on conciseness and - the use of keywords. We believe that short keywords are a useful visual aid when scanning - through source code and trying to quickly understand its structure. The static type system - of Flix supports (local) type inference and is based on Hindley-Milner like those of OCaml - and Haskell. The concurrency model of Flix is based on channels and processes as known from - Go. Flix aspires to have understandable and detailed error-messages like those found in Elm. -

- -

- The Flix language and compiler is open source and freely available under the permissive - Apache 2.0 license. -

- -

Hello World

- -

- The much celebrated hello world program in Flix: -

- - - {`def main(): Unit \\ IO = + render() { + return ( + + + +

About

+ +

+ Flix is a functional-, logic-, and imperative- programming language focusing on making it simple to write + safe, robust, and elegant programs. It tries to achieve this through a combination of a strong static type + system with type inference, an extensive collection of compile-time checks, and by avoiding many of the + mistakes of programming languages past. Flix aims to have a principled design that does not{' '} + sacrifice safety nor elegance at the altar of expediency.{' '} + Flix is a language for programmers that care about programs. +

+ +

+ Flix draws inspiration from many other excellent languages, including Elm, F#, OCaml, Haskell, Rust, and + Scala. Flix visually resembles Scala with an emphasis on conciseness and the use of keywords. We believe + that short keywords are a useful visual aid when scanning through source code and trying to quickly + understand its structure. The static type system of Flix supports (local) type inference and is based on + Hindley-Milner like those of OCaml and Haskell. The concurrency model of Flix is based on channels and + processes as known from Go. Flix aspires to have understandable and detailed error-messages like those + found in Elm. +

+ +

+ The Flix language and compiler is open source and freely available under the permissive Apache 2.0 + license. +

+ +

Hello World

+ +

The much celebrated hello world program in Flix:

+ + + {`def main(): Unit \\ IO = println("Hello World!")`} - +
-

Basic Functional Programming

+

Basic Functional Programming

-

- Flix allows us to define our own data types and use pattern matching to take them apart: -

+

Flix allows us to define our own data types and use pattern matching to take them apart:

- - {`/// An algebraic data type for shapes. + + {`/// An algebraic data type for shapes. enum Shape { case Circle(Int32), // circle radius case Square(Int32), // side length @@ -77,14 +69,12 @@ def area(s: Shape): Int32 = match s { // Computes the area of a 2 by 4. def main(): Unit \\ IO = area(Rectangle(2, 4)) |> println`} - + -

- Flix supports parametric polymorphism (generics) as the following example shows: -

+

Flix supports parametric polymorphism (generics) as the following example shows:

- - {`/// An algebraic data type for binary trees where each leaf carries a value of type a. + + {`/// An algebraic data type for binary trees where each leaf carries a value of type a. enum Tree[a] { case Leaf(a), case Node(Tree[a], Tree[a]) @@ -111,28 +101,27 @@ def tree(): Tree[Int32] = Node(Leaf(1), Leaf(2)) /// Squares all elements in the simple tree. def main(): Unit \\ IO = map(x -> x * x, tree()) |> println`} - + -

- Note that type parameters may be omitted from function signatures, as shown in definition - of map. -

+

+ Note that type parameters may be omitted from function signatures, as shown in definition of{' '} + map. +

-

Concurrency

+

Concurrency

-

- The concurrency model of Flix is inspired by Go. In Flix, processes communicate - via immutable message-passing over channels. -

+

+ The concurrency model of Flix is inspired by Go. In Flix, processes communicate via immutable + message-passing over channels. +

-

- The following program spawns a new process to perform a (trivial) computation and then sends - the - result to the main process using a channel. -

+

+ The following program spawns a new process to perform a (trivial) computation and then sends the result to + the main process using a channel. +

- - {`/// Computes the sum of \`x\` and \`y\` and sends the result on the channel \`tx\`. + + {`/// Computes the sum of \`x\` and \`y\` and sends the result on the channel \`tx\`. def sum(x: Int32, y: Int32, tx: Sender[Int32, r]): Unit \\ { Write(r) } = Channel.send((x + y), tx) @@ -142,18 +131,16 @@ def main(): Int32 \\ IO = region r { spawn sum(21, 42, tx) @ r; // spawn sum to run in a separate process. Channel.recv(rx) // wait for the result to arrive on the channel. }`} - - -

- The powerful select expression can be used to choose the first element that - becomes - available from a collection of channels. The select expression can be used to implement many - useful - features, such as load balancing, producer-consumer patterns, and timeouts. -

- - - {`/// Sends the string \`s\` on the channel \`tx\` up to \`n\` times. + + +

+ The powerful select expression can be used to choose the first element that becomes available + from a collection of channels. The select expression can be used to implement many useful features, such + as load balancing, producer-consumer patterns, and timeouts. +

+ + + {`/// Sends the string \`s\` on the channel \`tx\` up to \`n\` times. def animal(s: String, n: Int32, tx: Sender[String, r]): Unit \\ { Write(r), IO } = match n { case 0 => () @@ -174,104 +161,97 @@ def main(): Unit \\ IO = region r { case hiss <- Channel.recv(rx3) => hiss /// rx3 became ready first } |> println }`} - +
-

Polymorphic Effects: Separating Pure and Impure Code

+

Polymorphic Effects: Separating Pure and Impure Code

-

- A unique feature of Flix is its polymorphic effect system. The Flix type and effect system - cleanly separates pure and impure code. If an expression is pure then it always - evaluates to the same value and it cannot have a side-effect. If a function is pure then it - always evaluates to the same value when given the same arguments. -

+

+ A unique feature of Flix is its polymorphic effect system. The Flix type and effect system cleanly + separates pure and impure code. If an expression is pure then it always evaluates to the same value and it + cannot have a side-effect. If a function is pure then it always evaluates to the same value when given the + same arguments. +

-

- We can write a pure function: -

+

We can write a pure function:

- - {`/// A pure function + + {`/// A pure function def sum(x: Int32, y: Int32): Int32 = x + y`} - + -

- And we can also write an impure function: -

+

And we can also write an impure function:

- - {`/// An impure function + + {`/// An impure function def sayHello(): Unit \\ IO = Console.printLine("Hello World")`} - + -

- We can also write a higher-order function that requires a pure function argument: -

+

+ We can also write a higher-order function that requires a pure function argument: +

- - {`def exists(f: a -> Bool, xs: List[a]): Bool = match xs { + + {`def exists(f: a -> Bool, xs: List[a]): Bool = match xs { case Nil => false case x :: rs => if (f(x)) true else exists(f, rs) }`} - - -

- Here f is a pure function, because every function definition - is implicitly marked as Pure in Flix. -

- -

- It is a compile-time error to call map with an impure function! -

- -

- A major challenge for type and effect systems is effect polymorphism. The problem is the - following: for higher-order functions the effect of a function depends on the effects of its - arguments. For example, if map is passed a pure function f then the - expression List.map(f, 1 :: Nil) is pure. On the other hand, if map is passed - an impure function g then the expression List.map(g, 1 :: Nil) is - impure. The effect of map depends on the effect of its first argument: it is effect - polymorphic. -

- -

- We can express such effect polymorphic function types in Flix. For example: -

- - - {`def map(f: a -> b \\ ef, xs: List[a]): List[b] \\ ef = match xs { + + +

+ Here f is a pure function, because every function definition is implicitly marked as{' '} + Pure in Flix. +

+ +

+ + It is a compile-time error to call map with an impure function! + +

+ +

+ A major challenge for type and effect systems is effect polymorphism. The problem is the following: for + higher-order functions the effect of a function depends on the effects of its arguments. For example, if + map is passed a pure function f then the expression List.map(f, 1 :: Nil) is + pure. On the other hand, if map is passed an impure function g then the expression{' '} + List.map(g, 1 :: Nil) is impure. The effect of map depends on the effect of its first + argument: it is effect polymorphic. +

+ +

+ We can express such effect polymorphic function types in Flix. For example: +

+ + + {`def map(f: a -> b \\ ef, xs: List[a]): List[b] \\ ef = match xs { case Nil => Nil case x :: rs => f(x) :: map(f, rs) }`} - - -

- Here the effect map depends on the effect of f (expressed with the - effect parameter ef). -

- -

- The effect system of Flix is part of what makes the language functional-first: the - programmer can trust that a pure function behaves as a mathematical function. Most other - contemporary programming languages cannot offer such guarantees. -

- -

First-class Datalog Constraints

- -

- Another unique feature of Flix is its support for logic programming with first-class Datalog - constraints. Datalog is a simple, yet surprisingly powerful, declarative logic programming - language - particularly well-suited for recursive queries on graphs. In Flix, Datalog constraints can - be - embedded inside Flix programs as first-class values that can be stored in local - variables, - passed and returned from functions, and composed with other Datalog program values. In a - way, Flix - is a strongly-typed meta-programming language for Datalog. -

- - - {`// Declarations of predicate symbols. + + +

+ Here the effect map depends on the effect of f (expressed with the effect + parameter ef). +

+ +

+ The effect system of Flix is part of what makes the language functional-first: the programmer can trust + that a pure function behaves as a mathematical function. Most other contemporary programming languages + cannot offer such guarantees. +

+ +

First-class Datalog Constraints

+ +

+ Another unique feature of Flix is its support for logic programming with first-class Datalog constraints. + Datalog is a simple, yet surprisingly powerful, declarative logic programming language particularly + well-suited for recursive queries on graphs. In Flix, Datalog constraints can be embedded inside{' '} + Flix programs as first-class values that can be stored in local variables, passed and returned from + functions, and composed with other Datalog program values. In a way, Flix is a strongly-typed + meta-programming language for Datalog. +

+ + + {`// Declarations of predicate symbols. rel Road(src: String, speed: Int32, dst: String) rel Connected(src: String, dst: String) @@ -295,22 +275,18 @@ def main(): Unit \\ IO = Road("Hamburg", 130, "Berlin"). }; drivable(g, "Aarhus", "Berlin", 110) |> println`} - - -

- The solution to a Datalog program, its minimal model, is itself a Datalog program (i.e. a - set of - facts). Consequently, with first-class constraints, it becomes possible to construct - pipelines of - Datalog programs. -

- -

- First-class Datalog constraints can also be polymorphic, as this example demonstrates: -

- - - {`def edgesWithNumbers(): #{ LabelledEdge(String, Int32, String) | r } = #{ + + +

+ The solution to a Datalog program, its minimal model, is itself a Datalog program (i.e. a set of facts). + Consequently, with first-class constraints, it becomes possible to construct pipelines of Datalog + programs. +

+ +

First-class Datalog constraints can also be polymorphic, as this example demonstrates:

+ + + {`def edgesWithNumbers(): #{ LabelledEdge(String, Int32, String) | r } = #{ LabelledEdge("a", 1, "b"). LabelledEdge("b", 1, "c"). LabelledEdge("c", 2, "d"). @@ -333,113 +309,103 @@ def main(): Unit \\ IO = select (x, l, z) from LabelledPath(x, l, z) |> println; query edgesWithColor(), closure() select (x, l, z) from LabelledPath(x, l, z) |> println`} - - - -
- - - - -

Innovations

- -

- We say that Flix is a next-generation programming language because it pioneers several new - features that are not found in any other contemporary programming language. However, - innovation is not a goal in itself; Flix also builds on decades of research in programming - language theory and relies on well-established theoretical foundations. -

- -

Polymorphic Effects

- -

- Flix comes with a polymorphic type and effect system. The effect system separates pure - and impure code. If an expression is pure then it always evaluates to the same value and it - cannot have a side-effect. If a function is pure then it always evaluates to the same value - when given the same arguments. -

- -

- Flix supports effect polymorphism which means that the effect of a higher-order - function can depend on the effect of its function arguments. For example, the effect - of List.map depends on the effect of the function passed to it. That is, - if List.map is given a pure function then computation is pure, whereas if - it is given an impure function then computation is impure. As an another example, - the effect of >> (forward function composition) depends on both of - its arguments: composition is only pure if both its function arguments are pure. -

- -

- The Flix type and effect system is fine-grained and expressive. It is fine-grained because - it precisely captures the purity of every expression and sub-expression. It is expressive - because it allows arbitrary boolean formulas to describe - the effect of an expression. For example, the system can express that a higher-order - function requires at most one impure function. -

- -

- The Flix type and effect system supports type and effect inference. That is, explicit - type or effect annotations are never required for any program fragment. (However, as a - design choice, type and effect annotations are required for every top-level definition.) The - inference is complete which means that it never infers the wrong type or effect, i.e. - annotations are never required to guide the type checker. -

- -

- The theoretical foundation of the Flix type and effect system is based on the Hindley-Milner - type system extended with boolean constraints. Type inference is supported by - an extension of Algorithm W with boolean unification. For more details, we refer to our - research papers. -

- -

First-class Datalog Constraints

- -

- Datalog is a simple, yet surprisingly powerful, declarative logic programming language. A - Datalog program is a collection of constraints. Each constraint is either a fact or a rule. - A Datalog rule is logic formula that allows us to derive new facts from existing facts. -

- -

- Every Datalog program has a unique solution; its so-called minimal model. - The minimal model always exists and is always computable, i.e. evaluation of a Datalog - program always terminates with the same solution. In Datalog, unlike in Prolog, the order - of constraints and of clauses within a rule is immaterial. That is, a Datalog program - can be freely reordered without changing its meaning. -

- -

- Datalog, unlike Prolog, is not Turing-complete: there are a programs that cannot be - expressed in Datalog. This is necessarily so, otherwise Datalog could not guarantee - termination. -

- -

- Flix supports Datalog constraints as first-class values that can be passed around, - composed with other constraints, and solved. The solution (i.e. minimal model) of a - constraint set is another constraint set, hence it is possible to construct pipelines of - Datalog computations within a Flix program. -

- -

- Flix can be thought of as a meta-programming language for Datalog. The Flix type system - and stratification algorithm guarantees the well-formedness of Datalog programs constructed - at run-time. Moreover, Flix significantly increases the expressive power of Datalog by - allowing guard expressions and expressions as head terms in Datalog rules. -

- -

- Alternatively, Flix can be thought of as a functional programming language with a very - powerful embedded query language. A Flix programmer can use first-class Datalog constraints - to simply, elegantly, and efficiently solve fixpoint computations inside functional code. - It should be noted that Datalog is strictly more expressive than relational algebra (~= - simple SQL). -

- - -
-
); - } + + + + + + +

Innovations

+ +

+ We say that Flix is a next-generation programming language because it pioneers several new features that + are not found in any other contemporary programming language. However, innovation is not a goal in itself; + Flix also builds on decades of research in programming language theory and relies on well-established + theoretical foundations. +

+ +

Polymorphic Effects

+ +

+ Flix comes with a polymorphic type and effect system. The effect system separates pure and impure code. If + an expression is pure then it always evaluates to the same value and it cannot have a side-effect. If a + function is pure then it always evaluates to the same value when given the same arguments. +

+ +

+ Flix supports effect polymorphism which means that the effect of a higher-order function can depend + on the effect of its function arguments. For example, the effect of List.map depends on the + effect of the function passed to it. That is, if List.map is given a pure function then + computation is pure, whereas if it is given an impure function then computation is impure. As an another + example, the effect of >> (forward function composition) depends on both of its + arguments: composition is only pure if both its function arguments are pure. +

+ +

+ The Flix type and effect system is fine-grained and expressive. It is fine-grained because it precisely + captures the purity of every expression and sub-expression. It is expressive because it allows arbitrary + boolean formulas to describe the effect of an expression. For example, the system can express that a + higher-order function requires at most one impure function. +

+ +

+ The Flix type and effect system supports type and effect inference. That is, explicit type or + effect annotations are never required for any program fragment. (However, as a design choice, type and + effect annotations are required for every top-level definition.) The inference is complete which + means that it never infers the wrong type or effect, i.e. annotations are never required to guide the type + checker. +

+ +

+ The theoretical foundation of the Flix type and effect system is based on the Hindley-Milner type system + extended with boolean constraints. Type inference is supported by an extension of Algorithm W with boolean + unification. For more details, we refer to our research papers. +

+ +

First-class Datalog Constraints

+ +

+ Datalog is a simple, yet surprisingly powerful, declarative logic programming language. A Datalog program + is a collection of constraints. Each constraint is either a fact or a rule. A Datalog rule is logic + formula that allows us to derive new facts from existing facts. +

+ +

+ Every Datalog program has a unique solution; its so-called minimal model. The minimal model always + exists and is always computable, i.e. evaluation of a Datalog program always terminates with the same + solution. In Datalog, unlike in Prolog, the order of constraints and of clauses within a rule is + immaterial. That is, a Datalog program can be freely reordered without changing its meaning. +

+ +

+ Datalog, unlike Prolog, is not Turing-complete: there are a programs that cannot be expressed in + Datalog. This is necessarily so, otherwise Datalog could not guarantee termination. +

+ +

+ Flix supports Datalog constraints as first-class values that can be passed around, composed with + other constraints, and solved. The solution (i.e. minimal model) of a constraint set is another constraint + set, hence it is possible to construct pipelines of Datalog computations within a Flix program. +

+ +

+ Flix can be thought of as a meta-programming language for Datalog. The Flix type system and stratification + algorithm guarantees the well-formedness of Datalog programs constructed at run-time. Moreover, Flix + significantly increases the expressive power of Datalog by allowing guard expressions and expressions as + head terms in Datalog rules. +

+ +

+ Alternatively, Flix can be thought of as a functional programming language with a very powerful embedded + query language. A Flix programmer can use first-class Datalog constraints to simply, elegantly, and + efficiently solve fixpoint computations inside functional code. It should be noted that Datalog is + strictly more expressive than relational algebra (~= simple SQL). +

+ +
+ + ) + } } -export default About; +export default About diff --git a/src/page/Blog.js b/src/page/Blog.js index a3a9b9b..9159dcd 100644 --- a/src/page/Blog.js +++ b/src/page/Blog.js @@ -1,102 +1,91 @@ -import React, {Component} from 'react'; -import {Col, Container, Row} from "reactstrap"; -import {Link} from "react-router-dom"; -import {Route, Switch} from "react-router"; -import ProgrammingLanguageDefense from "./blog/ProgrammingLanguageDefense"; -import DesignFlaws from "./blog/DesignFlaws"; -import Naming from "./blog/Naming"; -import Redundancies from "./blog/Redundancies"; -import PolymorphicEffects from "./blog/PolymorphicEffects"; +import React, { Component } from 'react' +import { Col, Container, Row } from 'reactstrap' +import { Link } from 'react-router-dom' +import { Route, Switch } from 'react-router' +import ProgrammingLanguageDefense from './blog/ProgrammingLanguageDefense' +import DesignFlaws from './blog/DesignFlaws' +import Naming from './blog/Naming' +import Redundancies from './blog/Redundancies' +import PolymorphicEffects from './blog/PolymorphicEffects' class Blog extends Component { - - componentDidMount() { - if (!document.title) { - document.title = "Flix | Blog"; - } + componentDidMount() { + if (!document.title) { + document.title = 'Flix | Blog' } + } - render() { - return ( - - - - - -

Blog

- -

- We maintain a blog about the development of the Flix programming language. - The blog is slow-moving since most of our writing bandwidth is directed towards - writing documentation and research papers. -

- -

- The blog has a more personal style and its intended audience is primarily - programming language enthusiasts. -

+ render() { + return ( + + + + + +

Blog

- -
+

+ We maintain a blog about the development of the Flix programming language. The blog is slow-moving + since most of our writing bandwidth is directed towards writing documentation and research papers. +

- - -
    -
  • - - In Defense of Programming Languages - -
  • -
  • - - Taming Impurity with Polymorphic Effects - -
  • -
  • - - Naming Functional and Destructive Operations - -
  • -
  • - - Redundancies as Compile-Time Errors - -
  • -
  • - - Design Flaws in the Flix Programming Language - -
  • -
- -
-
+

+ The blog has a more personal style and its intended audience is primarily programming language + enthusiasts. +

+ +
+ + +
    +
  • + In Defense of Programming Languages +
  • +
  • + + Taming Impurity with Polymorphic Effects + +
  • +
  • + + Naming Functional and Destructive Operations + +
  • +
  • + Redundancies as Compile-Time Errors +
  • +
  • + Design Flaws in the Flix Programming Language +
  • +
+ +
+
- - - + + + - - - + + + - - - + + + - - - + + + - - - - -
-
- ); - } + + + + + + ) + } } -export default Blog; +export default Blog diff --git a/src/page/Contribute.js b/src/page/Contribute.js index 7a46032..fc9d4a0 100644 --- a/src/page/Contribute.js +++ b/src/page/Contribute.js @@ -1,100 +1,91 @@ -import React, {Component} from 'react'; -import {Card, CardBody, CardTitle, Col, Container, Row} from "reactstrap"; +import React, { Component } from 'react' +import { Card, CardBody, CardTitle, Col, Container, Row } from 'reactstrap' class Contribute extends Component { + componentDidMount() { + document.title = 'Flix | Contribute' + } - componentDidMount() { - document.title = "Flix | Contribute"; - } + render() { + return ( + + + +

Contribute

- render() { - return ( - - - +

+ Flix is developed at Aarhus University, at + the University of Waterloo, and by a community of{' '} + open source contributors. +

-

Contribute

+

+ Flix is open-source and freely available under the{' '} + Apache 2.0 license. +

-

- Flix is developed at Aarhus - University, at the University of Waterloo, and by - a community of open source - contributors. -

+

+ All source code is available on GitHub. We organize development + using GitHub issues and{' '} + Gitter. +

-

- Flix is open-source and freely available under the Apache 2.0 license. -

+

+ If you are interested working on Flix, please feel free to reach out to us. We have already had several + positive experiences with mentorship and open source contributors. +

+ +
-

- All source code is available on GitHub. +

Projects

- We organize development using GitHub issues and Gitter. -

- -

- If you are interested working on Flix, please feel free to reach out to us. We have already - had several positive experiences with mentorship and open source contributors. -

- -
- -

Projects

- - - - - - Open Source Mentoring -

- We are more than happy to mentor someone who is interested in working on compilers - and/or programming language design and wants to contribute to Flix. We have already - had several positive experiences working with open source - contributors. -

-
-
- - - - - - Talent Track Projects -

- Aarhus University offers a talent track program for capable students that are in - their second or third year of studies and are interested in working on a research - project for up to one year as an extracurricular activity. -

-
-
- + + + + + Open Source Mentoring +

+ We are more than happy to mentor someone who is interested in working on compilers and/or programming + language design and wants to contribute to Flix. We have already had several positive experiences + working with open source contributors. +

+
+
+ - - - - Bachelor and Master Projects -

- If you are a bachelor or master student at Aarhus University you can - write your thesis on a topic related to Flix. -

-
-
- -
+ + + + Talent Track Projects +

+ Aarhus University offers a talent track program for capable students that are in their second or third + year of studies and are interested in working on a research project for up to one year as an + extracurricular activity. +

+
+
+ + + + + Bachelor and Master Projects

- If you want to become involved, send us a ping on Gitter, and we can have a chat about your - interests. + If you are a bachelor or master student at Aarhus University you can write your thesis on a topic + related to Flix.

-
- ); - } + + + + + +

+ If you want to become involved, send us a ping on Gitter, and we + can have a chat about your interests. +

+ + ) + } } -export default Contribute; +export default Contribute diff --git a/src/page/Documentation.js b/src/page/Documentation.js index 5455589..f953e00 100644 --- a/src/page/Documentation.js +++ b/src/page/Documentation.js @@ -1,155 +1,136 @@ -import React, { Component } from 'react'; -import { Card, CardSubtitle, CardText, CardTitle, Col, Container, Row } from "reactstrap"; -import { Link } from "react-router-dom"; +import React, { Component } from 'react' +import { Card, CardSubtitle, CardText, CardTitle, Col, Container, Row } from 'reactstrap' +import { Link } from 'react-router-dom' -import { FaBookReader } from 'react-icons/fa'; -import { FaStream } from 'react-icons/fa'; -import { FaMicroblog } from 'react-icons/fa'; -import { FaGitter } from 'react-icons/fa'; -import { FaGithub } from 'react-icons/fa'; -import { FaRegLaughSquint } from 'react-icons/fa'; -import { MdSchool } from 'react-icons/md'; -import { GoGraph } from 'react-icons/go'; +import { FaBookReader } from 'react-icons/fa' +import { FaStream } from 'react-icons/fa' +import { FaMicroblog } from 'react-icons/fa' +import { FaGitter } from 'react-icons/fa' +import { FaGithub } from 'react-icons/fa' +import { FaRegLaughSquint } from 'react-icons/fa' +import { MdSchool } from 'react-icons/md' +import { GoGraph } from 'react-icons/go' class Documentation extends Component { + componentDidMount() { + document.title = 'Flix | Documentation' + } - componentDidMount() { - document.title = "Flix | Documentation"; - } + render() { + return ( + + + +

Documentation

+ +
- render() { - return ( - - - -

Documentation

- -
+ + + + + + + + Programming Flix + + The book provides an introduction to Flix for functional programmers. The book demonstrates the core + concepts of Flix through several examples. + + + + + + + + + + + Standard Library + + The documentation provides a Javadoc-style description of the Flix library. + + + + + - - - - - - - - - Programming Flix - - - The book provides an introduction to Flix for functional programmers. The book - demonstrates the core concepts of Flix through several examples. - - - - - - - - - - - - Standard Library - - - The documentation provides a Javadoc-style description of the Flix library. - - - - - + + +

You may also be interested in these additional resources:

+ +
- - -

- You may also be interested in these additional resources: -

- -
+ + + + + + + + Research Papers + + + - - - - - - - - - Research Papers - - - - + + + + + + + Blog + + + - - - - - - - - Blog - - - - + + + + + + + Gitter + + + - - - - - - - - Gitter - - - - + + + + + + + GitHub + + + - - - - - - - - GitHub - - - - + + + + + + + Compiler Perf + + + - - - - - - - - Compiler Perf - - - - - - - - - - - - - Language Checklist - - - - - -
- ); - } + + + + + + + Language Checklist + + + + +
+ ) + } } -export default Documentation; +export default Documentation diff --git a/src/page/Faq.js b/src/page/Faq.js index 9718c1e..4df212d 100644 --- a/src/page/Faq.js +++ b/src/page/Faq.js @@ -1,674 +1,567 @@ -import React, {Component} from 'react'; -import {Card, CardBody, CardTitle, Table} from 'reactstrap'; -import {Container} from 'reactstrap'; -import {Link} from "react-router-dom"; +import React, { Component } from 'react' +import { Card, CardBody, CardTitle, Table } from 'reactstrap' +import { Container } from 'reactstrap' +import { Link } from 'react-router-dom' class Faq extends Component { - - componentDidMount() { - document.title = "Flix | FAQ"; - } - - render() { - return ( - -

Frequently Asked Questions

- -

- A collection of information that did not seem to fit in anywhere else. -

- - - - Are you looking for help with the compiler or language design? - - - Yes! We welcome any contributions and we are happy to mentor someone who wants to work - on the compiler. We are also open to general feedback and discussion about the language - design. Head on over to GitHub or Gitter and reach out to us! - - - - - - Does Flix support integration with Java, Kotlin, Scala etc? - - -

- Yes, with the import mechanism. -

-
-
- - - - Flix looks quite similar to Scala. How are the two languages related? - - -

- Flix borrows a lot of syntax from Scala, hence the two languages have a similar feel. - We think Scala made many good design choices with respect to syntax, including: - (a) the use of short keywords, - (b) the x : T syntax for type annotations, - (c) the List[Int32] syntax for type parameters, and - (d) if, match, etc. as expressions. -

- -

- Other than syntax, the two languages are very different: Scala is object-oriented, Flix is - not. - Scala has sub-typing, Flix does not. The Scala type system is unsound and has imperfect - type inference, whereas the Flix type system is both sound and supports type inference. -

-
-
- - - - Does Flix have a read-eval-print-loop (REPL)? - - - - No, but the compiler does have an interactive mode that can be started by passing - the --interactive argument. - - - - - - Does Flix have IDE support? Language Server Protocol (LSP) support? - - - Yes. There is a Visual - Studio Code plugin available for Flix. - - - - - - Does Flix compile to LLVM? - - - No and we don't have any plans to target LLVM. - - - - - - Does Flix compile to WebAssembly (WASM)? - - - No, not at the moment. It is something we are open to, but we are waiting for WebAssembly to - gain a garbage collector and tail calls. - - - - - - Why does Flix target the Java Virtual Machine (JVM)? - - - The Java Virtual Machine is a state-of-the-art virtual machine that runs on virtually every - platform, comes with a huge ecosystem of libraries, and supports garbage collection. While not - the ideal target for functional programming languages, due to its absence of tail call - instructions, it is still a strong choice. Moreover, if Project Loom (or similar efforts) turn out - successful then the JVM will offer an unbeatable feature set. - - - - - - Is the Flix type system sound? - - - Yes. It is based on Hindley-Milner. - - - - - - I want to learn Flix! What should I know about before hand? - - -

- At the moment, Flix has not yet reached version 1.0. That means you will be an early - adopter, so you should expect for the language to evolve and be prepared for the rare - compiler bug. (The Flix compiler has 9,500 manually written tests and we - take correctness seriously.) -

- -

- To write Flix programs, a solid background in functional programming is useful. That means - prior experience with OCaml, Haskell, Scala, or similar programming languages. -

-
-
- - - - What is the best way to start learning Flix? - - -

- We recommend that you start with a small offering to the Great Dreamer, the Sleeper of - R'lyeh, Cthulhu. And coffee. Lots of coffee. All kidding aside, the best place to start is - to look at the examples and to read the Programming - Flix book. -

- -

- If you get stuck or need help feel free to reach out to us on Gitter. -

-
-
- - - - Is Flix a domain specific language (DSL)? - - -

- No, Flix is a full-blown functional programming language. -

- -

- That said, Flix could be considered a meta-programming language for Datalog. -

-
-
- - - - What is the runtime performance of Flix programs? - - -

- Flix runs on the Java Virtual Machine (JVM) hence the performance of Flix is limited by the - performance of the JVM. Luckily, the JVM is a mature and performant virtual machine. Flix - uses monomorphization which eliminates boxing and in theory can make code execute faster - than ordinary Java / Kotlin / Scala code. However, Flix also features full tail call - elimination - which has some run-time performance cost. -

- -

- Our goal to be faster than any interpreted language and within a few factors of - equivalent functional Scala code. -

-
-
- - - - Haskell and OCaml compile to native code thus – by definition – they must be faster than Flix. - - - This is not necessarily true. For example, Java sometimes beats both OCaml and Haskell in The Computer - Language Benchmarks Game. - - - - - - What is the performance of the Flix compiler? - - -

- To answer this question, it is important to distinguish between latency and throughput. -

- -

- The Flix compiler runs on the JVM hence startup times can be expensive, i.e. the compiler - has high latency. However, once the JVM has warmed up the compiler is quite fast, i.e. has - high throughput. -

- -

- A simple experiment shows that the compiler runs about 20.0x times faster - when warmed up compared to when cold. We estimate that Flix, when warmed up, compiles - around 20,000 lines of code per second, which we believe to be faster than the - Scala compiler, but slower than the Java compiler. We take compiler performance - seriously and continuously track the performance of - the compiler. -

-
-
- - - - Ok, but really, what is the performance of the Flix compiler? - - -

- Compiler throughput on an Intel i5-K8600k with 16GB of RAM. -

- -

- Experimental results from the 30th of May 2020. -

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
ThreadsThroughput (lines/sec)Ratio
19,6731.0x
214,4821.5x
317,1001.8x
418,7221.9x
520,1182.1x
621,0522.2x
-
-
- - - - What features are not supported by Flix? - - - -

- Flix will, by design, not support any of the following features: -

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
FeatureReason
ExceptionsExceptions have been used to deal with two types of errors that may arise during - execution of a program: predictable errors (e.g. a file does not exist) and program - bugs (e.g. stack overflow). Flix has no exceptions. Instead, all predictable errors - should be encoded as partial functions (e.g. using Option) or as - potentially error returning functions (e.g. using Result). We are still - in the process of deciding how to handle program bugs. -
Null Values - Null, famously called Tony - Hoare's billion dollar mistake, is simply a bad solution to the problem of how - to represent the (potential) absence of a value. Instead, in Flix, you should use - the Option data type. -
Non-Total Functions - The Flix standard library has been deliberately designed to avoid common - programming mistakes. We want functions that are safe and have accurate type - signatures. For example, unlike Scala or Haskell, - the List.head function returns an Option since we cannot - in general guarantee that a list is non-empty. -
Silent Coercions - Implicit coercions between data types (e.g. between booleans and other values or - between enums and integers) is a rich source of programming mistakes. In Flix, - no type is ever converted to another type without explicit instruction from the - programmer. -
No Code Before Main - In Flix, no code is ever executed before main. Flix has no static initializers (or - similar constructs) which are difficult to reason about, error-prone, and often lead - to buggy code. -
Equality and Assignment Confusion - In Flix, the equality test operator (==) is different from the - assignment operator (:=) which is different from the equality sign - (=) used in definitions and let-bindings. Flix has been designed such - that programs that mistake one for the other is unlikely to compile. -
Undefined BehaviorUndefined behavior is frequently the cause of program bugs and - vulnerabilities. We want every Flix program to have a well-defined semantics. -
- -

- This list was partially inspired by the blog post things rust shipped without. -

-
-
- - - - What controversial design choices are made in Flix? - - - - -

- The following design choices may be considered controversial by some: -

- -
    -
  • - Unused variables are compile-time errors. -
  • -
  • - Shadowed variables are compile-time errors. -
  • -
  • - Unused definitions, type declarations, etc. are compile-time errors. -
  • -
  • - No variadic or labelled function arguments. -
  • -
  • - No warnings, only compile-time errors. -
  • -
  • - Dividing by zero yields - zero. -
  • -
- -
-
- - - - Who works on Flix? Is Flix a hobby project? - - -

- Flix is a research project carried out by faculty and students at Aarhus University and the University - of Waterloo. -

- -

- Flix is partially supported by a grant of 2,500,000 DKK from the Independent - Research Fund of Denmark. -

-
-
- - - - Where does the "Flix" name come from? - - - We do not entirely remember, but we believe it came from FIXpoint Language. - - - - - - Does Flix have a network library with streaming support? - - - Not yet, but maybe we could call it net-flix? - - - - - - You do not seem to understand parsing / type theory / code generation / computers! - - - We are happy to learn and to revisit design decisions if we come to a greater understanding - of the problem at hand. If you think we have overlooked something, we would be happy to talk - about it if you post a ticket on GitHub or write to us on Gitter. - - - - - - Why does the website require JavaScript? - - - We built the website using the popular React framework. We use JavaScript for the online code - editor. Using React was easy. Ultimately, we want to spend our time writing compilers, not - writing websites. - - - - - - The page does not load without JavaScript enabled. If I cannot even view the website without - that bloat, I wonder what the language is like? - - - Indeed, if your computer is too old to run a modern browser that supports JavaScript then - probably your computer is too old to run a modern JVM that supports Flix. Sorry. - - - - - - It appears that an example on the website does not work? - - -

- The latest compiler version and the website is not always in sync, hence occasionally some - examples may stop to work. Feel free to open a bug report if this happens. -

- -

- Occasionally a mischievous visitor will crash the online editor (or rather the virtual - machine on which the compiler runs). We usually discover and fix this within a few days. -

-
-
- - - - The language is probably great from a technical point of view, but unless Apple, Google, - Mozilla, or Microsoft is on-board it is pointless. - - -

- Yes, no programming language developed outside of those four corporations have ever been - successful. See also C, C++, Java, Python, PHP, MatLab, Perl, R, Ruby, Scala, ... -

- -

- That said, if you work for a company and would like to help sponsor Flix, please feel free - to reach out to us :) -

-
-
- - - - Wow! Amazing! A language where you can iterate through lists and call functions recursively. - - - Magical, isn't it? - - - - - - Great! Yet-another-programming-language™. This is exactly what we need; the gazillion of - existing programming languages is not enough. - - - Flix aims to offer a combination of features that are not found in any existing programming - language. - - - - - - Do we really need any more programming languages? - - -

- Do we really need safer airplanes? Do we really need electric cars? Do we - really need more ergonomic chairs? -

- -

- I mean, after all, we already have airplanes, cars, and chairs! -

- -

- We build new programming languages and we conduct research on programming language design - because we want to offer developers better tools to write programs. We want to make - it simpler, safer, and faster to write correct programs. -

- -

- And we want to increase developer happiness. It should be a pleasure to use Flix! -

-
-
- - - - This sounds like vaporware. All big promises and no delivery. - - - The research, - the code, - and the performance. - - - - - - I was disappointed to learn that Flix has feature X instead of my favorite - feature Y. - - - We are deeply sorry to have let you down. - - - - - - How the fuck can anyone understand such weird syntax? I hate all these symbols. - - -

- The Flix syntax is based on keywords except when there is a - clearly established historical precedent for using a symbol. For example: -

- -
    -
  • The cons of an element x and a list xs is written as x - :: xs. -
  • -
  • The underscore _ denotes a wildcard (or an unused variable).
  • -
  • The expression <- c reads a value from a channel whereas c <- - v writes a value to a channel. -
  • -
  • The symbol :- denotes logical implication in Datalog rules.
  • -
-
-
- - - - This is – by far – the worst syntax I have ever seen in a functional language. Semicolons, - braces, symbolic soup, et al. It is like if Scala, Java and Haskell had a one night stand in the - center of Chernobyl. - - - Quite an achievement, wouldn't you say? - - - - - - The examples all look horrible. The site looks horrible. This needs a lot of work before it - gets close to anything I would even consider using. - - - Sorry, what was the question? - - - -
- ); - } + componentDidMount() { + document.title = 'Flix | FAQ' + } + + render() { + return ( + +

Frequently Asked Questions

+ +

A collection of information that did not seem to fit in anywhere else.

+ + + Are you looking for help with the compiler or language design? + + Yes! We welcome any contributions and we are happy to mentor someone who wants to work on the compiler. We + are also open to general feedback and discussion about the language design. Head on over to{' '} + GitHub or Gitter and + reach out to us! + + + + + Does Flix support integration with Java, Kotlin, Scala etc? + +

+ Yes, with the import mechanism. +

+
+
+ + + Flix looks quite similar to Scala. How are the two languages related? + +

+ Flix borrows a lot of syntax from Scala, hence the two languages have a similar feel. We think Scala made + many good design choices with respect to syntax, including: (a) the use of short keywords, (b) the{' '} + x : T syntax for type annotations, (c) the List[Int32] syntax for type + parameters, and (d) if, match, etc. as expressions. +

+ +

+ Other than syntax, the two languages are very different: Scala is object-oriented, Flix is not. Scala has + sub-typing, Flix does not. The Scala type system is unsound and has imperfect type inference, whereas the + Flix type system is both sound and supports type inference. +

+
+
+ + + Does Flix have a read-eval-print-loop (REPL)? + + + No, but the compiler does have an interactive mode that can be started by passing the{' '} + --interactive argument. + + + + + Does Flix have IDE support? Language Server Protocol (LSP) support? + + Yes. There is a{' '} + + Visual Studio Code plugin available for Flix + + . + + + + + Does Flix compile to LLVM? + No and we don't have any plans to target LLVM. + + + + Does Flix compile to WebAssembly (WASM)? + + No, not at the moment. It is something we are open to, but we are waiting for WebAssembly to gain a garbage + collector and tail calls. + + + + + Why does Flix target the Java Virtual Machine (JVM)? + + The Java Virtual Machine is a state-of-the-art virtual machine that runs on virtually every platform, comes + with a huge ecosystem of libraries, and supports garbage collection. While not the ideal target for + functional programming languages, due to its absence of tail call instructions, it is still a strong choice. + Moreover, if Project Loom (or similar efforts) turn + out successful then the JVM will offer an unbeatable feature set. + + + + + Is the Flix type system sound? + Yes. It is based on Hindley-Milner. + + + + I want to learn Flix! What should I know about before hand? + +

+ At the moment, Flix has not yet reached version 1.0. That means you will be an early adopter, so you + should expect for the language to evolve and be prepared for the rare compiler bug. (The Flix compiler has + 9,500 manually written tests and we take correctness seriously.) +

+ +

+ To write Flix programs, a solid background in functional programming is useful. That means prior + experience with OCaml, Haskell, Scala, or similar programming languages. +

+
+
+ + + What is the best way to start learning Flix? + +

+ We recommend that you start with a small offering to the Great Dreamer, the Sleeper of R'lyeh, Cthulhu. + And coffee. Lots of coffee. All kidding aside, the best place to start is to look at the examples and to + read the Programming Flix book. +

+ +

If you get stuck or need help feel free to reach out to us on Gitter.

+
+
+ + + Is Flix a domain specific language (DSL)? + +

No, Flix is a full-blown functional programming language.

+ +

That said, Flix could be considered a meta-programming language for Datalog.

+
+
+ + + What is the runtime performance of Flix programs? + +

+ Flix runs on the Java Virtual Machine (JVM) hence the performance of Flix is limited by the performance of + the JVM. Luckily, the JVM is a mature and performant virtual machine. Flix uses monomorphization which + eliminates boxing and in theory can make code execute faster than ordinary Java / Kotlin / Scala code. + However, Flix also features full tail call elimination which has some run-time performance cost. +

+ +

+ Our goal to be faster than any interpreted language and within a few factors of equivalent functional + Scala code. +

+
+
+ + + + Haskell and OCaml compile to native code thus – by definition – they must be faster than Flix. + + + This is not necessarily true. For example, Java sometimes beats both{' '} + OCaml and{' '} + Haskell in{' '} + + The Computer Language Benchmarks Game + + . + + + + + What is the performance of the Flix compiler? + +

+ To answer this question, it is important to distinguish between{' '} + latency and{' '} + throughput. +

+ +

+ The Flix compiler runs on the JVM hence startup times can be expensive, i.e. the compiler has high + latency. However, once the JVM has warmed up the compiler is quite fast, i.e. has high throughput. +

+ +

+ A simple experiment shows that the compiler runs about 20.0x times faster when warmed up + compared to when cold. We estimate that Flix, when warmed up, compiles around 20,000 lines of + code per second, which we believe to be faster than the Scala compiler, but slower than the Java compiler. + We take compiler performance seriously and continuously track the{' '} + performance of the compiler. +

+
+
+ + + Ok, but really, what is the performance of the Flix compiler? + +

Compiler throughput on an Intel i5-K8600k with 16GB of RAM.

+ +

Experimental results from the 30th of May 2020.

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ Threads + Throughput (lines/sec)Ratio
19,6731.0x
214,4821.5x
317,1001.8x
418,7221.9x
520,1182.1x
621,0522.2x
+
+
+ + + + What features are not supported by Flix? + + + +

+ Flix will, by design, not support any of the following features: +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FeatureReason
Exceptions + Exceptions have been used to deal with two types of errors that may arise during execution of a + program: predictable errors (e.g. a file does not exist) and program bugs (e.g. stack overflow). + Flix has no exceptions. Instead, all predictable errors should be encoded as partial functions (e.g. + using Option) or as potentially error returning functions (e.g. using{' '} + Result). We are still in the process of deciding how to handle program bugs. +
Null Values + Null, famously called{' '} + Tony Hoare's billion dollar mistake, is + simply a bad solution to the problem of how to represent the (potential) absence of a value. + Instead, in Flix, you should use the Option data type. +
Non-Total Functions + The Flix standard library has been deliberately designed to avoid common programming mistakes. We + want functions that are safe and have accurate type signatures. For example, unlike Scala or + Haskell, the List.head function returns an Option since we cannot in + general guarantee that a list is non-empty. +
Silent Coercions + Implicit coercions between data types (e.g. between booleans and other values or between enums and + integers) is a rich source of programming mistakes. In Flix, no type is ever converted to another + type without explicit instruction from the programmer. +
No Code Before Main + In Flix, no code is ever executed before main. Flix has no static initializers (or similar + constructs) which are difficult to reason about, error-prone, and often lead to buggy code. +
Equality and Assignment Confusion + In Flix, the equality test operator (==) is different from the assignment operator ( + :=) which is different from the equality sign (=) used in definitions and + let-bindings. Flix has been designed such that programs that mistake one for the other is unlikely + to compile. +
Undefined Behavior + Undefined behavior is frequently the cause of program bugs and vulnerabilities. We want every Flix + program to have a well-defined semantics. +
+ +

+ This list was partially inspired by the blog post{' '} + things rust shipped without. +

+
+
+ + + What controversial design choices are made in Flix? + + +

The following design choices may be considered controversial by some:

+ +
    +
  • Unused variables are compile-time errors.
  • +
  • Shadowed variables are compile-time errors.
  • +
  • Unused definitions, type declarations, etc. are compile-time errors.
  • +
  • No variadic or labelled function arguments.
  • +
  • No warnings, only compile-time errors.
  • +
  • + Dividing by zero yields zero. +
  • +
+
+
+ + + Who works on Flix? Is Flix a hobby project? + +

+ Flix is a research project carried out by faculty and students at{' '} + Aarhus University and the{' '} + University of Waterloo. +

+ +

+ Flix is partially supported by a grant of 2,500,000 DKK from the{' '} + Independent Research Fund of Denmark. +

+
+
+ + + Where does the "Flix" name come from? + + We do not entirely remember, but we believe it came from{' '} + FIXpoint Language. + + + + + Does Flix have a network library with streaming support? + Not yet, but maybe we could call it net-flix? + + + + You do not seem to understand parsing / type theory / code generation / computers! + + We are happy to learn and to revisit design decisions if we come to a greater understanding of the problem + at hand. If you think we have overlooked something, we would be happy to talk about it if you post a ticket + on GitHub or write to us on Gitter. + + + + + Why does the website require JavaScript? + + We built the website using the popular React framework. We use JavaScript for the online code editor. Using + React was easy. Ultimately, we want to spend our time writing compilers, not writing websites. + + + + + + The page does not load without JavaScript enabled. If I cannot even view the website without that bloat, I + wonder what the language is like? + + + Indeed, if your computer is too old to run a modern browser that supports JavaScript then probably your + computer is too old to run a modern JVM that supports Flix. Sorry. + + + + + It appears that an example on the website does not work? + +

+ The latest compiler version and the website is not always in sync, hence occasionally some examples may + stop to work. Feel free to open a bug report if this happens. +

+ +

+ Occasionally a mischievous visitor will crash the online editor (or rather the virtual machine on which + the compiler runs). We usually discover and fix this within a few days. +

+
+
+ + + + The language is probably great from a technical point of view, but unless Apple, Google, Mozilla, or + Microsoft is on-board it is pointless. + + +

+ Yes, no programming language developed outside of those four corporations have ever been successful. See + also C, C++, Java, Python, PHP, MatLab, Perl, R, Ruby, Scala, ... +

+ +

+ That said, if you work for a company and would like to help sponsor Flix, please feel free to reach out to + us :) +

+
+
+ + + + Wow! Amazing! A language where you can iterate through lists and call functions recursively. + + Magical, isn't it? + + + + + Great! Yet-another-programming-language™. This is exactly what we need; the gazillion of existing + programming languages is not enough. + + + Flix aims to offer a combination of features that are not found in any existing programming language. + + + + + Do we really need any more programming languages? + +

+ Do we really need safer airplanes? Do we really need electric cars?{' '} + Do we really need more ergonomic chairs? +

+ +

I mean, after all, we already have airplanes, cars, and chairs!

+ +

+ We build new programming languages and we conduct research on programming language design because we want + to offer developers better tools to write programs. We want to make it simpler, safer + , and faster to write correct programs. +

+ +

And we want to increase developer happiness. It should be a pleasure to use Flix!

+
+
+ + + This sounds like vaporware. All big promises and no delivery. + + The research, the code, and the{' '} + performance. + + + + + + I was disappointed to learn that Flix has feature X instead of my favorite feature Y. + + We are deeply sorry to have let you down. + + + + How the fuck can anyone understand such weird syntax? I hate all these symbols. + +

+ The Flix syntax is based on keywords except when there is a + clearly established historical precedent for using a symbol. For example: +

+ +
    +
  • + The cons of an element x and a list xs is written as x :: xs. +
  • +
  • + The underscore _ denotes a wildcard (or an unused variable). +
  • +
  • + The expression <- c reads a value from a channel whereas c <- v writes a + value to a channel. +
  • +
  • + The symbol :- denotes logical implication in Datalog rules. +
  • +
+
+
+ + + + This is – by far – the worst syntax I have ever seen in a functional language. Semicolons, braces, symbolic + soup, et al. It is like if Scala, Java and Haskell had a one night stand in the center of Chernobyl. + + Quite an achievement, wouldn't you say? + + + + + The examples all look horrible. The site looks horrible. This needs a lot of work before it gets close to + anything I would even consider using. + + Sorry, what was the question? + +
+ ) + } } class Question extends Component { - render() { - return ( - {this.props.children} - ); - } + render() { + return {this.props.children} + } } - class Answer extends Component { - render() { - return ( -
{this.props.children}
- ); - } + render() { + return
{this.props.children}
+ } } class QA extends Component { - render() { - return ( - - - {this.props.children} - - ); - } + render() { + return ( + + {this.props.children} + + ) + } } -export default Faq; +export default Faq diff --git a/src/page/GetStarted.js b/src/page/GetStarted.js index 8a775b9..bfcdf8e 100644 --- a/src/page/GetStarted.js +++ b/src/page/GetStarted.js @@ -1,77 +1,81 @@ -import React, {Component} from 'react'; -import {Card, CardImg, Col, Container, Row} from "reactstrap"; +import React, { Component } from 'react' +import { Card, CardImg, Col, Container, Row } from 'reactstrap' import VSCode from '../gif/vscode.png' class GetStarted extends Component { + componentDidMount() { + document.title = 'Flix | Getting Started' + } - componentDidMount() { - document.title = "Flix | Getting Started"; - } + render() { + return ( + +

Get Started

- render() { - return ( - -

Get Started

+ + +
Using the Online Playground
+

+ You can try Flix online at play.flix.dev. +

+ +
- - -
Using the Online Playground
-

- You can try Flix online at play.flix.dev. -

- -
+
-
+ + +
Using Visual Studio Code
- - -
Using Visual Studio Code
+

+ Or you can install Flix in Visual Studio Code: +

-

- Or you can install Flix in Visual Studio - Code: -

+ + + + +
- - - - -
+
-
- - - -
Manually downloading and running the compiler
-

-

    -
  1. Ensure that you have at least Java 11 installed. You can check with java - -version. -
  2. -
  3. Download the latest version of the Flix compiler (flix.jar) at https://github.com/flix/flix/releases/latest. -
  4. -
  5. Create an empty folder (e.g. mkdir flixproject) and place the - downloaded Flix JAR (flix.jar) into that folder. -
  6. -
  7. Enter the created directory (e.g. cd flixproject) and run java - -jar flix.jar init to create an empty Flix project. -
  8. -
  9. Run java -jar flix.jar run to compile and run the project.
  10. -
  11. Flix will compile the project and execute the main function (located - in src/Main.flix). -
  12. -
-

- -
- - -
- ); - } + + +
Manually downloading and running the compiler
+

+

    +
  1. + Ensure that you have at least Java 11 installed. You can check with java -version. +
  2. +
  3. + Download the latest version of the Flix compiler (flix.jar) at{' '} + + https://github.com/flix/flix/releases/latest + + . +
  4. +
  5. + Create an empty folder (e.g. mkdir flixproject) and place the downloaded Flix JAR ( + flix.jar) into that folder. +
  6. +
  7. + Enter the created directory (e.g. cd flixproject) and run{' '} + java -jar flix.jar init to create an empty Flix project. +
  8. +
  9. + Run java -jar flix.jar run to compile and run the project. +
  10. +
  11. + Flix will compile the project and execute the main function (located in{' '} + src/Main.flix). +
  12. +
+

+ +
+
+ ) + } } -export default GetStarted; +export default GetStarted diff --git a/src/page/Home.js b/src/page/Home.js index 1aac7c0..218bf17 100644 --- a/src/page/Home.js +++ b/src/page/Home.js @@ -1,161 +1,152 @@ -import React, {Component} from 'react'; +import React, { Component } from 'react' import { - Button, - Card, - CardBody, - CardImg, - CardText, - CardTitle, - Col, - Container, - Row, - UncontrolledCarousel -} from 'reactstrap'; -import {Link} from "react-router-dom"; -import Codebox from "../util/Codebox"; -import InlineEditor from "../util/InlineEditor"; -import {Timeline} from 'react-twitter-widgets' + Button, + Card, + CardBody, + CardImg, + CardText, + CardTitle, + Col, + Container, + Row, + UncontrolledCarousel, +} from 'reactstrap' +import { Link } from 'react-router-dom' +import Codebox from '../util/Codebox' +import InlineEditor from '../util/InlineEditor' +import { Timeline } from 'react-twitter-widgets' class Home extends Component { - - componentDidMount() { - document.title = "The Flix Programming Language"; - } - - carousel = [ - { - src: '/images/vscode1.png', - caption: 'Slide 1', - header: 'VSCode: Syntax Highlighting', - }, - { - src: '/images/vscode2.png', - caption: 'Slide 2', - header: 'VSCode: Contextual Information', - }, - { - src: '/images/vscode3.png', - caption: 'Slide 3', - header: 'VSCode: Highlight References', - }, - { - src: '/images/vscode4.png', - caption: 'Slide 4', - header: 'VSCode: Inline Errors', - } - ]; - - render() { - return ( - - - -

The Flix Programming Language

- -

Next-generation reliable, safe, concise, and functional-first programming - language.

- -

- Flix is a principled functional, imperative, and logic programming language - developed at Aarhus University, at the University of Waterloo, and by a community of open source contributors. -

- -

- Flix is inspired by OCaml and Haskell with ideas from Rust and Scala. Flix looks like - Scala, but its type system is based on Hindley-Milner. Two unique features - of Flix are its polymorphic effect system and its support for first-class Datalog - constraints. -

- -

- Flix compiles to JVM bytecode, runs on the Java Virtual Machine, and supports full tail call - elimination. A VSCode plugin for Flix is available. -

- -

- - - - - - - - - - - -

- - - - - -
- -
- - - - -

Why Flix?

- -

- Flix aims to offer a unique combination of - features that no other programming language offers, including: algebraic - data types and pattern matching (like Haskell, OCaml), extensible records (like Elm), type classes (like Haskell, Rust), higher-kinded types (like Haskell), typematch (like Scala), type inference (like Haskell, OCaml), structured channel and process-based concurrency (like - Go), a polymorphic effect system (a unique - feature), region-based local mutation (a unique - feature), purity reflection (a unique - feature), first-class Datalog constraints (a unique - feature), - and compilation to JVM bytecode (like Scala). -

- - -
- -
- - - - - -

Algebraic Data Types and Pattern Matching

- -

- Algebraic data types and pattern matching are the bread-and-butter of functional - programming and are supported by Flix with minimal fuss. -

-
-
-
- - - - - {`enum Shape { + componentDidMount() { + document.title = 'The Flix Programming Language' + } + + carousel = [ + { + src: '/images/vscode1.png', + caption: 'Slide 1', + header: 'VSCode: Syntax Highlighting', + }, + { + src: '/images/vscode2.png', + caption: 'Slide 2', + header: 'VSCode: Contextual Information', + }, + { + src: '/images/vscode3.png', + caption: 'Slide 3', + header: 'VSCode: Highlight References', + }, + { + src: '/images/vscode4.png', + caption: 'Slide 4', + header: 'VSCode: Inline Errors', + }, + ] + + render() { + return ( + + + +

The Flix Programming Language

+ +

+ Next-generation reliable, safe, concise, and functional-first programming language. +

+ +

+ Flix is a principled functional, imperative, and logic programming language developed at{' '} + Aarhus University, at the{' '} + University of Waterloo, and by a community of{' '} + open source contributors. +

+ +

+ Flix is inspired by OCaml and Haskell with ideas from Rust and Scala. Flix looks like Scala, but its type + system is based on Hindley-Milner. Two unique features of Flix are its polymorphic effect system and its + support for first-class Datalog constraints. +

+ +

+ Flix compiles to JVM bytecode, runs on the Java Virtual Machine, and supports full tail call elimination. + A VSCode plugin for Flix is available. +

+ +

+ + + + + + + + + + + +

+ + + + +
+ +
+ + + +

Why Flix?

+ +

+ Flix aims to offer a unique combination of features{' '} + that no other programming language offers, including:{' '} + algebraic data types and pattern matching (like Haskell, OCaml),{' '} + extensible records (like Elm),{' '} + type classes (like Haskell, Rust),{' '} + higher-kinded types (like Haskell),{' '} + typematch (like Scala),{' '} + type inference (like Haskell, OCaml),{' '} + structured channel and process-based concurrency (like Go),{' '} + a polymorphic effect system (a unique feature),{' '} + region-based local mutation (a unique feature),{' '} + purity reflection (a unique feature),{' '} + first-class Datalog constraints (a unique feature), + and compilation to JVM bytecode (like Scala). +

+ +
+ +
+ + + + + + +

Algebraic Data Types and Pattern Matching

+
+ +

+ Algebraic data types and pattern matching are the bread-and-butter of functional programming and are + supported by Flix with minimal fuss. +

+
+
+
+ + + + + {`enum Shape { case Circle(Int32), case Square(Int32), case Rectangle(Int32, Int32) @@ -166,14 +157,14 @@ def area(s: Shape): Int32 = match s { case Square(w) => w * w case Rectangle(h, w) => h * w }`} - - -
+
+ +
- - - - {`def origin(): (Int32, Int32) = (0, 0) + + + + {`def origin(): (Int32, Int32) = (0, 0) def oneByOne(): {w = Int32, h = Int32} = {w = 1, h = 1} @@ -183,48 +174,46 @@ def area(rect: {w = Int32, h = Int32 | r}): Int32 = rect.w * rect.h def f(): Int32 = area({h = 1, color = "Blue", w = 2})`} - - - - - - -

Tuples and Records

- -

- Flix has built-in support for tuples and records. -

- -

- Records use structural typing and are extensible. -

-
-
-
- -
- - - - - -

Purity and Impurity

- -

- Flix precisely tracks the purity of every expression in a program. -

- -

- The Flix compiler provides an ironclad guarantee that if an expression is pure - then it cannot have side-effects and it is referentially transparent. -

-
-
-
- - - - {`/// A pure function is annotated with \`\\ {}\`. + + + + + + + +

Tuples and Records

+
+ +

Flix has built-in support for tuples and records.

+ +

Records use structural typing and are extensible.

+
+
+
+ +
+ + + + + + +

Purity and Impurity

+
+ +

Flix precisely tracks the purity of every expression in a program.

+ +

+ The Flix compiler provides an ironclad guarantee that if an expression is pure then it cannot have + side-effects and it is referentially transparent. +

+
+
+
+ + + + {`/// A pure function is annotated with \`\\ {}\`. def inc1(x: Int32): Int32 \\ {} = x + 1 /// An impure function is annotated with \`\\ IO\`. @@ -236,14 +225,14 @@ def f(): Int32 \\ IO = // f is impure let r1 = inc1(123); // pure let r2 = inc2(456); // impure r1 + r2 // pure`} - - -
- - - - - {`/// + + + + + + + + {`/// /// The purity of \`map\` depends on the purity of \`f\`. /// def map(f: a -> b \\ ef, l: List[a]): List[b] \\ ef = @@ -252,56 +241,57 @@ def map(f: a -> b \\ ef, l: List[a]): List[b] \\ ef = case x :: xs => f(x) :: map(f, xs) } `} - - - - - -

Polymorphic Effects

- -

- Flix is able to track purity through higher-order effect polymorphic functions. -

- -

- For example, Flix knows that the purity of List.map depends on the - purity of its function argument f. -

-
-
-
- -
- - - - - -

Region-based Local Mutation

- -

- Flix supports region-based local mutation, which makes it possible to - implement pure functions that internally uses mutable state and - destructive operations, as long as these operations are confined to the region. -

- -

- We can use local mutation when it is more natural to write a function using - mutable data and in a familiar imperative-style while still remaining pure - to the outside world. -

- -

- We can also use local mutation when it is more efficient to use mutable - data structures, e.g. when implementing a sorting algorithm. -

-
-
-
- - - - {`/// + + + + + + +

Polymorphic Effects

+
+ +

Flix is able to track purity through higher-order effect polymorphic functions.

+ +

+ For example, Flix knows that the purity of List.map depends on the purity of its + function argument f. +

+
+
+
+ +
+ + + + + + +

Region-based Local Mutation

+
+ +

+ Flix supports region-based local mutation, which makes it possible to implement pure{' '} + functions that internally uses mutable state and destructive operations, as long as these operations + are confined to the region. +

+ +

+ We can use local mutation when it is more natural to write a function using mutable data and in a + familiar imperative-style while still remaining pure to the outside world. +

+ +

+ We can also use local mutation when it is more efficient to use mutable data structures, e.g. when + implementing a sorting algorithm. +

+
+
+
+ + + + {`/// /// We can implement a *pure* \`sort\` function which /// internally converts an immutable list to an array, /// sorts the array in-place, and then converts it @@ -326,14 +316,14 @@ def toString(l: List[a]): String with ToString[a] = StringBuilder.toString(sb) } `} - - -
- - - - - {`/// + + + + + + + + {`/// /// We can inspect the purity of a function argument. /// def inspect(f: a -> b \\ ef): Unit \\ IO = @@ -352,56 +342,55 @@ def map(f: a -> b \\ ef, l: LazyList[a]): LazyList[b] \\ ef = case Pure(g) => mapL(g, l) case _ => mapE(f, l) }`} - - - - - -

Purity Reflection

- -

- Flix supports a meta-programming construct that enables higher-order functions - to inspect the purity of a function argument and use that information to vary - their behavior. -

- -

- For example, the DelayList.map function varies its behavior between - eager and lazy evaluation depending on the purity of its function argument. -

- -

- We can exploit purity reflection to selectively use lazy or parallel - evaluation inside a library without changing the semantics from the - point-of-view of the clients. -

-
-
-
- -
- - - - - -

Type Classes

- -

- Flix uses type classes to abstract over types that support a common set of - operations. -

-

- For example, the Eq type class captures the notion of equality - and is used throughout the standard library. -

-
-
-
- - - - {`class Eq[a] { + + + + + + +

Purity Reflection

+
+ +

+ Flix supports a meta-programming construct that enables higher-order functions to inspect the purity + of a function argument and use that information to vary their behavior. +

+ +

+ For example, the DelayList.map function varies its behavior between eager and lazy + evaluation depending on the purity of its function argument. +

+ +

+ We can exploit purity reflection to selectively use lazy or parallel evaluation inside a library + without changing the semantics from the point-of-view of the clients. +

+
+
+
+ +
+ + + + + + +

Type Classes

+
+ +

Flix uses type classes to abstract over types that support a common set of operations.

+

+ For example, the Eq type class captures the notion of equality and is used throughout + the standard library. +

+
+
+
+ + + + {`class Eq[a] { def eq(x: a, y: a): Bool def neq(x: a, y: a): Bool = not Eq.eq(x, y) } @@ -412,14 +401,14 @@ instance Eq[(a1, a2)] with Eq[a1], Eq[a2] { let (y1, y2) = t2; x1 == y1 and x2 == y2 }`} - - -
+
+ +
- - - - {`class Foldable[t : Type -> Type] { + + + + {`class Foldable[t : Type -> Type] { /// /// Left-associative fold of a structure. @@ -432,49 +421,51 @@ instance Eq[(a1, a2)] with Eq[a1], Eq[a2] { def foldRight(f: (a, b) -> b \\ ef, s: b, t: t[a]): b \\ ef }`} - - - - - -

Higher-Kinded Types

- -

- Flix supports higher-kinded types making it possible to abstract over type - constructors. For example, - both Option and List implement Foldable. -

- -

- The Flix standard library ships with many common type classes, such - as Monoid, Functor, and Foldable. -

-
-
-
- -
- - - - - -

Monadic For-Yield

- -

- Flix supports a monadic forM-yield construct similar to Scala's - for-comprehensions and Haskell's do notation. - The forM construct is syntactic sugar for uses - of point and flatMap (which are provided by the Monad - type class). -

-
-
-
- - - - {`def divide(x: Int32, y: Int32): Option[Int32] = + + + + + + +

Higher-Kinded Types

+
+ +

+ Flix supports higher-kinded types making it possible to abstract over type constructors. For + example, both Option and List implement Foldable. +

+ +

+ The Flix standard library ships with many common type classes, such as Monoid,{' '} + Functor, and Foldable. +

+
+
+
+ +
+ + + + + + +

Monadic For-Yield

+
+ +

+ Flix supports a monadic forM-yield construct similar to Scala's + for-comprehensions and Haskell's do notation. The forM{' '} + construct is syntactic sugar for uses of point and flatMap (which are + provided by the Monad type class). +

+
+
+
+ + + + {`def divide(x: Int32, y: Int32): Option[Int32] = if (y == 0) None else Some(x / y) def f(): Option[Int32] = @@ -484,14 +475,14 @@ def f(): Option[Int32] = z <- divide(9, y) ) yield x + y + z `} - - -
+
+ +
- - - - {`def validateUser(s: String): Validation[Err, String] = ... + + + + {`def validateUser(s: String): Validation[Err, String] = ... def validatePass(s: String): Validation[Err, String] = ... @@ -501,98 +492,97 @@ def conn(u: String, p: String): Validation[Err, Connection] = pass <- validatePass(p) ) yield Connection(user, pass) `} - - - - - -

Applicative For-Yield

- -

- In addition to the monadic forM expression, Flix supports an - applicative forA expression that builds on - the Applicative type class. The forA construct makes - it simple to write error-handling code which uses the Validation[e, - t] data type. -

-
-
-
- -
- - - - - -

First-class Datalog Constraints

- -

- Another unique feature of Flix is its embedded Datalog support. Datalog, a - powerful logic programming language in its own right, makes it simple and - elegant to express many fixpoint problems (including various graph reachability - problems): -

- - - {`def reachable(g: List[(String, Int32, String)], minSpeed: Int32): List[(String, String)] = + + + + + + +

Applicative For-Yield

+
+ +

+ In addition to the monadic forM expression, Flix supports an applicative{' '} + forA expression that builds on the Applicative type class. The{' '} + forA construct makes it simple to write error-handling code which uses the{' '} + Validation[e, t] data type. +

+
+
+
+ +
+ + + + + + +

First-class Datalog Constraints

+
+ +

+ Another unique feature of Flix is its embedded Datalog support. Datalog, a powerful logic + programming language in its own right, makes it simple and elegant to express many fixpoint problems + (including various graph reachability problems): +

+ + + {`def reachable(g: List[(String, Int32, String)], minSpeed: Int32): List[(String, String)] = let facts = inject g into Road; let rules = #{ Path(x, y) :- Road(x, maxSpeed, y), if maxSpeed >= minSpeed. Path(x, z) :- Path(x, y), Road(y, maxSpeed, z), if maxSpeed >= minSpeed. }; query facts, rules select (src, dst) from Path(src, dst) |> Foldable.toList`} - - -

- Datalog constraints are first-class which means that they may be passed - to and returned from functions, stored in data structures, composed with other - Datalog constraints, and solved. This makes it possible to express families of - Datalog programs. -

-
-
-
- -
- - - - - -

Datalog Enriched with Lattice Semantics

- -

- Flix supports Datalog constraints enriched with lattice semantics. -

- -

- The program on the right computes the delivery date for a collection of - parts. Each part is assembled from a collection of sub-components with various - delivery dates. For example, a car depends on a chassis and an engine. To build - a car, we need to wait for the chassis and engine to assembled and then we can - assemble the car itself. -

- -

- Note that parts may depend on sub-components that themselves may depend on other - sub-components. In other words, the problem is recursive. -

- -
- -

- Datalog constraints enriched with lattice semantics is one of the more - advanced features of Flix and requires some background knowledge of lattice - theory and fixpoints. -

-
-
-
- - - - {`let p = #{ + + +

+ Datalog constraints are first-class which means that they may be passed to and returned from + functions, stored in data structures, composed with other Datalog constraints, and solved. This + makes it possible to express families of Datalog programs. +

+ + + + +
+ + + + + + +

Datalog Enriched with Lattice Semantics

+
+ +

Flix supports Datalog constraints enriched with lattice semantics.

+ +

+ The program on the right computes the delivery date for a collection of parts. Each part is + assembled from a collection of sub-components with various delivery dates. For example, a car + depends on a chassis and an engine. To build a car, we need to wait for the chassis and engine to + assembled and then we can assemble the car itself. +

+ +

+ Note that parts may depend on sub-components that themselves may depend on other sub-components. In + other words, the problem is recursive. +

+ +
+ +

+ Datalog constraints enriched with lattice semantics is one of the more advanced features of Flix and + requires some background knowledge of lattice theory and fixpoints. +

+
+
+
+ + + + {`let p = #{ /// Parts and the components they depend on. PartDepends("Car", "Chassis"). PartDepends("Car", "Engine"). @@ -622,180 +612,173 @@ def conn(u: String, p: String): Validation[Err, Connection] = // Computes the delivery date for each component. let r = query p select (c, d) from ReadyDate(c; d) `} - - -
- -
- - - -

Complete Feature List

- - -
    -
  • algebraic data types
  • -
  • pattern matching
  • -
  • first-class functions
  • -
  • extensible records
  • -
  • parametric polymorphism
  • -
  • type classes
  • -
  • higher-kinded types
  • -
  • light-weight polymorphic effects
  • -
  • type aliases
  • -
  • Hindley-Milner type inference
  • -
- - - -
    -
  • CSP-style concurrency
  • -
  • buffered & unbuffered channels
  • -
  • first-class datalog constraints
  • -
  • polymorphic datalog predicates
  • -
  • constraints with lattice semantics
  • -
  • stratified negation
  • -
  • interoperability with Java
  • -
  • unboxed primitives
  • -
  • keyword-based syntax
  • -
  • redundancy checks
  • -
- - - -
    -
  • monadic forM expressions
  • -
  • applicative forA expressions
  • -
  • expressions holes
  • -
  • compilation to JVM bytecode
  • -
  • full tail call elimination
  • -
  • core standard library
  • -
  • parallel compiler architecture
  • -
  • human friendly errors
  • -
  • interactive mode
  • -
  • Visual Studio Code support
  • -
- -
- -
- - - -

Visual Studio Code Support

- -

- The Flix compiler integrates with Visual Studio Code providing a richer development - experience: -

- - - -
- -
- - - - - - Principled Design - - It is our goal to build Flix on a solid foundation of ideas from programming - language research. We aim to identify and document a collection of design - principles. We try to adopt great and proven ideas from other programming languages - such as F#, Go, OCaml, Haskell, Rust, and Scala. - - - - - - - Visual Studio Code Support - - Flix supports integration with Visual Studio Code through LSP. This includes support - for features such as: inline compiler errors, hover to show the type and effect of - an expression, jump to definition, find all usages of local variables, functions, - algebraic data types, and rename support. - - - - - - - - - - -
- - - -

Sponsors, Funding, and Collaborations

- - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - -
- - - -

- We kindly thank EJ Technologies for providing - us with JProfiler and JetBrains for providing us with IntelliJ IDEA. -

- -
- -
- ); - } + + + + +
+ + + +

Complete Feature List

+ + +
    +
  • algebraic data types
  • +
  • pattern matching
  • +
  • first-class functions
  • +
  • extensible records
  • +
  • parametric polymorphism
  • +
  • type classes
  • +
  • higher-kinded types
  • +
  • light-weight polymorphic effects
  • +
  • type aliases
  • +
  • Hindley-Milner type inference
  • +
+ + + +
    +
  • CSP-style concurrency
  • +
  • buffered & unbuffered channels
  • +
  • first-class datalog constraints
  • +
  • polymorphic datalog predicates
  • +
  • constraints with lattice semantics
  • +
  • stratified negation
  • +
  • interoperability with Java
  • +
  • unboxed primitives
  • +
  • keyword-based syntax
  • +
  • redundancy checks
  • +
+ + + +
    +
  • monadic forM expressions
  • +
  • applicative forA expressions
  • +
  • expressions holes
  • +
  • compilation to JVM bytecode
  • +
  • full tail call elimination
  • +
  • core standard library
  • +
  • parallel compiler architecture
  • +
  • human friendly errors
  • +
  • interactive mode
  • +
  • Visual Studio Code support
  • +
+ +
+ +
+ + + +

Visual Studio Code Support

+ +

The Flix compiler integrates with Visual Studio Code providing a richer development experience:

+ + + +
+ +
+ + + + + + Principled Design + + It is our goal to build Flix on a solid foundation of ideas from programming language research. We aim + to identify and document a collection of design principles. We try to adopt great and proven ideas + from other programming languages such as F#, Go, OCaml, Haskell, Rust, and Scala. + + + + + + + Visual Studio Code Support + + Flix supports integration with Visual Studio Code through LSP. This includes support for features such + as: inline compiler errors, hover to show the type and effect of an expression, jump to definition, + find all usages of local variables, functions, algebraic data types, and rename support. + + + + + + + + + + +
+ + + +

Sponsors, Funding, and Collaborations

+ + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + +
+ + + +

+ We kindly thank EJ Technologies for providing us with{' '} + JProfiler and{' '} + JetBrains for providing us with{' '} + IntelliJ IDEA. +

+ +
+ + ) + } } -export default Home; +export default Home diff --git a/src/page/Internships.js b/src/page/Internships.js index b80854c..fc73a14 100644 --- a/src/page/Internships.js +++ b/src/page/Internships.js @@ -1,63 +1,58 @@ -import React, {Component} from 'react'; -import {Col, Container, Row} from "reactstrap"; +import React, { Component } from 'react' +import { Col, Container, Row } from 'reactstrap' class Internships extends Component { - - componentDidMount() { - document.title = "Flix | Internships"; - } - - render() { - return ( - - - -

Research Internships

- -

- Interested in programming language research? - Want to work on compilers? - Want to work on Flix? -

- -

- Then join us for a research internship at Aarhus University in Denmark! -

- -
- -

- To apply, send an email to magnusm at cs dot au dot dk with a bit of - information about yourself. -

- -

- A typical research internship lasts 6-8 weeks in the summer period (but other times of year - may also be possible). -

- -
- -

- Aarhus University will reimburse travel and visa costs for coming to Denmark. - In addition, it is possible to apply for a small stipend to help cover housing and living - expenses. -

- -

- To apply, you should be enrolled in a bachelor's or master's degree programme at an accredited - university. You should have taken one or two courses on programming languages and on compilers - (exceptions can be made for especially talented students.) -

- -

- If you have questions, feel free to head over to Gitter and talk to us. -

- -
-
); - } + componentDidMount() { + document.title = 'Flix | Internships' + } + + render() { + return ( + + + +

Research Internships

+ +

+ Interested in programming language research? Want to work on compilers? Want to work on Flix? +

+ +

Then join us for a research internship at Aarhus University in Denmark!

+ +
+ +

+ To apply, send an email to magnusm at cs dot au dot dk with a bit of information about + yourself. +

+ +

+ A typical research internship lasts 6-8 weeks in the summer period (but other times of year may also be + possible). +

+ +
+ +

+ Aarhus University will reimburse travel and visa costs for coming to Denmark. In addition, it is possible + to apply for a small stipend to help cover housing and living expenses. +

+ +

+ To apply, you should be enrolled in a bachelor's or master's degree programme at an accredited university. + You should have taken one or two courses on programming languages and on compilers (exceptions can be made + for especially talented students.) +

+ +

+ If you have questions, feel free to head over to Gitter and + talk to us. +

+ +
+
+ ) + } } -export default Internships; +export default Internships diff --git a/src/page/Koans.js b/src/page/Koans.js index cde7fd0..10bbf06 100644 --- a/src/page/Koans.js +++ b/src/page/Koans.js @@ -1,61 +1,55 @@ -import React, {Component} from "react"; -import {Col, Container, Row} from "reactstrap"; -import InlineEditor from "../util/InlineEditor"; +import React, { Component } from 'react' +import { Col, Container, Row } from 'reactstrap' +import InlineEditor from '../util/InlineEditor' class Koans extends Component { - - componentDidMount() { - document.title = "Flix | Koans"; - } - - render() { - return ( - - - -

Flix Koans

- -

- Koan — A paradox to be meditated upon that is used to train Zen Buddhist monks - to abandon ultimate dependence on reason and to force them into gaining sudden intuitive - enlightenment. -

- -
- Given a road network, determine if it is possible to drive from one city to another city. -
- - - {`pub def drivable(roads: Array[(city, city)], source: city, destination: city): Bool with Boxable[city] = + componentDidMount() { + document.title = 'Flix | Koans' + } + + render() { + return ( + + + +

Flix Koans

+ +

+ Koan — A paradox to be meditated upon that is used to train Zen Buddhist monks to abandon + ultimate dependence on reason and to force them into gaining sudden intuitive enlightenment. +

+ +
Given a road network, determine if it is possible to drive from one city to another city.
+ + + {`pub def drivable(roads: Array[(city, city)], source: city, destination: city): Bool with Boxable[city] = let r = project roads into Road; let lp = #{ Path(x, y) :- Road(x, y). Path(x, z) :- Path(x, y), Road(y, z). }; not Array.isEmpty(query r, lp select () from Path(source, destination))`} - +
-
- Given a road network with speed limits on each road, determine if it is possible to drive - from one city to another city going at least a certain speed. -
+
+ Given a road network with speed limits on each road, determine if it is possible to drive from one city to + another city going at least a certain speed. +
- - {`pub def drivable(roads: Array[(city, Int32, city)], source: city, destination: city, minimumSpeed: Int32): Bool with Boxable[city] = + + {`pub def drivable(roads: Array[(city, Int32, city)], source: city, destination: city, minimumSpeed: Int32): Bool with Boxable[city] = let r = project roads into Road; let lp = #{ Path(x, y) :- Road(x, maximumSpeed, y), if maximumSpeed > minimumSpeed. Path(x, z) :- Path(x, y), Road(y, maximumSpeed, z), if maximumSpeed > minimumSpeed. }; not Array.isEmpty(query r, lp select () from Path(source, destination))`} - + -
- Given a road network, compute all pairs of cities that are not connected by roads. -
+
Given a road network, compute all pairs of cities that are not connected by roads.
- - {`pub def unconnected(roads: Array[(city, city)]): Array[(city, city)] with Boxable[city] = + + {`pub def unconnected(roads: Array[(city, city)]): Array[(city, city)] with Boxable[city] = let r = project roads into Road; let lp = #{ City(x) :- Road(x, _). @@ -65,15 +59,15 @@ class Koans extends Component { Unconnected(x, y) :- City(x), City(y), not Path(x, y). }; query r, lp select (x, y) from Unconnected(x, y)`} - + -
- Given a train and a bus network, compute if there is a path from one city to another city - with at most the given maximum number of bus connections. -
+
+ Given a train and a bus network, compute if there is a path from one city to another city with at most the + given maximum number of bus connections. +
- - {`pub def travelWithLimitedBusses(trainConnections: Array[(city, city)], busConnections: Array[(city, city)], source: city, destination: city, maxBusTrips: Int32): Bool with Boxable[city] = + + {`pub def travelWithLimitedBusses(trainConnections: Array[(city, city)], busConnections: Array[(city, city)], source: city, destination: city, maxBusTrips: Int32): Bool with Boxable[city] = let tc = project trainConnections into Train; let bc = project busConnections into Bus; let lp = #{ @@ -84,28 +78,24 @@ class Koans extends Component { }; let possibleBusTrips = query tc, bc, lp select busses from Path(source, busses, destination) where busses <= maxBusTrips; possibleBusTrips.length > 0`} - + -
- Given a family tree, compute the pairs of half siblings. -
+
Given a family tree, compute the pairs of half siblings.
- - {`pub def halfSiblings(siblings: Array[(person, person)]): Array[(person, person)] with Boxable[person] = + + {`pub def halfSiblings(siblings: Array[(person, person)]): Array[(person, person)] with Boxable[person] = let s = project siblings into Sibling; let lp = #{ Sibling(x, y) :- Parent(x, p), Parent(y, p), if x != y. HalfSibling(x, y) :- Sibling(x, y), Parent(y, p), not Parent(x, p). }; query s, lp select (x, y) from HalfSibling(x, y)`} - + -
- Given a family tree of Roman emperors, compute the heir of every emperor and every usurper. -
+
Given a family tree of Roman emperors, compute the heir of every emperor and every usurper.
- - {`pub def heirsAndUsurpers(parents: Array[(person, person)], emperors: Array[person]): {heirs = Array[person], usurpers = Array[person]} with Boxable[person] = + + {`pub def heirsAndUsurpers(parents: Array[(person, person)], emperors: Array[person]): {heirs = Array[person], usurpers = Array[person]} with Boxable[person] = let p = project parents into Parent; let e = project emperors into Emperor; let lp = #{ @@ -120,16 +110,15 @@ class Koans extends Component { let heirs = query solution select x from Heir(x); let usurpers = query solution select x from Usurper(x); {heirs = heirs, usurpers = usurpers}`} - + -
- Given a social network graph, compute a collection of friend suggestions. A person is a - possible friend if he or she is not (yet) my friend and is friends with at least three of my - current friends. -
+
+ Given a social network graph, compute a collection of friend suggestions. A person is a possible friend if + he or she is not (yet) my friend and is friends with at least three of my current friends. +
- - {`pub def friendSuggestions(friends: Array[(person, person)]): Array[(person, person)] with Boxable[person] = + + {`pub def friendSuggestions(friends: Array[(person, person)]): Array[(person, person)] with Boxable[person] = let f = project friends into Friend; let lp = #{ Suggestion(me, nf) :- @@ -139,19 +128,17 @@ class Koans extends Component { if f1 != f2 and f2 != f3 and f1 != f3. }; query f, lp select (x, y) from Suggestion(x, y)`} - + -
- Given a list of operating system processes, compute all orphaned and zombie processes. -
+
Given a list of operating system processes, compute all orphaned and zombie processes.
-

- An orphaned process has no parent or has init (pid 1) as its parent. A zombie - process is dead, but its parent is still alive. -

+

+ An orphaned process has no parent or has init (pid 1) as its parent. A zombie process{' '} + is dead, but its parent is still alive. +

- - {`pub def orphansAndZombies(processes: Array[(processId, String, processId)], rootId: processId): {orphans = Array[processId], zombies = Array[processId]} with Boxable[processId] = + + {`pub def orphansAndZombies(processes: Array[(processId, String, processId)], rootId: processId): {orphans = Array[processId], zombies = Array[processId]} with Boxable[processId] = let p = project processes into Process; let lp = #{ Zombie(pid) :- Process(pid, "dead", parent), Process(parent, "alive", _). @@ -163,14 +150,12 @@ class Koans extends Component { let zombies = query solution select pid from Zombie(pid); let orphans = query solution select pid from Orphan(pid); {zombies = zombies, orphans = orphans}`} - + -
- Given a list of graphs, find all pairs of graphs whose union is acyclic. -
+
Given a list of graphs, find all pairs of graphs whose union is acyclic.
- - {`def isCyclic(edges: Array[(Int32, Int32)]): Bool = + + {`def isCyclic(edges: Array[(Int32, Int32)]): Bool = let e = project edges into Edge; let lp = #{ Path(x, y) :- Edge(x, y). @@ -184,12 +169,12 @@ class Koans extends Component { pub def pairwiseAcyclic(graphs: List[Array[(Int32, Int32)]]): List[List[Array[(Int32, Int32)]]] = let combineGraphs = (g1, g2) -> query (project g1 into Edge) <+> (project g2 into Edge) select (x, y) from Edge(x, y); graphs |> List.groupBy((g1, g2) -> not isCyclic(combineGraphs(g1, g2)))`} - - - -
-
); - } + + + + + ) + } } -export default Koans; +export default Koans diff --git a/src/page/Principles.js b/src/page/Principles.js index 7216f07..74fac70 100644 --- a/src/page/Principles.js +++ b/src/page/Principles.js @@ -1,622 +1,565 @@ -import React, {Component} from 'react'; -import {Badge, Card, CardBody, CardColumns, CardTitle, Container} from 'reactstrap'; +import React, { Component } from 'react' +import { Badge, Card, CardBody, CardColumns, CardTitle, Container } from 'reactstrap' class Principles extends Component { - - componentDidMount() { - document.title = "Flix | Principles"; - } - - render() { - return ( - -

Design Principles

- -

- We believe that the development of a programming language should follow a set of principles. - That is, when a design decision is made there should exist some rationale for why that - decision was made. By outlining these principles, as we develop Flix, we hope to keep ourselves - honest and to communicate the kind of language Flix aspires to be. -

- -

- Many of these ideas and principles come from languages that have inspired Flix, including Ada, Elm, - F#, Go, Haskell, OCaml, Rust, and Scala. -

- -

- Update: The Flix Principles has been published in a paper at Onward! '22. Read it here: The Principles of the Flix Programming - Language. -

- -

Language Principles

- - - - - We believe in Rich Hickey's creed: simple - is not easy. We prefer a language that gets things right to one that makes things - easy. Such a language might take longer to learn in the short run, but its simplicity pays off - in the long run. - - - - Flix is a functional language and embraces the idea that everything should be an expression. - Flix has no local variable declarations or if-then-else statements, instead it has - let-bindings and if-then-else expressions. - - However, Flix does not take this idea as far as the Scheme languages. Flix still has - declarations, namespaces, and so forth that are not expressions. - - - - Flix supports functional, imperative, and logic programming. The type and effect system of Flix - cleanly and safely separates pure code from impure code. That is, if a function is pure then the - programmer can trust that the function behaves like a mathematical function: it returns the same - value when given the same arguments and it has no side-effects. - - - - Flix aims to support developer productivity; the ability to do a lot with little ceremony or - boilerplate. A hand-crafted C program might run faster than a Flix program, but it won't be as - short, concise, or expressive as the Flix program. Flix aims to be a language with powerful - constructs and high-level abstractions. This does not mean that Flix is slow. - - - - Flix aims to ensure program correctness and considers it more important than raw performance. - Languages such as C and C++ often rely on undefined behaviour to achieve stellar performance, - whereas most other languages, including Flix, try to eschew undefined behaviour in favor of - runtime checks for things that are hard to statically ensure. For example, most languages will - dynamically check that array accesses are not out of bounds. The cost is a small performance - hit, but in our view the benefit towards correctness is immense. Inspired by Ada, Flix aims to - offer strong guarantees, ideally ensured statically, but when necessary with dynamic checks. - - - - Flix is one programming language. The Flix compiler does not have feature flags or - compiler plugins that change or extend the semantics of the language. We want to avoid - fragmentation in the ecosystem where programs end up being written in different "dialects" of - the language. There is one language, now and forever. Of course that does not imply that the - language will not evolve over time. - - - - We should strive to adhere to the principle of least - surprise. That is, we should favor sane defaults, and when there is no immediately obvious - default, we should not have a default at all, but force the programmer to be explicit about his - or her intention. - - - - The Flix type system is based on Hindley-Milner which - supports full type inference. As a design choice, we require all functions to be annotated with - their argument and return types. We believe that requiring type signatures has three distinct - advantages that outweigh the disadvantages. -
    -
  1. Type signatures are useful as documentation and to aid program understanding.
  2. -
  3. Type signatures accurately assign blame for type errors.
  4. -
  5. Type signatures enable parallel type checking.
  6. -
- Of these, we think the former two are significantly more important than the latter. -
- - - While we require type signatures, we also believe that such signatures should be as minimal - as possible. Thus, while the full type signature of List.map is def map[a, - b](f: a -{'>'} b, xs: List[a]): List[b], we can use type parameter elision to - write it - simply as: def map(f: a -{'>'} b, xs: List[a]): List[b] omitting the type - arguments. - - - - Syntax is important. Semantics are important. But we should not confuse the two. A syntactic - issue should not be resolved by a enrichment of the semantics. For example, extension methods and implicit - classes seem to be semantic solutions to (mostly) syntactic issues. Flix aims to avoid such - pitfalls. - - - - The Flix syntax is inspired by Scala. We believe that short key words make it - easy to visually identify the overall structure of a piece of code. Flix tries to use - three letter keywords where appropriate: def, let, law, - rel, but not for commonly established concepts: if ... else - and match. - - - - Flix aims to have consistent and predictable syntax. As an example, we try to have the - syntax of types mirror that of expressions: -
    -
  • - A function application is written as f(a, b, c) whereas a type application - is written as f[a, b, c]. -
  • -
  • - A function expression is written as x -> x + 1 whereas a function type - is written as Int -> Int. -
  • -
  • - A tuple is written as (true, 12345) whereas a tuple type - is written as (Bool, Int). -
  • -
-
- - - In the spirit of Elm and Rust, - Flix aims to have human readable and understandable compiler messages. - Messages should describe the problem in - detail and provide information about the context, including suggestions for how to correct - the problem. - - - - Flix embraces the principle of least privilege. In Flix, declarations are hidden by default - (i.e. private) and cannot be accessed from outside of their namespace (or - sub-namespaces). - We believe it is important that programmers are forced to make a conscious - choice about when to make a declaration publicly visible. - - - - Flix requires all code to be available at compile-time. This enables a range of - compilation techniques, such as: - -
    -
  • Monomorphization to avoid unnecessary boxing of primitives.
  • -
  • Aggressive dead code elimination ("tree shaking") to remove unused functions.
  • -
  • Inlining across namespaces.
  • -
  • Whole-program analysis.
  • -
-
- - - We believe that a language should make it easy to make illegal states unrepresentable. - For example, algebraic data types can be used to precisely define the possible values of a type. - In Flix, in the future, we want to take this a step further, and allow refinement of some types. - For example, to express that some value must not only be an integer, but also that it must - fall within a range, e.g. [0-99]. -
- in progress -
- - - In Flix, main is the entry point of a program. - No (user-defined) code is ever executed before main. - No static initializers, no static fields. No class loaders. Main is always first. - This makes it easy to reason about startup behavior. - - - - Flix has a small standard library with a few common data types, - e.g. Option, List, Set, - and Map, but for these it offers a comprehensive collection of functionality. - For example, the standard library has more than 65 functions for working with lists. - We want the standard library to offer a common set of abstractions which are usable - by most programs, but not much else. - - - - In Flix things must be defined before they can be used. - Algebraic data types, functions, local variables, and other programming elements must be - declared before they can be used by other program parts. Declarations make it easy to assign - blame; we assume declarations to be correct and check every use against its declaration. - For example, an algebraic data type declares a set of cases, and the compiler checks that - every use refers to one of these cases, and that every case is covered. - - - - We believe that a programming language should reduce the volume of declarations - it requires. Declarations may be useful and are sometimes necessary, but Flix aims to - minimize its internal dependence on them. To give an example, Flix supports extensible - records which permits the usage of flexible and type-safe records without a strict - requirement that record types must be declared upfront. - - - - In Flix there is no global shared state. - This avoids a plethora of issues, including difficulties with initialization order and race - conditions in the presence of concurrency. A Flix programmer is free to construct some - state in the main function and pass it around, but there is no built-in mechanism to declare - global variables. In a real system, the programmer still has to deal with the state of - the world, e.g. the state of the file system, the network, and other resources. - - - - Flix follows the Go mantra: Do - not communicate by sharing memory; instead, share memory by - communicating. In other words: mutable memory should never be shared between processes. - Processes should only share immutable messages (and data structures). We believe this - significantly reduces the risk of race - conditions. - - - - We believe in the Midori Error - Model; that is, there are two kinds of errors: recoverable errors and program - bugs. Recoverable errors are things like illegal user input, network errors, etc. Errors - that can be anticipated and where there is a chance of recovery. Program bugs, on the other - hand, are unanticipated and we cannot expect to recover from them. We should treat these two - types of errors differently: For recoverable errors, we should enforce that they are checked - and handled. For program bugs, we should terminate execution as quickly as possible to prevent - data corruption and security issues. - - - - To aid debugging and prevent potential harmful behaviour, Flix aborts execution when an - unrecoverable error is encountered. - - In the presence of concurrency, if a process fails, Flix aborts the entire program. - This ensures that the outside environment is duly notified and can take corrective action, e.g. - to restart the program. - - - - Flix does not have and will not have a pre-processor. Programs that use pre-processing for - textual code generation are notoriously difficult to understand and debug. We want to avoid - that for Flix. Instead, Flix may some day have a macro system, but so far there has been little - need. - - - - Flix does not have the null value. The null value is now widely considered a - mistake and languages such as C#, Dart, Kotlin and Scala are scrambling to adopt mechanisms to - ensure non-nullness. - In Flix, we adopt the standard solution from functional languages which is to represent - the absence of a value using the Option type. This solution is simple to - understand, works well, and guarantees the absence of dreaded NullPointerExceptions. - - - - In Flix, a value of one type is never implicitly coerced or converted into a value of another - type. For example, - -
    -
  • No value is ever coerced to a boolean.
  • -
  • No value is ever coerced to a string.
  • -
  • Integers and floating-point are never truncated or promoted.
  • -
-
- - - Flix does not support reflection, i.e. the ability to inspect the structure of the program - at run-time. Reflection tends to break the kind of program reasoning that both compilers and - humans rely on. At some point in the future, Flix might support some notion of compile-time - meta programming. - - - - The Flix compiler never emits warnings; only compile-time errors which abort compilation. - Warnings can be ignored or turned off. People disagree on whether a warning is harmless or not. - For Flix, we believe that any code that appears troublesome or incorrect to the compiler should - outright be rejected. - - - - Inspired by Rust, - the Flix compiler will reject programs that contain unused declarations. We believe - that rejecting such programs will help programmers avoid mistakes where some algebraic data - type or function is unintentionally left unused. - - - - Flix disallows unused local variables, whether they are introduced by let, introduced by pattern - matching, or part of the formal parameters of a function. Research [1] [2] has repeatedly shown that minor - mistakes are a common source of bugs, e.g. using the wrong local variable. Disallowing unused - local variables help avoid such mistakes. - - - - Flix disallows variable shadowing for the same reasons why it disallows unused local variables: - it is a persistent and common source of minor mistakes leading to bugs. - - - - Flix does not support function overloading (using the same name for different functions). - Instead, Flix encourages the use of meaningful names, - e.g. Map.filter and Map.filterWithKey, for functions that share - similar functionality. - - - - Flix does not support variadic (varargs) functions. It is not clear to us how a language design - can support both currying and variadic functions cleanly. Moreover, it seems that the supposed - benefits of variadic functions is not that great in a language which already has concise syntax - for list and array literals. - - - - Flix does not support labelled function arguments. The motivation for labelled arguments is a - reasonable: to avoid calling a function with arguments of the same type, but in the wrong order. - Unfortunately, labelled function arguments do not work in the presence of higher-order - functions. Instead, we suggest to overcome the problem by either (i) using richer types - (e.g. Celsius instead of Int) or alternatively (ii) using record types - which can be used to emulate the same functionality and works with higher-order functions. - - - - Flix does not support binary or octal literals. It is our understanding that these features are - rarely used in practice. - - - - The Flix compiler enforces that pattern matches handle all cases of an algebraic data type. - If a match expression is found to be non-exhaustive, the program is rejected. We believe this - encourages more robust code and enables safer refactoring of algebraic data types. - - - - A few years ago HTML was all the rage. Hence it was only natural that Java adopted HTML-style - comments. A bit later, XML was all the rage, hence it was only natural that Scala - added support for native XML literals. Today, JSON and Markdown are all the rage, but if - history is any guide, we should not add any special support for these to Flix. - - - - Flix supports comments as part of the language. We believe such integration avoids fragmentation - of the ecosystem and ultimately leads to better tool support. - - - - Flix supports unit tests as part of the language. We believe such integration avoids - fragmentation of the ecosystem and ultimately leads to better tool support. - - -
- -

Compiler Message Principles

- -

- Compiler messages are the main interface between Flix and programmers. We should invest into it. -

- - - -

- The rule states that 80% of the time a developer will need minimal information to understand - a compiler message. Most likely the developer will already have seen the specific - error message hundreds of times before. But 20% of the time, the developer will - never have seen the message before and will need more information. -

- -

- Flix compiler messages should accommodate both scenarios. -

-
- - -

- A compiler message consists of three components: - -

    -
  • Summary: A one sentence summary. The message shown on hover in Visual Studio - Code. -
  • -
  • Message: A multi-line text that contains all relevant details, including - the program symbol(s) and fragment(s) relevant for the message. -
  • -
  • Explanation: A description of why the problem occurs and what can be done - to fix it. -
  • -
-

-
- - -

- A message should be crisp, concise, and clear. - The language should be friendly or neutral. An error message should not blame - the programmer. For example, we should prefer Unexpected foo over Illegal - foo, since the latter implies that the programmer did something wrong. -

-
- - -

- The error message: Duplicate definition: 'foo' is better than the error - message: The definition 'foo' is defined twice because in the former the - programmer only has to scan the first word to understand what is wrong. -

-
- - -

- When relevant, a Flix compiler error should explain how Flix differs from other languages - and explain how the specific problem can be solved in Flix. -

-
-
- -

Type Class Principles

- - - -

- A type class is conceptually a function from a type to a set of lawful - operations (called signatures) on values of that type. -

- -

- For example, the Eq type class takes a type and returns - the eq and neq functions where eq must be reflexive, - symmetric, and transitive, and neq must be - the negation of eq. -

-
- - -

- Every type class must specify a collection of laws that instances of the type - class must satisfy. If a type class does not specify any laws it is lawless. -

- -

- Here are some examples of lawful and lawless type classes: - -

    -
  • Lawful: Eq, Order, Functor, Foldable. -
  • -
  • Lawless: FromString, ToString, Add.
  • -
-

- -

- A type class is lawful if every signature of the class is used in at least one law. -

- -

- Note: Laws are not checked by the compiler – that is an undecidable - problem – but they may be used in a future SmallCheck / QuickCheck library. -

- -
- - -

- A sub-class (i.e. a type class A that refines a type class B) must - specify additional laws that its instances must satisfy. -

- -

- For example, the Applicative type class extends the Functor type - class with additional operations and laws. -

-
- - - An instance must be declared in the same namespace as either: - -
    -
  1. the type class declaration, or
  2. -
  3. the type declaration of the instance
  4. -
-
- - - A type class may be declared sealed in which case no further instances, other than - those in the same namespace, can be defined. A sealed type class can be used when the programmer - wants to maintain tight control over what instances should be permitted. - - - -

- The Flix compiler ensures that the selection of type class instances is always unambiguous. -

- -

- In the future, we may allow a limited form of overlapping instances. -

-
- - -

- Every type class belongs to a namespace. Hence it is possible to define multiple operations - with the same name, as long as they belong to type classes in different namespaces. -

- -

- Every type class also defines a companion namespace which typically holds functions - that are not part of the type class, but nevertheless are related to the functionality of - the type class. -

-
- - -

- Type classes may provide default implementations of functions. -

- -

- For example, the Foldable type class may provide default function - implementations, e.g. count and length, based on - the foldLeft and foldRight signatures defined in that class. -

- -

- A default implementation can always be overriden by a specific type class instance. - For example, to provide a more efficient version. -

-
- - - A type class instance that wants to override a default implementation must explicitly do - so using the override keyword. This ensures that there are no dangling overrides, - i.e. functions definitions that do not match any signature of the type class. - - -
- -

Library Principles

- - - - - The Flix standard library is implemented in Flix. It has no special support from the compiler. - If you don't like it or if you don't need it, you can replace it. - - - - The Flix prelude contains algebraic data types and functions that are imported into every - compilation unit. Therefore we aim to keep the prelude very small and only include extremely - common functionality. - - - - In Flix, every mutable data structure supports functional operations. - For example, mutable collections, such as Array and MutSet support - the map operation. Flix, being functional-first, reserves functional names for - functional operations. Across the standard library map has the same name and the - same type signature. - - - - In Flix, every destructive operation is suffixed with an exclamation point. For - example, Array.reverse(a) returns a new array with the elements - of a in reverse - order, whereas Array.reverse!(a) destructively re-orders the elements - of a. Note: This principle applies to destructive operations that operate on data - structures, not to impure functions in general, e.g. Console.printLine. - - - - In Flix, functional and destructive operations that share (i) similar behavior and (ii) similar - type signatures share similar names. For - example, Array.reverse and Array.reverse! share the - same name. On the other hand, Array.transform! is - called transform! and not map! because its type signature is - dissimilar to map (i.e. map works on functions of type a -{'>'} b, but transform - requires functions of type a -{'>'} a.) - - - - -
- ); - } + componentDidMount() { + document.title = 'Flix | Principles' + } + + render() { + return ( + +

Design Principles

+ +

+ We believe that the development of a programming language should follow a set of principles. That is, when a + design decision is made there should exist some rationale for why that decision was made. By outlining these + principles, as we develop Flix, we hope to keep ourselves honest and to communicate the kind of language Flix + aspires to be. +

+ +

+ Many of these ideas and principles come from languages that have inspired Flix, including Ada, Elm, F#, Go, + Haskell, OCaml, Rust, and Scala. +

+ +

+ Update: The Flix Principles has been published in a paper at Onward! '22. Read it here:{' '} + The Principles of the Flix Programming Language. +

+ +

Language Principles

+ + + + We believe in Rich Hickey's creed:{' '} + simple is not easy. We prefer a language + that gets things right to one that makes things easy. Such a language might take longer to learn in the + short run, but its simplicity pays off in the long run. + + + + Flix is a functional language and embraces the idea that everything should be an expression. Flix has no + local variable declarations or if-then-else statements, instead it has let-bindings and if-then-else + expressions. However, Flix does not take this idea as far as the Scheme languages. Flix still has + declarations, namespaces, and so forth that are not expressions. + + + + Flix supports functional, imperative, and logic programming. The type and effect system of Flix cleanly and + safely separates pure code from impure code. That is, if a function is pure then the programmer can trust + that the function behaves like a mathematical function: it returns the same value when given the same + arguments and it has no side-effects. + + + + Flix aims to support developer productivity; the ability to do a lot with little ceremony or boilerplate. A + hand-crafted C program might run faster than a Flix program, but it won't be as short, concise, or + expressive as the Flix program. Flix aims to be a language with powerful constructs and high-level + abstractions. This does not mean that Flix is slow. + + + + Flix aims to ensure program correctness and considers it more important than raw performance. Languages such + as C and C++ often rely on undefined behaviour to achieve stellar performance, whereas most other languages, + including Flix, try to eschew undefined behaviour in favor of runtime checks for things that are hard to + statically ensure. For example, most languages will dynamically check that array accesses are not out of + bounds. The cost is a small performance hit, but in our view the benefit towards correctness is immense. + Inspired by Ada, Flix aims to offer strong guarantees, ideally ensured statically, but when necessary with + dynamic checks. + + + + Flix is one programming language. The Flix compiler does not have feature flags or compiler plugins + that change or extend the semantics of the language. We want to avoid fragmentation in the ecosystem where + programs end up being written in different "dialects" of the language. There is one language, now and + forever. Of course that does not imply that the language will not evolve over time. + + + + We should strive to adhere to the{' '} + principle of least surprise. + That is, we should favor sane defaults, and when there is no immediately obvious default, we should not have + a default at all, but force the programmer to be explicit about his or her intention. + + + + The Flix type system is based on{' '} + Hindley-Milner which supports + full type inference. As a design choice, we require all functions to be annotated with their argument and + return types. We believe that requiring type signatures has three distinct advantages that outweigh the + disadvantages. +
    +
  1. Type signatures are useful as documentation and to aid program understanding.
  2. +
  3. Type signatures accurately assign blame for type errors.
  4. +
  5. Type signatures enable parallel type checking.
  6. +
+ Of these, we think the former two are significantly more important than the latter. +
+ + + While we require type signatures, we also believe that such signatures should be as minimal as possible. + Thus, while the full type signature of List.map is{' '} + def map[a, b](f: a -{'>'} b, xs: List[a]): List[b], we can use type parameter elision to + write it simply as: def map(f: a -{'>'} b, xs: List[a]): List[b] omitting the type arguments. + + + + Syntax is important. Semantics are important. But we should not confuse the two. A syntactic issue should + not be resolved by a enrichment of the semantics. For example,{' '} + extension methods and{' '} + implicit classes seem to be + semantic solutions to (mostly) syntactic issues. Flix aims to avoid such pitfalls. + + + + The Flix syntax is inspired by Scala. We believe that short key words make it easy to visually identify the + overall structure of a piece of code. Flix tries to use three letter keywords where appropriate:{' '} + def, let, law,rel, but not for commonly established + concepts: if ... else + and match. + + + + Flix aims to have consistent and predictable syntax. As an example, we try to have the syntax of types + mirror that of expressions: +
    +
  • + A function application is written as f(a, b, c) whereas a type application is written as{' '} + f[a, b, c]. +
  • +
  • + A function expression is written as x -> x + 1 whereas a function type is written as{' '} + Int -> Int. +
  • +
  • + A tuple is written as (true, 12345) whereas a tuple type is written as{' '} + (Bool, Int). +
  • +
+
+ + + In the spirit of Elm and{' '} + Rust, Flix aims to have + human readable and understandable compiler messages. Messages should describe the problem in detail and + provide information about the context, including suggestions for how to correct the problem. + + + + Flix embraces the principle of least privilege. In Flix, declarations are hidden by default (i.e. private) + and cannot be accessed from outside of their namespace (or sub-namespaces). We believe it is important that + programmers are forced to make a conscious choice about when to make a declaration publicly visible. + + + + Flix requires all code to be available at compile-time. This enables a range of compilation techniques, such + as: +
    +
  • Monomorphization to avoid unnecessary boxing of primitives.
  • +
  • Aggressive dead code elimination ("tree shaking") to remove unused functions.
  • +
  • Inlining across namespaces.
  • +
  • Whole-program analysis.
  • +
+
+ + + We believe that a language should make it easy to make illegal states unrepresentable. For example, + algebraic data types can be used to precisely define the possible values of a type. In Flix, in the future, + we want to take this a step further, and allow refinement of some types. For example, to express that some + value must not only be an integer, but also that it must fall within a range, e.g. [0-99]. +
+ in progress +
+ + + In Flix, main is the entry point of a program. No (user-defined) code is ever executed before{' '} + main. No static initializers, no static fields. No class loaders. Main is always first. This + makes it easy to reason about startup behavior. + + + + Flix has a small standard library with a few common data types, e.g. Option, List,{' '} + Set, and Map, but for these it offers a comprehensive collection of functionality. + For example, the standard library has more than 65 functions for working with lists. We want the standard + library to offer a common set of abstractions which are usable by most programs, but not much else. + + + + In Flix things must be defined before they can be used. Algebraic data types, functions, local variables, + and other programming elements must be declared before they can be used by other program parts. Declarations + make it easy to assign blame; we assume declarations to be correct and check every use against its + declaration. For example, an algebraic data type declares a set of cases, and the compiler checks that every + use refers to one of these cases, and that every case is covered. + + + + We believe that a programming language should reduce the volume of declarations it requires. + Declarations may be useful and are sometimes necessary, but Flix aims to minimize its internal dependence on + them. To give an example, Flix supports extensible records which permits the usage of flexible and + type-safe records without a strict requirement that record types must be declared upfront. + + + + In Flix there is no global shared state. This avoids a plethora of issues, including difficulties with + initialization order and race conditions in the presence of concurrency. A Flix programmer is free to + construct some state in the main function and pass it around, but there is no built-in mechanism to declare + global variables. In a real system, the programmer still has to deal with the state of the world, e.g. the + state of the file system, the network, and other resources. + + + + Flix follows the Go mantra:{' '} + + Do not communicate by sharing memory; instead, share memory by communicating. + {' '} + In other words: mutable memory should never be shared between processes. Processes should only share + immutable messages (and data structures). We believe this significantly reduces the risk of{' '} + race conditions. + + + + We believe in the Midori Error Model; that + is, there are two kinds of errors: recoverable errors and program bugs. Recoverable errors are + things like illegal user input, network errors, etc. Errors that can be anticipated and where there is a + chance of recovery. Program bugs, on the other hand, are unanticipated and we cannot expect to recover from + them. We should treat these two types of errors differently: For recoverable errors, we should enforce that + they are checked and handled. For program bugs, we should terminate execution as quickly as possible to + prevent data corruption and security issues. + + + + To aid debugging and prevent potential harmful behaviour, Flix aborts execution when an unrecoverable error + is encountered. In the presence of concurrency, if a process fails, Flix aborts the entire program. This + ensures that the outside environment is duly notified and can take corrective action, e.g. to restart the + program. + + + + Flix does not have and will not have a pre-processor. Programs that use pre-processing for textual code + generation are notoriously difficult to understand and debug. We want to avoid that for Flix. Instead, Flix + may some day have a macro system, but so far there has been little need. + + + + Flix does not have the null value. The null value is now widely considered a mistake and + languages such as C#, Dart, Kotlin and Scala are scrambling to adopt mechanisms to ensure non-nullness. In + Flix, we adopt the standard solution from functional languages which is to represent the absence of a value + using the Option type. This solution is simple to understand, works well, and guarantees the + absence of dreaded NullPointerExceptions. + + + + In Flix, a value of one type is never implicitly coerced or converted into a value of another type. For + example, +
    +
  • No value is ever coerced to a boolean.
  • +
  • No value is ever coerced to a string.
  • +
  • Integers and floating-point are never truncated or promoted.
  • +
+
+ + + Flix does not support reflection, i.e. the ability to inspect the structure of the program at run-time. + Reflection tends to break the kind of program reasoning that both compilers and humans rely on. At some + point in the future, Flix might support some notion of compile-time meta programming. + + + + The Flix compiler never emits warnings; only compile-time errors which abort compilation. Warnings can be + ignored or turned off. People disagree on whether a warning is harmless or not. For Flix, we believe that + any code that appears troublesome or incorrect to the compiler should outright be rejected. + + + + Inspired by Rust, the Flix + compiler will reject programs that contain unused declarations. We believe that rejecting such programs will + help programmers avoid mistakes where some algebraic data type or function is unintentionally left unused. + + + + Flix disallows unused local variables, whether they are introduced by let, introduced by pattern matching, + or part of the formal parameters of a function. Research [ + 1] [ + 2] has repeatedly shown that minor mistakes are a + common source of bugs, e.g. using the wrong local variable. Disallowing unused local variables help avoid + such mistakes. + + + + Flix disallows variable shadowing for the same reasons why it disallows unused local variables: it is a + persistent and common source of minor mistakes leading to bugs. + + + + Flix does not support function overloading (using the same name for different functions). Instead, Flix + encourages the use of meaningful names, e.g. Map.filter and Map.filterWithKey, for + functions that share similar functionality. + + + + Flix does not support variadic (varargs) functions. It is not clear to us how a language design can support + both currying and variadic functions cleanly. Moreover, it seems that the supposed benefits of variadic + functions is not that great in a language which already has concise syntax for list and array literals. + + + + Flix does not support labelled function arguments. The motivation for labelled arguments is a reasonable: to + avoid calling a function with arguments of the same type, but in the wrong order. Unfortunately, labelled + function arguments do not work in the presence of higher-order functions. Instead, we suggest to overcome + the problem by either (i) using richer types (e.g. Celsius instead of Int) or + alternatively (ii) using record types which can be used to emulate the same functionality and works with + higher-order functions. + + + + Flix does not support binary or octal literals. It is our understanding that these features are rarely used + in practice. + + + + The Flix compiler enforces that pattern matches handle all cases of an algebraic data type. If a match + expression is found to be non-exhaustive, the program is rejected. We believe this encourages more robust + code and enables safer refactoring of algebraic data types. + + + + A few years ago HTML was all the rage. Hence it was only natural that Java adopted HTML-style comments. A + bit later, XML was all the rage, hence it was only natural that Scala added support for native XML literals. + Today, JSON and Markdown are all the rage, but if history is any guide, we should not add any special + support for these to Flix. + + + + Flix supports comments as part of the language. We believe such integration avoids fragmentation of the + ecosystem and ultimately leads to better tool support. + + + + Flix supports unit tests as part of the language. We believe such integration avoids fragmentation of the + ecosystem and ultimately leads to better tool support. + +
+ +

Compiler Message Principles

+ +

Compiler messages are the main interface between Flix and programmers. We should invest into it.

+ + + +

+ The rule states that 80% of the time a developer will need minimal information to understand a compiler + message. Most likely the developer will already have seen the specific error message hundreds of times + before. But 20% of the time, the developer will never have seen the message before and will need more + information. +

+ +

Flix compiler messages should accommodate both scenarios.

+
+ + +

+ A compiler message consists of three components: +

    +
  • + Summary: A one sentence summary. The message shown on hover in Visual Studio Code. +
  • +
  • + Message: A multi-line text that contains all relevant details, including the program symbol(s) + and fragment(s) relevant for the message. +
  • +
  • + Explanation: A description of why the problem occurs and what can be done to fix it. +
  • +
+

+
+ + +

+ A message should be crisp, concise, and clear. The language should be friendly or + neutral. An error message should not blame the programmer. For example, we should prefer{' '} + Unexpected foo over Illegal foo, since the latter implies that the programmer + did something wrong. +

+
+ + +

+ The error message: Duplicate definition: 'foo' is better than the error message:{' '} + The definition 'foo' is defined twice because in the former the programmer only has to scan + the first word to understand what is wrong. +

+
+ + +

+ When relevant, a Flix compiler error should explain how Flix differs from other languages and explain how + the specific problem can be solved in Flix. +

+
+
+ +

Type Class Principles

+ + + +

+ A type class is conceptually a function from a type to a set of lawful operations (called + signatures) on values of that type. +

+ +

+ For example, the Eq type class takes a type and returns the eq and{' '} + neq functions where eq must be reflexive, symmetric, and transitive, and{' '} + neq must be the negation of eq. +

+
+ + +

+ Every type class must specify a collection of laws that instances of the type class must satisfy. If a + type class does not specify any laws it is lawless. +

+ +

+ Here are some examples of lawful and lawless type classes: +

    +
  • + Lawful: Eq, Order, Functor, Foldable. +
  • +
  • + Lawless: FromString, ToString, Add. +
  • +
+

+ +

A type class is lawful if every signature of the class is used in at least one law.

+ +

+ Note: Laws are not checked by the compiler – that is an undecidable problem – but they may be + used in a future SmallCheck / QuickCheck library. +

+
+ + +

+ A sub-class (i.e. a type class A that refines a type class B) must specify + additional laws that its instances must satisfy. +

+ +

+ For example, the Applicative type class extends the Functor type class with + additional operations and laws. +

+
+ + + An instance must be declared in the same namespace as either: +
    +
  1. the type class declaration, or
  2. +
  3. the type declaration of the instance
  4. +
+
+ + + A type class may be declared sealed in which case no further instances, other than those in the + same namespace, can be defined. A sealed type class can be used when the programmer wants to maintain tight + control over what instances should be permitted. + + + +

The Flix compiler ensures that the selection of type class instances is always unambiguous.

+ +

In the future, we may allow a limited form of overlapping instances.

+
+ + +

+ Every type class belongs to a namespace. Hence it is possible to define multiple operations with the same + name, as long as they belong to type classes in different namespaces. +

+ +

+ Every type class also defines a companion namespace which typically holds functions that are not + part of the type class, but nevertheless are related to the functionality of the type class. +

+
+ + +

Type classes may provide default implementations of functions.

+ +

+ For example, the Foldable type class may provide default function implementations, e.g.{' '} + count and length, based on the foldLeft and foldRight{' '} + signatures defined in that class. +

+ +

+ A default implementation can always be overriden by a specific type class instance. For example, to + provide a more efficient version. +

+
+ + + A type class instance that wants to override a default implementation must explicitly do so using the{' '} + override keyword. This ensures that there are no dangling overrides, i.e. functions definitions + that do not match any signature of the type class. + +
+ +

Library Principles

+ + + + The Flix standard library is implemented in Flix. It has no special support from the compiler. If you don't + like it or if you don't need it, you can replace it. + + + + The Flix prelude contains algebraic data types and functions that are imported into every compilation unit. + Therefore we aim to keep the prelude very small and only include extremely common functionality. + + + + In Flix, every mutable data structure supports functional operations. For example, mutable collections, such + as Array and MutSet support the map operation. Flix, being + functional-first, reserves functional names for functional operations. Across the standard library{' '} + map has the same name and the same type signature. + + + + In Flix, every destructive operation is suffixed with an exclamation point. For example,{' '} + Array.reverse(a) returns a new array with the elements of a in reverse order, + whereas Array.reverse!(a) destructively re-orders the elements of a. Note: This + principle applies to destructive operations that operate on data structures, not to impure functions in + general, e.g. Console.printLine. + + + + In Flix, functional and destructive operations that share (i) similar behavior and (ii) similar type + signatures share similar names. For example, Array.reverse and Array.reverse!{' '} + share the same name. On the other hand, Array.transform! is called transform! and + not map! because its type signature is dissimilar to map (i.e. map works on functions of type{' '} + a -{'>'} b, but transform requires functions of type a -{'>'} a.) + + +
+ ) + } } class Principle extends Component { - render() { - return ( - - - {this.props.name} - {this.props.children} - - - ); - } + render() { + return ( + + + {this.props.name} + {this.props.children} + + + ) + } } -export default Principles; +export default Principles diff --git a/src/page/Research.js b/src/page/Research.js index 14b2c33..03bba1a 100644 --- a/src/page/Research.js +++ b/src/page/Research.js @@ -1,159 +1,166 @@ -import React, {Component} from 'react'; -import {Container} from "reactstrap"; +import React, { Component } from 'react' +import { Container } from 'reactstrap' class Research extends Component { - - componentDidMount() { - document.title = "Flix | Research"; - } - - render() { - return ( - -

Research

- -

- Research on Flix takes place at Aarhus - University and the University of Waterloo. -

- -

- Below is a selection of research papers published on Flix: -

- -

Talks

- - - -

Peer-Reviewed

- -
    - - - - - - - - - - - - - - - - - -
- -

Workshops

- -
    - -
- -

Theses

- -
    - -
- -

Media

- - - -
- ); - } + componentDidMount() { + document.title = 'Flix | Research' + } + + render() { + return ( + +

Research

+ +

+ Research on Flix takes place at{' '} + Aarhus University and the{' '} + University of Waterloo. +

+ +

Below is a selection of research papers published on Flix:

+ +

Talks

+ + + +

Peer-Reviewed

+ +
    + + + + + + + + + + + + + + + + + +
+ +

Workshops

+ +
    + +
+ +

Theses

+ +
    + +
+ +

Media

+ + +
+ ) + } } class Paper extends Component { - render() { - return ( -
  • - {this.props.title} [{this.props.venue}] -
  • - ); - } + render() { + return ( +
  • + {this.props.title} [{this.props.venue}] +
  • + ) + } } -export default Research; +export default Research diff --git a/src/page/blog/DesignFlaws.js b/src/page/blog/DesignFlaws.js index cbba24f..20c40f5 100644 --- a/src/page/blog/DesignFlaws.js +++ b/src/page/blog/DesignFlaws.js @@ -1,318 +1,281 @@ -import React, {Component} from "react"; -import {Col, Container, Row} from "reactstrap"; -import InlineEditor from "../../util/InlineEditor"; +import React, { Component } from 'react' +import { Col, Container, Row } from 'reactstrap' +import InlineEditor from '../../util/InlineEditor' class DesignFlaws extends Component { - - componentDidMount() { - document.title = "Flix | Design Flaws in Flix"; - } - - render() { - return ( - - - - -

    Design Flaws in Flix

    - -

    - Posted January 2020 by Magnus Madsen. -

    - -

    - Inspired by the blog post Design Flaws in - Futhark, I decided to take stock and reflect on some of the design flaws that I believe - we made during the development of the Flix programming language. I went through old - Github issues and pull requests to discover some of the challenging issues that we have been - or still are struggling with. I will classify the design flaws into four categories: - (i) design flaws that still plague the Flix language, (ii) design flaws that have been - fixed, (iii) poor designs that were thankfully never implemented, and finally (iv) design - choices where the jury is still out. -

    - -

    - I want to emphasize that language design and implementation is a herculean task and that - there are features planned for Flix which have not yet been implemented. The lack of a - specific feature is not a design flaw, but rather a question of when we can get around to - it. -

    - -

    Design Flaws Present in Flix

    - -

    - The following design flaws are still present in Flix. Hopefully some day they will be fixed. -

    - -
    The Switch Expression
    - -

    - Flix supports the switch expression: -

    - - - {`switch { + componentDidMount() { + document.title = 'Flix | Design Flaws in Flix' + } + + render() { + return ( + + + +

    Design Flaws in Flix

    + +

    + Posted January 2020 by Magnus Madsen. +

    + +

    + Inspired by the blog post{' '} + + Design Flaws in Futhark + + , I decided to take stock and reflect on some of the design flaws that I believe we made during the + development of the Flix programming language. I went through old Github issues and pull requests to + discover some of the challenging issues that we have been or still are struggling with. I will classify + the design flaws into four categories: (i) design flaws that still plague the Flix language, (ii) design + flaws that have been fixed, (iii) poor designs that were thankfully never implemented, and finally (iv) + design choices where the jury is still out. +

    + +

    + I want to emphasize that language design and implementation is a herculean task and that there are + features planned for Flix which have not yet been implemented. The lack of a specific feature is not a + design flaw, but rather a question of when we can get around to it. +

    + +

    Design Flaws Present in Flix

    + +

    The following design flaws are still present in Flix. Hopefully some day they will be fixed.

    + +
    The Switch Expression
    + +

    + Flix supports the switch expression: +

    + + + {`switch { case cond1 => exp1 case cond2 => exp2 case cond3 => exp3 }`} - - -

    - where the boolean expressions cond1, cond2, - and cond3 are evaluated from top to bottom until one of them returns true and - then its associated body expression is evaluated. The idea, quite simply, is to have a - control-flow structure that visually resembles an ordinary pattern match, but where there is - no match value. -

    - -

    - In hind-sight, the switch expression is nothing more than a - glorified if-then-else-if construct that does not carry its own weight. - It is an expenditure on the complexity and strangeness budget that offers almost no gain - over using plain if-then-else-if. Moreover, it is error-prone, because it lacks - and explicit else branch in case none of the conditions evaluate to true. We - plan to remove it in future versions of Flix. -

    - -
    String Concatenation with Plus
    - -

    - Like most contemporary languages, Flix uses + for string concatenation. While - this is an uncontroversial design choice, it does not make much sense since strings are not - commutative, e.g. "abc" + "def" is not the same as "def" + - "abc". A better alternative would be to use ++ as in Haskell. However, I - believe an even better design choice would be to forgo string concatenation and - instead rely entirely on string interpolation. String interpolation is a much more powerful - and elegant solution to the problem of building complex strings. -

    - -

    Design Flaws No Longer Present in Flix

    - -

    - The following design flaws have been fixed. -

    - -
    Compilation of Option to Null
    - -

    - Flix compiles to JVM bytecode and runs on the virtual machine. An earlier version of Flix - had an optimization that would take the Option enum: -

    - - - {`Option[a] { + + +

    + where the boolean expressions cond1, cond2, and cond3 are evaluated + from top to bottom until one of them returns true and then its associated body expression is evaluated. + The idea, quite simply, is to have a control-flow structure that visually resembles an ordinary pattern + match, but where there is no match value. +

    + +

    + In hind-sight, the switch expression is nothing more than a glorified{' '} + if-then-else-if construct that does not carry its own weight. It is an expenditure on the + complexity and strangeness budget that offers almost no gain over using plain if-then-else-if + . Moreover, it is error-prone, because it lacks and explicit else branch in case none of the + conditions evaluate to true. We plan to remove it in future versions of Flix. +

    + +
    String Concatenation with Plus
    + +

    + Like most contemporary languages, Flix uses + for string concatenation. While this is an + uncontroversial design choice, it does not make much sense since strings are not commutative, e.g.{' '} + "abc" + "def" is not the same as "def" + "abc". A better alternative + would be to use ++ as in Haskell. However, I believe an even better design choice would be to + forgo string concatenation and instead rely entirely on string interpolation. String interpolation is a + much more powerful and elegant solution to the problem of building complex strings. +

    + +

    Design Flaws No Longer Present in Flix

    + +

    The following design flaws have been fixed.

    + +
    Compilation of Option to Null
    + +

    + Flix compiles to JVM bytecode and runs on the virtual machine. An earlier version of Flix had an + optimization that would take the Option enum: +

    + + + {`Option[a] { case None, case Some(a) }`} - - -

    - and compile the None value to null and Some(a) to - the underlying value of a. The idea was to save allocation and de-allocation - of Some values, speeding up evaluation. -

    - -

    - But, this screws up interoperability with Java libraries. In Java null might - be given a special meaning that is incompatible with the meaning None. For - example, certain Java collections cannot contain null and trying to - put None into one of these would raise an unexpected exception. Consequently, - Flix no longer has this optimization. -

    - -
    Useless Library Functions
    - -

    - Flix aims to have a robust standard library that avoids some of the pitfalls of other - standard libraries. We have been particularly focused on two aspects: (i) ensuring that - functions and types have consistent names, e.g. map is - named map for both Option and List, and (ii) to - avoid partial functions, such as List.head and List.tail which are - not defined for empty lists. -

    - -

    - Yet, despite these principles, we still managed to implement some problematic functions in - the library. For example, we used to have the - functions Option.isNone and Options.isSome. The problem with these - functions is that they are not really useful and they lead to brittle code. For - example, if Options.isSome returns true then that - information cannot be used to unwrap the option anyway. Thus such functions are not - really useful. -

    - -

    Function Call Syntax

    - -

    - Inspired by Scala, early versions of Flix did not always use parentheses to mark a - function call. For example, the function: -

    - - - {`def f: Int32 = 21`} - - -

    - could be called by writing: -

    - - - {`def g: Int32 = f + 42 // returns 63`} - - -

    - The problem with this design is at least two-fold: (i) it hides when a function is applied, - which is terrible in a language with side-effects, and (ii) how does one express the - closure of f? (In Scala the answer is to write f _ ). -

    - -

    - Today, in Flix, the code is written as: -

    - - - - {`def f(): Int32 = 21 + + +

    + and compile the None value to null and Some(a) to the underlying + value of a. The idea was to save allocation and de-allocation of Some values, + speeding up evaluation. +

    + +

    + But, this screws up interoperability with Java libraries. In Java null might be given a + special meaning that is incompatible with the meaning None. For example, certain Java + collections cannot contain null and trying to put None into one of these would + raise an unexpected exception. Consequently, Flix no longer has this optimization. +

    + +
    Useless Library Functions
    + +

    + Flix aims to have a robust standard library that avoids some of the pitfalls of other standard libraries. + We have been particularly focused on two aspects: (i) ensuring that functions and types have consistent + names, e.g. map is named map for both Option and List, + and (ii) to avoid partial functions, such as List.head and List.tail which are + not defined for empty lists. +

    + +

    + Yet, despite these principles, we still managed to implement some problematic functions in the library. + For example, we used to have the functions Option.isNone and Options.isSome. The + problem with these functions is that they are not really useful and they lead to brittle code. For + example, if Options.isSome returns true then that information cannot be + used to unwrap the option anyway. Thus such functions are not really useful. +

    + +

    Function Call Syntax

    + +

    + Inspired by Scala, early versions of Flix did not always use parentheses to mark a function call. For + example, the function: +

    + + {`def f: Int32 = 21`} + +

    could be called by writing:

    + + {`def g: Int32 = f + 42 // returns 63`} + +

    + The problem with this design is at least two-fold: (i) it hides when a function is applied, which is + terrible in a language with side-effects, and (ii) how does one express the closure of f? (In + Scala the answer is to write f _ ). +

    + +

    Today, in Flix, the code is written as:

    + + + {`def f(): Int32 = 21 def g: Int32 = f() + 42 // returns 63`} - - -

    - which makes it clear when there is a function call. -

    - -
    Infix Type Application
    - -

    - In Flix, a function f can be called with the - arguments x and y in three ways: In standard prefix-style f(x, - y), in infix-style x `f` y, and in postfix-style x.f(y). - The latter is also sometimes referred to as universal function call syntax. I personally - feel reasonably confident that all three styles are worth supporting. The postfix-style fits - well for function calls such as a.length() where - the length function feels closely associated with the receiver argument. The - infix-style fits well with user-defined binary operations such as x `lub` - y where lub is the least upper bound - of x and y. And of course the prefix-style is the standard way - to perform a function call. -

    - -

    - Type constructors, such as Option and Result can be thought of - a special type of functions. Hence, it makes sense that their syntax should mirror function - applications. For example, we can write the type - applications Option[Int32] and Result[Int32, - Int32] mirroring the prefix style of regular function applications. Similarly, for a - while, Flix supported infix and postfix type applications. That is, the former could - also be expressed as: Int32.Option[] and Int32.Result[Int32], or even - as Int32 `Result` Int32. Thankfully, those days are gone. Striving for such - uniformity in every place does not seem worth it. -

    - -
    Unit Tests that Manually Construct Abstract Syntax Trees
    - -

    - The Flix compiler comes with more than 6,500 manually written unit tests. Each unit test is - a Flix function that performs some computation, often with an expected result. The unit - tests are expressed in Flix itself. For example: -

    - - - {`@test + + +

    which makes it clear when there is a function call.

    + +
    Infix Type Application
    + +

    + In Flix, a function f can be called with the arguments x and y in + three ways: In standard prefix-style f(x, y), in infix-style x `f` y, and in + postfix-style x.f(y). The latter is also sometimes referred to as universal function call + syntax. I personally feel reasonably confident that all three styles are worth supporting. The + postfix-style fits well for function calls such as a.length() where the length{' '} + function feels closely associated with the receiver argument. The infix-style fits well with user-defined + binary operations such as x `lub` y where lub is the least upper bound of{' '} + x and y. And of course the prefix-style is the standard way to perform a + function call. +

    + +

    + Type constructors, such as Option and Result can be thought of a special type of + functions. Hence, it makes sense that their syntax should mirror function applications. For example, we + can write the type applications Option[Int32] and Result[Int32, Int32] mirroring + the prefix style of regular function applications. Similarly, for a while, Flix supported infix and + postfix type applications. That is, the former could also be expressed as:{' '} + Int32.Option[] and Int32.Result[Int32], or even as{' '} + Int32 `Result` Int32. Thankfully, those days are gone. Striving for such uniformity in every + place does not seem worth it. +

    + +
    Unit Tests that Manually Construct Abstract Syntax Trees
    + +

    + The Flix compiler comes with more than 6,500 manually written unit tests. Each unit test is a Flix + function that performs some computation, often with an expected result. The unit tests are expressed in + Flix itself. For example: +

    + + + {`@test def testArrayStore01(): Unit = let x = [1]; x[0] = 42`} - +
    -

    - In earlier versions of Flix such unit tests were expressed by manually constructing "small" - abstract syntax tree fragments. For example, the above test would be expressed as something - like: -

    +

    + In earlier versions of Flix such unit tests were expressed by manually constructing "small" abstract + syntax tree fragments. For example, the above test would be expressed as something like: +

    - - {`Let(Var("x", ...), ArrayNew(...), ArrayStore(Var("x"), Int32(0), Int32(42)))`} - + + {`Let(Var("x", ...), ArrayNew(...), ArrayStore(Var("x"), Int32(0), Int32(42)))`} + -

    - The problem with such tests is at least two-fold: (i) the tests turn out to be anything - but small and (ii) maintenance becomes an absolute nightmare. I found that the surface - syntax of Flix has remained relatively stable over time, but the abstract syntax trees - have changed frequently, making maintenance of such test cases tedious and time - consuming. -

    +

    + The problem with such tests is at least two-fold: (i) the tests turn out to be anything but small and (ii) + maintenance becomes an absolute nightmare. I found that the surface syntax of Flix has remained relatively + stable over time, but the abstract syntax trees have changed frequently, making maintenance of such test + cases tedious and time consuming. +

    -

    Bad Ideas that were Never Implemented

    +

    Bad Ideas that were Never Implemented

    -

    - These ideas were fortunately never implemented in Flix. -

    +

    These ideas were fortunately never implemented in Flix.

    -
    The Itself Keyword
    +
    The Itself Keyword
    -

    - The idea was to introduce a special keyword that within a pattern match would refer to - the match value. For example: -

    +

    + The idea was to introduce a special keyword that within a pattern match would refer to the match value. + For example: +

    - - {`def foo(e: Exp): Exp = match e { + + {`def foo(e: Exp): Exp = match e { // ... many lines ... case IfThenElse(e1, e2, e3) => itself // refers to the value of e. }`} - - -

    - The keyword itself refers to the value of the match expression, i.e. the - value of e. The idea was that in very large and complicated pattern matches, - with many local variables, the itself keyword could always be used to refer to - the innermost match value. The thinking was that this would make it easier to avoid mistakes - such as returning e0 instead of e or the like. -

    - -

    - The problem with this idea is at least three-fold: (i) it seems like overkill for a very - specific problem, (ii) it is not worth it on the complexity and strangeness - budget, and finally (iii) it is still brittle in the presence of nested pattern matches. -

    - -

    Potential Design Flaws

    - -

    - It is debatable whether the following feature is a design flaw or not. -

    - -
    Built-in Syntax for Lists, Sets, and Maps
    - -

    - Flix has a principle that states that the standard library should not be "blessed". - That is, the standard library should be independent of the Flix compiler and language. - It should just be like any other library: A collection of Flix code. -

    - -

    - Yet, despite this principle, Flix has special syntax for Lists, Sets and Maps: -

    - - - {`1 :: 2 :: Nil + + +

    + The keyword itself refers to the value of the match expression, i.e. the value of{' '} + e. The idea was that in very large and complicated pattern matches, with many local + variables, the itself keyword could always be used to refer to the innermost match value. The + thinking was that this would make it easier to avoid mistakes such as returning e0 instead of{' '} + e or the like. +

    + +

    + The problem with this idea is at least three-fold: (i) it seems like overkill for a very specific problem, + (ii) it is not worth it on the complexity and strangeness budget, and finally (iii) it is still brittle in + the presence of nested pattern matches. +

    + +

    Potential Design Flaws

    + +

    It is debatable whether the following feature is a design flaw or not.

    + +
    Built-in Syntax for Lists, Sets, and Maps
    + +

    + Flix has a principle that states that the standard library should not be "blessed". That is, the standard + library should be independent of the Flix compiler and language. It should just be like any other library: + A collection of Flix code. +

    + +

    Yet, despite this principle, Flix has special syntax for Lists, Sets and Maps:

    + + + {`1 :: 2 :: Nil Set#{1, 2, 3} Map#{1 -> 2, 3 -> 4}`} - - -

    - which is built-in to the language. While technically these constructs are merely - syntactic sugar for Cons, and calls - to Set.empty, Set.insert, Map.empty and Map.insert there - is no getting around the fact that this is a special kind of blessing of the standard - library. In particular, it is not possible to define your - own Foo#... syntax for anything. -

    - - -
    -
    - ) - } + + +

    + which is built-in to the language. While technically these constructs are merely syntactic sugar for{' '} + Cons, and calls to Set.empty, Set.insert, Map.empty{' '} + and Map.insert there is no getting around the fact that this is a special kind of blessing of + the standard library. In particular, it is not possible to define your own Foo#...{' '} + syntax for anything. +

    + + + + ) + } } export default DesignFlaws diff --git a/src/page/blog/Naming.js b/src/page/blog/Naming.js index a319502..db091e1 100644 --- a/src/page/blog/Naming.js +++ b/src/page/blog/Naming.js @@ -1,368 +1,338 @@ -import React, {Component} from "react"; -import {Card, CardText, CardTitle, Col, Container, Row} from "reactstrap"; -import InlineEditor from "../../util/InlineEditor"; +import React, { Component } from 'react' +import { Card, CardText, CardTitle, Col, Container, Row } from 'reactstrap' +import InlineEditor from '../../util/InlineEditor' class Naming extends Component { - - componentDidMount() { - document.title = "Flix | Naming Functional and Destructive Operations"; - } - - render() { - return ( - - - - -

    Naming Functional and Destructive Operations

    - -

    - Posted April 2020 by Magnus Madsen. -

    - -

    - It has been said that there are only two hard problems in computer science: (i) naming, (ii) - cache invalidation, and (iii) off-by-one errors. In this blog post, I will explain a name - consistency issue that arises when a programming language wants to support both - functional and destructive operations. (A functional operation always returns new data, - whereas a destructive operation mutates existing data. For example, functionally reversing - an array returns a new array with its elements reversed, whereas destructively - reversing an array mutates the array in place.) -

    - -

    - Flix supports functional, imperative, and logic programming. Flix is intended to - be functional-first which simply means that if there is a trade-off between - having better functional- or imperative programming support, we tend to favor design choices - that support functional programming. For example, the Flix effect system separates pure - and impure functions mostly to the benefit of functional programming. -

    - -

    - Flix, being imperative, wants to support mutable data structures such as arrays, mutable - sets and maps. We have recently added support for all three. But let us for a moment - consider a simpler data structure: the humble list. -

    - -

    - We can map a function f: a -{">"} b over a list l to - obtain a new list of type List[b]: -

    - - - {`def map(f: a -> b \\ ef, l: List[a]): List[b] \\ ef`} - - -

    - (Here the ef denotes that the function is effect polymorphic, but that - is for another day.) -

    - -

    - We can also map a function over an option: -

    - - - {`def map(f: a -> b \\ ef, o: Option[a]): Option[b] \\ ef`} - - -

    - We can also map a function over an array: -

    - - - {`def map(f: a -> b \\ ef, a: Array[a]): Array[b] \\ IO`} - - -

    - This is good news: we can program with arrays in a functional-style. Mapping over an array - is certainly meaningful and useful. It might even be faster than mapping over a list! - Nevertheless, the main reason for having arrays (and mutable sets and maps) is to program - with them imperatively. We want to have operations that mutate their data. -

    - -

    - We want an operation that applies a function to every element of an array changing - it in place. What should such an operation be called? We cannot name - it map because that name is already taken by the functional version. - Let us simply call it mapInPlace for now: -

    - - - {`def mapInPlace(f: a -> a \\ ef, a: Array[a]): Unit \\ IO`} - - -

    - The signature of mapInPlace is different from the signature - of map in two important ways: - -

      -
    • The function returns Unit instead of returning an array.
    • -
    • The function takes an argument of type a -{">"} a rather than a function of - type a -{">"} b. -
    • -
    -

    - -

    - The latter is required because the type of an array is fixed. An array of bytes cannot - be replaced by an array of strings. Consequently, mapInPlace must take a - less generic function of type a -{">"} a. -

    - -

    - We have seen that it is useful to have both functional and destructive functions such - as map and mapInPlace, but what should such functions be called? - Are they sufficiently similar that they should share similar names? What should be the - general rule for naming functional operations and their counter-part destructive operations? -

    - -

    - To answer these questions, we surveyed the Flix standard library to understand what - names are currently being used. The table below shows a small cross section of the results: -

    - -

    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    Functional OperationDestructive Equivalent
    Array.mapArray.mapInPlace
    Array.reverseArray.reverseInPlace
    missingArray.sortByInPlace
    Set.insertnot relevant – immutable
    Set.unionnot relevant – immutable
    missingMutSet.add
    missingMutSet.addAll
    MutSet.mapMutSet.transform
    -

    - -

    - The table exposes the lack of any established naming convention. Let us consider some of the - many inconsistencies: For arrays, the functional and destructive operations are - named Array.map and Array.mapInPlace, but for mutable sets the - operations are named MutSet.map and MutSet.transform. - As another example, for immutable sets, we - have Set.insert and Set.union, but these - functional operations are missing on the mutable set. Moreover, the mutable version - of Set.union is called Set.addAll. - Finally, Array.sortByInPlace, what a name! -

    - -

    Exploring the Design Space

    - -

    - With these examples in mind, we tried to come up with a principled approach to naming. Our - exploration ended up with the following options: -

    - - - Option I: Distinct names - - Proposal: We give distinct names to functional and destructive operations. For - example, we will have Array.map and Array.transform, - and MutSet.union and MutSet.addAll. We reserve the most - common names (e.g. map) for the functional operations. - - - Discussion: With distinct names there is little room for confusion, but it may be - difficult to come up with meaningful names. For example, what should the - destructive version of reverse be called? - - - - - Option II: Use similar names but with a prefix or suffix - - Proposal: We reuse names between functional and destructive operations. To - distinguish operations, we add a prefix or suffix to the name. For - example, reverseInPlace, inPlaceReverse, reverseMut, - or similar. - - - Discussion: The advantage of this approach is that names are immediately - consistent. The disadvantages are that: (i) it may be difficult to come up with a good - prefix or suffix word, (ii) some users may dislike the chosen prefix or suffix, and - (iii) it may be confusing that the signatures for two similarly named operations differ - not only in the return type, but also in the polymorphism of the arguments. - - - - - Option III: Use similar names but with a prefix or suffix symbol - - Proposal: Similar to the previous proposal, but instead we use a symbol. For - example: reverse!, reverse*, or the like. - - - Discussion: The same advantages and disadvantages of the previous proposal, but - with the difference that using a symbol may be more or less appealing to programmers. - - - - - Option IV: Use namespaces - - Proposal: We place all functional operations into one namespace and all - destructive operations into another. For example, we might - have Array.reverse and MutArray.reverse. - - - Discussion: While this solution appears simple, it has two downsides: (i) we now - have multiple functions named reverse with different semantics and (ii) we - get a plethora of namespaces for data structures that exist in both - immutable and mutable variants. For example, we might end up - with Set.map (functional map on an immutable - set), MutSet.Mut.map (destructive map on a mutable set), - and MutSet.Imm.map (functional map on a mutable set). - - - - - Option V: The Python approach: sort vs. sorted - - Proposal: In Python the sorted operation functionally returns a new - sorted list whereas the sort operation destructively sorts a list in place. - We use the same scheme - for reverse and reversed, map and mapped, - and so forth. - - - Discussion: An internet search reveals that many programmers are puzzled by the - Python naming scheme. Another disadvantage is that the common functional names, - e.g. map and reverse would be reserved for destructive - operations (unless we adopt the opposite convention of Python). - - - - - Option VI: Drop functional operations for mutable data - - Proposal: We drop support for functional operations on mutable data structures. - If the user wants to map a function over an array, mutable set, or mutable - map he or she must first convert it to an immutable data structure. For example, to - functionally reverse an array one would - write a.toList().reverse().toArray(). - - - Discussion: The "stick your head in the sand approach". The programmer must - explicitly convert back and forth between immutable and mutable data structures. - While such an approach side-steps the naming issue, it is verbose and slow - (because we have to copy collections back and forth). Deliberately leaving functionality - out of the standard library does not mean that programmers will not miss it; instead we - are just passing the problem onto them. - - - -

    The Principles

    - -

    - We debated these options and slept on them for a few nights before we ultimately ended up - with the following hybrid principles: -

    - - - Library: Mutable Data is Functional Data - - In Flix, every mutable data structure supports functional operations. - For example, mutable collections, such - as Array and MutSet support - the map operation. Flix, being functional-first, reserves functional names - for functional operations. Across the standard library map has the same - name and the same type signature. - - - - - Library: Destructive Operations are Marked with '!' - - In Flix, every destructive operation is suffixed with an exclamation point. For - example, Array.reverse(a) returns a new array with the elements - of a in reverse order, whereas Array.reverse!(a) destructively - re-orders the elements of a. Note: This principle applies to destructive - operations that operate on data structures, not to impure functions in general, - e.g. Console.printLine. - - - -

    - As a side-note: Scheme has used ! to indicate destructive operations for a - long-time. -

    - - - Library: Consistent Names of Functional and Destructive Operations - - In Flix, functional and destructive operations that share (i) similar behavior and (ii) - similar type signatures share similar names. For - example, Array.reverse and Array.reverse! share the - same name. On the other hand, Array.transform! is - called transform! and not map! because its type signature is - dissimilar to map (i.e. map works on functions of type a -{">"} b, but - transform requires functions of type a -{">"} a.) - - - -

    - We are in the process of refactoring the standard library to satisfy these new principles. -

    - -

    - Going forward, we are sensitive to at least four potential issues: - -

      -
    • Whether users come to like the aesthetics of names that end in exclamation point. -
    • -
    • If there is confusion about when exclamation points should be part of a name.
    • -
    • If there is confusion about when two operations should share the same name.
    • -
    • That Rust uses exclamation points for macro applications.
    • -
    -

    - -

    - As Flix continues to mature, we will keep an eye on these issues. -

    - -

    - Until next time, happy hacking. -

    - - -
    -
    - ); - } - + componentDidMount() { + document.title = 'Flix | Naming Functional and Destructive Operations' + } + + render() { + return ( + + + +

    Naming Functional and Destructive Operations

    + +

    + Posted April 2020 by Magnus Madsen. +

    + +

    + It has been said that there are only two hard problems in computer science: (i) naming, (ii) cache + invalidation, and (iii) off-by-one errors. In this blog post, I will explain a{' '} + name consistency issue that arises when a programming language wants to support both functional and + destructive operations. (A functional operation always returns new data, whereas a destructive operation + mutates existing data. For example, functionally reversing an array returns a new array with its + elements reversed, whereas destructively reversing an array mutates the array in place.) +

    + +

    + Flix supports functional, imperative, and logic programming. Flix is intended to be{' '} + functional-first which simply means that if there is a trade-off between having better functional- + or imperative programming support, we tend to favor design choices that support functional programming. + For example, the Flix effect system separates pure and impure functions mostly to the benefit of + functional programming. +

    + +

    + Flix, being imperative, wants to support mutable data structures such as arrays, mutable sets and maps. We + have recently added support for all three. But let us for a moment consider a simpler data structure: the + humble list. +

    + +

    + We can map a function f: a -{'>'} b over a list l to obtain a new + list of type List[b]: +

    + + {`def map(f: a -> b \\ ef, l: List[a]): List[b] \\ ef`} + +

    + (Here the ef denotes that the function is effect polymorphic, but that is for another + day.) +

    + +

    + We can also map a function over an option: +

    + + {`def map(f: a -> b \\ ef, o: Option[a]): Option[b] \\ ef`} + +

    + We can also map a function over an array: +

    + + {`def map(f: a -> b \\ ef, a: Array[a]): Array[b] \\ IO`} + +

    + This is good news: we can program with arrays in a functional-style. Mapping over an array is certainly + meaningful and useful. It might even be faster than mapping over a list! Nevertheless, the main reason for + having arrays (and mutable sets and maps) is to program with them imperatively. We want to have + operations that mutate their data. +

    + +

    + We want an operation that applies a function to every element of an array changing it in place.{' '} + What should such an operation be called? We cannot name it map because that name is + already taken by the functional version. Let us simply call it mapInPlace for now: +

    + + {`def mapInPlace(f: a -> a \\ ef, a: Array[a]): Unit \\ IO`} + +

    + The signature of mapInPlace is different from the signature of map in two + important ways: +

      +
    • + The function returns Unit instead of returning an array. +
    • +
    • + The function takes an argument of type a -{'>'} a rather than a function of type{' '} + a -{'>'} b. +
    • +
    +

    + +

    + The latter is required because the type of an array is fixed. An array of bytes cannot be replaced by an + array of strings. Consequently, mapInPlace must take a less generic function of type{' '} + a -{'>'} a. +

    + +

    + We have seen that it is useful to have both functional and destructive functions such as map{' '} + and mapInPlace, but what should such functions be called? Are they sufficiently similar that + they should share similar names? What should be the general rule for naming functional operations and + their counter-part destructive operations? +

    + +

    + To answer these questions, we surveyed the Flix standard library to understand what names are currently + being used. The table below shows a small cross section of the results: +

    + +

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Functional OperationDestructive Equivalent
    Array.mapArray.mapInPlace
    Array.reverseArray.reverseInPlace
    + missing + Array.sortByInPlace
    Set.insertnot relevant – immutable
    Set.unionnot relevant – immutable
    + missing + MutSet.add
    + missing + MutSet.addAll
    MutSet.mapMutSet.transform
    +

    + +

    + The table exposes the lack of any established naming convention. Let us consider some of the many + inconsistencies: For arrays, the functional and destructive operations are named Array.map{' '} + and Array.mapInPlace, but for mutable sets the operations are named MutSet.map{' '} + and MutSet.transform. As another example, for immutable sets, we have Set.insert{' '} + and Set.union, but these functional operations are missing on the mutable set. Moreover, the + mutable version of Set.union is called Set.addAll. Finally,{' '} + Array.sortByInPlace, what a name! +

    + +

    Exploring the Design Space

    + +

    + With these examples in mind, we tried to come up with a principled approach to naming. Our exploration + ended up with the following options: +

    + + + Option I: Distinct names + + Proposal: We give distinct names to functional and destructive operations. For example, we will + have Array.map and Array.transform, and MutSet.union and{' '} + MutSet.addAll. We reserve the most common names (e.g. map) for the functional + operations. + + + Discussion: With distinct names there is little room for confusion, but it may be difficult to + come up with meaningful names. For example, what should the destructive version of reverse{' '} + be called? + + + + + Option II: Use similar names but with a prefix or suffix + + Proposal: We reuse names between functional and destructive operations. To distinguish + operations, we add a prefix or suffix to the name. For example, reverseInPlace,{' '} + inPlaceReverse, reverseMut, or similar. + + + Discussion: The advantage of this approach is that names are immediately consistent. The + disadvantages are that: (i) it may be difficult to come up with a good prefix or suffix word, (ii) some + users may dislike the chosen prefix or suffix, and (iii) it may be confusing that the signatures for two + similarly named operations differ not only in the return type, but also in the polymorphism of the + arguments. + + + + + Option III: Use similar names but with a prefix or suffix symbol + + Proposal: Similar to the previous proposal, but instead we use a symbol. For example:{' '} + reverse!, reverse*, or the like. + + + Discussion: The same advantages and disadvantages of the previous proposal, but with the + difference that using a symbol may be more or less appealing to programmers. + + + + + Option IV: Use namespaces + + Proposal: We place all functional operations into one namespace and all destructive operations + into another. For example, we might have Array.reverse and MutArray.reverse. + + + Discussion: While this solution appears simple, it has two downsides: (i) we now have multiple + functions named reverse with different semantics and (ii) we get a plethora of namespaces + for data structures that exist in both immutable and mutable variants. For example, we might end up with{' '} + Set.map (functional map on an immutable set), MutSet.Mut.map (destructive map + on a mutable set), and MutSet.Imm.map (functional map on a mutable set). + + + + + Option V: The Python approach: sort vs. sorted + + Proposal: In Python the sorted operation functionally returns a new sorted list + whereas the sort operation destructively sorts a list in place. We use the same scheme for{' '} + reverse and reversed, map and mapped, and so forth. + + + Discussion: An internet search reveals that many programmers are puzzled by the Python naming + scheme. Another disadvantage is that the common functional names, e.g. map and{' '} + reverse would be reserved for destructive operations (unless we adopt the opposite{' '} + convention of Python). + + + + + Option VI: Drop functional operations for mutable data + + Proposal: We drop support for functional operations on mutable data structures. If the user wants + to map a function over an array, mutable set, or mutable map he or she must first convert it to an + immutable data structure. For example, to functionally reverse an array one would write{' '} + a.toList().reverse().toArray(). + + + Discussion: The "stick your head in the sand approach". The programmer must explicitly convert + back and forth between immutable and mutable data structures. While such an approach side-steps the + naming issue, it is verbose and slow (because we have to copy collections back and forth). Deliberately + leaving functionality out of the standard library does not mean that programmers will not miss it; + instead we are just passing the problem onto them. + + + +

    The Principles

    + +

    + We debated these options and slept on them for a few nights before we ultimately ended up with the + following hybrid principles: +

    + + + Library: Mutable Data is Functional Data + + In Flix, every mutable data structure supports functional operations. For example, mutable collections, + such as Array and MutSet support the map operation. Flix, being + functional-first, reserves functional names for functional operations. Across the standard library{' '} + map has the same name and the same type signature. + + + + + Library: Destructive Operations are Marked with '!' + + In Flix, every destructive operation is suffixed with an exclamation point. For example,{' '} + Array.reverse(a) returns a new array with the elements of a in reverse order, + whereas Array.reverse!(a) destructively re-orders the elements of a. Note: + This principle applies to destructive operations that operate on data structures, not to impure + functions in general, e.g. Console.printLine. + + + +

    + As a side-note: Scheme has used ! to indicate destructive operations for a long-time. +

    + + + Library: Consistent Names of Functional and Destructive Operations + + In Flix, functional and destructive operations that share (i) similar behavior and (ii) similar type + signatures share similar names. For example, Array.reverse and Array.reverse!{' '} + share the same name. On the other hand, Array.transform! is called transform!{' '} + and not map! because its type signature is dissimilar to map (i.e. map works on functions + of type a -{'>'} b, but transform requires functions of type a -{'>'} a.) + + + +

    We are in the process of refactoring the standard library to satisfy these new principles.

    + +

    + Going forward, we are sensitive to at least four potential issues: +

      +
    • Whether users come to like the aesthetics of names that end in exclamation point.
    • +
    • If there is confusion about when exclamation points should be part of a name.
    • +
    • If there is confusion about when two operations should share the same name.
    • +
    • That Rust uses exclamation points for macro applications.
    • +
    +

    + +

    As Flix continues to mature, we will keep an eye on these issues.

    + +

    Until next time, happy hacking.

    + +
    +
    + ) + } } export default Naming diff --git a/src/page/blog/PolymorphicEffects.js b/src/page/blog/PolymorphicEffects.js index e161774..affd647 100644 --- a/src/page/blog/PolymorphicEffects.js +++ b/src/page/blog/PolymorphicEffects.js @@ -1,547 +1,470 @@ -import React, {Component} from "react"; -import {Col, Container, Row} from "reactstrap"; -import InlineEditor from "../../util/InlineEditor"; +import React, { Component } from 'react' +import { Col, Container, Row } from 'reactstrap' +import InlineEditor from '../../util/InlineEditor' class PolymorphicEffects extends Component { - - componentDidMount() { - document.title = "Flix | Taming Impurity with Polymorphic Effects"; - } - - render() { - return ( - - - - -

    Taming Impurity with Polymorphic Effects

    - -

    - Posted May 2020 by Magnus Madsen. -

    - -

    - In the blog post Patterns of Bugs, Walter Bright, - the author of the D programming Language, writes about his - experience working at Boeing and their attitude towards failure: -

    - -
    -

    - "[...] The best people have bad days and make mistakes, so the solution is to - change the process so the mistakes cannot happen or cannot propagate." -

    - -

    - "One simple example is an assembly that is bolted onto the frame with four bolts. The - obvious bolt pattern is a rectangle. Unfortunately, a rectangle pattern can be assembled - in two different ways, one of which is wrong. The solution is to offset one of the bolt - holes — then the assembly can only be bolted on in one orientation. The possible - mechanic's mistake is designed out of the system." -

    - -

    - "[...] Parts can only be assembled one way, the correct - way." -

    -
    - -

    - (Emphasis mine). -

    - -

    - Bright continues to explain that these ideas are equally applicable to software: We should - build software such that it can only be assembled correctly. In this blog post, I will - discuss how this idea can be applied to the design of a type and effect system. In - particular, I will show how the Flix programming language and, by extension, its standard - library ensure that pure and impure functions are not assembled incorrectly. -

    - -

    Impure Functional Programming

    - -

    - A major selling point of functional programming is that it supports equational reasoning. - Informally, equational reasoning means that we can reason about programs by replacing an - expression by another one, provided they're both equal. For example, we can substitute - variables with the expressions they are bound to. -

    - -

    - For example, if we have the program fragment: -

    - - - {`let x = 1 + 2; + componentDidMount() { + document.title = 'Flix | Taming Impurity with Polymorphic Effects' + } + + render() { + return ( + + + +

    Taming Impurity with Polymorphic Effects

    + +

    + Posted May 2020 by Magnus Madsen. +

    + +

    + In the blog post Patterns of Bugs, Walter + Bright, the author of the D programming Language, writes about his + experience working at Boeing and their attitude towards failure: +

    + +
    +

    + "[...] The best people have bad days and make mistakes, so the solution is to change the process so the + mistakes cannot happen or cannot propagate." +

    + +

    + "One simple example is an assembly that is bolted onto the frame with four bolts. The obvious bolt + pattern is a rectangle. Unfortunately, a rectangle pattern can be assembled in two different ways, one + of which is wrong. The solution is to offset one of the bolt holes — then the assembly can only be + bolted on in one orientation. The possible mechanic's mistake is designed out of the system." +

    + +

    + "[...] Parts can only be assembled one way, the correct way." +

    +
    + +

    (Emphasis mine).

    + +

    + Bright continues to explain that these ideas are equally applicable to software: We should build software + such that it can only be assembled correctly. In this blog post, I will discuss how this idea can be + applied to the design of a type and effect system. In particular, I will show how the Flix programming + language and, by extension, its standard library ensure that pure and impure functions are not assembled + incorrectly. +

    + +

    Impure Functional Programming

    + +

    + A major selling point of functional programming is that it supports{' '} + equational reasoning. Informally, + equational reasoning means that we can reason about programs by replacing an expression by another one, + provided they're both equal. For example, we can substitute variables with the expressions they are bound + to. +

    + +

    For example, if we have the program fragment:

    + + + {`let x = 1 + 2; (x, x)`} - +
    -

    - We can substitute for x and understand this program as: -

    +

    + We can substitute for x and understand this program as: +

    - - {`(1 + 2, 1 + 2)`} - + {`(1 + 2, 1 + 2)`} -

    - Unfortunately, in the presence of side-effects, such reasoning breaks down. -

    +

    Unfortunately, in the presence of side-effects, such reasoning breaks down.

    -

    - For example, the program fragment: -

    +

    For example, the program fragment:

    - - {`let x = Console.printLine("Hello World"); + + {`let x = Console.printLine("Hello World"); (x, x)`} - - -

    - is not equivalent to the program: -

    - - - {`(Console.printLine("Hello World"), Console.printLine("Hello World"))`} - - - -

    - Most contemporary functional programming languages, including Clojure, OCaml, and Scala, - forgo equational reasoning by allow arbitrary side-effects inside functions. To be clear, - it is still common to write purely functional programs in these languages and to reason - about them using equational reasoning. The major concern is that there is no language - support to guarantee when such reasoning is valid. Haskell is the only major programming - language that guarantees equational reasoning at the cost of a total and absolute ban on - side-effects. -

    - -

    - Flix aims to walk on the middle of the road: We want to support equational reasoning with - strong guarantees while still allowing side-effects. Our solution is a type and effect - system that cleanly separates pure and impure code. The idea of using an effect system - to separate pure and impure code is old, but our implementation, which supports type - inference and polymorphism, is new. -

    - -

    Pure and Impure Functions

    - -

    - Flix functions are pure by default. We can write a pure function: -

    - - - {`def inc(x: Int): Int = x + 1`} - - -

    - If we want to be explicit, but non-idiomatic, we can write: -

    - - - {`def inc(x: Int): Int \\ {} = x + 1`} - - -

    - where \ {} specifies that the inc function is pure. -

    - -

    - We can also write an impure function: -

    - - - {`def sayHello(): Unit \\ IO = Console.printLine("Hello World!")`} - - -

    - where \ IO specifies that the sayHello function is impure. -

    - -

    - The Flix type and effect system is sound, hence if we forget the \ IO annotation - on the sayHello function, the compiler will emit a type (or rather effect) error. -

    - -

    - The type and effect system cleanly separates pure and impure code. If an expression is pure - then it always evaluates to the same value and it cannot have side-effects. This is part - of what makes Flix functional-first: We can trust that pure functions behave like - mathematical functions. -

    - -

    - We have already seen that printing to the screen is impure. Other sources of impurity are - mutation of memory (e.g. writing to main memory, writing to the disk, writing to the - network, etc.). Reading from mutable memory is also impure because there is no guarantee - that we will get the same value if we read the same location twice. -

    - -

    - In Flix, the following operations are impure: -

    - -
      -
    • Any use of channels (creating, sending, receiving, or selecting).
    • -
    • Any use of references (creating, accessing, or updating).
    • -
    • Any use of arrays (creating, accessing, updating, or slicing).
    • -
    • Any interaction with the Java world.
    • -
    - -

    Higher-Order Functions

    - -

    - We can use the type and effect system to restrict the purity (or impurity) of function - arguments that are passed to higher-order functions. This is useful for at least two - reasons: (i) it prevents leaky abstractions where the caller can observe implementation - details of the callee, and (ii) it can help avoid bugs in the sense of Walter Bright's - "Parts can only be assembled one way, the correct way." -

    - -

    - We will now look at several examples of how type signatures can control purity or impurity. -

    - -

    - We can enforce that the predicate f passed - to Set.exists is pure: -

    - - - {`def exists(f: a -> Bool, xs: Set[a]): Bool = ...`} - - -

    - The signature f: a -{">"} Bool denotes a pure function - from a to Bool. Passing an impure function - to exists is a compile-time type error. We want to enforce - that f is pure because the contract for exists makes no guarantees - about how f is called. The implementation of exists may - call f on the elements in xs in any order and any number of times. - This requirement is beneficial because its allows freedom in the implementation - of Set, including in the choice of the underlying data structure and in the - implementation of its operations. For example, we can implement sets using search trees or - with hash tables, and we can perform existential queries in parallel using - fork-join. If f was impure such implementation details would leak and be - observable by the client. Functions can only be assembled one way, the correct way. -

    - -

    - We can enforce that the function f passed to the - function List.foreach is impure: -

    - - - {`def foreach(f: a -> Unit \\ IO, xs: List[a]): Unit \\ IO = ...`} - - -

    - The signature f: a -{">"} Unit \ IO denotes an impure function - from b to Unit. Passing a pure function to foreach is - a compile-time type error. Given that f is impure and f is called - within foreach, it is itself impure. We enforce that - the f function is impure because it is pointless to apply - a pure function with a Unit return type to every element of a list. Functions - can only be assembled one way, the correct way. -

    - -

    - We can enforce that event listeners are impure: -

    - - - {`def onMouseDn(f: MouseEvent -> Unit \\ IO): Unit \\ IO = ... + + +

    + is not equivalent to the program: +

    + + {`(Console.printLine("Hello World"), Console.printLine("Hello World"))`} + +

    + Most contemporary functional programming languages, including Clojure, OCaml, and Scala, forgo equational + reasoning by allow arbitrary side-effects inside functions. To be clear, it is still common to write + purely functional programs in these languages and to reason about them using equational reasoning. The + major concern is that there is no language support to guarantee when such reasoning is valid. Haskell is + the only major programming language that guarantees equational reasoning at the cost of a total and + absolute ban on side-effects. +

    + +

    + Flix aims to walk on the middle of the road: We want to support equational reasoning with strong + guarantees while still allowing side-effects. Our solution is a type and effect system that cleanly + separates pure and impure code. The idea of using an effect system to separate pure and impure code is + old, but our implementation, which supports type inference and polymorphism, is new. +

    + +

    Pure and Impure Functions

    + +

    Flix functions are pure by default. We can write a pure function:

    + + {`def inc(x: Int): Int = x + 1`} + +

    If we want to be explicit, but non-idiomatic, we can write:

    + + {`def inc(x: Int): Int \\ {} = x + 1`} + +

    + where \ {} specifies that the inc function is pure. +

    + +

    We can also write an impure function:

    + + {`def sayHello(): Unit \\ IO = Console.printLine("Hello World!")`} + +

    + where \ IO specifies that the sayHello function is impure. +

    + +

    + The Flix type and effect system is sound, hence if we forget the \ IO annotation on + the sayHello function, the compiler will emit a type (or rather effect) error. +

    + +

    + The type and effect system cleanly separates pure and impure code. If an expression is pure then it always + evaluates to the same value and it cannot have side-effects. This is part of what makes Flix + functional-first: We can trust that pure functions behave like mathematical functions. +

    + +

    + We have already seen that printing to the screen is impure. Other sources of impurity are mutation of + memory (e.g. writing to main memory, writing to the disk, writing to the network, etc.). Reading from + mutable memory is also impure because there is no guarantee that we will get the same value if we read the + same location twice. +

    + +

    In Flix, the following operations are impure:

    + +
      +
    • Any use of channels (creating, sending, receiving, or selecting).
    • +
    • Any use of references (creating, accessing, or updating).
    • +
    • Any use of arrays (creating, accessing, updating, or slicing).
    • +
    • Any interaction with the Java world.
    • +
    + +

    Higher-Order Functions

    + +

    + We can use the type and effect system to restrict the purity (or impurity) of function arguments that are + passed to higher-order functions. This is useful for at least two reasons: (i) it prevents leaky + abstractions where the caller can observe implementation details of the callee, and (ii) it can help avoid + bugs in the sense of Walter Bright's "Parts can only be assembled one way, the correct way." +

    + +

    We will now look at several examples of how type signatures can control purity or impurity.

    + +

    + We can enforce that the predicate f passed to Set.exists is pure: +

    + + {`def exists(f: a -> Bool, xs: Set[a]): Bool = ...`} + +

    + The signature f: a -{'>'} Bool denotes a pure function from a to{' '} + Bool. Passing an impure function to exists is a compile-time type error. We want + to enforce that f is pure because the contract for exists makes no guarantees + about how f is called. The implementation of exists may call f on + the elements in xs in any order and any number of times. This requirement is{' '} + beneficial because its allows freedom in the implementation of Set, including in the + choice of the underlying data structure and in the implementation of its operations. For example, we can + implement sets using search trees or with hash tables, and we can perform existential queries in parallel + using fork-join. If f was impure such implementation details would leak and be observable by + the client. Functions can only be assembled one way, the correct way. +

    + +

    + We can enforce that the function f passed to the function List.foreach is{' '} + impure: +

    + + {`def foreach(f: a -> Unit \\ IO, xs: List[a]): Unit \\ IO = ...`} + +

    + The signature f: a -{'>'} Unit \ IO denotes an impure function from b to{' '} + Unit. Passing a pure function to foreach is a compile-time type error. Given + that f is impure and f is called within foreach, it is itself + impure. We enforce that the f function is impure because it is pointless to apply a{' '} + pure function with a Unit return type to every element of a list.{' '} + Functions can only be assembled one way, the correct way. +

    + +

    We can enforce that event listeners are impure:

    + + + {`def onMouseDn(f: MouseEvent -> Unit \\ IO): Unit \\ IO = ... def onMouseUp(f: MouseEvent -> Unit \\ IO): Unit \\ IO = ...`} - +
    -

    - Event listeners are always executed for their side-effect: it would be pointless to register - a pure function as an event listener. -

    +

    + Event listeners are always executed for their side-effect: it would be pointless to register a pure + function as an event listener. +

    -

    - We can enforce that assertion and logging facilities are given pure functions: -

    +

    We can enforce that assertion and logging facilities are given pure functions:

    - - {`def assert(f: Unit -> Bool): Unit = ... + + {`def assert(f: Unit -> Bool): Unit = ... def log(f: Unit -> String , l: LogLevel): Unit = ...`} - - -

    - We want to support assertions and log statements that can be enabled and disabled at - run-time. For efficiency, it is critical that when assertions or logging is disabled, we do - not perform any computations that are redundant. We can achieve this by having the assert - and log functions take callbacks that are only invoked when required. A critical property of - these functions is that they must not influence the execution of the program. Otherwise, we - risk situations where enabling or disabling assertions or logging may impact the presence or - absence of a buggy execution. We can prevent such situations by requiring that the functions - passed to assert and log are pure. -

    - -

    - We can enforce that user-defined equality functions are pure. We want purity because the - programmer should not make any assumptions about how such functions are used. Moreover, most - collections (e.g. sets and maps) require that equality does not change over time to maintain - internal data structure invariants. Similar considerations apply to hash and comparator - functions. -

    - -

    - In the same spirit, we can enforce that one-shot comparator functions are pure: -

    - - - {`def minBy(f: a -> b, l: List[a]): Option[a] = ... + + +

    + We want to support assertions and log statements that can be enabled and disabled at run-time. For + efficiency, it is critical that when assertions or logging is disabled, we do not perform any computations + that are redundant. We can achieve this by having the assert and log functions take callbacks that are + only invoked when required. A critical property of these functions is that they must not influence the + execution of the program. Otherwise, we risk situations where enabling or disabling assertions or logging + may impact the presence or absence of a buggy execution. We can prevent such situations by requiring that + the functions passed to assert and log are pure. +

    + +

    + We can enforce that user-defined equality functions are pure. We want purity because the programmer should + not make any assumptions about how such functions are used. Moreover, most collections (e.g. sets and + maps) require that equality does not change over time to maintain internal data structure invariants. + Similar considerations apply to hash and comparator functions. +

    + +

    In the same spirit, we can enforce that one-shot comparator functions are pure:

    + + + {`def minBy(f: a -> b, l: List[a]): Option[a] = ... def maxBy(f: a -> b, l: List[a]): Option[a] = ... def sortBy(f: a -> Int32, l: List[a]): List[a] = ... def groupBy(f: a -> k, l: List[a]): Map[k, List[a]] = ...`} - - -

    - We can enforce that the next function passed - to List.unfoldWithIter is impure: -

    - - - {`def unfoldWithIter(next: Unit -> Option[a] \\ IO): List[a] \\ IO`} - - -

    - The unfoldWithIter function is a variant of the unfoldWith function where each - invocation of next changes some mutable state until the unfold completes. For - example, unfoldWithIter is frequently used to convert Java-style iterators into - lists. We want to enforce that next is impure because otherwise it is pointless - to use unfoldWithIter. If next is pure then it must always either - (i) return None which results in the empty list or (ii) - return Some(v) for a value v which would result in an infinite - execution. -

    - -

    - We can use purity to reject useless statement expressions. For example, the program: -

    - - - {`def main(): Int = + + +

    + We can enforce that the next function passed to List.unfoldWithIter is impure: +

    + + {`def unfoldWithIter(next: Unit -> Option[a] \\ IO): List[a] \\ IO`} + +

    + The unfoldWithIter function is a variant of the unfoldWith function where each invocation of{' '} + next changes some mutable state until the unfold completes. For example,{' '} + unfoldWithIter is frequently used to convert Java-style iterators into lists. We want to + enforce that next is impure because otherwise it is pointless to use{' '} + unfoldWithIter. If next is pure then it must always either (i) return{' '} + None which results in the empty list or (ii) return Some(v) for a value{' '} + v which would result in an infinite execution. +

    + +

    We can use purity to reject useless statement expressions. For example, the program:

    + + + {`def main(): Int = List.map(x -> x + 1, 1 :: 2 :: Nil); 123`} - +
    -

    - is rejected with the compiler error: -

    +

    is rejected with the compiler error:

    - - {`-- Redundancy Error ------------------ foo.flix + + {`-- Redundancy Error ------------------ foo.flix >> Useless expression: It has no side-effect(s) and its result is discarded. 2 | List.map(x -> x + 1, 1 :: 2 :: Nil); ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ useless expression.`} - - -

    - Notice that the List.map(...) expression is pure because the function x - -> x + 1 is pure. -

    - -

    Polymorphic Effects

    - -

    - Flix supports effect polymorphism which means that the effect of a higher-order function can - depend on the effects of its function arguments. -

    - -

    - For example, here is the type signature of List.map: -

    - - - {`def map(f: a -> b \\ ef, xs: List[a]): List[b] \\ ef = ...`} - - -

    - The syntax f: a -{">"} b \ ef denotes a function - from a to b with latent effect ef. The signature of - the map function captures that its - effect ef depends on the effect of its argument f. - That is, if map is called with a pure function then its evaluation is pure, - whereas if it is called with an impure function then its evaluation is impure. The effect - signature is conservative (i.e. over-approximate). That is, - the map function is considered impure even in the special case when the list is - empty and its execution is actually pure. -

    - -

    - The type and effect system can express combinations of effects using boolean operations. - We can, for example, express that forward function composition >> is pure - if both its arguments are pure: -

    - - - {` def >>(f: a -> b \\ ef1, g: b -> c \\ ef2): a -> c \\ { ef1, ef2 } = x -> g(f(x))`} - - -

    - Here the function f has effect ef1 and g has - effect ef2. The returned function has effect ef1 and ef2, i.e. for it - to be pure both ef1 and ef2 must be pure. Otherwise it is impure. -

    - -

    Type Equivalences

    - -

    - Let us take a short detour. -

    - -

    - In a purely functional programming language, such as Haskell, mapping two - functions f and g over a list xs is equivalent to - mapping their composition over the list. That is: -

    - - - {`map(f, map(g, xs)) == map(f >> g, xs)`} - - -

    - We can use such an equation to (automatically) rewrite the program to one that executes more - efficiently because the code on the right only traverses the list once and avoids - allocation of an intermediate list. Haskell already has support for such rewrite rules built into the language. -

    - -

    - It would be desirable if we could express the same rewrite rules for programming languages - such as Clojure, OCaml, and Scala. Unfortunately, identities - such as the above - do not - hold in the presence of side-effects. For example, the program: -

    - - - {`let f = x -> {Console.printLine(x); x}; + + +

    + Notice that the List.map(...) expression is pure because the function{' '} + x -> x + 1 is pure. +

    + +

    Polymorphic Effects

    + +

    + Flix supports effect polymorphism which means that the effect of a higher-order function can depend on the + effects of its function arguments. +

    + +

    + For example, here is the type signature of List.map: +

    + + {`def map(f: a -> b \\ ef, xs: List[a]): List[b] \\ ef = ...`} + +

    + The syntax f: a -{'>'} b \ ef denotes a function from a to b with + latent effect ef. The signature of the map function captures that its effect{' '} + ef depends on the effect of its argument f. That is, if map is + called with a pure function then its evaluation is pure, whereas if it is called with an impure function + then its evaluation is impure. The effect signature is conservative (i.e. over-approximate). That + is, the map function is considered impure even in the special case when the list is empty and + its execution is actually pure. +

    + +

    + The type and effect system can express combinations of effects using boolean operations. We can, for + example, express that forward function composition >> is pure if both its arguments are + pure: +

    + + + {` def >>(f: a -> b \\ ef1, g: b -> c \\ ef2): a -> c \\ { ef1, ef2 } = x -> g(f(x))`} + + +

    + Here the function f has effect ef1 and g has effect{' '} + ef2. The returned function has effect ef1 and ef2, i.e. for it to be pure both{' '} + ef1 and ef2 must be pure. Otherwise it is impure. +

    + +

    Type Equivalences

    + +

    Let us take a short detour.

    + +

    + In a purely functional programming language, such as Haskell, mapping two functions f and{' '} + g over a list xs is equivalent to mapping their composition over the list. That + is: +

    + + {`map(f, map(g, xs)) == map(f >> g, xs)`} + +

    + We can use such an equation to (automatically) rewrite the program to one that executes more efficiently + because the code on the right only traverses the list once and avoids allocation of an intermediate list. + Haskell already has support for such rewrite rules{' '} + built into the language. +

    + +

    + It would be desirable if we could express the same rewrite rules for programming languages such as + Clojure, OCaml, and Scala. Unfortunately, identities - such as the above - do not hold in the presence of + side-effects. For example, the program: +

    + + + {`let f = x -> {Console.printLine(x); x}; let g = y -> {Console.printLine(y); y}; List.map(f, List.map(g, 1 :: 2 :: Nil))`} - - -

    - prints 1, 2, 3, 1, 2, 3. But, if we apply the rewrite rule, the transformed - program now prints 1, 1, 2, 2, 3, 3! In the presence of side-effects we cannot - readily apply such rewrite rules. -

    - -

    - We can use the Flix type and effect to ensure that a rewrite rule like the above is only - applied when both f and g are pure! -

    - -

    - We can, in fact, go even further. If at most - one of f and g is impure then it is still safe to apply - the above rewrite rule. Furthermore, the Flix type and effect system is sufficiently - expressive to capture such a requirement! -

    - -

    - We can distill the essence of this point into the type signature: -

    - - - {`def mapCompose(f: a -> b \\ e1, g: b -> c \\ {{(not e1) or e2}}, xs: List[a]): ... = ...`} - - -

    - It is not important exactly what mapCompose does (or even if it makes sense). - What is important is that it has a function signature that requires two function - arguments f and g of which at most one may be impure. -

    - -

    - To understand why, let us look closely at the signature of mapCompose: -

    - - - {`def mapCompose(f: a -> b \\ e1, g: b -> c \\ {{(not e1) or e2}}, xs: List[a]): ... = ...`} - - -

    -

      -
    • - If e1 = T (i.e. f is pure) then (not e1) or e2 = F - or e2 = e2. In other words, g may be pure or impure. Its purity - is not constrained by the type signature. -
    • -
    • - If, on the other hand, e1 = F (i.e. f is impure) - then (not e1) or e2 = T or e2 = T . - In other words, g must be pure, otherwise there is a type error. -
    • -
    -

    - -

    - If you think about it, the above is equivalent to the requirement that at most one - of f and g may be impure. -

    - -

    - Without going into detail, an interesting aspect of the type and effect system is - that we might as well have given mapCompose the equivalent (equi-most general) - type signature: -

    - - - {`def mapCompose(f: a -> b \\ {{(not e1) or e2}}, g: b -> c \\ e1, xs: List[a]): ... = ...`} - - -

    - where the effects of f and g are swapped. -

    - -

    Benign Impurity

    - -

    - It is not uncommon for functions to be internally impure but observationally pure. - That is, a function may use mutation and perform side-effects without it being observable - by the external world. We say that such side-effects are benign. Fortunately, we can - still treat such functions as pure with an explicit effect cast. -

    - -

    - For example, we can call a Java method (which may have arbitrary side-effects) but - explicitly mark it as pure with an effect cast: -

    - - - {`/// + + +

    + prints 1, 2, 3, 1, 2, 3. But, if we apply the rewrite rule, the transformed program now + prints 1, 1, 2, 2, 3, 3! In the presence of side-effects we cannot readily apply such rewrite + rules. +

    + +

    + We can use the Flix type and effect to ensure that a rewrite rule like the above is only applied when both{' '} + f and g are pure! +

    + +

    + We can, in fact, go even further. If at most one of f and g is impure + then it is still safe to apply the above rewrite rule. Furthermore, the Flix type and effect system is + sufficiently expressive to capture such a requirement! +

    + +

    We can distill the essence of this point into the type signature:

    + + + {`def mapCompose(f: a -> b \\ e1, g: b -> c \\ {{(not e1) or e2}}, xs: List[a]): ... = ...`} + + +

    + It is not important exactly what mapCompose does (or even if it makes sense). What is + important is that it has a function signature that requires two function arguments f and{' '} + g of which at most one may be impure. +

    + +

    + To understand why, let us look closely at the signature of mapCompose: +

    + + + {`def mapCompose(f: a -> b \\ e1, g: b -> c \\ {{(not e1) or e2}}, xs: List[a]): ... = ...`} + + +

    +

      +
    • + If e1 = T (i.e. f is pure) then (not e1) or e2 = F or e2 = e2. + In other words, g may be pure or impure. Its purity is not constrained by the type + signature. +
    • +
    • + If, on the other hand, e1 = F (i.e. f is impure) then{' '} + (not e1) or e2 = T or e2 = T . In other words, g must be pure, + otherwise there is a type error. +
    • +
    +

    + +

    + If you think about it, the above is equivalent to the requirement that at most one of f and{' '} + g may be impure. +

    + +

    + Without going into detail, an interesting aspect of the type and effect system is that we might as well + have given mapCompose the equivalent (equi-most general) type signature: +

    + + + {`def mapCompose(f: a -> b \\ {{(not e1) or e2}}, g: b -> c \\ e1, xs: List[a]): ... = ...`} + + +

    + where the effects of f and g are swapped. +

    + +

    Benign Impurity

    + +

    + It is not uncommon for functions to be internally impure but observationally pure. That is, a function may + use mutation and perform side-effects without it being observable by the external world. We say that such + side-effects are benign. Fortunately, we can still treat such functions as pure with an explicit{' '} + effect cast. +

    + +

    + For example, we can call a Java method (which may have arbitrary side-effects) but explicitly mark it as + pure with an effect cast: +

    + + + {`/// /// Returns the character at position \`i\` in the string \`s\`. /// def charAt(i: Int, s: String): Char = import java.lang.String.charAt(Int32); s.charAt(i) as \\ {}`} - - -

    - We know that java.lang.String.charAt has is pure hence the cast is safe. -

    - -

    - An effect cast, like an ordinary cast, must be used with care. A cast is a mechanism - that allows the programmer to subvert the type (and effect) system. It is the - responsibility of the programmer to ensure that the cast is safe. Unlike type casts, an - effect cast cannot be checked at run-time with the consequence that an unsound effect cast - may silently lead to undefined behavior. -

    - -

    - Here is an example of a pure function that is implemented internally using mutation: -

    - - - {`/// + + +

    + We know that java.lang.String.charAt has is pure hence the cast is safe. +

    + +

    + An effect cast, like an ordinary cast, must be used with care. A cast is a mechanism that allows the + programmer to subvert the type (and effect) system. It is the responsibility of the programmer to ensure + that the cast is safe. Unlike type casts, an effect cast cannot be checked at run-time with the + consequence that an unsound effect cast may silently lead to undefined behavior. +

    + +

    Here is an example of a pure function that is implemented internally using mutation:

    + + + {`/// /// Strip every indented line in string \`s\` by \`n\` spaces. \`n\` must be greater than \`0\`. /// Note, tabs are counted as a single space. /// @@ -565,56 +488,50 @@ def stripIndentHelper(n: Int32, s: String): String \\ IO = }; List.foreach(step, lines(s)); StringBuilder.toString(sb)`} - - -

    - Internally, stripIndentHelper uses a mutable string builder. -

    - -

    Type Inference and Boolean Unification

    - -

    - The Flix type and effect system supports inference. Explicit type annotations are never - required locally within a function. As a design choice, we do require type signatures for - top-level definitions. Within a function, the programmer never has to worry about pure and - impure expressions; the compiler automatically infers whether an expression is pure, impure, - or effect polymorphic. The programmer only has to ensure that the declared type and effect - matches the type and effect of the function body. -

    - -

    - The details of the type and effect system are the subject of a forthcoming research paper - and will be made available in due time. -

    - -

    Closing Thoughts

    - -

    - The Flix type and effect system separates pure and impure code. The upshot is that a - functional programmer can trust that a pure function behaves like a mathematical function: - it returns the same result when given the same arguments. At the same time, we are still - allowed to write parts of the program in an impure, imperative style. Effect polymorphism - ensures that both pure and impure code can be used with higher-order functions. -

    - -

    - We can also use effects to control when higher-order functions require pure (or impure) - functions. We have seen several examples of such use cases, e.g. requiring - that Set.count takes a pure function or - that List.unfoldWithIter takes an impure function. Together, these restrictions - ensure that functions can only be assembled in one way, the correct way. -

    - -

    - Until next time, happy hacking. -

    - - -
    -
    - ); - } - + + +

    + Internally, stripIndentHelper uses a mutable string builder. +

    + +

    Type Inference and Boolean Unification

    + +

    + The Flix type and effect system supports inference. Explicit type annotations are never required locally + within a function. As a design choice, we do require type signatures for top-level definitions. Within a + function, the programmer never has to worry about pure and impure expressions; the compiler automatically + infers whether an expression is pure, impure, or effect polymorphic. The programmer only has to ensure + that the declared type and effect matches the type and effect of the function body. +

    + +

    + The details of the type and effect system are the subject of a forthcoming research paper and will be made + available in due time. +

    + +

    Closing Thoughts

    + +

    + The Flix type and effect system separates pure and impure code. The upshot is that a functional programmer + can trust that a pure function behaves like a mathematical function: it returns the same result when given + the same arguments. At the same time, we are still allowed to write parts of the program in an impure, + imperative style. Effect polymorphism ensures that both pure and impure code can be used with higher-order + functions. +

    + +

    + We can also use effects to control when higher-order functions require pure (or impure) functions. We have + seen several examples of such use cases, e.g. requiring that Set.count takes a pure function + or that List.unfoldWithIter takes an impure function. Together, these restrictions ensure + that functions can only be assembled in one way, the correct way. +

    + +

    Until next time, happy hacking.

    + + + + ) + } } export default PolymorphicEffects diff --git a/src/page/blog/ProgrammingLanguageDefense.js b/src/page/blog/ProgrammingLanguageDefense.js index abe8282..d941644 100644 --- a/src/page/blog/ProgrammingLanguageDefense.js +++ b/src/page/blog/ProgrammingLanguageDefense.js @@ -1,341 +1,320 @@ -import React, {Component} from "react"; -import {Col, Container, Row} from "reactstrap"; -import {Link} from "react-router-dom"; +import React, { Component } from 'react' +import { Col, Container, Row } from 'reactstrap' +import { Link } from 'react-router-dom' class ProgrammingLanguageDefense extends Component { - - componentDidMount() { - document.title = "Flix | In Defense of Programming Languages"; - } - - render() { - return ( - - - - -

    In Defense of Programming Languages

    - -

    - Posted July 2021 by Magnus Madsen. Thanks to Matthew Lutze for discussion and - comments on an early draft. -

    - -

    - This blog post is written in defense of programming language enthusiasts; whether they are - compiler hackers, programming language hobbyists, industry professionals, or academics. -

    - -

    - In this blog post, I want to examine the discourse around programming languages and - especially how new programming languages are received. My hope is to improve communication - between programming languages designers and software developers. I understand that - we cannot all agree, but it would be fantastic if everyone could at least try to be - friendly, to be intellectually curious, and to give constructive feedback! -

    - -

    A Few Quotes from the Internet

    - -

    - Let me set the stage with a few quotes from social media tech sites (e.g. Reddit, - HackerNews, Twitter, etc.). I have lightly edited and anonymized the following quotes: -

    - -
    -

    "Great! Yet-another-programming-language™. This is exactly what we - need; the gazillion of existing programming languages is not enough!"

    -
    Furious Panda via - Reddit -
    -
    - -
    -

    "This is – by far – the worst syntax I have ever seen in a functional - language!"

    -
    Irate Penguin via - Reddit -
    -
    - -
    -

    "The language is probably great from a technical point of view, but - unless Apple, Google, Mozilla, or Microsoft is on-board it is pointless."

    -
    Angry Beaver via - HackerNews -
    -
    - -
    -

    "How can anyone understand such weird syntax? I hate all these - symbols."

    -
    Bitter - Turtle via Reddit -
    -
    - -
    -

    "The examples all look horrible. The site looks horrible. This needs a - lot of work before it gets close to anything I would even consider using."

    -
    Enraged - Koala via Twitter -
    -
    - -

    - While all of the above quotes are in response to news about the Flix programming language - (on whose website you are currently reading this blog post), depressingly similar comments - are frequently posted in response to news about other new programming languages. -

    - -

    - Why do people post such comments? And what can be done about it? -

    - -

    Where do such comments come from?

    - -

    - I think there are two reasons which are grounded in legitimate concerns: -

    - -

    -

      -
    • - Fatigue: I think there is a sense that there are new programming - languages coming out all the time. Paradoxically, I think there is both a dread of - having to keep up with ever-changing programming languages (and other technologies) - and simultaneously a sense that these new programming languages are all the same. -
    • - -
    • - Speech: Programming languages are the material with which we craft programs: - It is our way of "speaking" algorithmically. They are about what we say, how we say - it, and even what can be said. Like prose, what is beautiful and elegant is - in the eye of the beholder. It is not surprising then that when a new programming - language comes along and suggests a different form of expression that some may have - strong reactions to. -
    • -
    -

    - -

    - Of course there are also internet trolls; but let us ignore them. -

    - -

    A Point-by-Point Rebuttal

    - -

    - I want to give a point-by-point rebuttal to the most common refrains heard whenever a new - programming language is proposed. -

    - -
    Do we really need new programming languages?
    - -

    - The Flix FAQ joking responds to this question with a rhetorical - question: Do we really need safer airplanes? Do we really need electric cars? Do we - really need more ergonomic chairs? -

    - -

    - I think it is a valid argument. We want better programming languages because we want to - offer software developers better tools to write their programs. You might say that existing - programming languages already have all the feature we need, but I think that there are - exciting developments; both brand new ideas and old research ideas that are making their - way into new programming languages: -

    - -

    -

      -
    • - Safety: region-based memory management, lifetimes, ownership types, linear - types, 2nd class values, and capabilities. -
    • -
    • - Expressiveness: union and intersection types, polymorphic effect systems, - algebraic effects, type-driven development, increasingly powerful type inference. -
    • -
    • - Development Experience: the Visual Studio Code ecosystem, the language server - protocol, GitHub code-spaces. -
    • -
    -

    - -

    - I don't think we are anywhere near to the point where programming languages are as good as - they are ever going to get. On the contrary, I think we are still in the infancy of - programming language design. -

    - -
    All programming languages are the same
    - -

    - I strongly disagree. I think we are experiencing a period of programming language - fragmentation after a long period of consolidation and stagnation. For the last 15-years or - so, the industry has been dominated by C, C++, C# and Java. The market share of these - programming languages was always increasing and they were the default safe choice for new - software projects. -

    - -

    - Today that is no longer the case. The ecosystem of programming languages is much more - diverse (and stronger for it). We have Rust. We have Scala. We also have Go, Python, and - JavaScript. There is also Crystal, Elixir, Elm, and Nim (Oh, and Flix of course!) We are in - a period of fragmentation. After a decade of object-oriented ossification we are entering a - new and exciting period! -

    - -

    - If history repeats itself then at some point we will enter a new period of consolidation. - It is too early to speculate on which programming languages will be the winners, but I feel - confident that they will be much better than C, C++, C#, and Java! (Ok, maybe C++30 will be - one of them – that language changes as much as Haskell!) -

    - -

    - (Addendum: That said, it is true that many hobby programming languages look the same. But - there is a reason for that: if you want to learn about compilers it makes sense to start by - implementing a minimal functional or object-oriented programming language.) -

    - -
    New programming languages are too complicated!
    - -

    - That's the way of the world. -

    - -

    - What do you think an airline pilot from the 1950's would say if he or she entered the flight - deck of an Airbus A350? Sure, the principles of flying are the same, and indeed iteration - and recursion are not going anywhere. But we cannot expect everything to stay the same. All - those instruments are there for a reason and they make flying safer and more efficient. -

    - -

    - As another example, once universities start teaching Rust (and we will!) then programming - with ownership and lifetimes will become commonplace. As yet another example, today every - programmer can reasonably be expected to know about filter and map, - but that was certainly not the case 15 years ago! -

    - -
    A programming language cannot be successful unless a major tech company is behind it
    - -

    - Historically that has not been true. Neither PHP, Python, Ruby, Rust, or Scala had - major tech companies behind them. If industry support came, it came at a later time. -

    - -

    Ideas for Better Communication

    - -

    - With these points in mind, I want to suggest some ways to improve communication between - aspiring programming language designers and software developers: -

    - -

    - When presenting a new programming language (or ideas related to a new language): -

    - -

    -

      -
    • - Scope: State the intended scope of the project. Is it a hobby project - made for fun? Is it an open source project hoping to gain traction? Is it a research - prototype? Is it a commercially backed project? What is the intended use case? Is - there a "killer-app"? -
    • - -
    • - Implementation: What has been implemented? A compiler? An interpreter? Do you - have a standard library? How big is it? How many lines is the project? -
    • - -
    • - Novelty: What is new in the programming language? Are there some new takes - on old ideas? Is there something novel? How is the language an improvement compared - to existing languages? Does the language make you think in a new way about - programming? -
    • - -
    • - Resources: What resources are behind the programming language? Is it a hobby - project? An open source project? An academic project? Are you open to collaboration? - Do you have backing (from industry or otherwise)? -
    • - -
    • - Feedback: What kind of feedback are you looking for? What other people think? - Suggestions for improvements and related work? Constructive criticism about the - design? What it would take for someone to consider using it? -
    • - -
    • - Reality Check: Try to avoid grandiose or unsubstantiated claims: Do your - compiler really outperform modern state-of-the-art C compilers? Is your type system - really more expressive than Haskell or Idris? Is your language really safer than - Ada? -
    • -
    -

    - -

    What about Flix?

    - -

    - The time has come to nail our colors to the flag: -

    - -

    -

      -
    • - Scope: We are building a real programming language intended for real-world - use. It is an open-source project lead by academic programming language researchers. -
    • - -
    • - Implementation: The Flix compiler project is ~137,000 lines of code. We have - a realistic compiler, a standard library (extensive, but still under development), a - Visual Studio Code extension (with auto-complete!), an online playground, online - documentation, and several published papers on the novel aspects of the language. -
    • - -
    • - Novelty: We have a whole page (Innovations) - that covers this, but briefly: a unique combination of features, combined with - first-class Datalog constraints and a polymorphic effect system. -
    • - -
    • - Resources: We are a group of programming language researchers from Aarhus - University and the University of Waterloo together with a small community of open - source contributors. Through our research we have funding for working on Flix. -
    • - -
    • - Feedback: We want to know what people think about Flix, how we can make Flix - better, and what it would take for someone to consider using it. -
    • - -
    • - Reality Check: We aim to under-promise and over-deliver. We do not promote - features before they exist. Our typical pipeline is: (Research) Idea → - Implementation → Documentation → Presentation to the World. Development is not - secret; everything is on GitHub. We just don't promote anything before it is ready. - We have exciting things in the pipeline, but you will have to wait a bit before - learning about them (or spoil yourself by diving into the GitHub issues!) -
    • -
    -

    - -

    - Until next time, happy hacking. -

    - - -
    -
    - ); - } - + componentDidMount() { + document.title = 'Flix | In Defense of Programming Languages' + } + + render() { + return ( + + + +

    In Defense of Programming Languages

    + +

    + Posted July 2021 by Magnus Madsen. Thanks to Matthew Lutze for discussion and comments on an early + draft. +

    + +

    + This blog post is written in defense of programming language enthusiasts; whether they are compiler + hackers, programming language hobbyists, industry professionals, or academics. +

    + +

    + In this blog post, I want to examine the discourse around programming languages and especially how new + programming languages are received. My hope is to improve communication between programming languages + designers and software developers. I understand that we cannot all agree, but it would be fantastic if + everyone could at least try to be friendly, to be intellectually curious, and to give constructive + feedback! +

    + +

    A Few Quotes from the Internet

    + +

    + Let me set the stage with a few quotes from social media tech sites (e.g. Reddit, HackerNews, Twitter, + etc.). I have lightly edited and anonymized the following quotes: +

    + +
    +

    + "Great! Yet-another-programming-language™. This is exactly what we need; the gazillion of existing + programming languages is not enough!" +

    +
    + Furious Panda via Reddit +
    +
    + +
    +

    "This is – by far – the worst syntax I have ever seen in a functional language!"

    +
    + Irate Penguin via Reddit +
    +
    + +
    +

    + "The language is probably great from a technical point of view, but unless Apple, Google, Mozilla, or + Microsoft is on-board it is pointless." +

    +
    + Angry Beaver via HackerNews +
    +
    + +
    +

    "How can anyone understand such weird syntax? I hate all these symbols."

    +
    + Bitter Turtle via Reddit +
    +
    + +
    +

    + "The examples all look horrible. The site looks horrible. This needs a lot of work before it gets close + to anything I would even consider using." +

    +
    + Enraged Koala via Twitter +
    +
    + +

    + While all of the above quotes are in response to news about the Flix programming language (on whose + website you are currently reading this blog post), depressingly similar comments are frequently posted in + response to news about other new programming languages. +

    + +

    Why do people post such comments? And what can be done about it?

    + +

    Where do such comments come from?

    + +

    I think there are two reasons which are grounded in legitimate concerns:

    + +

    +

      +
    • + Fatigue: I think there is a sense that there are new programming languages coming out all the + time. Paradoxically, I think there is both a dread of having to keep up with ever-changing programming + languages (and other technologies) and simultaneously a sense that these new programming languages are + all the same. +
    • + +
    • + Speech: Programming languages are the material with which we craft programs: It is our way of + "speaking" algorithmically. They are about what we say, how we say it, and even what can be said. Like + prose, what is beautiful and elegant is in the eye of the beholder. It is not surprising then that + when a new programming language comes along and suggests a different form of expression that some may + have strong reactions to. +
    • +
    +

    + +

    Of course there are also internet trolls; but let us ignore them.

    + +

    A Point-by-Point Rebuttal

    + +

    + I want to give a point-by-point rebuttal to the most common refrains heard whenever a new programming + language is proposed. +

    + +
    Do we really need new programming languages?
    + +

    + The Flix FAQ joking responds to this question with a rhetorical question:{' '} + + Do we really need safer airplanes? Do we really need electric cars? Do we really need more ergonomic + chairs? + +

    + +

    + I think it is a valid argument. We want better programming languages because we want to offer software + developers better tools to write their programs. You might say that existing programming languages already + have all the feature we need, but I think that there are exciting developments; both brand new ideas and + old research ideas that are making their way into new programming languages: +

    + +

    +

      +
    • + Safety: region-based memory management, lifetimes, ownership types, linear types, 2nd class + values, and capabilities. +
    • +
    • + Expressiveness: union and intersection types, polymorphic effect systems, algebraic effects, + type-driven development, increasingly powerful type inference. +
    • +
    • + Development Experience: the Visual Studio Code ecosystem, the language server protocol, GitHub + code-spaces. +
    • +
    +

    + +

    + I don't think we are anywhere near to the point where programming languages are as good as they are ever + going to get. On the contrary, I think we are still in the infancy of programming language design. +

    + +
    All programming languages are the same
    + +

    + I strongly disagree. I think we are experiencing a period of programming language fragmentation after a + long period of consolidation and stagnation. For the last 15-years or so, the industry has been dominated + by C, C++, C# and Java. The market share of these programming languages was always increasing and they + were the default safe choice for new software projects. +

    + +

    + Today that is no longer the case. The ecosystem of programming languages is much more diverse (and + stronger for it). We have Rust. We have Scala. We also have Go, Python, and JavaScript. There is also + Crystal, Elixir, Elm, and Nim (Oh, and Flix of course!) We are in a period of fragmentation. After a + decade of object-oriented ossification we are entering a new and exciting period! +

    + +

    + If history repeats itself then at some point we will enter a new period of consolidation. It is too early + to speculate on which programming languages will be the winners, but I feel confident that they will be + much better than C, C++, C#, and Java! (Ok, maybe C++30 will be one of them – that language changes + as much as Haskell!) +

    + +

    + (Addendum: That said, it is true that many hobby programming languages look the same. But there is a + reason for that: if you want to learn about compilers it makes sense to start by implementing a minimal + functional or object-oriented programming language.) +

    + +
    New programming languages are too complicated!
    + +

    That's the way of the world.

    + +

    + What do you think an airline pilot from the 1950's would say if he or she entered the flight deck of an + Airbus A350? Sure, the principles of flying are the same, and indeed iteration and recursion are not going + anywhere. But we cannot expect everything to stay the same. All those instruments are there for a reason + and they make flying safer and more efficient. +

    + +

    + As another example, once universities start teaching Rust (and we will!) then programming with ownership + and lifetimes will become commonplace. As yet another example, today every programmer can reasonably be + expected to know about filter and map, but that was certainly not the case 15 + years ago! +

    + +
    A programming language cannot be successful unless a major tech company is behind it
    + +

    + Historically that has not been true. Neither PHP, Python, Ruby, Rust, or Scala had major tech companies + behind them. If industry support came, it came at a later time. +

    + +

    Ideas for Better Communication

    + +

    + With these points in mind, I want to suggest some ways to improve communication between aspiring + programming language designers and software developers: +

    + +

    When presenting a new programming language (or ideas related to a new language):

    + +

    +

      +
    • + Scope: State the intended scope of the project. Is it a hobby project made for fun? Is it an + open source project hoping to gain traction? Is it a research prototype? Is it a commercially backed + project? What is the intended use case? Is there a "killer-app"? +
    • + +
    • + Implementation: What has been implemented? A compiler? An interpreter? Do you have a standard + library? How big is it? How many lines is the project? +
    • + +
    • + Novelty: What is new in the programming language? Are there some new takes on old ideas? Is + there something novel? How is the language an improvement compared to existing languages? Does the + language make you think in a new way about programming? +
    • + +
    • + Resources: What resources are behind the programming language? Is it a hobby project? An open + source project? An academic project? Are you open to collaboration? Do you have backing (from industry + or otherwise)? +
    • + +
    • + Feedback: What kind of feedback are you looking for? What other people think? Suggestions for + improvements and related work? Constructive criticism about the design? What it would take for someone + to consider using it? +
    • + +
    • + Reality Check: Try to avoid grandiose or unsubstantiated claims: Do your compiler really + outperform modern state-of-the-art C compilers? Is your type system really more expressive than + Haskell or Idris? Is your language really safer than Ada? +
    • +
    +

    + +

    What about Flix?

    + +

    The time has come to nail our colors to the flag:

    + +

    +

      +
    • + Scope: We are building a real programming language intended for real-world use. It is an + open-source project lead by academic programming language researchers. +
    • + +
    • + Implementation: The Flix compiler project is ~137,000 lines of code. We have a realistic + compiler, a standard library (extensive, but still under development), a Visual Studio Code extension + (with auto-complete!), an online playground, online documentation, and several published papers on the + novel aspects of the language. +
    • + +
    • + Novelty: We have a whole page (Innovations) that covers this, + but briefly: a unique combination of features, combined with first-class Datalog constraints and a + polymorphic effect system. +
    • + +
    • + Resources: We are a group of programming language researchers from Aarhus University and the + University of Waterloo together with a small community of open source contributors. Through our + research we have funding for working on Flix. +
    • + +
    • + Feedback: We want to know what people think about Flix, how we can make Flix better, and what + it would take for someone to consider using it. +
    • + +
    • + Reality Check: We aim to under-promise and over-deliver. We do not promote features before they + exist. Our typical pipeline is: (Research) Idea → Implementation → Documentation → Presentation to the + World. Development is not secret; everything is on GitHub. We just don't promote anything before it is + ready. We have exciting things in the pipeline, but you will have to wait a bit before learning about + them (or spoil yourself by diving into the GitHub issues!) +
    • +
    +

    + +

    Until next time, happy hacking.

    + +
    +
    + ) + } } export default ProgrammingLanguageDefense diff --git a/src/page/blog/Redundancies.js b/src/page/blog/Redundancies.js index e0e57be..c83482c 100644 --- a/src/page/blog/Redundancies.js +++ b/src/page/blog/Redundancies.js @@ -1,44 +1,39 @@ -import React, {Component} from "react"; -import {Col, Container, Row} from "reactstrap"; -import InlineEditor from "../../util/InlineEditor"; +import React, { Component } from 'react' +import { Col, Container, Row } from 'reactstrap' +import InlineEditor from '../../util/InlineEditor' class Redundancies extends Component { + componentDidMount() { + document.title = 'Flix | Redundancies as Compile-Time Errors' + } - componentDidMount() { - document.title = "Flix | Redundancies as Compile-Time Errors"; - } - - render() { - return ( - - - - -

    Redundancies as Compile-Time Errors

    - -

    - Posted February 2020 by Magnus Madsen. -

    - -

    - As software developers, we strive to write correct and maintainable code. - Today, I want to share some code where I failed in these two goals. -

    - -

    - I will show you real-world code from the Flix compiler and ask you to determine what is - wrong with the code. Then, later, I will argue how programming languages can help avoid the - type of problems you will see. (Note to the reader: The Flix compiler is (currently) written - in Scala, so the code is in Scala, but the lessons learned are applied to the Flix - programming language. I hope that makes sense.) -

    - -

    - Let us begin our journey by looking at the following code fragment: -

    - - - {`case Expression.ApplyClo(exp, args, tpe, loc) => + render() { + return ( + + + +

    Redundancies as Compile-Time Errors

    + +

    + Posted February 2020 by Magnus Madsen. +

    + +

    + As software developers, we strive to write correct and maintainable code. Today, I want to share some code + where I failed in these two goals. +

    + +

    + I will show you real-world code from the Flix compiler and ask you to determine what is wrong with the + code. Then, later, I will argue how programming languages can help avoid the type of problems you will + see. (Note to the reader: The Flix compiler is (currently) written in Scala, so the code is in Scala, but + the lessons learned are applied to the Flix programming language. I hope that makes sense.) +

    + +

    Let us begin our journey by looking at the following code fragment:

    + + + {`case Expression.ApplyClo(exp, args, tpe, loc) => val e = visitExp(exp) val as = args map visitExp Expression.ApplyClo(e, as, tpe, loc) @@ -55,36 +50,26 @@ case Expression.Binary(op, exp1, exp2, tpe, loc) => val e1 = visitExp(exp1) val e2 = visitExp(exp2) Expression.Binary(op, e1, e2, tpe, loc)`} - - -

    - Do you see any issues? -

    - -

    - If not, look again. -

    - -

    - Ok, got it? -

    - -

    - The code has a subtle bug: In the case for Unary the local - variable e holds the result of the recursion on exp. But by - mistake the reconstruction of Unary uses exp and - not e as intended. The local variable e is unused. Consequently, - the specific transformations applied by visitExp under unary expressions are - silently discarded. This bug was in the Flix compiler for some time before it was - discovered. -

    - -

    - Let us continue our journey with the following code fragment: -

    - - - {`case ResolvedAst.Expression.IfThenElse(exp1, exp2, exp3, tvar, evar, loc) => + + +

    Do you see any issues?

    + +

    If not, look again.

    + +

    Ok, got it?

    + +

    + The code has a subtle bug: In the case for Unary the local variable e holds the + result of the recursion on exp. But by mistake the reconstruction of Unary uses{' '} + exp and not e as intended. The local variable e is unused. + Consequently, the specific transformations applied by visitExp under unary expressions are + silently discarded. This bug was in the Flix compiler for some time before it was discovered. +

    + +

    Let us continue our journey with the following code fragment:

    + + + {`case ResolvedAst.Expression.IfThenElse(exp1, exp2, exp3, tvar, evar, loc) => for { (tpe1, eff1) <- visitExp(exp1) (tpe2, eff2) <- visitExp(exp2) @@ -93,32 +78,24 @@ case Expression.Binary(op, exp1, exp2, tpe, loc) => resultTyp <- unifyTypM(tvar, tpe2, tpe3, loc) resultEff <- unifyEffM(evar, eff1, eff2, loc) } yield (resultTyp, resultEff)`} - +
    -

    - Do you see any issues? -

    +

    Do you see any issues?

    -

    - If not, look again. -

    +

    If not, look again.

    -

    - Ok, got it? -

    +

    Ok, got it?

    -

    - The code has a similar bug: The local variable eff3 is not used, but it - should have been used to compute resultEff. While this bug never made it into - any release of Flix, it did cause a lot of head-scratching. -

    +

    + The code has a similar bug: The local variable eff3 is not used, but it should have been used + to compute resultEff. While this bug never made it into any release of Flix, it did cause a + lot of head-scratching. +

    -

    - Now we are getting the hang of things! What about this code fragment?: -

    +

    Now we are getting the hang of things! What about this code fragment?:

    - - {`/** + + {`/** * Returns the disjunction of the two effects \`eff1\` and \`eff2\`. */ def mkOr(ef1f: Type, eff2: Type): Type = eff1 match { @@ -130,34 +107,27 @@ def mkOr(ef1f: Type, eff2: Type): Type = eff1 match { case _ => Type.Apply(Type.Apply(Type.Cst(TypeConstructor.Or), eff1), eff2) } }`} - - -

    - Do you see any issues? -

    - -

    - I am sure you did. -

    - -

    - The bug is the following: The formal parameter to mkOr is - misspelled ef1f instead of eff1. But how does this even compile, - you ask? Well, unfortunately the mkOr function is nested inside another - function that just so happens to have an argument also named eff1! - Damn Murphy and his laws. The intention was for the formal parameters - of mkOr to shadow eff1 (and eff2), but because of the - misspelling, ef1f ended up as unused and eff1 (a completely - unrelated variable) was used instead. The issue was found during development, but not - before several hours of wasted work. Not that I am bitter or anything... -

    - -

    - We are almost at the end of our journey! But what about this beast: -

    - - - {`/** + + +

    Do you see any issues?

    + +

    I am sure you did.

    + +

    + The bug is the following: The formal parameter to mkOr is misspelled ef1f{' '} + instead of eff1. But how does this even compile, you ask? Well, unfortunately the{' '} + mkOr function is nested inside another function that just so happens to have an argument also + named eff1! Damn Murphy and his laws. The intention was for the formal parameters of{' '} + mkOr to shadow eff1 (and eff2), but because of the misspelling,{' '} + ef1f ended up as unused and eff1 (a completely unrelated variable) was used + instead. The issue was found during development, but not before several hours of wasted work. Not that I + am bitter or anything... +

    + +

    We are almost at the end of our journey! But what about this beast:

    + + + {`/** * Returns the result of looking up the given \`field\` on the given \`klass\`. */ def lookupNativeField(klass: String, field: String, loc: Location): ... = try { @@ -180,141 +150,118 @@ def lookupNativeField(klass: String, field: String, loc: Location): ... = try { case ex: ClassNotFoundException => Err(NameError.UndefinedNativeClass(klass, loc)) }`} - - -

    - Do you see any issues? -

    - -

    - If not, look again. -

    - -

    - Ok, got it? -

    - -

    - Still nothing? -

    - -

    - Pause for dramatic effect. -

    - -

    - Morpheus: What if I told you... -

    - -

    - Morpheus: ... that the function has been maintained over a long period of time... -

    - -

    - Morpheus: But that there is no place where the function is called! -

    - -

    - I am sorry if that was unfair. But was it really? The Flix code base is more than 100,000 - lines of code, so it is hard to imagine that a single person can hold it in his or her head. -

    - -

    - As these examples demonstrate, and as has been demonstrated in the research literature (see - e.g. Xie and Engler 2002), - redundant or unused code is often buggy code. -

    - -

    - To overcome such issues, Flix is very strict about redundant and unused code. -

    - -
    Flix Treats Unused Code as Compile-Time Errors
    - -

    - The Flix compiler emits a compile-time error for the following redundancies: -

    - -

    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    TypeDescription
    Unused DefA function is declared, but never used.
    Unused EnumAn enum type is declared, but never used.
    Unused Enum CaseA case (variant) of an enum is declared, but never used.
    Unused Formal ParameterA formal parameter is declared, but never used.
    Unused Type ParameterA function or enum declares a type parameter, but it is never used.
    Unused Local VariableA function declares a local variable, but it is never used.
    Shadowed Local VariableA local variable hides another local variable.
    Unconditional RecursionA function unconditionally recurses on all control-flow paths.
    Useless Expression StatementAn expression statement discards the result of a pure expression.
    -

    - -

    - As the Flix language grows, we will continue to expand the list. -

    - -

    - Let us look at three concrete examples of such compile-time errors. -

    - -
    Example I: Unused Local Variable
    - -

    - Given the program fragment: -

    - - - {`def main(): Bool = + + +

    Do you see any issues?

    + +

    If not, look again.

    + +

    Ok, got it?

    + +

    Still nothing?

    + +

    + Pause for dramatic effect. +

    + +

    Morpheus: What if I told you...

    + +

    Morpheus: ... that the function has been maintained over a long period of time...

    + +

    + Morpheus: But that there is no place where the function is called! +

    + +

    + I am sorry if that was unfair. But was it really? The Flix code base is more than 100,000 lines of code, + so it is hard to imagine that a single person can hold it in his or her head. +

    + +

    + As these examples demonstrate, and as has been demonstrated in the research literature (see e.g.{' '} + Xie and Engler 2002), redundant or unused code + is often buggy code. +

    + +

    To overcome such issues, Flix is very strict about redundant and unused code.

    + +
    Flix Treats Unused Code as Compile-Time Errors
    + +

    + The Flix compiler emits a compile-time error for the following redundancies: +

    + +

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    TypeDescription
    Unused DefA function is declared, but never used.
    Unused EnumAn enum type is declared, but never used.
    Unused Enum CaseA case (variant) of an enum is declared, but never used.
    Unused Formal ParameterA formal parameter is declared, but never used.
    Unused Type ParameterA function or enum declares a type parameter, but it is never used.
    Unused Local VariableA function declares a local variable, but it is never used.
    Shadowed Local VariableA local variable hides another local variable.
    Unconditional RecursionA function unconditionally recurses on all control-flow paths.
    Useless Expression StatementAn expression statement discards the result of a pure expression.
    +

    + +

    As the Flix language grows, we will continue to expand the list.

    + +

    Let us look at three concrete examples of such compile-time errors.

    + +
    Example I: Unused Local Variable
    + +

    Given the program fragment:

    + + + {`def main(): Bool = let l1 = List.range(0, 10); let l2 = List.intersperse(42, l1); let l3 = List.range(0, 10); let l4 = List.map(x -> x :: x :: Nil, l2); let l5 = List.flatten(l4); List.exists(x -> x == 0, l5)`} - +
    -

    - The Flix compiler emits the compile-time error: -

    +

    The Flix compiler emits the compile-time error:

    - - - {`-- Redundancy Error -------------------------------------------------- foo.flix + + {`-- Redundancy Error -------------------------------------------------- foo.flix >> Unused local variable 'l3'. The variable is not referenced within its scope. @@ -331,38 +278,33 @@ Possible fixes: Compilation failed with 1 error(s).`} - + -

    - The error message offers suggestions for how to fix the problem or alternatively how to make - the compiler shut up (by explicitly marking the variable as unused). -

    +

    + The error message offers suggestions for how to fix the problem or alternatively how to make the compiler + shut up (by explicitly marking the variable as unused). +

    -

    - Modern programming languages like Elm and Rust offer a similar feature. -

    +

    Modern programming languages like Elm and Rust offer a similar feature.

    -
    Example II: Unused Enum Case
    +
    Example II: Unused Enum Case
    -

    - Given the enum declaration: -

    +

    Given the enum declaration:

    - - {`enum Color { + + {`enum Color { case Red, case Green, case Blue }`} - + -

    - If only Red and Green are used then we get the Flix compile-time - error: -

    +

    + If only Red and Green are used then we get the Flix compile-time error: +

    - - {`-- Redundancy Error -------------------------------------------------- foo.flix + + {`-- Redundancy Error -------------------------------------------------- foo.flix >> Unused case 'Blue' in enum 'Color'. @@ -377,30 +319,24 @@ Possible fixes: (3) Prefix the case with an underscore. Compilation failed with 1 error(s).`} - + -

    - Again, programming languages like Elm and Rust offer a similar feature. -

    +

    Again, programming languages like Elm and Rust offer a similar feature.

    -
    Example III: Useless Expression Statement
    +
    Example III: Useless Expression Statement
    -

    - Given the program fragment: -

    +

    Given the program fragment:

    - - {`def main(): Int = + + {`def main(): Int = List.map(x -> x + 1, 1 :: 2 :: Nil); 123`} - + -

    - The Flix compiler emits the compile-time error: -

    +

    The Flix compiler emits the compile-time error:

    - - {`-- Redundancy Error -------------------------------------------------- foo.flix + + {`-- Redundancy Error -------------------------------------------------- foo.flix >> Useless expression: It has no side-effect(s) and its result is discarded. @@ -416,79 +352,67 @@ Possible fixes: (3) Introduce a let-binding with a wildcard name. Compilation failed with 1 error(s).`} - - - -

    - The problem with the code is that the evaluation of List.map(x -{">"} x + 1, 1 :: 2 :: - Nil) has no side-effect(s) and its result is discarded. -

    - -

    - Another classic instance of this problem is when someone calls - e.g. checkPermission(...) and expects it to throw an exception if the user has - insufficient permissions, but in fact, the function simply returns a boolean which is then - discarded. -

    - -

    - But this is not your Grandma's average compile-time error. At the time of writing, I - know of no other programming language that offers a similar warning or error with - the same precision as Flix. If you do, please drop me a line on Gitter. (Before someone - rushes to suggest must_use and friends, please consider whether they work in - the presence of polymorphism as outlined below). -

    - -

    - The key challenge is to (automatically) determine whether an expression is pure - (side-effect free) in the presence of polymorphism. Specifically, the call - to List.map is pure because the function argument x -{">"} x + - 1 is pure. In other words, the purity of List.map depends on the purity - of its argument: it is effect polymorphic. The combination of type inference, - fine-grained effect inference, and effect polymorphism is a strong cocktail that I plan to - cover in a future blog post. -

    - -

    - Note: The above is fully implemented in master, but has not yet been "released". -

    - -
    Closing Thoughts
    - -

    - I hope that I have convinced you that unused code is a threat to correct and maintainable - code. However, it is a threat that can be neutralized by better programming language design - and with minor changes to development practices. Moreover, I believe that a compiler that - reports redundant or unused code can help programmers – whether inexperienced or - seasoned – avoid stupid mistakes that waste time during development. -

    - -

    - A reasonable concern is whether working with a compiler that rejects programs with unused - code is too cumbersome or annoying. In my experience, the answer is no. After - a small learning period, whenever you want to introduce a new code fragment that will not - immediately be used, you simple remember to prefix it with an underscore, and then later you - come back and remove the underscore when you are ready to use it. -

    - -

    - While there might be a short adjustment period, the upside is huge: The compiler - provides an iron-clad guarantee that all my code is used. Moreover, whenever I refactor some - code, I am immediately informed if some code fragment becomes unused. I think such long-term - maintainability concerns are significantly more important than a little bit of extra work - during initial development. -

    - -

    - Until next time, happy hacking. -

    - - -
    -
    - ); - } - + + +

    + The problem with the code is that the evaluation of List.map(x -{'>'} x + 1, 1 :: 2 :: Nil){' '} + has no side-effect(s) and its result is discarded. +

    + +

    + Another classic instance of this problem is when someone calls e.g. checkPermission(...) and + expects it to throw an exception if the user has insufficient permissions, but in fact, the function + simply returns a boolean which is then discarded. +

    + +

    + But this is not your Grandma's average compile-time error. At the time of writing, I know of no + other programming language that offers a similar warning or error with the same precision as Flix. If you + do, please drop me a line on Gitter. (Before someone rushes to suggest must_use and friends, + please consider whether they work in the presence of polymorphism as outlined below). +

    + +

    + The key challenge is to (automatically) determine whether an expression is pure (side-effect free) in the + presence of polymorphism. Specifically, the call to List.map is pure because the{' '} + function argument x -{'>'} x + 1 is pure. In other words, the purity of{' '} + List.map depends on the purity of its argument: it is effect polymorphic. The + combination of type inference, fine-grained effect inference, and effect polymorphism is a strong cocktail + that I plan to cover in a future blog post. +

    + +

    Note: The above is fully implemented in master, but has not yet been "released".

    + +
    Closing Thoughts
    + +

    + I hope that I have convinced you that unused code is a threat to correct and maintainable code. However, + it is a threat that can be neutralized by better programming language design and with minor changes to + development practices. Moreover, I believe that a compiler that reports redundant or unused code can help + programmers – whether inexperienced or seasoned – avoid stupid mistakes that waste time during + development. +

    + +

    + A reasonable concern is whether working with a compiler that rejects programs with unused code is too + cumbersome or annoying. In my experience, the answer is no. After a small learning period, whenever you + want to introduce a new code fragment that will not immediately be used, you simple remember to prefix it + with an underscore, and then later you come back and remove the underscore when you are ready to use it. +

    + +

    + While there might be a short adjustment period, the upside is huge: The compiler provides an + iron-clad guarantee that all my code is used. Moreover, whenever I refactor some code, I am immediately + informed if some code fragment becomes unused. I think such long-term maintainability concerns are + significantly more important than a little bit of extra work during initial development. +

    + +

    Until next time, happy hacking.

    + + + + ) + } } export default Redundancies diff --git a/src/page/misc/Checklist.js b/src/page/misc/Checklist.js index f8a0532..078fdfc 100644 --- a/src/page/misc/Checklist.js +++ b/src/page/misc/Checklist.js @@ -1,24 +1,24 @@ -import React, {Component} from 'react'; -import {Container} from "reactstrap"; +import React, { Component } from 'react' +import { Container } from 'reactstrap' class Checklist extends Component { + componentDidMount() { + document.title = 'Flix | Programming Language Checklist' + } - componentDidMount() { - document.title = "Flix | Programming Language Checklist"; - } + render() { + return ( + +

    Programming Language Checklist

    - render() { - return ( - -

    Programming Language Checklist

    +

    + For completeness (and fun), here is the{' '} + programming language check list for Flix: +

    -

    - For completeness (and fun), here is the programming - language check list for Flix: -

    - - -
    {`You appear to be advocating a new:
    +        
    +          
    +            {`You appear to be advocating a new:
     [x] functional [x] imperative [ ] object-oriented [ ] procedural [ ] stack-based
     [x] "multi-paradigm" [x] lazy [x] eager [x] statically-typed [ ] dynamically-typed
     [x] pure [x] impure [ ] non-hygienic [ ] visual [ ] beginner-friendly
    @@ -119,11 +119,11 @@ In conclusion, this is what I think of you:
     [ ] This is a bad language, and you should feel bad for inventing it.
     [ ] Programming in this language is an adequate punishment for inventing it.
     `}
    -                            
    -
    - - ); - } +
    +
    +
    + ) + } } -export default Checklist \ No newline at end of file +export default Checklist diff --git a/src/util/Codebox.js b/src/util/Codebox.js index af3e2dc..f060cd3 100644 --- a/src/util/Codebox.js +++ b/src/util/Codebox.js @@ -1,128 +1,132 @@ -import React, {Component} from "react"; -import SamplesData from "../data/Samples"; -import Editor from "./Editor"; -import { - Button, Col, Container, InputGroup, Row -} from "reactstrap"; -import FontAwesome from 'react-fontawesome'; -import PulseLoader from 'react-spinners/PulseLoader'; +import React, { Component } from 'react' +import SamplesData from '../data/Samples' +import Editor from './Editor' +import { Button, Col, Container, InputGroup, Row } from 'reactstrap' +import FontAwesome from 'react-fontawesome' +import PulseLoader from 'react-spinners/PulseLoader' class Codebox extends Component { - - constructor(props) { - super(props); - let randomChoice = getRandomInt(SamplesData.length); - this.state = { - choice: randomChoice, - samples: SamplesData, - dropdown: false, - input: SamplesData[randomChoice].code, - output: undefined - }; - } - - toggleDropDown() { - this.setState({dropdown: !this.state.dropdown}); + constructor(props) { + super(props) + let randomChoice = getRandomInt(SamplesData.length) + this.state = { + choice: randomChoice, + samples: SamplesData, + dropdown: false, + input: SamplesData[randomChoice].code, + output: undefined, } + } - onDropdownChoice(event) { - let newChoice = Number(event.target.value); - this.setState({ - choice: newChoice, - input: this.state.samples[newChoice].code, - output: undefined - }); - } + toggleDropDown() { + this.setState({ dropdown: !this.state.dropdown }) + } - onTextChanged(input) { - this.setState({input: input}); - } + onDropdownChoice(event) { + let newChoice = Number(event.target.value) + this.setState({ + choice: newChoice, + input: this.state.samples[newChoice].code, + output: undefined, + }) + } - onRunClick = () => { - this.setState({output: null}, () => { - this.props.flix.run(this.state.input, data => - this.setState({output: data}) - ); - }); - }; + onTextChanged(input) { + this.setState({ input: input }) + } - getRunButton() { - let enabled = this.props.flix.connected; - return ; - } + onRunClick = () => { + this.setState({ output: null }, () => { + this.props.flix.run(this.state.input, data => this.setState({ output: data })) + }) + } - getDropDown() { - return - } + getRunButton() { + let enabled = this.props.flix.connected + return ( + + ) + } - getNameOfSelection() { - return this.state.samples[this.state.choice].name; - } + getDropDown() { + return ( + + ) + } - getEditor() { - return - } + getNameOfSelection() { + return this.state.samples[this.state.choice].name + } - getOutput() { - if (this.state.output === undefined) { - return undefined; - } else if (this.state.output === null) { - return - - - - - } else { - if (this.state.output.status === "success") { - return ( - -
    Standard Output
    -
    {this.state.output.result}
    -
    ); - } else { - return ( - -
    Standard Output
    -
    {this.state.output.result}
    -
    ); - } - } - } + getEditor() { + return ( + + ) + } - render() { + getOutput() { + if (this.state.output === undefined) { + return undefined + } else if (this.state.output === null) { + return ( + + + + + + ) + } else { + if (this.state.output.status === 'success') { + return ( + +
    Standard Output
    +
    {this.state.output.result}
    +
    + ) + } else { return ( -
    - - {this.getRunButton()} - {this.getDropDown()} - - {this.getEditor()} - {this.getOutput()} -
    - ); + +
    Standard Output
    +
    {this.state.output.result}
    +
    + ) + } } + } + + render() { + return ( +
    + + {this.getRunButton()} + {this.getDropDown()} + + {this.getEditor()} + {this.getOutput()} +
    + ) + } } function getRandomInt(max) { - return Math.floor(Math.random() * Math.floor(max)); + return Math.floor(Math.random() * Math.floor(max)) } -export default Codebox; +export default Codebox diff --git a/src/util/Editor.js b/src/util/Editor.js index fd8816a..21e729e 100644 --- a/src/util/Editor.js +++ b/src/util/Editor.js @@ -1,36 +1,38 @@ -import React, {Component} from 'react'; +import React, { Component } from 'react' import AceEditor from 'react-ace' import 'brace/mode/scala' import FlixMode from './FlixMode' -import 'brace/theme/chrome'; +import 'brace/theme/chrome' class Editor extends Component { - componentDidMount() { - let customMode = new FlixMode(); - this.refs.aceEditor.editor.getSession().setMode(customMode); - } + componentDidMount() { + let customMode = new FlixMode() + this.refs.aceEditor.editor.getSession().setMode(customMode) + } - onChange(input) { - this.props.notifyTextChanged(input) - }; + onChange(input) { + this.props.notifyTextChanged(input) + } - render() { - return ( - - ) - } + render() { + return ( + + ) + } } export default Editor diff --git a/src/util/FlixMode.js b/src/util/FlixMode.js index 7d0ec39..92e983d 100644 --- a/src/util/FlixMode.js +++ b/src/util/FlixMode.js @@ -1,77 +1,86 @@ -import 'brace/mode/java'; +import 'brace/mode/java' -export class CustomHighlightRules extends window.ace.acequire("ace/mode/text_highlight_rules").TextHighlightRules { - constructor() { - super(); +export class CustomHighlightRules extends window.ace.acequire('ace/mode/text_highlight_rules').TextHighlightRules { + constructor() { + super() - var keywords = ( - "namespace|def|case|enum|opaque|type|alias|rel|lat|use|null|ref|deref|if|else|let|let*|new|as|get|set|import|match|match?|select|spawn|???|solve|project|fold|query|instance|class|with|from|into|static" - ); + var keywords = + 'namespace|def|case|enum|opaque|type|alias|rel|lat|use|null|ref|deref|if|else|let|let*|new|as|get|set|import|match|match?|select|spawn|???|solve|project|fold|query|instance|class|with|from|into|static' - var builtinConstants = ( - "true|false|Nil" - ); + var builtinConstants = 'true|false|Nil' - var builtinFunctions = ( - "avg" - ); + var builtinFunctions = 'avg' - var dataTypes = ( - "Unit|Bool|Char|Float32|Float64|Int|Int8|Int16|Int32|Int64|BigInt|String|Array|Channel|Ref|true|false|not|and|or|Pure|Impure" - ); + var dataTypes = + 'Unit|Bool|Char|Float32|Float64|Int|Int8|Int16|Int32|Int64|BigInt|String|Array|Channel|Ref|true|false|not|and|or|Pure|Impure' - var keywordMapper = this.createKeywordMapper({ - "support.function": builtinFunctions, - "keyword": keywords, - "constant.language": builtinConstants, - "storage.type": dataTypes - }, "identifier", true); + var keywordMapper = this.createKeywordMapper( + { + 'support.function': builtinFunctions, + keyword: keywords, + 'constant.language': builtinConstants, + 'storage.type': dataTypes, + }, + 'identifier', + true, + ) - this.$rules = { - "start" : [ { - token : "comment", - regex : "\\/\\/.*$" - }, { - token : "comment", - start : "/\\*", - end : "\\*/" - }, { - token : "string", // " string - regex : '".*?"' - }, { - token : "string", // ' string - regex : "'.*?'" - }, { - token : "string", // ` string (apache drill) - regex : "`.*?`" - }, { - token : "constant.numeric", // float - regex : "[+-]?\\d+(?:(?:\\.\\d*)?(?:[eE][+-]?\\d+)?)?\\b" - }, { - token : keywordMapper, - regex : "[a-zA-Z_$][a-zA-Z0-9_$]*\\b" - }, { - token : "keyword.operator", - regex : "\\+|\\-|\\/|\\/\\/|%|<@>|@>|<@|&|\\^|~|<|>|<=|=>|==|!=|<>|=" - }, { - token : "paren.lparen", - regex : "[\\(]" - }, { - token : "paren.rparen", - regex : "[\\)]" - }, { - token : "text", - regex : "\\s+" - } ] - }; - this.normalizeRules(); + this.$rules = { + start: [ + { + token: 'comment', + regex: '\\/\\/.*$', + }, + { + token: 'comment', + start: '/\\*', + end: '\\*/', + }, + { + token: 'string', // " string + regex: '".*?"', + }, + { + token: 'string', // ' string + regex: "'.*?'", + }, + { + token: 'string', // ` string (apache drill) + regex: '`.*?`', + }, + { + token: 'constant.numeric', // float + regex: '[+-]?\\d+(?:(?:\\.\\d*)?(?:[eE][+-]?\\d+)?)?\\b', + }, + { + token: keywordMapper, + regex: '[a-zA-Z_$][a-zA-Z0-9_$]*\\b', + }, + { + token: 'keyword.operator', + regex: '\\+|\\-|\\/|\\/\\/|%|<@>|@>|<@|&|\\^|~|<|>|<=|=>|==|!=|<>|=', + }, + { + token: 'paren.lparen', + regex: '[\\(]', + }, + { + token: 'paren.rparen', + regex: '[\\)]', + }, + { + token: 'text', + regex: '\\s+', + }, + ], } + this.normalizeRules() + } } export default class FlixMode extends window.ace.acequire('ace/mode/java').Mode { - constructor() { - super(); - this.HighlightRules = CustomHighlightRules; - } + constructor() { + super() + this.HighlightRules = CustomHighlightRules + } } - diff --git a/src/util/InlineEditor.js b/src/util/InlineEditor.js index c067512..40328ce 100644 --- a/src/util/InlineEditor.js +++ b/src/util/InlineEditor.js @@ -1,50 +1,51 @@ -import React, {Component} from 'react'; +import React, { Component } from 'react' import AceEditor from 'react-ace' import 'brace/mode/scala' import FlixMode from './FlixMode' -import 'brace/theme/chrome'; +import 'brace/theme/chrome' class InlineEditor extends Component { - constructor(props) { - super(props); - this.state = this.getInitialState(); - } + constructor(props) { + super(props) + this.state = this.getInitialState() + } - getInitialState() { - return { - input: this.props.children - } + getInitialState() { + return { + input: this.props.children, } + } - componentDidMount() { - const customMode = new FlixMode(); - this.refs.aceEditor.editor.getSession().setMode(customMode); - } + componentDidMount() { + const customMode = new FlixMode() + this.refs.aceEditor.editor.getSession().setMode(customMode) + } - render() { - return ( -
    -
    - -
    -
    - ) - } + render() { + return ( +
    +
    + +
    +
    + ) + } } -export default InlineEditor \ No newline at end of file +export default InlineEditor