Anonymous functions in parenscript - common-lisp

I've got some JavaScript that, along with cl-who does what I want:
(:script :type "text/javascript"
(cl-who:str
"
$(document).ready(function ()
{$('#mtcars-table').DataTable({
ajax: { url: '/data?sym=mtcars&fmt=dt',
dataSrc: 'mtcars' },
columns: [
{data: 'model', title: 'Model'},
{data: 'mpg', title: 'MPG'},
{data: 'cyl', title: 'Cylinders'},
{data: 'disp', title: 'Displacement'},
{data: 'hp', title: 'Horsepower'},
{data: 'drat', title: 'Axle Ratio'},
{data: 'wt', title: 'Weight'},
{data: 'qsec', title: 'Quarter mile time'},
{data: 'vs', title: 'V or Straight'},
{data: 'am', title: 'Transmission'},
{data: 'gear', title: '# gears'},
{data: 'carb', title: '# carbs'},
],
});
});
"))
but I'm struggling a bit with its conversion to JavaScript via ParenScript. At the moment, this seems to do most of what I need:
(:script :type "text/javascript"
(cl-who:str
(ps:ps (ps:chain ($ document)
(ready (lambda ()
(foo)))))
This gets me:
"$(document).ready(function () {
__PS_MV_REG = [];
return foo();
});"
from ParenScript, so leave replacing foo with $(mtcars-table ....
Getting to this point though was trial, error and guesswork, and I know little about JavaScript. I'm not convinced I'm doing this the 'right way', and the documentation and tutorials on ParenScript are scarce.
Can any experts suggest a better approach for converting this JavaScript snippet?

Firstly, although I mostly don't :use a package (in defpackage) I make a separate package that :use #:parenscript because it's painful otherwise.
I used a couple of helpers just to make the columns array less typing (group is in my personal util library, but I copied it just in case you need it)
(defun group (n seq)
(if (zerop n)
(error "0 length")
(do ((result nil)
(chunk nil nil))
((not seq) (nreverse result))
(dotimes (i n)
(push (car seq) chunk)
(setf seq (cdr seq)))
(push (nreverse chunk) result))))
(defmacro+ps temporary-columns (&body forms)
`(array ,# (mapcar (lambda (pair)
`(create data ,(first pair)
title ,(second pair)))
(group 2 forms))))
(ps
(chain ($ document)
(ready (lambda ()
(chain ($ "#mtcars-table")
(-data-table (create ajax (create url "/data?sym=mtcars&fmt=dt"
data-src "mtcars")
columns (temporary-columns
"model" "Model"
"mpg" "MPG"
"cyl" "Cylinders"
"disp" "Displacement"
"hp" "Horsepower"
"drat" "Axle Ratio"
"wt" "Weight"
"qsec" "Quarter Mile Time"
"vs" "V or Straight"
"am" "Transmission"
"gear" "# gears"
"carb" "# carbs"))))))))
Which expands to:
$(document).ready(function () {
__PS_MV_REG = [];
return $('#mtcars-table').DataTable({ ajax : { url : '/data?sym=mtcars&fmt=dt', dataSrc : 'mtcars' }, columns : [{ data : 'model', title : 'Model' }, { data : 'mpg', title : 'MPG' }, { data : 'cyl', title : 'Cylinders' }, { data : 'disp', title : 'Displacement' }, { data : 'hp', title : 'Horsepower' }, { data : 'drat', title : 'Axle Ratio' }, { data : 'wt', title : 'Weight' }, { data : 'qsec', title : 'Quarter Mile Time' }, { data : 'vs', title : 'V or Straight' }, { data : 'am', title : 'Transmission' }, { data : 'gear', title : '# gears' }, { data : 'carb', title : '# carbs' }] });
});
It's an unfortunate reality that code written in parenscript doesn't end up looking that much like idiomatic common-lisp, because their data models are rather different, and you need to kowtow to js for interoperability with the js ecosystem.
I still prefer writing parenscipt though, because macros are still useful, and parenscript does paper over some of the horrible-ness of js as well.

Related

Update expression for a list of maps in DynamoDB

I have the following DynamoDB table:
{
record_id: Decimal(10),
...,
options: [ # This is a List of maps
{option_id: 1, counter: Decimal(0), ...},
{option_id: 2, counter: Decimal(0), ...},
],
...
}
Which consists of some items, with unique record_id and the target options list. That list contains maps. In those maps, there is an option_id attribute, and I would like to access the item in the options list whose option_id equals to some target my_option_id and increment its counter.
For example, for the above example, given my_record_id=10 and my_option_id=2, I would like to update the second option item, with option_id=2, and increment its counter by 1, so this {option_id: 2, counter: Decimal(0), ...} becomes {option_id: 2, counter: Decimal(1), ...}.
I am using Python and boto3, but I imagine the syntax here is specific to DynamoDB. Here is what I have so far:
response = table.update_item(
Key={
'record_id': my_record_id,
},
UpdateExpression='SET options.#s.counter = options.#s.counter + :val',
ExpressionAttributeNames={
"#s": my_option_id
},
ExpressionAttributeValues={
':val': Decimal(1)
},
ReturnValues="UPDATED_NEW"
)
It seems the easy fix was to make options a map instead of a list, and then make the option_id the key of that map. Then my code works as expected:
response = table.update_item(
Key={
'record_id': my_record_id,
},
UpdateExpression='SET options.#s.counter = options.#s.counter + :val',
ExpressionAttributeNames={
"#s": my_option_id
},
ExpressionAttributeValues={
':val': Decimal(1)
},
ReturnValues="UPDATED_NEW"
)

Updating values of map using for in Clojure

I have a json like below and have a requirement to add values in it
{ "10 days refund": [
{
"paymentType": "CreditCard",
"amount": "40$",
"expiryDate": "20/10/2025"
},
{
"paymentType": "CreditCard",
"amount": "20$",
"expiryDate": "20/1/2024"
}
],
"3 hours refund": [
{
"paymentType": "DebitCard",
"amount": "10$",
"expiryDate": "20/10/2026"
},
{
"paymentType": "DebitCard",
"amount": "5$",
"expiryDate": "20/10/2027"
}
]
}
In the above map, I would need to add a field "message" to last value of each category of map. So after adding the value I'm expecting the resultant map like below
{ "10 days refund": [
{
"paymentType": "CreditCard",
"amount": "40$",
"expiryDate": "20/10/2025"
},
{
"paymentType": "CreditCard",
"amount": "20$",
"expiryDate": "20/1/2024",
"message" : "Refund will be processed in 10 days"
}
],
"3 hours refund": [
{
"paymentType": "DebitCard",
"amount": "10$",
"expiryDate": "20/10/2026"
},
{
"paymentType": "DebitCard",
"amount": "5$",
"expiryDate": "20/10/2027",
"message" : "Refund will be processed in 3 hours"
}
]
}
I have tried implementing it using below method
(defn ^:private payment-methods
[pm-sorted]
(let [categories (group-by pm-fingerprint pm-sorted)]
(for [[pm-fingerprint pm-sorted] categories
:let [last-payment-method (last pm-sorted)
dropped-last (drop-last pm-sorted)]]
(if (= (:refund-category pm-fingerprint) "10 hours refund")
((println "10 days refund")
(doall (conj (doall dropped-last) (assoc last-payment-method :message
(text (str "Refund will be processed in 10 days"))))))
((println "3 hours refund")
(doall (conj (doall dropped-last) (assoc last-payment-method :message
(text (str "Refund will be processed in 3 hours"))))))))))
(defn ^:private pm-fingerprint
[payment-method]
(let [payment-type ("paymentType" payment-method)]
{:refund-category (if (= payment-type "CreditCard"))
"10 days refund"
"3 hours refund")}))
I expect the resultant output by executing the function to be a vector like below
[
{
"paymentType": "CreditCard",
"amount": "40$",
"expiryDate": "20/10/2025"
},
{
"paymentType": "CreditCard",
"amount": "20$",
"expiryDate": "20/1/2024",
"message" : "Refund will be processed in 10 days"
}
{
"paymentType": "DebitCard",
"amount": "10$",
"expiryDate": "20/10/2026"
},
{
"paymentType": "DebitCard",
"amount": "5$",
"expiryDate": "20/10/2027",
"message" : "Refund will be processed in 3 hours"
}
]
And I get null as the output as no value is returned. Can someone please help wrt the issue ?
Here is an example that works. First, declarations & data:
(ns tst.demo.core
(:use tupelo.core tupelo.test)
(:require
[schema.core :as s]
[tupelo.string :as str]
))
(def input
(str/quotes->double
"{ '10-days-refund': [
{ 'paymentType': 'CreditCard',
'amount': '40$',
'expiryDate': '20/10/2025' },
{ 'paymentType': 'CreditCard',
'amount': '20$',
'expiryDate': '20/1/2024' }
],
'3-hours-refund': [
{
'paymentType': 'DebitCard',
'amount': '10$',
'expiryDate': '20/10/2026' },
{ 'paymentType': 'DebitCard',
'amount': '5$',
'expiryDate': '20/10/2027' } ] }"))
Then the processing:
(dotest
(let [data (json->edn input)
data2 (vec
(for [entry data]
(let [[the-key pmt-lst] entry ; destruture into key and list of vals
pmt-lst-butlast (butlast pmt-lst)
pmt-lst-last (last pmt-lst)
last-new (assoc pmt-lst-last :message "Coming Soon!")
pmt-lst-new (append pmt-lst-butlast last-new)
entry-new [the-key pmt-lst-new]]
entry-new)))]
and the result verified:
(is= data2
[[:10-days-refund
[{:amount "40$" :expiryDate "20/10/2025" :paymentType "CreditCard"}
{:amount "20$"
:expiryDate "20/1/2024"
:message "Coming Soon!"
:paymentType "CreditCard"}]]
[:3-hours-refund
[{:amount "10$" :expiryDate "20/10/2026" :paymentType "DebitCard"}
{:amount "5$"
:expiryDate "20/10/2027"
:message "Coming Soon!"
:paymentType "DebitCard"}]]]
)))
Please see the list of documentation in this template project, especially the Clojure CheatSheet.

Conditional if/then/else for JMESPath?

Am trying to do a simple if/then/else using JMESPath
For example: 'if the input is a string, return the string, else return the "value" property of the input'. An input of "abc" would return "abc". An input of {"value":"def"} would return "def"
With jq this is easy: if .|type == "string" then . else .value end
With JMESPath, I can get the type
type(#)
or the input:
#
or the value property:
value
but I have not found a way to combine them into an if-then-else. Is there any way to do this?
It is possible but not cleanly. The general form is to:
Make the value you are testing an array (wrap in square braces)
Apply the map function to map the filtered array to what value you want if true
At this point you have an array that is populated with one (true) item if the array filter passed, otherwise it is empty
Concat to that array one item (the false value)
Finally, take item at index 0 in this array - which will be the result of the condition
This should allow you to also derive possible transformations for both the false and true conditions
For example, if the test data is as so:
{
"test": 11
}
Depending on the value you can get either produce the results (using test data 11 and 2 as example):
"Yes, the value is 11 which is greater than 10"
OR
"No the value is 2 which is less than or equal to 10"
Like so:
[
map(
&join(' ', ['Yes, the value is', to_string(#), 'which is greater than 10']),
[test][? # > `10`]
),
join(' ', ['No the value is', to_string(test), ' which is less than or equal to 10'])
][] | #[0]
So to abstract a template:
[
map(
&<True Expression Here>,
[<Expression you are testing>][? # <Test Expression>]
),
<False Expression Here>)
][] | #[0]
people[?general.id !=100] || people
{
"people": [
{
"general": {
"id": 100,
"age": 20,
"other": "foo",
"name": "Bob"
},
"history": {
"first_login": "2014-01-01",
"last_login": "2014-01-02"
}
},
{
"general": {
"id": 101,
"age": 30,
"other": "bar",
"name": "Bill"
},
"history": {
"first_login": "2014-05-01",
"last_login": "2014-05-02"
}
}
]
}
if else condition works here

Find path to object in object nested array

I have an object, of which parameters contain and array of object. I receive 1 object id and I need to find its position in that whole mess. With procedural programming I got it working with:
const opportunitiesById = {
1: [
{ id: 1, name: 'offer 1' },
{ id: 2, name: 'offer 1' }
],
2: [
{ id: 3, name: 'offer 1' },
{ id: 4, name: 'offer 1' }
],
3: [
{ id: 5, name: 'offer 1' },
{ id: 6, name: 'offer 1' }
]
};
const findObjectIdByOfferId = (offerId) => {
let opportunityId;
let offerPosition;
const opportunities = Object.keys(opportunitiesById);
opportunities.forEach(opportunity => {
const offers = opportunitiesById[opportunity];
offers.forEach((offer, index) => {
if (offer.id === offerId) {
opportunityId = Number(opportunity);
offerPosition = index;
}
})
});
return { offerPosition, opportunityId };
}
console.log(findObjectIdByOfferId(6)); // returns { offerPosition: 1, opportunityId: 3 }
However this is not pretty and I want to do that in a functional way.
I've looked into Ramda, and I can find an offer when I'm looking into a single array of offers, but I can't find a way to look through the entire object => each array to find the path to my offer.
R.findIndex(R.propEq('id', offerId))(opportunitiesById[1]);
The reason I need to know the path is because I then need tho modify that offer with new data and update it back where it is.
Thanks for any help
You could piece it together using lots of little functions but I want to show you how to encode your intentions in a more straightforward way. This program has an added benefit that it will return immediately. Ie, it will not continue to search thru additional key/value pairs after a match is found.
Here's a way you can do it using mutual recursion. First we write findPath -
const identity = x =>
x
const findPath =
( f = identity
, o = {}
, path = []
) =>
Object (o) === o
? f (o) === true
? path
: findPath1 (f, Object .entries (o), path)
: undefined
If the input is an object, we pass it to the user's search function f. If the user's search function returns true, a match has been found and we return the path. If there is not match, we search each key/value pair of the object using a helper function. Otherwise, if the input is not an object, there is no match and nothing left to search, so return undefined. We write the helper, findPath1 -
const None =
Symbol ()
const findPath1 =
( f = identity
, [ [ k, v ] = [ None, None ], ...more ]
, path = []
) =>
k === None
? undefined
: findPath (f, v, [ ...path, k ])
|| findPath1 (f, more, path)
If the key/value pairs have been exhausted, there is nothing left to search so return undefined. Otherwise we have a key k and a value v; append k to the path and recursively search v for a match. If there is not a match, recursively search the remaining key/values, more, using the same path.
Note the simplicity of each function. There's nothing happening except for the absolute minimum number of steps to assemble a path to the matched object. You can use it like this -
const opportunitiesById =
{ 1:
[ { id: 1, name: 'offer 1' }
, { id: 2, name: 'offer 1' }
]
, 2:
[ { id: 3, name: 'offer 1' }
, { id: 4, name: 'offer 1' }
]
, 3:
[ { id: 5, name: 'offer 1' }
, { id: 6, name: 'offer 1' }
]
}
findPath (offer => offer.id === 6, opportunitiesById)
// [ '3', '1' ]
The path returned leads us to the object we wanted to find -
opportunitiesById['3']['1']
// { id: 6, name: 'offer 1' }
We can specialize findPath to make an intuitive findByOfferId function -
const findByOfferId = (q = 0, data = {}) =>
findPath (o => o.id === q, data)
findByOfferId (3, opportunitiesById)
// [ '2', '0' ]
opportunitiesById['2']['0']
// { id: 3, name: 'offer 1' }
Like Array.prototype.find, it returns undefined if a match is never found -
findByOfferId (99, opportunitiesById)
// undefined
Expand the snippet below to verify the results in your own browser -
const identity = x =>
x
const None =
Symbol ()
const findPath1 =
( f = identity
, [ [ k, v ] = [ None, None ], ...more ]
, path = []
) =>
k === None
? undefined
: findPath (f, v, [ ...path, k ])
|| findPath1 (f, more, path)
const findPath =
( f = identity
, o = {}
, path = []
) =>
Object (o) === o
? f (o) === true
? path
: findPath1 (f, Object .entries (o), path)
: undefined
const findByOfferId = (q = 0, data = {}) =>
findPath (o => o.id === q, data)
const opportunitiesById =
{ 1:
[ { id: 1, name: 'offer 1' }
, { id: 2, name: 'offer 1' }
]
, 2:
[ { id: 3, name: 'offer 1' }
, { id: 4, name: 'offer 1' }
]
, 3:
[ { id: 5, name: 'offer 1' }
, { id: 6, name: 'offer 1' }
]
}
console .log (findByOfferId (3, opportunitiesById))
// [ '2', '0' ]
console .log (opportunitiesById['2']['0'])
// { id: 3, name: 'offer 1' }
console .log (findByOfferId (99, opportunitiesById))
// undefined
In this related Q&A, I demonstrate a recursive search function that returns the matched object, rather than the path to the match. There's other useful tidbits to go along with it so I'll recommend you to give it a look.
Scott's answer inspired me to attempt an implementation using generators. We start with findPathGen -
const identity = x =>
x
const findPathGen = function*
( f = identity
, o = {}
, path = []
)
{ if (Object (o) === o)
if (f (o) === true)
yield path
else
yield* findPathGen1 (f, Object .entries (o), path)
}
And using mutual recursion like we did last time, we call on helper findPathGen1 -
const findPathGen1 = function*
( f = identity
, entries = []
, path = []
)
{ for (const [ k, v ] of entries)
yield* findPathGen (f, v, [ ...path, k ])
}
Finally, we can implement findPath and the specialization findByOfferId -
const first = ([ a ] = []) =>
a
const findPath = (f = identity, o = {}) =>
first (findPathGen (f, o))
const findByOfferId = (q = 0, data = {}) =>
findPath (o => o.id === q, data)
It works the same -
findPath (offer => offer.id === 3, opportunitiesById)
// [ '2', '0' ]
findPath (offer => offer.id === 99, opportunitiesById)
// undefined
findByOfferId (3, opportunitiesById)
// [ '2', '0' ]
findByOfferId (99, opportunitiesById)
// undefined
And as a bonus, we can implement findAllPaths easily using Array.from -
const findAllPaths = (f = identity, o = {}) =>
Array .from (findPathGen (f, o))
findAllPaths (o => o.id === 3 || o.id === 6, opportunitiesById)
// [ [ '2', '0' ], [ '3', '1' ] ]
Verify the results by expanding the snippet below
const identity = x =>
x
const findPathGen = function*
( f = identity
, o = {}
, path = []
)
{ if (Object (o) === o)
if (f (o) === true)
yield path
else
yield* findPathGen1 (f, Object .entries (o), path)
}
const findPathGen1 = function*
( f = identity
, entries = []
, path = []
)
{ for (const [ k, v ] of entries)
yield* findPathGen (f, v, [ ...path, k ])
}
const first = ([ a ] = []) =>
a
const findPath = (f = identity, o = {}) =>
first (findPathGen (f, o))
const findByOfferId = (q = 0, data = {}) =>
findPath (o => o.id === q, data)
const opportunitiesById =
{ 1:
[ { id: 1, name: 'offer 1' }
, { id: 2, name: 'offer 1' }
]
, 2:
[ { id: 3, name: 'offer 1' }
, { id: 4, name: 'offer 1' }
]
, 3:
[ { id: 5, name: 'offer 1' }
, { id: 6, name: 'offer 1' }
]
}
console .log (findByOfferId (3, opportunitiesById))
// [ '2', '0' ]
console .log (findByOfferId (99, opportunitiesById))
// undefined
// --------------------------------------------------
const findAllPaths = (f = identity, o = {}) =>
Array .from (findPathGen (f, o))
console .log (findAllPaths (o => o.id === 3 || o.id === 6, opportunitiesById))
// [ [ '2', '0' ], [ '3', '1' ] ]
I would transform your object into pairs.
So for example transforming this:
{ 1: [{id:10}, {id:20}],
2: [{id:11}, {id:21}] }
into that:
[ [1, [{id:10}, {id:20}]],
[2, [{id:11}, {id:21}]] ]
Then you can iterate over that array and reduce each array of offers to the index of the offer you're looking for. Say you're looking for offer #21, the above array would become:
[ [1, -1],
[2, 1] ]
Then you return the first tuple which second element isn't equal to -1:
[2, 1]
Here's how I'd suggest doing this:
const opportunitiesById = {
1: [ { id: 10, name: 'offer 1' },
{ id: 20, name: 'offer 2' } ],
2: [ { id: 11, name: 'offer 3' },
{ id: 21, name: 'offer 4' } ],
3: [ { id: 12, name: 'offer 5' },
{ id: 22, name: 'offer 6' } ]
};
const findOfferPath = (id, offers) =>
pipe(
toPairs,
transduce(
compose(
map(over(lensIndex(1), findIndex(propEq('id', id)))),
reject(pathEq([1], -1)),
take(1)),
concat,
[]))
(offers);
console.log(findOfferPath(21, opportunitiesById));
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.26.1/ramda.min.js"></script>
<script>const {pipe, transduce, compose, map, over, lensIndex, findIndex, propEq, reject, pathEq, take, concat, toPairs} = R;</script>
Then you can take that path to modify your offer as you see fit:
const opportunitiesById = {
1: [ { id: 10, name: 'offer 1' },
{ id: 20, name: 'offer 2' } ],
2: [ { id: 11, name: 'offer 3' },
{ id: 21, name: 'offer 4' } ],
3: [ { id: 12, name: 'offer 5' },
{ id: 22, name: 'offer 6' } ]
};
const updateOffer = (path, update, offers) =>
over(lensPath(path), assoc('name', update), offers);
console.log(updateOffer(["2", 1], '🌯', opportunitiesById));
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.26.1/ramda.min.js"></script>
<script>const {over, lensPath, assoc} = R;</script>
Here's another approach:
We start with this generator function:
function * getPaths(o, p = []) {
yield p
if (Object(o) === o)
for (let k of Object .keys (o))
yield * getPaths (o[k], [...p, k])
}
which can be used to find all the paths in an object:
const obj = {a: {x: 1, y: 3}, b: {c: 2, d: {x: 3}, e: {f: {x: 5, g: {x: 3}}}}}
;[...getPaths(obj)]
//~> [[], ["a"], ["a", "x"], ["a", "y"], ["b"], ["b", "c"], ["b", "d"],
// ["b", "d", "x"], ["b", "e"], ["b", "e", "f"], ["b", "e", "f", "x"],
// ["b", "e", "f", "g"], ["b", "e", "f", "g", "x"]]
and then, with this little helper function:
const path = (ps, o) => ps.reduce((o, p) => o[p] || {}, o)
we can write
const findPath = (predicate, o) =>
[...getPaths(o)] .find (p => predicate (path (p, o) ) )
which we can call like
console.log(
findPath (a => a.x == 3, obj)
) //~> ["b","d"]
We can then use these functions to write a simple version of your function:
const findByOfferId = (id, data) =>
findPath (o => o.id === id, data)
const opportunitiesById = {
1: [ { id: 10, name: 'offer 1' }, { id: 20, name: 'offer 2' } ],
2: [ { id: 11, name: 'offer 3' }, { id: 21, name: 'offer 4' } ],
3: [ { id: 12, name: 'offer 5' }, { id: 22, name: 'offer 6' } ]
}
console.log(
findByOfferId (22, opportunitiesById)
) //~> ["3", "1"]
console.log(
findByOfferId (42, opportunitiesById)
) //~> undefined
It is trivial to extend this to get all paths for which the value satisfies the predicate, simply replacing find with filter:
const findAllPaths = (predicate, o) =>
[...getPaths(o)] .filter (p => predicate (path(p, o) ) )
console.log(
findAllPaths (a => a.x == 3, obj)
) //=> [["b","d"],["b","e","f","g"]]
There is a concern with all this, though. Even though findPath only needs to find the first match, and even though getPaths is a generator and hence lazy, we force the full run of it with [...getPaths(o)]. So it might be worth using this uglier, more imperative version:
const findPath = (predicate, o) => {
let it = getPaths(o)
let res = it.next()
while (!res.done) {
if (predicate (path (res.value, o) ) )
return res.value
res = it.next()
}
}
This is what it looks like all together:
function * getPaths(o, p = []) {
yield p
if (Object(o) === o)
for (let k of Object .keys (o))
yield * getPaths (o[k], [...p, k])
}
const path = (ps, o) => ps.reduce ((o, p) => o[p] || {}, o)
// const findPath = (pred, o) =>
// [...getPaths(o)] .find (p => pred (path (p, o) ) )
const findPath = (predicate, o) => {
let it = getPaths(o)
let res = it.next()
while (!res.done) {
if (predicate (path (res.value, o) ) )
return res.value
res = it.next()
}
}
const obj = {a: {x: 1, y: 3}, b: {c: 2, d: {x: 3}, e: {f: {x: 5, g: {x: 3}}}}}
console.log(
findPath (a => a.x == 3, obj)
) //~> ["b","d"]
const findAllPaths = (pred, o) =>
[...getPaths(o)] .filter (p => pred (path(p, o) ) )
console.log(
findAllPaths (a => a.x == 3, obj)
) //~> [["b","d"],["b","e","f","g"]]
const findByOfferId = (id, data) =>
findPath (o => o.id === id, data)
const opportunitiesById = {
1: [ { id: 10, name: 'offer 1' }, { id: 20, name: 'offer 2' } ],
2: [ { id: 11, name: 'offer 3' }, { id: 21, name: 'offer 4' } ],
3: [ { id: 12, name: 'offer 5' }, { id: 22, name: 'offer 6' } ]
}
console.log(
findByOfferId (22, opportunitiesById)
) //~> ["3", "1"]
console.log(
findByOfferId (42, opportunitiesById)
) //~> undefined
Another brief note: the order in which the paths are generated is only one possibility. If you want to change from pre-order to post-order, you can move the yield p line in getPaths from the first line to the last one.
Finally, you asked about doing this with functional techniques, and mentioned Ramda. As the solution from customcommander shows, you can do this with Ramda. And the (excellent as always) answer from user633183 demonstrates, it's possible to do this with mainly functional techniques.
I still find this a somewhat simpler approach. Kudos to customcommander for finding a Ramda version, because Ramda is not particularly well-suited for recursive tasks, but still the obvious approach to something that has to visit the nodes of a recursive structure like a JS object is to use a recursive algorithm. I'm one of the authors of Ramda, and I haven't even tried to understand how that solution works.
Update
user633183 pointed out that this would be simpler, and still lazy:
const findPath = (predicate, o) => {
for (const p of getPaths(o))
if (predicate (path (p, o)) )
return p
}

MOP: acess any slot definition ? (mito's col-type)

I define a class which uses the Mito ORM, the slots define a :col-type:
(isbn
:accessor isbn
:initarg :isbn
:col-type (or (:varchar 128) :null))
How to get the :col-type definition ? Since this is a slot in my class definition, is there no generic way to access it, like slot-definition :col-type ... ?
On the clos-mop documentation, I only find how to access
slot-definition-allocation
slot-definition-initargs
slot-definition-initform
slot-definition-initfunction
slot-definition-name
slot-definition-type
The isbn slot shows like this:
#<MITO.DAO.COLUMN:DAO-TABLE-COLUMN-CLASS {1005928483}>
--------------------
Name: BOOKSHOPS.MODELS:ISBN
Init args: (:ISBN)
Init form: #<unspecified>
Init function: NIL
--------------------
Group slots by inheritance [ ]
Sort slots alphabetically [X]
All Slots:
[ ] %CLASS = #<DAO-TABLE-CLASS BOOK>
[ ] %DOCUMENTATION = NIL
[ ] %TYPE = T
[ ] ALLOCATION = :INSTANCE
[ ] ALLOCATION-CLASS = NIL
[ ] COL-TYPE = (OR (:VARCHAR 128) :NULL)
[ ] DEFLATE = #<unbound>
[ ] GHOST = NIL
[ ] INFLATE = #<unbound>
[ ] INITARGS = (:ISBN)
[ ] INITFORM = NIL
[ ] INITFUNCTION = NIL
[ ] NAME = BOOKSHOPS.MODELS:ISBN
[ ] PRIMARY-KEY = NIL
[ ] READERS = (BOOKSHOPS.MODELS:ISBN)
[ ] REFERENCES = NIL
[ ] WRITERS = ((SETF BOOKSHOPS.MODELS:ISBN))
Thanks.
The col-type is an extension provided by mito.class.column:table-column-class. It has an accessor %table-column-type, which is wrapped by mito.class.column:table-column-type.

Resources