Skip to content

Commit 3fa0d4a

Browse files
authored
Support custom data types (#352)
* Elementary support for deftype* form * Add support for `set!`ing deftype* fields * Remove unused gen-type code * Miscellany * Handle recur in deftype* methods * Slight refactoring * Remove unused Protocol impl * I hate linting * Check for interface implementation * Symbol resolution test * Merge master * Docstring improvements * Disallow extra methods in interface definition * Teeeeeeeeests
1 parent e437ea5 commit 3fa0d4a

File tree

7 files changed

+1030
-34
lines changed

7 files changed

+1030
-34
lines changed

src/basilisp/core/__init__.lpy

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -841,6 +841,10 @@
841841
;; Higher Order Functions ;;
842842
;;;;;;;;;;;;;;;;;;;;;;;;;;;;
843843

844+
(defn apply-kw
845+
[f & args]
846+
(basilisp.lang.runtime/apply-kw f args))
847+
844848
(defmacro lazy-seq
845849
"Takes a body of expressions which will produce a seq or nil. When
846850
seq is first called on the resulting lazy-seq, the sequence will be
@@ -2312,3 +2316,114 @@
23122316
(basilisp.lang.runtime/to-lisp o))
23132317
([o & {:keys [keywordize-keys] :or {keywordize-keys true}}]
23142318
(basilisp.lang.runtime/to-lisp o keywordize-keys)))
2319+
2320+
;;;;;;;;;;;;;;;;;;;;;;;;;;;;
2321+
;; Data Types & Protocols ;;
2322+
;;;;;;;;;;;;;;;;;;;;;;;;;;;;
2323+
2324+
(import* abc)
2325+
2326+
(defmulti ^:private munge
2327+
"Munge the input value into a Python-safe string. Converts keywords and
2328+
symbols into strings as by `name` prior to munging. Returns a string."
2329+
builtins/type)
2330+
2331+
(defmethod munge basilisp.lang.keyword/Keyword
2332+
[kw]
2333+
(basilisp.lang.util/munge (name kw)))
2334+
2335+
(defmethod munge basilisp.lang.symbol/Symbol
2336+
[s]
2337+
(basilisp.lang.util/munge (name s)))
2338+
2339+
(defmethod munge builtins/str
2340+
[s]
2341+
(basilisp.lang.util/munge s))
2342+
2343+
(defn gen-interface
2344+
"Generate and return a new Python interface (abstract base clase).
2345+
2346+
Callers should use `definterface` to generate new interfaces."
2347+
[interface-name methods]
2348+
(let [methods (reduce (fn [m [method-name args]]
2349+
(let [method-args (->> (concat ['self] args)
2350+
(apply vector))
2351+
method (->> (list 'fn* method-name method-args)
2352+
(eval)
2353+
(abc/abstractmethod))]
2354+
(assoc m (munge method-name) method)))
2355+
{}
2356+
methods)]
2357+
(builtins/type interface-name
2358+
#py (abc/ABC)
2359+
(lisp->py methods))))
2360+
2361+
(defmacro definterface
2362+
"Define a new Python interface (abstract base clase) with the given name
2363+
and method signatures.
2364+
2365+
Method signatures are in the form (method-name [arg1 arg2 ...]).
2366+
2367+
Interface objects cannot be directly instantiated. Instead, you must create
2368+
a concrete implementation of an interface (perhaps via deftype) and supply
2369+
implementations for all of the abstract methods.
2370+
2371+
The generated interface derives from Python's `abc.ABC` and all method
2372+
definitions are declared as `abc.abstractmethod`s and thus must be implemented
2373+
by a concrete type.
2374+
2375+
The generated interface will be immediately available after this form has
2376+
been evaluated and its utility is not limited to compilation."
2377+
[interface-name & methods]
2378+
(let [name-str (name interface-name)
2379+
method-sigs (map #(list 'quote %) methods)]
2380+
`(def ~interface-name
2381+
(gen-interface ~name-str [~@method-sigs]))))
2382+
2383+
(defmacro deftype
2384+
"Define a new Python concrete type which can implement 0 or more Python
2385+
interfaces or Basilisp protocols.
2386+
2387+
The new type will have fields matching the names in `fields`. Fields may
2388+
be referred to unqualified in the bodies of implemented methods. By default
2389+
the fields of this type are immutable. Attempting to set non-mutable fields
2390+
from a method body will result in a compiler error.
2391+
2392+
Fields may be made mutable by annotating their definition with `:mutable`
2393+
metadata. Mutable fields may be set within method bodies using the `set!`
2394+
special form. It is not advised to use mutable fields unless you are sure
2395+
you know what you are doing. As a consequence of Python's lax policy towards
2396+
immutability, types with even one mutable field may be mutated by outside
2397+
callers using `set!`, so bear that in mind when choosing mutability.
2398+
2399+
Interface or protocol implementations are declared as the name of the
2400+
interface or protocol as a symbol, followed by 1 or more method
2401+
definitions for that interface. Types are not required to declare
2402+
any interface implementations. Types which do declare interface
2403+
implementations are required to implement all interface methods. Failing
2404+
to implement all interface methods is a compile time error. Types
2405+
implementing `object` are not required to implement all `object` methods.
2406+
2407+
Method declarations should appear as:
2408+
2409+
(method-name [arg1 arg2 ...] & body)
2410+
2411+
Type objects are created with sensible `object` defaults as by `attrs`.
2412+
New types may override `object` defaults. An `__init__` function is
2413+
automatically created which takes positional arguments matching the
2414+
order of definition in `fields`. Additionally, given a name `NewType`,
2415+
a factory function will be created `->NewType` which can be used to
2416+
generate new instances of the type.
2417+
2418+
Methods must supply a `this` or `self` parameter. `recur` special forms
2419+
used in the body of a method should not include that parameter, as it
2420+
will be supplied automatically."
2421+
[type-name fields & method-impls]
2422+
(let [ctor-name (with-meta
2423+
(symbol (str "->" (name type-name)))
2424+
(meta type-name))]
2425+
`(do
2426+
(deftype* ~type-name ~fields
2427+
~@method-impls)
2428+
(def ~ctor-name ~type-name)
2429+
~type-name)))

src/basilisp/lang/compiler/constants.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ class SpecialForm:
66
AWAIT = sym.symbol("await")
77
CATCH = sym.symbol("catch")
88
DEF = sym.symbol("def")
9+
DEFTYPE = sym.symbol("deftype*")
910
DO = sym.symbol("do")
1011
FINALLY = sym.symbol("finally")
1112
FN = sym.symbol("fn*")
@@ -30,6 +31,7 @@ class SpecialForm:
3031
SYM_ASYNC_META_KEY = kw.keyword("async")
3132
SYM_DYNAMIC_META_KEY = kw.keyword("dynamic")
3233
SYM_MACRO_META_KEY = kw.keyword("macro")
34+
SYM_MUTABLE_META_KEY = kw.keyword("mutable")
3335
SYM_NO_WARN_ON_REDEF_META_KEY = kw.keyword("no-warn-on-redef")
3436
SYM_NO_WARN_WHEN_UNUSED_META_KEY = kw.keyword("no-warn-when-unused")
3537
SYM_REDEF_META_KEY = kw.keyword("redef")

0 commit comments

Comments
 (0)