sharkdp / numbat

A statically typed programming language for scientific computations with first class support for physical dimensions and units
https://numbat.dev
Apache License 2.0
1.24k stars 52 forks source link

Notepad/soulver-like interface for Numbat? #536

Open endigma opened 2 months ago

endigma commented 2 months ago

Are there any plans to develop a Notepad/soulver-like interface for Numbat? Something like:

I think using numbat as the backend for something like this could be really good, most of these struggle with actually manipulating the units they "support", for example:

image or image

I think this could likely be achieved by essentially re-running numbat with 1..n lines of the file provided and adding the output per-line, however there's probably a more clever way to do this. If it's supported, adding something like total or subtotal could also be very useful.

sharkdp commented 2 months ago

I would love that, too. I mentioned something similar here. Something that comes quite close are the interactive examples in my article about the Numbat type system here. The HTML/JS/CSS for that (here) can probably serve as a good starting point. It feels like it should be possible to build a PoC rather quickly.

sharkdp commented 2 months ago

In order to run this locally, one needs to build the WASM version of Numbat using

cd numbat-wasm
bash build.sh

And then copy the generated www/pkg/ folder over to the directory mentioned above.

endigma commented 2 months ago

This looks interesting, what do you think of how to get outputs working per-line of the doc? I'm pretty sure implementing it with a for loop basically feeding lines 1..n of the file into numbat would "work", but I'd really rather see some more sane implementation. What if numbat could have some sort of machine readable output mode where it spits out incremental results with source line numbers?

maybe something like:

input:

let x = 5
x + 4
x + 7

output:

[{line: 1, output: "let x: Scalar = 5"}, { line: 2, output: "9"}, {line: 3, output: "12"}]

(maybe with some more machine readable way of representing assignments, results, conversions etc)

Also, what do you think about impl for stuff like total and subtotal? I'm not super familiar with how numbat works but I assume you're parsing everything into an AST, does whitespace awareness get lost in that process?

sharkdp commented 2 months ago

This looks interesting, what do you think of how to get outputs working per-line of the doc? I'm pretty sure implementing it with a for loop basically feeding lines 1..n of the file into numbat would "work", but I'd really rather see some more sane implementation. What if numbat could have some sort of machine readable output mode where it spits out incremental results with source line numbers?

So if the input is already split into single statements (usually a single line, but can be more), that would work even today by creating one numbat::Context and then feeding each statement in one by one. We wouldn't have to do inputs 1..1, 1..2, 1..3, etc. because the Context remembers previous statements. So we would feed in statement/line 1 and get an output; feed in statement/line 2, get and output, etc.

[{line: 1, output: "let x: Scalar = 5"}, { line: 2, output: "9"}, {line: 3, output: "12"}]

Something like this might be needed eventually (and wouldn't be too hard to build, I believe), but I think we could start playing with what I suggested above to get an impression if this would work.

Also, what do you think about impl for stuff like total and subtotal? I'm not super familiar with how numbat works but I assume you're parsing everything into an AST, does whitespace awareness get lost in that process?

Do you mean total similar to what figr.app does? that should be doable today?

image

Or total as in similar to what numara.io does?

image

That is something I'd be cautious about implementing. It's not really clear to me if this feature doesn't have any hidden footguns. It feels very much like a "global hidden state" thing. And I'd rather have a language with clear semantics where identifiers don't change their meaning if they are being moved one line below.

sharkdp commented 2 months ago

Look: I built a first prototype here: #550 (try it online here: https://numbat.dev/editor.html)

endigma commented 2 months ago

For total I did mean something context-aware like numara, or numi. I think there's definitely some footguns in there but especially for finance stuff it's a super useful feature to have the subtotal and total keywords. Subtotal grabs the total of the previous unbroken lines outputs and total grabs all the subtotals, even if not shown with keyword "subtotal"

What if there was a way to make it clear which statements were being totaled? Maybe something like this? I'm very unfamiliar with numbat as whole so please excuse my naivete if this is nonsensical

let Nights = 10

let total = sum {
    let Accom = $ 120 * Nights
    let Food = $ 200 * Nights 
    ... and so on...
 }

 total = ... [Money]

and subtotals with something like


sum {
    sum {
        ...
    }
    sum {
        ...
    }
    sum {
        ...
    }
    any_arbitrary_statement
}
sharkdp commented 2 months ago

To be honest, I'm not convinced. Can you show me some real-world examples?

By the way, the example above with the travel costs could also be written with a custom unit. Let's say we would have

@aliases(nights)
unit night

then we could write it as:

let flights = $ 600

let accomodation = 120 $ per night
let food = 60 $ per night
let spending_money = 50 $ per night

flights + 10 nights × (accomodation + food + spending_money)
endigma commented 2 months ago

It's useful in pure-finance type calculations where you could do things like:

sum {
    # Funds
    sum {
        main = 798.88 CAD
        joint = 5059.42 CAD
        tfsa = 31439.60 CAD
        rent = 2033.14 CAD
        ...
    }

    # Expenses
    sum {
        rent = -1750 CAD * 12
        power = -200 CAD * 6
        food = -500 CAD * 12
        ...
    }
}

the sum operator or a context-aware total/subtotal would allow you to use it instead of large (x + y + z + p + q + r) statements that have to be updated every time you add a new item.

in an imperative language, I would write something like

package main

import "fmt"

type Summable interface {
    Sum() float64
}

type (
    Item struct {
        memo   string
        amount float64
    }
    Items []Summable
)

func (item Item) Sum() float64 {
    return item.amount
}

func (items Items) Sum() float64 {
    total := 0.0

    for _, i := range items {
        total += i.Sum()
    }

    return total
}

func main() {
    total := Items{
        Items{
            Item{"main", 798.88},
            Item{"joint", 5958.42},
            Item{"main", 31439.60},
            Item{"main", 2033.14},
        },
        Items{
            Item{"rent", -1750 * 12},
            Item{"power", -200 * 6},
            Item{"food", -500 * 12},
        },
    }.Sum()

    fmt.Println(total)
}

which may inspire a different implementation idea in Numbat

sharkdp commented 2 months ago

I realize that this could be slightly more ergonomic, but you can do similar things in Numbat. For example:

struct Item {
  what: String,
  amount: Money
}

fn fund(what, amount) = Item { what: what, amount: amount }
fn expense(what, amount) = Item { what: what, amount: -amount }

let funds = [
    fund("main", 798.88 CAD),
    fund("joint", 5059.42 CAD),
    fund("tfsa", 31439.60 CAD),
    fund("rent", 2033.14 CAD),
]

let expenses = [
    expense("rent", 1750 CAD * 12),
    expense("power", 200 CAD * 6),
    expense("food", 500 CAD * 12),
]

fn amount(i: Item) = i.amount
fn total(items) = sum(map(amount, items))

print("Funds:    {total(funds):>10.0}")
print("Expenses: {total(expenses):>10.0}")
print("Total:    {total(concat(funds, expenses)):>10.0}")

which prints:

Funds:         39331 CAD
Expenses:     -28200 CAD
Total:         11131 CAD
endigma commented 1 month ago

thats cool actually, i didn't realize numbat had structs, i should probably rtfm before posting ideas lol

ValentinLeTallec commented 1 month ago

Would it be possible to also integrate it to the CLI ? What I have in mind is a pseudo "formatter" that would add special comments (#>>> in my example) with the results.

Advantage of making it a formatter :

Before formatting

8 km / (1 h + 25 min)

atan2(30 cm, 1 m) -> deg
#>>>  "An old result that will be overwritten" [String]

let ω = 2π c / 660 nm
ℏ ω -> eV

fn breaking_distance(v) = v t_reaction + v² / 2 µ g0
  where t_reaction = 1 s # driver reaction time
    and µ = 0.7          # coefficient of friction

breaking_distance(50 km/h) -> m

After formatting

8 km / (1 h + 25 min)
#>>>  5.64706 km/h [Velocity]

atan2(30 cm, 1 m) -> deg
#>>>  16.6992°

let ω = 2π c / 660 nm
ℏ ω -> eV
#>>> 1.87855 eV [Energy or Torque]

fn breaking_distance(v) = v t_reaction + v² / 2 µ g0
  where t_reaction = 1 s # driver reaction time
    and µ = 0.7          # coefficient of friction

breaking_distance(50 km/h) -> m
#>>>  27.9392 m [Length] 
sharkdp commented 1 month ago

New preview version is here: https://numbat.dev/editor.html (thanks @Goju-Ryu)