summary refs log tree commit diff stats
path: root/lib/pure/lenientops.nim
blob: a8fc78e39163663c37a2aa74d33b2f31e9d27bdb (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
#
#
#            Nim's Runtime Library
#        (c) Copyright 2017 Nim contributors
#
#    See the file "copying.txt", included in this
#    distribution, for details about the copyright.
#

## This module offers implementations of common binary operations
## like `+`, `-`, `*`, `/` and comparison operations,
## which work for mixed float/int operands.
## All operations convert the integer operand into the
## type of the float operand. For numerical expressions, the return
## type is always the type of the float involved in the expression,
## i.e., there is no auto conversion from float32 to float64.
##
## **Note:** In general, auto-converting from int to float loses
## information, which is why these operators live in a separate
## module. Use with care.
##
## Regarding binary comparison, this module only provides unequal operators.
## The equality operator `==` is omitted, because depending on the use case
## either casting to float or rounding to int might be preferred, and users
## should make an explicit choice.

func `+`*[I: SomeInteger, F: SomeFloat](i: I, f: F): F {.inline.} =
  F(i) + f
func `+`*[I: SomeInteger, F: SomeFloat](f: F, i: I): F {.inline.} =
  f + F(i)

func `-`*[I: SomeInteger, F: SomeFloat](i: I, f: F): F {.inline.} =
  F(i) - f
func `-`*[I: SomeInteger, F: SomeFloat](f: F, i: I): F {.inline.} =
  f - F(i)

func `*`*[I: SomeInteger, F: SomeFloat](i: I, f: F): F {.inline.} =
  F(i) * f
func `*`*[I: SomeInteger, F: SomeFloat](f: F, i: I): F {.inline.} =
  f * F(i)

func `/`*[I: SomeInteger, F: SomeFloat](i: I, f: F): F {.inline.} =
  F(i) / f
func `/`*[I: SomeInteger, F: SomeFloat](f: F, i: I): F {.inline.} =
  f / F(i)

func `<`*[I: SomeInteger, F: SomeFloat](i: I, f: F): bool {.inline.} =
  F(i) < f
func `<`*[I: SomeInteger, F: SomeFloat](f: F, i: I): bool {.inline.} =
  f < F(i)
func `<=`*[I: SomeInteger, F: SomeFloat](i: I, f: F): bool {.inline.} =
  F(i) <= f
func `<=`*[I: SomeInteger, F: SomeFloat](f: F, i: I): bool {.inline.} =
  f <= F(i)

# Note that we must not defined `>=` and `>`, because system.nim already has a
# template with signature (x, y: untyped): untyped, which would lead to
# ambiguous calls.
pan class="nv">0:literal break-unless zero?:boolean reply 1:literal } ; return n*factorial(n-1) tmp1:integer <- subtract n:integer, 1:literal tmp2:integer <- factorial tmp1:integer result:integer <- multiply n:integer, tmp2:integer reply result:integer ] ``` Programs are lists of instructions, each on a line, sometimes grouped with brackets. Each instruction operates on some *operands* and returns some *results*. ``` [results] <- instruction [operands] ``` Result and operand values have to be simple; you can't nest operations. But you can have any number of values. In particular you can have any number of results. For example, you can perform integer division as follows: ``` quotient:integer, remainder:integer <- divide-with-remainder 11:literal, 3:literal ``` Each value provides its name as well as its type separated by a colon. Types can be multiple words, like: ```lisp x:integer-array:3 ; x is an array of 3 integers y:list:integer ; y is a list of integers ``` Try out the factorial program now: ```shell $ ./mu factorial.mu result: 120 # factorial of 5 ... # ignore the memory dump for now ``` (The code in `factorial.mu` has a few more parentheses than the idealized syntax above. We'll drop them when we build a real parser.) --- The name of a value is for humans, but what the computer needs to access it is its address. Mu maps names to addresses for you like in other languages, but in a more deconstructed, decentralized, programmable manner. This instruction: ```lisp z:integer <- add x:integer, y:integer ``` might turn into this: ```lisp 3:integer <- add 1:integer, 2:integer ``` You shouldn't rely on the specific address mu chooses for a variable, but it will be unique (other variables won't clobber it) and consistent (all mentions of the name will map to the same address inside a function). Things get more complicated when your functions call other functions. Mu doesn't preserve uniqueness of names across functions, so you need to organize your names into spaces. At the start of each function (like `factorial` above), set its *default space*: ```lisp default-space:space-address <- new space:literal, 30:literal ``` Without this line, all variables in the function will be *global*, something you rarely want. (Luckily, this is also the sort of mistake that will be easily caught by tests. Later we'll automatically generate this boilerplate.) *With* this line, all addresses in your function will by default refer to one of the 30 slots inside this local space. Spaces can do more than just implement local variables. You can string them together, pass them around, return them from functions, share them between parallel routines, and much else. However, any function receiving a space has to know the names and types of variables in it, so any instruction should always receive spaces created by the same function, no matter how many times it's run. (If you're familiar with lexical scope, this constraint is identical to it.) To string two spaces together, write one into slot 0 of the other. This instruction chains a space received from its caller: ```lisp 0:space-address <- next-input ``` Once you've chained spaces together, you can access variables in them by adding a 'space' property to values: ```lisp 3:integer/space:1 ``` This value is the integer in slot 3 of the space chained in slot 0 of the default space. We usually call it slot 3 in the 'next space'. `/space:2` would be the next space of the next space, and so on. See `counters.mu` for an example of managing multiple accumulators at once without allowing them to clobber each other. This is a classic example of the sorts of things closures and objects are useful for in other languages. Spaces in mu provide the same functionality. --- You can append arbitrary properties to values besides types and spaces. Just separate them with slashes. ```lisp x:integer-array:3/uninitialized y:string/tainted:yes z:list:integer/assign-once:true/assigned:false ``` Most properties are meaningless to mu, and it'll silently skip them when running, but they are fodder for *meta-programs* to check or modify your programs, a task other languages typically hide from their programmers. For example, where other programmers are restricted to the checks their type system permits and forces them to use, you'll learn to create new checks that make sense for your specific program. If it makes sense to perform different checks in different parts of your program, you'll be able to do that. To summarize: mu instructions have multiple operand and result values. Values can have multiple rows separated by slashes, and rows can have multiple columns separated by colons. The address of a value is always in the very first column of the first row of its 'table'. --- An alternative way to define factorial is by inserting *labels* and later inserting code at them. ```lisp function factorial [ default-space:space-address <- new space:literal, 30:literal n:integer <- next-operand { base-case: } recursive-case: ] after base-case [ ; if n=0 return 1 zero?:boolean <- equal n:integer, 0:literal break-unless zero?:boolean reply 1:literal ] after recursive-case [ ; return n*factorial(n-1) tmp1:integer <- subtract n:integer, 1:literal tmp2:integer <- factorial tmp1:integer result:integer <- multiply n:integer, tmp2:integer reply result:integer ] ``` (You'll find this version in `tangle.mu`.) --- Another example, this time with concurrency. ```shell $ ./mu fork.mu ``` Notice that it repeatedly prints either '34' or '35' at random. Hit ctrl-c to stop. Yet another example forks two 'routines' that communicate over a channel: ```shell $ ./mu channel.mu produce: 0 produce: 1 produce: 2 produce: 3 consume: 0 consume: 1 consume: 2 produce: 4 consume: 3 consume: 4 # The exact order above might shift over time, but you'll never see a number # consumed before it's produced. error - deadlock detected ``` Channels are the unit of synchronization in mu. Blocking on channels are the only way tasks can sleep waiting for results. The plan is to do all I/O over channels that wait for data to return. Routines are expected to communicate purely by message passing, though nothing stops them from sharing memory since all routines share a common address space. However, idiomatic mu will make it hard to accidentally read or clobber random memory locations. Bounds checking is baked deeply into the semantics, and pointer arithmetic will be mostly forbidden (except inside the memory allocator and a few other places). Notice also the error at the end. Mu can detect deadlock when running tests: routines waiting on channels that nobody will ever write to. --- Try running the tests: ```shell $ ./mu test mu.arc.t $ # all tests passed! ``` Now start reading `mu.arc.t` to see how it works. A colorized copy of it is at mu.arc.t.html and http://akkartik.github.io/mu. You might also want to peek in the `.traces` directory, which automatically includes logs for each test showing you just how it ran on my machine. If mu eventually gets complex enough that you have trouble running examples, these logs might help figure out if my system is somehow different from yours or if I've just been insufficiently diligent and my documentation is out of date. The immediate goal of mu is to build up towards an environment for parsing and visualizing these traces in a hierarchical manner, and to easily turn traces into reproducible tests by flagging inputs entering the log and outputs leaving it. The former will have to be faked in, and the latter will want to be asserted on, to turn a trace into a test. **Credits** Mu builds on many ideas that have come before, especially: - [Peter Naur](http://alistair.cockburn.us/ASD+book+extract%3A+%22Naur,+Ehn,+Musashi%22) for articulating the paramount problem of programming: communicating a codebase to others; - [Christopher Alexander](http://www.amazon.com/Notes-Synthesis-Form-Harvard-Paperbacks/dp/0674627512) and [Richard Gabriel](http://dreamsongs.net/Files/PatternsOfSoftware.pdf) for the intellectual tools for reasoning about the higher order design of a codebase; - Unix and C for showing us how to co-evolve language and OS, and for teaching the (much maligned, misunderstood and underestimated) value of concise *implementation* in addition to a clean interface; - Donald Knuth's [literate programming](http://www.literateprogramming.com/knuthweb.pdf) for liberating "code for humans to read" from the tyranny of compiler order; - [David Parnas](http://www.cs.umd.edu/class/spring2003/cmsc838p/Design/criteria.pdf) and others for highlighting the value of separating concerns and stepwise refinement; - [Lisp](http://www.paulgraham.com/rootsoflisp.html) for showing the power of dynamic languages, late binding and providing the right primitives a la carte, especially lisp macros; - The folklore of debugging by print and the trace facility in many lisp systems; - Automated tests for showing the value of developing programs inside an elaborate harness; - [Python doctest](http://docs.python.org/2/library/doctest.html) for exemplifying interactive documentation that doubles as tests; - [ReStructuredText](https://en.wikipedia.org/wiki/ReStructuredText) and [its antecedents](https://en.wikipedia.org/wiki/Setext) for showing that markup can be clean; - BDD for challenging us all to write tests at a higher level; - JavaScript and CSS for demonstrating the power of a DOM for complex structured documents.