klongpy 0.6.9__py3-none-any.whl → 0.7.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (70) hide show
  1. klongpy/__init__.py +17 -1
  2. klongpy/adverbs.py +84 -82
  3. klongpy/autograd.py +299 -0
  4. klongpy/backend.py +38 -103
  5. klongpy/backends/__init__.py +26 -0
  6. klongpy/backends/base.py +469 -0
  7. klongpy/backends/numpy_backend.py +123 -0
  8. klongpy/backends/registry.py +76 -0
  9. klongpy/backends/torch_backend.py +1047 -0
  10. klongpy-0.6.9.data/scripts/kgpy → klongpy/cli.py +110 -90
  11. klongpy/core.py +113 -974
  12. klongpy/db/sys_fn_db.py +7 -6
  13. klongpy/db/sys_fn_kvs.py +2 -4
  14. klongpy/dyads.py +332 -160
  15. klongpy/interpreter.py +60 -15
  16. klongpy/monads.py +121 -75
  17. klongpy/parser.py +328 -0
  18. klongpy/repl.py +23 -5
  19. klongpy/sys_fn.py +170 -21
  20. klongpy/sys_fn_autograd.py +290 -0
  21. klongpy/sys_fn_ipc.py +22 -15
  22. klongpy/sys_fn_timer.py +13 -3
  23. klongpy/types.py +503 -0
  24. klongpy/web/sys_fn_web.py +14 -4
  25. klongpy/writer.py +122 -0
  26. klongpy/ws/sys_fn_ws.py +5 -8
  27. klongpy-0.7.1.dist-info/METADATA +544 -0
  28. klongpy-0.7.1.dist-info/RECORD +52 -0
  29. {klongpy-0.6.9.dist-info → klongpy-0.7.1.dist-info}/WHEEL +1 -1
  30. klongpy-0.7.1.dist-info/entry_points.txt +2 -0
  31. {klongpy-0.6.9.dist-info → klongpy-0.7.1.dist-info}/top_level.txt +0 -1
  32. klongpy-0.6.9.dist-info/METADATA +0 -448
  33. klongpy-0.6.9.dist-info/RECORD +0 -77
  34. tests/__init__.py +0 -6
  35. tests/gen_join_over.py +0 -119
  36. tests/gen_py_suite.py +0 -77
  37. tests/gen_test_fn.py +0 -259
  38. tests/perf_async.py +0 -25
  39. tests/perf_avg.py +0 -18
  40. tests/perf_duckdb.py +0 -32
  41. tests/perf_gen.py +0 -38
  42. tests/perf_ipc_overhead.py +0 -34
  43. tests/perf_join.py +0 -53
  44. tests/perf_load.py +0 -17
  45. tests/perf_prog.py +0 -18
  46. tests/perf_serdes.py +0 -52
  47. tests/perf_sys_fn_db.py +0 -263
  48. tests/perf_vector.py +0 -40
  49. tests/test_accel.py +0 -227
  50. tests/test_df_cache.py +0 -85
  51. tests/test_eval_monad_list.py +0 -34
  52. tests/test_examples.py +0 -64
  53. tests/test_extra_suite.py +0 -382
  54. tests/test_file_cache.py +0 -185
  55. tests/test_interop.py +0 -180
  56. tests/test_kg_asarray.py +0 -94
  57. tests/test_kgtests.py +0 -65
  58. tests/test_known_bugs.py +0 -206
  59. tests/test_prog.py +0 -107
  60. tests/test_reshape_strings.py +0 -33
  61. tests/test_suite.py +0 -1480
  62. tests/test_suite_file.py +0 -153
  63. tests/test_sys_fn.py +0 -420
  64. tests/test_sys_fn_db.py +0 -88
  65. tests/test_sys_fn_ipc.py +0 -587
  66. tests/test_sys_fn_timer.py +0 -133
  67. tests/test_sys_fn_web.py +0 -50
  68. tests/test_util.py +0 -233
  69. tests/utils.py +0 -126
  70. {klongpy-0.6.9.dist-info → klongpy-0.7.1.dist-info}/licenses/LICENSE +0 -0
klongpy/interpreter.py CHANGED
@@ -2,10 +2,12 @@ import time
2
2
  from collections import deque
3
3
 
4
4
  from .adverbs import get_adverb_fn
5
+ from .backends import get_backend
5
6
  from .core import *
6
7
  from .dyads import create_dyad_functions
7
8
  from .monads import create_monad_functions
8
9
  from .sys_fn import create_system_functions
10
+ from .sys_fn_autograd import create_system_functions_autograd
9
11
  from .sys_fn_ipc import create_system_functions_ipc, create_system_var_ipc
10
12
  from .sys_fn_timer import create_system_functions_timer
11
13
  from .sys_var import *
@@ -52,9 +54,10 @@ class KlongContext():
52
54
 
53
55
  """
54
56
 
55
- def __init__(self, system_contexts):
57
+ def __init__(self, system_contexts, strict_mode=1):
56
58
  self._context = deque([{}, *system_contexts])
57
59
  self._min_ctx_count = len(system_contexts)
60
+ self._strict_mode = strict_mode
58
61
 
59
62
  def start_module(self, name):
60
63
  self.push(KGModule(name))
@@ -68,11 +71,28 @@ class KlongContext():
68
71
 
69
72
  def __setitem__(self, k, v):
70
73
  assert isinstance(k, KGSym)
74
+
71
75
  if k not in reserved_fn_symbols:
76
+ # Check if variable exists in any scope
72
77
  for d in self._context:
73
78
  if in_map(k, d):
74
79
  d[k] = v
75
80
  return k
81
+
82
+ # Variable doesn't exist - check strict mode
83
+ if self._strict_mode >= 1:
84
+ # Check if we're inside a function (more than just global scope)
85
+ in_function = len(self._context) > self._min_ctx_count + 1
86
+
87
+ if in_function:
88
+ # Inside function - disallow creating new variables
89
+ raise KlongException(
90
+ f"undefined variable: {k}\n"
91
+ f" To create a local variable, declare it in the parameter list: {{[{k}]; ...}}\n"
92
+ f" To modify an existing global, ensure it exists before calling the function"
93
+ )
94
+
95
+ # Create new variable in current scope
76
96
  set_context_var(self._context[0], k, v)
77
97
  return k
78
98
 
@@ -141,6 +161,7 @@ def create_system_contexts():
141
161
 
142
162
  sys_d = {}
143
163
  add_context_key_values(sys_d, create_system_functions())
164
+ add_context_key_values(sys_d, create_system_functions_autograd())
144
165
  add_context_key_values(sys_d, create_system_functions_ipc())
145
166
  add_context_key_values(sys_d, create_system_functions_timer())
146
167
  set_context_var(sys_d, KGSym('.e'), eval_sys_var_epsilon()) # TODO: support lambda
@@ -210,13 +231,36 @@ def chain_adverbs(klong, arr):
210
231
 
211
232
  class KlongInterpreter():
212
233
 
213
- def __init__(self):
214
- self._context = KlongContext(create_system_contexts())
234
+ def __init__(self, backend=None, device=None):
235
+ """
236
+ Initialize a Klong interpreter.
237
+
238
+ Parameters
239
+ ----------
240
+ backend : str, optional
241
+ Backend name ('numpy' or 'torch'). Defaults to 'numpy'.
242
+ device : str, optional
243
+ Device for torch backend ('cpu', 'cuda', 'mps'). Only applies
244
+ when backend='torch'. If None, auto-selects best available device.
245
+ """
246
+ self._backend = get_backend(backend, device=device)
247
+ strict_mode = 0 # 0=unsafe (default for backward compat), 1=strict, 2=pedantic
248
+ self._context = KlongContext(create_system_contexts(), strict_mode=strict_mode)
215
249
  self._vd = create_dyad_functions(self)
216
250
  self._vm = create_monad_functions(self)
217
251
  self._start_time = time.time()
218
252
  self._module = None
219
253
 
254
+ @property
255
+ def backend(self):
256
+ """Return the backend provider for this interpreter."""
257
+ return self._backend
258
+
259
+ @property
260
+ def np(self):
261
+ """Return the numpy-compatible array module for this interpreter."""
262
+ return self._backend.np
263
+
220
264
  def __setitem__(self, k, v):
221
265
  k = k if isinstance(k, KGSym) else KGSym(k)
222
266
  self._context[k] = v
@@ -224,7 +268,8 @@ class KlongInterpreter():
224
268
  def __getitem__(self, k):
225
269
  k = k if isinstance(k, KGSym) else KGSym(k)
226
270
  r = self._context[k]
227
- return KGFnWrapper(self, r) if issubclass(type(r), KGFn) else r
271
+ # Pass the symbol name to avoid O(n) context search
272
+ return KGFnWrapper(self, r, sym=k) if issubclass(type(r), KGFn) else r
228
273
 
229
274
  def __delitem__(self, k):
230
275
  k = k if isinstance(k, KGSym) else KGSym(k)
@@ -304,7 +349,7 @@ class KlongInterpreter():
304
349
  return i+1,arr
305
350
  k = i
306
351
  while True:
307
- ii,c = kg_read(t,i,ignore_newline=True,module=self.current_module())
352
+ ii,c = kg_read(t, i, ignore_newline=True, module=self.current_module())
308
353
  if safe_eq(c, ';'):
309
354
  i = ii
310
355
  if k == i - 1:
@@ -352,7 +397,7 @@ class KlongInterpreter():
352
397
  | V P
353
398
 
354
399
  """
355
- i,a = kg_read(t, i, ignore_newline=ignore_newline, module=self.current_module())
400
+ i,a = kg_read_array(t, i, self._backend, ignore_newline=ignore_newline, module=self.current_module())
356
401
  if a is None:
357
402
  return i,a
358
403
  if safe_eq(a, '{'): # read fn
@@ -569,14 +614,14 @@ class KlongInterpreter():
569
614
  ctx = {} if f_args is None else {reserved_fn_symbol_map[p]: self.call(q) for p,q in zip(reserved_fn_args,f_args)}
570
615
 
571
616
  if is_list(f) and len(f) > 1 and is_list(f[0]) and len(f[0]) > 0:
572
- have_locals = True
573
- for q in f[0]:
574
- if not isinstance(q, KGSym):
575
- have_locals = False
576
- break
617
+ # Filter out semicolons and check if all remaining elements are symbols
618
+ params = [q for q in f[0] if isinstance(q, KGSym)]
619
+ have_locals = len(params) > 0 and all(isinstance(q, KGSym) for q in params)
577
620
  if have_locals:
578
- for q in f[0]:
579
- ctx[q] = q
621
+ for q in params:
622
+ # Don't overwrite function parameters (x, y, z)
623
+ if q not in ctx:
624
+ ctx[q] = q
580
625
  f = f[1:]
581
626
 
582
627
  ctx[reserved_dot_f_symbol] = f
@@ -618,7 +663,7 @@ class KlongInterpreter():
618
663
  f = self._get_op_fn(x.a.a, x.a.arity)
619
664
  fa = (x.args if isinstance(x.args, list) else [x.args]) if x.args is not None else x.args
620
665
  _y = self.eval(fa[1]) if x.a.arity == 2 else None
621
- _x = fa[0] if x.a.a == '::' else self.eval(fa[0])
666
+ _x = fa[0] if x.a.a in ['::','∇'] else self.eval(fa[0])
622
667
  return f(_x) if x.a.arity == 1 else f(_x, _y)
623
668
  elif x.is_adverb_chain():
624
669
  return chain_adverbs(self, x.a)()
@@ -626,7 +671,7 @@ class KlongInterpreter():
626
671
  return self._eval_fn(x)
627
672
  elif isinstance(x, KGCond):
628
673
  q = self.call(x[0])
629
- p = not ((is_number(q) and q == 0) or is_empty(q))
674
+ p = not ((self._backend.is_number(q) and q == 0) or is_empty(q))
630
675
  return self.call(x[1]) if p else self.call(x[2])
631
676
  elif isinstance(x,list) and len(x) > 0:
632
677
  return [self.call(y) for y in x][-1]
klongpy/monads.py CHANGED
@@ -1,5 +1,6 @@
1
1
  from .core import *
2
- import sys
2
+ from .autograd import grad_of_fn
3
+
3
4
 
4
5
  def eval_monad_atom(a):
5
6
  """
@@ -18,7 +19,7 @@ def eval_monad_atom(a):
18
19
  return kg_truth(is_atom(a))
19
20
 
20
21
 
21
- def eval_monad_char(a):
22
+ def eval_monad_char(a, backend):
22
23
  """
23
24
 
24
25
  :#a [Char]
@@ -31,10 +32,10 @@ def eval_monad_char(a):
31
32
  :#10 --> :"newline character"
32
33
 
33
34
  """
34
- return rec_fn(a, lambda x: KGChar(chr(x))) if is_list(a) else KGChar(chr(a))
35
+ return backend.rec_fn(a, lambda x: KGChar(chr(x))) if is_list(a) else KGChar(chr(a))
35
36
 
36
37
 
37
- def eval_monad_enumerate(a):
38
+ def eval_monad_enumerate(a, backend):
38
39
  """
39
40
 
40
41
  !a [Enumerate]
@@ -46,9 +47,9 @@ def eval_monad_enumerate(a):
46
47
  !10 --> [0 1 2 3 4 5 6 7 8 9]
47
48
 
48
49
  """
49
- if not is_integer(a):
50
+ if not backend.is_integer(a):
50
51
  raise RuntimeError(f"enumerate: invalid type error: {a}")
51
- return np.arange(int(a))
52
+ return bknp.arange(int(a))
52
53
 
53
54
 
54
55
  def eval_monad_expand_where(a):
@@ -75,7 +76,7 @@ def eval_monad_expand_where(a):
75
76
 
76
77
  """
77
78
  arr = a if is_list(a) else [a]
78
- return np.repeat(np.arange(len(arr)), arr)
79
+ return bknp.repeat(bknp.arange(len(arr)), arr)
79
80
 
80
81
 
81
82
  def eval_monad_first(a):
@@ -97,7 +98,7 @@ def eval_monad_first(a):
97
98
  return a if is_empty(a) or not is_iterable(a) else a[0]
98
99
 
99
100
 
100
- def eval_monad_floor(a):
101
+ def eval_monad_floor(a, backend):
101
102
  """
102
103
 
103
104
  _a [Floor]
@@ -115,10 +116,10 @@ def eval_monad_floor(a):
115
116
  _1e100 --> 1.0e+100 :"if precision < 100 digits"
116
117
 
117
118
  """
118
- return vec_fn(a, lambda x: np.floor(np.asarray(x, dtype=float)).astype(int))
119
+ return backend.vec_fn(a, backend.floor_to_int)
119
120
 
120
121
 
121
- def eval_monad_format(a):
122
+ def eval_monad_format(a, backend):
122
123
  """
123
124
 
124
125
  $a [Format]
@@ -136,10 +137,10 @@ def eval_monad_format(a):
136
137
  $:foo --> ":foo"
137
138
 
138
139
  """
139
- return f":{a}" if isinstance(a, KGSym) else vec_fn(a, eval_monad_format) if is_list(a) else str(a)
140
+ return f":{a}" if isinstance(a, KGSym) else backend.vec_fn(a, lambda x: eval_monad_format(x, backend)) if is_list(a) else str(a)
140
141
 
141
142
 
142
- def eval_monad_grade_up(a):
143
+ def eval_monad_grade_up(a, backend):
143
144
  """
144
145
 
145
146
  <a [Grade-Up]
@@ -164,10 +165,10 @@ def eval_monad_grade_up(a):
164
165
  >[[1] [2] [3]] --> [2 1 0]
165
166
 
166
167
  """
167
- return kg_argsort(kg_asarray(a))
168
+ return kg_argsort(backend.kg_asarray(a), backend)
168
169
 
169
170
 
170
- def eval_monad_grade_down(a):
171
+ def eval_monad_grade_down(a, backend):
171
172
  """
172
173
 
173
174
  >a [Grade-Down]
@@ -175,10 +176,10 @@ def eval_monad_grade_down(a):
175
176
  See [Grade-Up].
176
177
 
177
178
  """
178
- return kg_argsort(kg_asarray(a), descending=True)
179
+ return kg_argsort(backend.kg_asarray(a), backend, descending=True)
179
180
 
180
181
 
181
- def eval_monad_groupby(a):
182
+ def eval_monad_groupby(a, backend):
182
183
  """
183
184
 
184
185
  =a [Group]
@@ -195,15 +196,15 @@ def eval_monad_groupby(a):
195
196
  ="hello foo" --> [[0] [1] [2 3] [4 7 8] [5] [6]]
196
197
 
197
198
  """
198
- arr = kg_asarray(a)
199
- if arr.size == 0:
199
+ arr = backend.kg_asarray(a)
200
+ if backend.array_size(arr) == 0:
200
201
  return arr
201
- vals, inverse = np.unique(arr, return_inverse=True)
202
- groups = [np.where(inverse == i)[0] for i in range(len(vals))]
203
- return kg_asarray(groups)
202
+ vals, inverse = bknp.unique(arr, return_inverse=True)
203
+ groups = [bknp.where(inverse == i)[0] for i in range(len(vals))]
204
+ return backend.kg_asarray(groups)
204
205
 
205
206
 
206
- def eval_monad_list(a):
207
+ def eval_monad_list(a, backend):
207
208
  """
208
209
 
209
210
  ,a [List]
@@ -215,14 +216,12 @@ def eval_monad_list(a):
215
216
  ,"xyz" --> ["xyz"]
216
217
  ,[1] --> [[1]]
217
218
  """
218
- if isinstance(a, KGChar):
219
+ if is_char(a):
219
220
  return str(a)
220
- if isinstance(a, KGSym):
221
- return np.asarray([a],dtype=object) # np interprets ':foo" as ':fo"
222
- return np.asarray([a])
221
+ return backend.kg_asarray([a])
223
222
 
224
223
 
225
- def eval_monad_negate(a):
224
+ def eval_monad_negate(a, backend):
226
225
  """
227
226
 
228
227
  -a [Negate]
@@ -235,10 +234,10 @@ def eval_monad_negate(a):
235
234
  -1.23 --> -1.23
236
235
 
237
236
  """
238
- return vec_fn(a, lambda x: np.negative(kg_asarray(x)))
237
+ return backend.vec_fn(a, lambda x: backend.np.negative(backend.kg_asarray(x)))
239
238
 
240
239
 
241
- def eval_monad_not(a):
240
+ def eval_monad_not(a, backend):
242
241
  """
243
242
 
244
243
  ~a [Not]
@@ -254,11 +253,11 @@ def eval_monad_not(a):
254
253
 
255
254
  """
256
255
  def _neg(x):
257
- return 1 if is_empty(x) else 0 if is_dict(x) or isinstance(x, (KGFn, KGSym)) else kg_truth(np.logical_not(np.asarray(x, dtype=object)))
258
- return vec_fn(a, _neg) if not is_empty(a) else _neg(a)
256
+ return 1 if is_empty(x) else 0 if is_dict(x) or isinstance(x, (KGFn, KGSym)) else kg_truth(bknp.logical_not(bknp.asarray(x, dtype=object)))
257
+ return backend.vec_fn(a, _neg) if not is_empty(a) else _neg(a)
259
258
 
260
259
 
261
- def eval_monad_range(a):
260
+ def eval_monad_range(a, backend):
262
261
  """
263
262
 
264
263
  ?a [Range]
@@ -271,28 +270,19 @@ def eval_monad_range(a):
271
270
  ?"aaabbcccd" --> "abcd"
272
271
 
273
272
  """
273
+ np_backend = backend.np
274
274
  if isinstance(a, str):
275
- return ''.join(np.unique(str_to_chr_arr(a)))
276
- elif np.isarray(a):
277
- if a.dtype != 'O' and a.ndim > 1:
278
- _,ids = np.unique(a,axis=0,return_index=True)
275
+ return ''.join(bknp.unique(backend.str_to_chr_arr(a)))
276
+ elif np_backend.isarray(a):
277
+ dtype_kind = backend.get_dtype_kind(a)
278
+ if dtype_kind != 'O' and a.ndim > 1:
279
+ # Use numpy for unique with return_index across backends
280
+ a_np = backend.to_numpy(a) if backend.is_backend_array(a) else a
281
+ _, ids = bknp.unique(a_np, axis=0, return_index=True)
282
+ ids.sort()
283
+ return a[ids]
279
284
  else:
280
285
  # handle the jagged / mixed array case
281
- # from functools import total_ordering
282
- # @total_ordering
283
- # class Wrapper:
284
- # def __init__(self, x):
285
- # self.x = x
286
- # def __eq__(self,o):
287
- # print("eq")
288
- # return array_equal(self.x, o.x)
289
- # def __ne__(self,o):
290
- # return not array_equal(self.x, o.x)
291
- # def __lt__(self, o):
292
- # u = np.sort(np.asarray([self.x, o.x]))
293
- # return u[0] == self.x
294
- # # return u[0] if isinstance(u,np.ndarray) else u
295
- # _,ids = np.unique([Wrapper(x) for x in a], return_index=True)
296
286
  # TODO: Make UNIQUE work. this feels so dirty.
297
287
  s = set()
298
288
  arr = []
@@ -301,13 +291,11 @@ def eval_monad_range(a):
301
291
  if sx not in s:
302
292
  s.add(sx)
303
293
  arr.append(x)
304
- return np.asarray(arr, dtype=object)
305
- ids.sort()
306
- a = a[ids]
294
+ return backend.kg_asarray(arr)
307
295
  return a
308
296
 
309
297
 
310
- def eval_monad_reciprocal(a):
298
+ def eval_monad_reciprocal(a, backend):
311
299
  """
312
300
 
313
301
  %a [Reciprocal]
@@ -321,7 +309,11 @@ def eval_monad_reciprocal(a):
321
309
  %0.1 --> 10.0
322
310
 
323
311
  """
324
- return vec_fn(a, lambda x: np.reciprocal(np.asarray(x,dtype=float)))
312
+ if not is_list(a) and backend.is_number(a):
313
+ a_val = backend.scalar_to_python(a) if backend.is_backend_array(a) else a
314
+ if a_val == 0:
315
+ return KLONG_UNDEFINED
316
+ return backend.vec_fn(a, lambda x: bknp.reciprocal(bknp.asarray(x, dtype=float)))
325
317
 
326
318
 
327
319
  def eval_monad_reverse(a):
@@ -341,7 +333,7 @@ def eval_monad_reverse(a):
341
333
  return a[::-1]
342
334
 
343
335
 
344
- def eval_monad_shape(a):
336
+ def eval_monad_shape(a, backend):
345
337
  """
346
338
 
347
339
  ^a [Shape]
@@ -393,12 +385,20 @@ def eval_monad_shape(a):
393
385
 
394
386
  """
395
387
 
388
+ def _normalize_backend_array(x):
389
+ return backend.to_numpy(x) if backend.is_backend_array(x) else x
390
+
396
391
  def _a(x): # use numpy's natural shape by replacing all strings with arrays
397
- return np.asarray([np.empty(len(y)) if isinstance(y,str) else (_a(y) if is_list(y) else y) for y in x])
398
- return 0 if is_atom(a) else np.asarray([len(a)]) if isinstance(a,str) else np.asarray(_a(a).shape)
392
+ x = _normalize_backend_array(x)
393
+ return bknp.asarray([
394
+ bknp.empty(len(y)) if isinstance(y, str) else (_a(y) if is_list(y) else _normalize_backend_array(y))
395
+ for y in x
396
+ ])
397
+ a = _normalize_backend_array(a)
398
+ return 0 if is_atom(a) else bknp.asarray([len(a)]) if isinstance(a, str) else bknp.asarray(_a(a).shape)
399
399
 
400
400
 
401
- def eval_monad_size(a):
401
+ def eval_monad_size(a, backend):
402
402
  """
403
403
 
404
404
  #a [Size]
@@ -417,7 +417,7 @@ def eval_monad_size(a):
417
417
  #0cA --> 65
418
418
 
419
419
  """
420
- return np.abs(a) if is_number(a) else ord(a) if is_char(a) else len(a)
420
+ return backend.np.abs(a) if backend.is_number(a) else ord(a) if is_char(a) else len(a)
421
421
 
422
422
 
423
423
  def eval_monad_transpose(a):
@@ -432,10 +432,10 @@ def eval_monad_transpose(a):
432
432
  +[] --> []
433
433
 
434
434
  """
435
- return np.transpose(np.asarray(a))
435
+ return bknp.transpose(bknp.asarray(a))
436
436
 
437
437
 
438
- def eval_monad_undefined(a):
438
+ def eval_monad_undefined(a, backend):
439
439
  """
440
440
 
441
441
  :_a [Undefined]
@@ -450,20 +450,66 @@ def eval_monad_undefined(a):
450
450
  :_:valid --> 0
451
451
 
452
452
  """
453
- return kg_truth(a is None or (np.isinf(a) if is_number(a) else False))
453
+ return kg_truth(a is None or a is KLONG_UNDEFINED)
454
454
 
455
455
 
456
- def create_monad_functions(klong):
457
- def _get_name(s):
458
- s = s.strip()
459
- return s[:s.index('a')]
456
+ def eval_monad_track(a):
457
+ """
458
+
459
+ ˙a [Track]
460
+
461
+ Identity operator used when marking values for gradient tracking.
462
+
463
+ """
464
+ return a
465
+
466
+
467
+ def eval_monad_grad(klong, a):
468
+ """
460
469
 
461
- registry = {}
470
+ ∇a [Grad]
462
471
 
463
- m = sys.modules[__name__]
464
- for x in filter(lambda n: n.startswith("eval_monad_"), dir(m)):
465
- fn = getattr(m,x)
466
- name = _get_name(fn.__doc__)
467
- registry[name] = fn
472
+ Return a function that computes the numeric gradient of ``a``.
468
473
 
469
- return registry
474
+ """
475
+ return KGLambda(lambda x, fn=a, k=klong: grad_of_fn(k, fn, x))
476
+
477
+
478
+ def create_monad_functions(klong):
479
+ backend = klong._backend
480
+
481
+ # Simple monads that don't need backend or klong
482
+ simple = {
483
+ '@': eval_monad_atom,
484
+ '&': eval_monad_expand_where,
485
+ '*': eval_monad_first,
486
+ '|': eval_monad_reverse,
487
+ '+': eval_monad_transpose,
488
+ '˙': eval_monad_track,
489
+ }
490
+
491
+ # Monads needing backend
492
+ backend_monads = {
493
+ ',': lambda a: eval_monad_list(a, backend),
494
+ ':#': lambda a: eval_monad_char(a, backend),
495
+ '!': lambda a: eval_monad_enumerate(a, backend),
496
+ '_': lambda a: eval_monad_floor(a, backend),
497
+ '$': lambda a: eval_monad_format(a, backend),
498
+ '<': lambda a: eval_monad_grade_up(a, backend),
499
+ '>': lambda a: eval_monad_grade_down(a, backend),
500
+ '=': lambda a: eval_monad_groupby(a, backend),
501
+ '-': lambda a: eval_monad_negate(a, backend),
502
+ '~': lambda a: eval_monad_not(a, backend),
503
+ '?': lambda a: eval_monad_range(a, backend),
504
+ '%': lambda a: eval_monad_reciprocal(a, backend),
505
+ '#': lambda a: eval_monad_size(a, backend),
506
+ ':_': lambda a: eval_monad_undefined(a, backend),
507
+ '^': lambda a: eval_monad_shape(a, backend),
508
+ }
509
+
510
+ # Monads needing klong
511
+ klong_monads = {
512
+ '∇': lambda a: eval_monad_grad(klong, a),
513
+ }
514
+
515
+ return {**simple, **backend_monads, **klong_monads}