{"id":13395216,"url":"https://github.com/topshell-language/topshell","last_synced_at":"2025-03-13T20:31:58.483Z","repository":{"id":66802137,"uuid":"140954531","full_name":"topshell-language/topshell","owner":"topshell-language","description":"TopShell - a purely functional, reactive scripting language","archived":false,"fork":false,"pushed_at":"2021-08-23T22:06:46.000Z","size":1713,"stargazers_count":479,"open_issues_count":2,"forks_count":9,"subscribers_count":16,"default_branch":"master","last_synced_at":"2024-07-31T17:23:42.105Z","etag":null,"topics":["functional-programming","programming-language","reactive","type-safety"],"latest_commit_sha":null,"homepage":"","language":"Scala","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/topshell-language.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null}},"created_at":"2018-07-14T14:57:03.000Z","updated_at":"2024-07-09T09:50:07.000Z","dependencies_parsed_at":null,"dependency_job_id":"655e048a-6368-4351-83a2-8201827187b7","html_url":"https://github.com/topshell-language/topshell","commit_stats":null,"previous_names":[],"tags_count":8,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/topshell-language%2Ftopshell","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/topshell-language%2Ftopshell/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/topshell-language%2Ftopshell/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/topshell-language%2Ftopshell/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/topshell-language","download_url":"https://codeload.github.com/topshell-language/topshell/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":243478335,"owners_count":20297237,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["functional-programming","programming-language","reactive","type-safety"],"created_at":"2024-07-30T17:01:46.500Z","updated_at":"2025-03-13T20:31:57.721Z","avatar_url":"https://github.com/topshell-language.png","language":"Scala","readme":"# TopShell \u003cimg src=\"https://avatars2.githubusercontent.com/u/52890641?s=200\u0026v=4\" align=\"right\" height=\"40\"\u003e\n\n*Purely functional, reactive scripting language.*\n\n* Asynchronous I/O and reactive streaming with live updates\n* Purely functional scripting with type inference and autocompletion\n* Animated, graphical data visualization without leaving the editor\n* Includes modules for working with SSH, files, processes, HTTP and more\n\n**[Download now](https://github.com/topshell-language/topshell/releases)** (no installation required).\n\n\n# Live demo\n\n**[Open the playground](http://show.ahnfelt.net/topshell/)** (somewhat older than the latest release)\n\nPress Ctrl+Enter to run a top level binding.\n\nPress Ctrl+Space for autocompletion.\n\nPress Ctrl+E to switch files. \n\nThe online playground is restricted: no file I/O, no SSH, no HTTP proxying. \n\nThe very top line and the very bottom line of the UI is not yet implemented.\n\n\n# Examples\n\n## SSH example\n\n* Read a list of IP addresses from a local file.\n* SSH into all of them, in parallel, to discover their local hostname.\n* Append the resulting `ip hostname` lines to `/etc/hosts`.\n\n```haskell\nipsText \u003c- File.readText \"ips.txt\"\n\nips = String.lines ipsText \n    |\u003e List.filter (x -\u003e x != \"\")\n\nhostTask = host -\u003e\n    Ssh.do {user: \"root\", host: host} (\n        result \u003c- Process.shell {command: \"hostname\"},\n        Task.of {name: String.trim result.out, host: host}\n    )\n    \nhosts \u003c- Task.parallel (List.map hostTask ips)\n\nlines = hosts |\u003e List.map h -\u003e h.host + \" \" + h.name\n\n_ \u003c- File.appendText \"/etc/hosts\" (\n    \"\\n\\n\" + String.join \"\\n\" lines + \"\\n\\n\"\n)\n```\n\n\n## HTTP example\n\n* Fetch some JSON from the `reqres.in` mock data API.\n* Turn the JSON into a typed record.\n* Show it in a graphical table with image avatars.\n\n```haskell\njson \u003c- Http.fetchJson {url: \"https://reqres.in/api/users?page=2\"}\n\npeople : List {id: Int, \"first_name\": String, \"last_name\": String, avatar: String} = \n    Json.toAny json.data\n\nhtmlImage = url -\u003e Html.tag \"img\" [Html.attributes [\"src\" ~\u003e url]]\n\npeopleWithImages = people |\u003e List.map (\n    p -\u003e {image: htmlImage p.avatar, name: p.\"first_name\" + \" \" + p.\"last_name\"}\n)\n\npeopleWithImages |\u003e View.table\n```\n\n\n## Stream example\n\n* Make a stream that produces the current time each second.\n* Draw an animated clock with SVG.\n\n```haskell\ninterval = duration -\u003e\n    Stream.forever 0.0 t1 -\u003e \n        t2 \u003c- Task.now, \n        delta = t2 - t1,\n        delta \u003e= duration ? Task.of t2 ; \n        Task.sleep (duration - delta);\n        Task.now\n\ntime \u003c- interval 1.0\n\nt = time / 60\na = t * Float.pi * 2.0\n\nx = Float.cos a\ny = Float.sin a\n\nHtml.tag \"svg\" [\n    Html.attributes [\n        \"viewBox\" ~\u003e \"-1 -1 2 2\"\n    ],\n    Html.tag \"circle\" [\n        Html.styles [\n            \"fill\" ~\u003e \"#e0e0e0\"\n        ],\n        Html.attributes [\n            \"cx\" ~\u003e \"0\", \n            \"cy\" ~\u003e \"0\", \n            \"r\" ~\u003e \"1\"\n        ]\n    ],\n    Html.tag \"line\" [\n        Html.styles [\n            \"stroke\" ~\u003e \"cornflowerblue\", \n            \"stroke-width\" ~\u003e \"0.1\"\n        ],\n        Html.attributes [\n            \"x1\" ~\u003e \"0\", \n            \"y1\" ~\u003e \"0\", \n            \"x2\" ~\u003e String.ofFloat x, \n            \"y2\" ~\u003e String.ofFloat y\n        ]\n    ],\n]\n```\n\n\n# Literals\n\n```haskell\n\"foo\"           // String\n42              // Int\n7.3             // Float\n[1, 2]          // List Int\n{x: 7, y: 15}   // {x: Int, y: Int}\nSome 42         // eg. [None, Some Int]\nx -\u003e x          // a -\u003e a\n```\n\nComments start with `//` and last to the end of the line. \n\nStrings with placeholders, such as `\\{placeholder}`, are functions that take a record argument with a field for each placeholder, where every field must be naturally convertable to a string, enforced via the `Display` constraint.\n\n# Lambda functions\n\nLambda functions are on the form `x -\u003e e` where `x` is an arbitrary variable name `[a-z][a-zA-Z0-9]*` and `e` is an arbitrary expression.\n\nFunctions in TopShell are usually curried. For example, a function that adds two numbers may be written `x -\u003e y -\u003e x + y`. Let's call it `add`. To call it, you simply put the arguments after the function, eg. `add 1 2` evaluates to `3`. If the arguments are not simple literals, parenthesis can be used around each, eg. `add (2 * 3) (4 * 5)` evaluates to `26`.\n\nA shorthand for functions like `x -\u003e y -\u003e x + y` is `(+)`, where `+` is any binary operator.\n\nThe pipe operator can be used to cut down on parenthesis and make the data flow from left to right. \nInstead of `f (a b) (g x (h y z))`, you can write `h y z |\u003e g x |\u003e f (a b)`.\n\n\n# Let and bind\n\nThe \"let\" form introduces a variable. \n\n```haskell\nadd = x -\u003e y -\u003e x + y\n\nadd 8 9    // 17\n```\n\nLocal lets are on the form `x = e1, e2` where `x` is a variable, `e1` is the expression whose result will be stored in `x`, and `e2` is an expression that may use `x`. At top level, we leave out the `, e2` part.\n\nBinds are on the form `x \u003c- e1, e2`. They are syntactic sugar for `flatMap (x -\u003e e2) e1`, and useful for programming with monads, which is how you eg. write to a file in TopShell.\n\nIf the result of a bind is not of interest, you can ignore the result with a wildcard, `_ \u003c- e1, e2`, or simply use a semicolon `e1; e2`.\n\n\n# Lists\n\nLists can be constructed with a list literal, eg. `[1, 2, 3]` is a list of three elements 1, 2 and 3. Lists may be manipulated with the functions in the `List` module.\n\nList supports spread syntax, eg. `[...xs, y, ...zs]` is a list of all of the elements of `xs`, followed by `y`, followed by all of the elements of `zs`. So `[...[1, 2], 3, ...[4, 5, 6]]` evaluates to `[1, 2, 3, 4, 5, 6]`.\n\nElements in lists may be conditional, eg. `[x | c, y]` omits the `x` element if `c` is false. This is especially useful when building HTML visualizations, where you often want eg. a style to be conditional.\n\n\n# Records and fields\n\nRecords in TopShell are anonymous, ie. you don't have to declare them. They're introduced with `{...}` and their fields are accessed with the dot, as in the following example:\n\n```haskell\nmagnitude = v -\u003e Float.sqrt (v.x * v.x + v.y * v.y)\n\nmagnitude {x: 5.0, y: 7.0}    // 8.602\n```\n\nRecords also support spread syntax, eg. `{z: 9.0, ...r}` creates a new record that's a copy of `r`, but with the `z` field added or replaced.\n\nRecord labels may be unquoted `[a-z][a-zA-Z0-9]*`, or if they contain other characters, enclosed in double quotes, eg. `{\"my field\": 42}`.\n\nAs a shorthand for `r -\u003e r.l`, you can write `(.l)` for any label `l`.\n\nAs a shorthand for eg. `{name: name, age: age}`, you can use record punning `{name, age}`.\n\nAs a shorthand for eg. `n -\u003e a -\u003e {name: n, age: a}`, you can use `{-\u003e name, age}`.\n\nFor pairs on the form `{key: k, value: v}`, you can use the shorthand `k ~\u003e v`.\n\n\n## Record types and field constraints\n\nWhen accessing a field `v.x` as in magnitude above, the concrete type of `v` is not known yet. Thus, a field constraint is generated `| a.x: b`, where `a` is the type of the record and `b` is the type of the `x` field in this record. The complete type of `magnitude` is:\n\n```haskell\nmagnitude : a -\u003e Float | a.x: Float | a.y: Float\n```\n\nMeaning \"magnitude is a function that takes in any type `a` and returns `Float`, as long as `a` has two fields `x` and `y` of type `Float`\". When `magnitude` is later applied to `{x: 5.0, y: 7.0}`, the constraints are checked against the concrete record type `{x: Float, y: Float}`, and since it satisfies both constraints, it type checks.\n\n\n## Optional fields\n\nThe `Http.fetchJson` function has the following signature:\n\n```haskell\nHttp.fetchJson : c -\u003e Task Json | c ~ {\n    url : String \n    ?method : String \n    ?mode : String \n    ?body : String \n    ?check : Bool \n    ?headers : List {key: String, value: String}\n}\n```\n\nThis means that `c` is a record with a field called `url` of type `String`, and optionally one or more of the fields `method`, `mode`, `body`, `check` and `headers`. A common invocation is:\n\n```haskell\njson \u003c- Http.fetchJson {url: \"https://www.example.com/data.json\", mode: \"proxy\"}\n```\n\nSince the `url` field is present, the `mode` field doesn't have the wrong type, and none of the left out fields are required, the above compiles.\n\nOptional fields are accessed with the `.?` operator, eg. `r.?optionalField`, and they return `[None, Some a]`, where `a` is the field type. For open records, there is also the optional field type constraint `record.?label: type`.\n\n\n## Modules are records\n\nModules in TopShell are simply records. This is possible because record fields can have type parameters. \n\nTo get the record value from an imported module, use three dots after the module name, eg. `List...`.\n\nThe `List` module has type `{map: a =\u003e b =\u003e (a -\u003e b) -\u003e List a -\u003e List b, ...}`, where `...` is the rest of the functions. The fat arrow `=\u003e` is explicit syntax for a type parameter. \n\n\n# Sum types and pattern matching\n\nLike records, sum types are also anonymous in TopShell. \nA value like `Some 42` can be used both where `[None, Some Int]` and eg. `[Some Int, All]` is expected.\n\n```haskell\nfallback = default -\u003e {\n    | None =\u003e default\n    | Some x =\u003e x\n}\n\nfallback 0 (Some 42)    // 42\nfallback 0 None         // 0\n```\n\nThe `{| ... =\u003e ... }` syntax creates a lambda function that pattern matches on its argument. Each `|` begins a new pattern, followed by `=\u003e` and then the corresponding expression, which may use captured variables from the pattern. Use `_` as a wildcard.\n\nNote: Pattern matching is currently very limited.\n\n\n## Sum types and sum type constraints\n\nIn the above, the inferred type is `fallback : a -\u003e [None, Some a] -\u003e a`. On the other hand, `Some 42` has type `a | Some : Int -\u003e a`, which says that `Some` must be a constructor that takes an `Int` as a parameter. `[None, Some Int]` happens to be a type that satisfies this constraint. \n\n\n# If\n\nThe if-then-else construct is `condition ? thenBody ; elseBody`, eg.\n\n```haskell\nsafeDivision = x -\u003e y -\u003e\n    y == 0.0 ? None ;\n    Some (x / y)\n```\n\nIt's typically used the way that `if(condition) return value;` is used in imperative languages, eg. to return early if a condition is true. For example, if we define a stream that emits the time each second, we'd want to skip sleeping if enough time has already passed:\n\n```haskell\ninterval = duration -\u003e\n    Stream.forever 0.0 t1 -\u003e \n        t2 \u003c- Task.now, \n        delta = t2 - t1,\n        delta \u003e= duration ? Task.of t2 ; \n        Task.sleep (duration - delta);\n        Task.now\n```\n\n\n# Importing modules\n\nWhen a module function is used, eg. `List.map f l`, the compiler first checks if `List` has been explicitly imported. If not, it implicitly imports a module from the standard library of the corresponding name, eg. `core/List.js`.\n\nYou can import modules explicitly with the following syntax:\n\n```haskell\nMatrix @ \"https://www.example.com/topshell/Matrix.js\"\n```\n\nImported files such as `Matrix.js` must be annotated with TopShell types. Please see the modules in `core/...` for examples.\n\n\n# The top level\n\nEach expression, import or definition in the TopShell top level is ends either when encountering a new unindented non-space, non-closing-brace character, or when the file ends.\n\nThe top level is reactive - top level binds `x \u003c- e` convert their right hand side to a `Stream` and definitions that depend on `x` will automatically be updated whenever the stream produces a new value. Top level binds are consumed at a pace of 1 element per 100ms (or less, if the stream is slower).\n\nLets will automatically be evaluated, but binds needs to be started manually by placing the cursor on the line of the bind and pressing `Ctrl+Enter`. This is because it may be a task that eg. writes to a file.\n\n\n# Getting help\n\nYou're encouraged to [create an issue](https://github.com/Ahnfelt/topshell/issues/new) if you have a question about TopShell.\n\nYou can also read the [FAQ](https://github.com/topshell-language/topshell/wiki/FAQ).\n","funding_links":[],"categories":["Scala","Functional"],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftopshell-language%2Ftopshell","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftopshell-language%2Ftopshell","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftopshell-language%2Ftopshell/lists"}