Skip to content

Latest commit

 

History

History
193 lines (166 loc) · 8.82 KB

clojureRS.org

File metadata and controls

193 lines (166 loc) · 8.82 KB

Design Notes

Dynamic Typing

Let’s say we have a function like the invoke function (check out clojure.lang.IFn) for function-like types, that is meant to invoked on an certain number of arguments whose types are not known at compile time.

;; Example of a 'function like type'; {}, an IFn that is not a function that can nontheless
;; be invoked on arguments 
({:name "Cameron"
   :age 100} :name) => "Cameron"

In Java, this invoke function looks something like

public Object invoke(Object arg1,Object arg2 ...)  

In Rust, for now to my knowledge our two options for dynamic typing are to

  1. Use trait objects (combined with the Any trait specifically), something like
fn invoke<'a>(arg1: &'a dyn Any, ..) -> &'a dyn Any 

or

  1. To have a wrapper ADT (enum) that knows all possible types ahead of time; ie, something like
    fn invoke(arg1: &Value,..) -> Value  
    
    enum Value { 
      I32(i32),
      Symbol(Symbol),
      Keyword(Keyword), 
      .. 
    }
        

For now, I have moved forward with #2, as there appear to be some major issues one runs into with #1, although, like with all decisions on this sheet, I am open to hearing from others wiser than I in Rust.

Document problem with #1

4/15/2020 Note

It occured to me I forgot to mention a static dispatching situation like fn do_to_thing<T: Any>(thing: T)

Although I don’t think it matters, as we need heterogenous dynamic typing situations, and although that might work for a fixed argument function like

fn do_to_things<T: Any, T2: Any>(thing: T, thing2: T2)

or a fixed size data type like

Vec2<T: Any, T2: Any>(1,”cat”)

Clojure does not live in a world of fixed size anything

Exceptions

How best represent exceptions?

Conditions

First off, I’d like to play with having flat out Conditions, as you have in something like Common Lisp, over Exceptions. This would be a difference from Clojure, so I’m not sure if this sort of divergence would require me not call this Clojure (at the same time, ‘a full on Clojure that gets to live on its own, and be all that it wants to be without inheriting the restrictions of the JVM’ is part of what I want to play with here).

Implementation

There’s a few things to think about here, for now let’s just have erroneous situations flat out return a Condition type, and start adding more behavior when we get back to this.

Keeping the codebase close

Originally, at least ,the goal was to keep the Rust base as similar to the Java / C# codebase as possible for consistency, but now I am thinking the program may just as easily end up split up and designed completely differently.

Either way, each part in common will try to be as consistent with the original Java version as possible – and sometimes this will involve not going quite with Rust conventions, as is the example of the IFn trait, which for now is keeping the IFn name. See notes at top of IFn for more info

4/14/2020 Note

One glitch in my thinking that didn’t really compute when I wrote this is that the, say, Java version looks pretty straightforward underneath – if you want a hashmap, for instance, its just implemented in Java – but I am forgetting that, by virtue of the JVM, it is sort of automatically getting an efficient bytecode to evaluate to. Without that luxury, we likely do not want to just likewise implement things outright, we will likely also want to add an additional step where everything too perhaps compiles to a more efficient bytecode – or we might implement some sort of JIT action. In the long run, I’d love for this to be like SBCL, although I imagine that might take a few decades

Explore more the idea of a clean rust FFI / interop, in the usual spirit of Clojure

I get the impression that runtime reflection in Rust would be difficult if not downright impossible, so for now I would like to look into producing our interop functions at compile time (or rather, ‘pre compile time’; parsing our rust files, producing the appropriate ClojureRS rust code bridging the two, and the compiling the entire project after)

4/14/2020 Note

At the very least, we are closer, as we have some intermediate Rust traits and types we can use to create Rust values that can live inside ClojureRS. Perhaps the next step might be some derive macros, and then generation itself might become a lot easier, as the bulk of it would be carried out from within Rust itself

TypeTag

Put in separate file as Value or same? Let’s keep it separate and see how its used as the program progresses

IFn Values

If we notice, our Value enum, which just wraps all potential types (this is how we’re implementing dynamic typing), has some ‘types’ that are not concretions but interfaces. And then it has some types that are more complicated yet, like Conditions (Conditions technically are a value with a type, but are they exactly returned in the same ‘expression space’, or aren’t they sort of returned to this separate ‘exception channel’, and not to the environment where it was written but to a handler anywhere higher up awaiting it)

Anyways, I just want to keep this in mind. Right now Value wraps everything in a flat sort of way, but they aren’t all quite on the same ‘level’.

Conditions

Perhaps they could look like

(defn div [x y]
  (restart-case 
    (if (not= y 0)
      (/ x y)
      ;; Oh yeah this looks sexy 
      (error :divide-by-zero {:message "Tried to divide by zero"}))
    (return-zero [] 0)
    (return-value [r] r)
    (div-new-vals [x y] (/ x y))
    (div-new-denominator [v] (/ x v))))

(defn test-div-error
  (handler-bind 
    :on-zero-denominator 
    (fn [cond] 
      (println (:message cond))
      (invoke-restart :return-value 10))

    (div 5 0)))

Symbols, Interning

In Clojure proper, there is specific semantics for keywords and symbols – namely,

(intern "namespace" "name")

(intern "namespace/name")

Since Rust doesn’t have overloading, we can either do

pub fn intern(ns: Option<&str>, name: &str) 

Or just have two functions. So we’ve gone with the latter

intern(name)
intern_with_ns(ns,name) 

To be honest, from my time in other languages I’ve gotten used to the idea of naming all your functions, giving you all these little ‘offshoots’ of, say, intern in this case (although its basically namespacing in a way; anytime you’re doing something like blah__1 blah__other blah__3, your blah has become a ‘family name’ of sorts; a namespace).

Immutable Data Structures

Right now we likewise use naive immutables; the map is an O(n) associative-list, meaning its really a list of (key . value) pairs underneath, and a persistent ‘history’ is achieved by thinking of the list as a timeline of changes; the first element is the first change, the second the second, and if you hold the map as it was at this point in time, you simply point to this second entry. Then, to search for members of the map, you walk backwards through the timeline – but not forwards; things will continue to happen, but they won’t affect you because they’ll come afterwards.

The persistent vector is just a plain Vec, and more importantly I don’t think there’s any structural sharing there atm

The persistent list should be the closest to the proper Clojure structure, and is a Cons list, where likewise what sublist you reference depends on where your head is pointing to.

The plan is to look into `im` or implement the structures myself, if I find that’s necessary. I already started reverse engineering the PersistentHashMap myself, trying to come up with a structure with the same time / space complexities, although I will definitely not wait for me to figure that out – that could take weeks or years or forever – I’ll just look it up. But I’d be neato if I did

Memory management

Right now, the project uses plain, also-naive reference counting. Things in the ClojureRS world live inside Rc<Values> , where Value (again) is an enum wrapping all potential types.

It will be important for this to truly grow, I think, before we start trying to observe it under the hood, mapping its activities to visuals that show us just what kind of dance is going on underneath, and where the bottlenecks are.

It will be at that time that it will be best to truly start adding a deeper design to memory management, although until then I will keep reading on what others have found before me