Skip to content

Commit

Permalink
feat(docs): comparison, terminology and upd tutorial for switch
Browse files Browse the repository at this point in the history
  • Loading branch information
emil14 committed Jan 30, 2025
1 parent 1bc4a7f commit c6d3de4
Show file tree
Hide file tree
Showing 3 changed files with 106 additions and 33 deletions.
66 changes: 34 additions & 32 deletions docs/comparison.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,45 +2,47 @@

## Neva vs Go

Go is a simple reference since Nevalang is written in Go and aims for interopability with it:

| **Feature** | **Neva** | **Go** |
| ------------------------ | ------------------------------------------------------------------ | --------------------------------------------------------------------------------- |
| **Paradigm** | Dataflow - nodes send and receive messages through connections | Mixed - control-flow (imperative) + dataflow subset (CSP) |
| **Concurrency** | Implicit - everything is concurrent by default | Explicit - goroutines, channels, and mutexes |
| **Error Handling** | Errors as values with `?` operator to avoid boilerplate | Errors as values with `if err != nil {}` boilerplate |
| **Mutability** | Immutable - no variables and pointers; data races are not possible | Mutable - variables and pointers; programmer must avoid data races |
| **Null Safety** | Yes - nil pointer dereference is impossible | No - nil pointer dereference is possible |
| **Zero Values** | No zero values - everything must be explicitly initialized | Zero values by default - everything can be initialized implicitly |
| **Subtyping** | Structural - types are equal by their shape | Nominal - types are equal by their name |
| **Traceback** | Automatic - every message traces its path | Manual - programmer must explicitly wrap every error to add context |
| **Dependency Injection** | Built-in - any component with dependency expects injection | Manual - programmer must create constructor function that takes dependencies |
| **Stream Processing** | Native support with components like `Map/Filter/Reduce` | Programmer must manually implement dataflow patterns with goroutines and channels |
| **Visual Programming** | Aims for hybrid programming with Visual Editor (WIP) | Textual language, very little visual tooling support |
Neva is built on top of Go and it tries to borrow as much good things from there as possible, no surprise they have a lot in common: both statically typed, compiles to machine code, have garbage collector, builtin concurrency, both tries to be small and simple, and both aim for great developer experience with dependency management and other tooling.

The difference is that Go's paradigm is mixed - control-flow plus dataflow subset. Nevalang on the other hand is purely dataflow. It means that Go mostly operates in abstractions such as call/return, callstack, expression evaluation. It's actually expects you to control execution flow at the level of imperative instructions like `break`, `continue` and even `goto`. The dataflow subset is what's known as [CSP](https://en.wikipedia.org/wiki/Communicating_sequential_processes) implemented as goroutines and channels. CSP is indeed dataflow but it's usage is limited in Go and programs are mostly control-flow. Go also allows control-flow concurrency with mutexes and shared state. Concurrent Go code is usually considered as harder to reason about and more error prone, despite Go having state of the art concurrency support among all popular languages.

| **Feature** | **Neva** | **Go** |
| ------------------------ | ------------------------------------------------------------------- | --------------------------------------------------------------------------------- |
| **Paradigm** | Pure Dataflow - nodes send and receive messages through connections | Mixed - control-flow (imperative) + dataflow subset (CSP) |
| **Concurrency** | Defaults to concurrency. Requires explicit synchronicity | Defaults to synchronicity. Requires explicit concurrency. |
| **Error Handling** | Errors as values with `?` operator to avoid boilerplate | Errors as values with `if err != nil {}` boilerplate |
| **Mutability** | Immutable - no variables and pointers; data races are not possible | Mutable - variables and pointers; programmer must avoid data races |
| **Null Safety** | Yes - nil pointer dereference is impossible | No - nil pointer dereference is possible |
| **Zero Values** | No zero values - everything must be explicitly initialized | Zero values by default - everything can be initialized implicitly |
| **Subtyping** | Structural - types are equal by their shape | Nominal - types are equal by their name |
| **Traceback** | Automatic - every message traces its path | Manual - programmer must explicitly wrap every error to add context |
| **Dependency Injection** | Built-in - any component with dependency expects injection | Manual - programmer must create constructor function that takes dependencies |
| **Stream Processing** | Native support with components like `Map/Filter/Reduce` | Programmer must manually implement dataflow patterns with goroutines and channels |
| **Visual Programming** | Aims for hybrid programming with Visual Editor (WIP) | Textual language, very little visual tooling support |

## Neva vs Erlang/Elixir

People often compare Neva to BEAM langauges because of message-passing, immutability, concurrency and stream-processing:

| **Feature** | **Neva** | **Erlang/Elixir** |
| ------------------------ | ------------------------------------------------------------------ | ------------------------------------------------------------- |
| **Paradigm** | Pure Dataflow - no functions, no call-stack; Just message-passing | Mixed - control-flow (FP) with dataflow subset (actors) |
| **Message Passing** | Static connections defined at compile time | Dynamic message passing to PIDs |
| **Type-System** | Strongly Typed - types are always required | Dynamic (Erlang) / Gradually-typed (Elixir) |
| **Execution Model** | Compiles to machine code and can be deployed as a single executable | Needs Virtual-Machine (BEAM) to be installed on the server |
| **Syntax** | C-like syntax with curly-braces | Esoteric (Erlang) / Ruby-like (Elixir) |
| **Error Tolerance** | Everything must be type-safe; Errors must be explicitly handled | "Let it crash" |
| **Visual Programming** | Aims for hybrid programming with Visual Editor (WIP) | Textual language, very little visual tooling support |
| **Interop** | Aims for interopability with Golang (WIP) | Interopable with BEAM-compatible family (Erlang/Elixir/Gleam) |
| **Feature** | **Neva** | **Erlang/Elixir** |
| ---------------------- | ------------------------------------------------------------------- | ------------------------------------------------------------- |
| **Paradigm** | Pure Dataflow - no functions, no call-stack; Just message-passing | Mixed - control-flow (FP) with dataflow subset (actors) |
| **Message Passing** | Static connections defined at compile time | Dynamic message passing to PIDs |
| **Type-System** | Strongly Typed - types are always required | Dynamic (Erlang) / Gradually-typed (Elixir) |
| **Execution Model** | Compiles to machine code and can be deployed as a single executable | Needs Virtual-Machine (BEAM) to be installed on the server |
| **Syntax** | C-like syntax with curly-braces | Esoteric (Erlang) / Ruby-like (Elixir) |
| **Error Tolerance** | Everything must be type-safe; Errors must be explicitly handled | "Let it crash" |
| **Visual Programming** | Aims for hybrid programming with Visual Editor (WIP) | Textual language, very little visual tooling support |
| **Interop** | Aims for interopability with Golang (WIP) | Interopable with BEAM-compatible family (Erlang/Elixir/Gleam) |

## Neva vs Gleam

BEAM family includes language that is even closer to Neva because of static types. However, there are differences:

| **Feature** | **Neva** | **Gleam** |
| ------------------------ | ------------------------------------------------------------------ | ------------------------------------------------------------- |
| **Paradigm** | Pure Dataflow - no functions, no call-stack; Just message-passing | Mixed - control-flow (FP) with dataflow subset (actors) |
| **Subtyping** | Structural - types are equal by their shape | Nominal - types are equal by their name |
| **Execution Model** | Compiles to machine code and can be deployed as a single executable | Needs Virtual-Machine (BEAM) to be installed on the server |
| **Interop** | Aims for interopability with Golang (WIP) | Interopable with BEAM-compatible family (Erlang/Elixir/Gleam) |
| **Visual Programming** | Aims for hybrid programming with Visual Editor (WIP) | Textual language, very little visual tooling support |
| **Feature** | **Neva** | **Gleam** |
| ---------------------- | ------------------------------------------------------------------- | ------------------------------------------------------------- |
| **Paradigm** | Pure Dataflow - no functions, no call-stack; Just message-passing | Mixed - control-flow (FP) with dataflow subset (actors) |
| **Subtyping** | Structural - types are equal by their shape | Nominal - types are equal by their name |
| **Execution Model** | Compiles to machine code and can be deployed as a single executable | Needs Virtual-Machine (BEAM) to be installed on the server |
| **Interop** | Aims for interopability with Golang (WIP) | Interopable with BEAM-compatible family (Erlang/Elixir/Gleam) |
| **Visual Programming** | Aims for hybrid programming with Visual Editor (WIP) | Textual language, very little visual tooling support |
30 changes: 30 additions & 0 deletions docs/terminology.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# Terminology

This document explains key terms used throughout the documentation and how they are applied.

## Paradigm

A programming paradigm is a set of abstractions that describe computation and form a hierarchical tree of different approaches.

```mermaid
---
title: Simplified Overview
---
graph TD
paradigm --> control-flow
paradigm --> dataflow
control-flow --> structural
control-flow --> object-oriented
control-flow --> functional
dataflow --> flow-based
dataflow --> csp
dataflow --> actor-model
```

## Control-Flow

Control-flow is a top-level paradigm that describes computation as a series of steps that are executed sequentially.

## Dataflow

Dataflow is a top-level paradigm that describes computation as a network of nodes that perform message-passing.
43 changes: 42 additions & 1 deletion docs/tutorial.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Tutorial

Welcome to a tour of the Nevalang programming language. This tutorial will introduce you to Nevalang through a series of guided examples.
Welcome to a tour of the Neva programming language. This tutorial will introduce you to Nevalang through a series of guided examples.

1. [Welcome](#welcome)
- [What kind of language is this?](#what-kind-of-language-is-this)
Expand Down Expand Up @@ -944,6 +944,8 @@ You should never do that if it's possible to follow "switch-true" pattern, becau

#### Multiple Sources

> WARNING: This is important section. **Don't skip** it!

One might ask, why didn't we cover multiple case senders if we covered multiple receivers? When using switch with multiple case receivers, it works differently than in control flow languages. For example:

```neva
Expand All @@ -956,3 +958,42 @@ switch {
Is **not** "if either Alice or Bob then do uppercase". It's a fan-in, meaning `Alice` and `Bob` are concurrent. Switch will select the first value sent as a case, which is random since both are message literals.

> These semantics might change in the future. There's an [issue](https://github.com/nevalang/neva/issues/788) about that.

**How To Cover this Case?**

```neva
pass1 Pass{Upper}
pass2 Pass{Upper}
---
... -> switch {
'Alice' -> pass1
'Bob' -> pass2
_ -> lower
}
[pass1, pass2] -> upper
```

With a little bit of boilerplate

**WARNING** (Possible Concurrency Issues)

`[pass1, pass2] -> upper` guarantees that `upper` receives messages in the exact same order as they are sent by `pass1` and `pass2` nodes. And `switch` guarantees that it will never reiceve next message from it's sender `... ->` until previously selected receiver has received.

BUT switch doesn't know about `[pass1, pass2] -> upper`. It will wait for `pass1` and `pass2` to receive yes, but it will not wait `pass1` and `pass2` to _send_!

Example: let's say we send `'bob', 'alice'` sequence to our switch. This sequence of events is totally possible:

```
pass1 received alice and blocked
pass2 received bob and blocked
pass2 send BOB and unlocked
pass1 send ALICE and unlocked
```

For `[pass1, pass2] -> upper` connection it will mean that `upper` must receive `BOB` _before_ `Alice` which is not correct!

*Safe Solution*

This situation will never happen if you guarantee that your component never receive next input until it previous sent result is received. But this can only be guaranteed by the parent of your compnent.

Idiomatic way would be to use higher-order component, that can wrap your concurrent component and use it in a safe way, so it will process messages fully sequentionally - synchronized.

0 comments on commit c6d3de4

Please sign in to comment.