Fork me on GitHub
#clojure
<
2021-02-18
>
Adam Helins12:02:13

WAT, the Lisp format for WebAssembly, seems to be valid EDN (given it doesn't contain comments such as (; comment ;) ). I find it really exciting as it opens many possibilities. However, there is one caveat: numbers are parsed as numbers and this can be tricky. For instance, WASM can declare unsigned 64 bits constants which could be parsed as BigInts and mess things up. The EdnReader doesn't seem to be very hackable. What I would like is to gain control over how numbers are parsed, which is defined by this static method on the EdnReader: https://github.com/clojure/clojure/blob/ecd5ff59e07de649a9f9affb897d02165fe7e553/src/jvm/clojure/lang/EdnReader.java#L314 Any idea? Or another direction (besides writing another s-expr parser)?

borkdude12:02:41

@adam678 https://github.com/borkdude/edamame has a postprocess hook in which you could possibly fix this. This is after the number has already been parsed but after that you can do whatever

borkdude12:02:15

I'm not sure what the exact problem is. What is the data you expect and what is the data that currently comes out?

Adam Helins12:02:08

For instance, the parsed WAT cannot be reliably stringified without doing a second pass and transforming numbers to symbols (at least BigInts).

Adam Helins12:02:37

(i64.const 18446744073709551615) -> (i64.const 18446744073709551615N) (`N` is appended) In that particular case, control over how BigInts are printed would be a simple solution (but haven't look at implementation)

borkdude12:02:51

@adam678 Yeah, you could do it like this:

user=> (e/parse-string "(i64.const 18446744073709551615)" {:postprocess (fn [{:keys [:obj]}] (if (instance? clojure.lang.BigInt obj) (symbol (str obj)) obj))})
(i64.const 18446744073709551615)
or just use a postwalk over the EDN

borkdude12:02:16

Note that symbols starting with a number aren't actually valid, so consider this a hack

Adam Helins12:02:45

Yep, too bad it requires a second pass just for this. Then I am a bit worried about subtle differences in floats since WASM is using a newer revision of IEEE754 (2019 vs 2008 in the JVM I believe). If there is any difference, then a second pass won't do, all numbers should be kept as symbols (albeit a hack).

borkdude12:02:15

In edamame this is not a second pass, it transforms while parsing

borkdude12:02:46

or if you mean, after you parse it to bigint, then yes

borkdude12:02:48

Maybe you can look at the source of tools.reader.edn, edamame is mostly based on this. It's not actually that much work to write a decent parser for an alternative EDN

๐Ÿ‘ 3
Adam Helins12:02:40

Hmmm, probably best to go that road for providing CLJS support anyways

Adam Helins12:02:14

But other than that, I find it remarkable. Being able to easily parse WASM as Clojure data structures. There is nothing better for handling a LISP than a LISP (and we have a good one).

ivana13:02:00

Using this library https://github.com/cognitect-labs/aws-api for s3 service, everything is fine. Now need to add Amazon Gift Card Incentives API - which :api value should I provide on client creation? Tried dozen of words, such as :incentives, :AGCOD etc, but everytime got an error Cannot find resource cognitect/aws/AGCOD/service.edn . How can I find the right characters or where I can see the whole apis list? Or this library does not provide this api integration?

ghadi13:02:03

I think thatโ€™s not an AWS service, but an http://Amazon.com Retail API

ivana13:02:12

Hm, that means I can not use library above for integration with it?

ghadi13:02:21

Correct

โœ… 3
ivana13:02:34

Ok, thanks

Lyderic Dutillieux14:02:51

Hey, I just stumbled on this argument in favor of dynamic scoping : https://stackoverflow.com/a/3792028 I was wondering if anyone knows a practical code sample that uses and takes profit of dynamic scoping ? I would like to see a concrete use case to soak up this concept

Lyderic Dutillieux15:02:01

Note : In Clojure, when I do

(defn get-x [] x)
I get a
Syntax error compiling at (REPL:1:15).
Unable to resolve symbol: x in this context
Whereas in EmacsLisp, I can define such a function when x is not declared yet :
(defun get-x () x)
And rebind x later, at runtime.
(let ((x 999)) (get-x)) => 999
In clojure, I can reach 'almost' the same behavior, given that I first (def ^:dynamic x 666) And then use something like binding
(def ^:dynamic x 666)
(defn get-x [] x)
(binding [x 999] (get-x)) => 999
x => 666

Ed16:02:05

the errors you're getting that are different between elisp and clojure aren't to do with dynamic scope, but when the compiler/interpreter chooses to resolve the symbol. Clojure could equally have chosen to resolve the var x only when get-x in run without changing which x was resolved (you can do that by calling (resolve 'x))

๐Ÿ˜ƒ 3
Ed16:02:35

I've used dynamic scope to hide things from an API in the past, so the caller doesn't need to know about some implementation details ... I'm never very proud of myself when I've done this, but it's usually to get around some other limitation placed on what I'm trying to do by another bit of code (which I think what that RMS quote is getting at) ... and it usually restricts the utility of that code in ways that I wish it didn't ... but I think that I'm just not clever enough to solve the problem without dynamic scope, so I use it and accept the trade off ๐Ÿ˜‰

๐Ÿ‘ 3
Lyderic Dutillieux21:02:24

I didn't know about (resolve 'random-symbol) I just tested, and it actually works as well, thanks for the tips. Cool feedback about your experience with this, tx ๐Ÿ˜‰

Ben Sless20:02:12

This might as well be an argument in favor of openness and extensibility. Dynamic scope allows that, but it's indeterministic. You can solve the same problem by passing a context map instead of fixed arguments, use protocols and multimethods

๐Ÿ‘ 3
borkdude14:02:17

Are dynamic vars an alternative to this?

Lyderic Dutillieux14:02:57

I guess they are a sort of workaround. In the case of EmacsLisp (where I discovered the concept of dynamic scoping), there is the concept of free variable, that are resolved in runtime, from several possible scopes (not especially global)

Lyderic Dutillieux15:02:15

I looked a bit deeper, you are right, it does the job

Ed16:02:11

I thought dynamic vars were an implementation of dynamic scope ... and it looks different enough from lexical scope in Clojure, so you don't confuse the two, cos you usually want lexical scope

kingcode16:02:40

Is there an easy way to make failure messages part of clojure.test/are macro args? I have a large expr and many test values to run it over, and have to guess which test failed currently: I would like to avoid using a separate testing category and have to re-copy the are clauses over and over again. Thanks for any comment!

dpsutton16:02:34

user=> (require '[clojure.test :refer :all])
nil
(deftest foo
  (are [x y] (is (= x y) (str x " was not equal to " y))
    1 1
    2 2
    3 4))
#'user/foo
user=> (foo)

FAIL in (foo) (NO_SOURCE_FILE:2)
3 was not equal to 4
expected: (= 3 4)
  actual: (not (= 3 4))

FAIL in (foo) (NO_SOURCE_FILE:2)
expected: (is (= 3 4) (str 3 " was not equal to " 4))
  actual: false
nil
user=>

dpsutton16:02:46

you can use the good parts of is

kingcode17:02:15

Woaw cool! I didnโ€™t think of that, thank you ๐Ÿ™‚

dpsutton17:02:05

you'll see two test reports but small price to pay i guess

borkdude17:02:21

I usually avoid are and use doseq instead. ;)

kingcode18:02:35

Yes indeed, I could do without the extra report, but this is so much better than the alternative - was wondering whether writing a new/extended clojure.template for that would be much work..

kingcode18:02:29

@U04V15CAJ - thanks for the suggestion. How would you use the doseq to achieve the same efficiency as are?

kingcode18:02:10

..without the duplication? ๐Ÿ™‚

dpsutton18:02:42

(doseq [[expected input] coll] (is (= execpted input (str input " didn't work")))

kingcode18:02:21

hmmm..interesting. I will try that inside are

dpsutton18:02:01

instead of are

kingcode18:02:07

I think this is an alternative to are ๐Ÿ™‚ OK ths

kingcode18:02:34

That works well, thank you! A bit more to set up, with putting all the code at the end and partitioning but nice ๐Ÿ™‚

borkdude18:02:48

Yeah, like:

(deftest foo
  (doseq [[x y] [[1 1]
                 [2 2]
                 [3 4]]]
    (is (= x y) (str x " was not equal to " y))))
Obviously this is more verbose than are but for the life of me, I can't remember the are syntax

โž• 3
kingcode18:02:16

Actually it is the same as doseq, except for the code/data order and are doing the partitioning for you - it would be nice to have a new version of are that does the same. Thanks!

borkdude18:02:02

The partitioning is confusing imo. The grouping in a vector indicates... grouping :)

kingcode18:02:50

Indeed ๐Ÿ™‚ I agree, but I got used to the \n partitioning and not worrying about extra brackets, a matter of comfort I guess

vemv19:02:37

My way to avoid are's double failure report is wrap in testing and add a true at test position. This pattern is captured in an IDE snippet https://raw.githubusercontent.com/zenmacs/.emacs.d/62d24aac7b2e57b3d31123d5de1c5980651054d5/snippets/clojure-mode/are so I don't have to remember various intricacies

borkdude19:02:02

Another argument to never look at are again ;)

vemv19:02:55

Personally I'm a heavy arer and have convinced multiple teams of its benefits. There's also place for doseq, and occasionally use it. One day I'll like to summarize my thinking in an article

kingcode01:02:59

Another drawback of are is the forbidden de-structuring in the args list. During the few hours I have used it (in lieu of are), I have become more comfortable with doseq

kingcode01:02:12

I also like the natural flow of listing what I need (params + data), and as I type these the testing code foments..

kingcode16:02:37

Ideally:

(deftest my-test
 (are [...] (....)
  vals-1 "whoa val 1 bad!"
 ...))

Sergiy Shevchuk17:02:07

Hi all. Please correct me, if itโ€™s not right channel for tech question. Iโ€™ll move it to other Have some trouble with core.match pattern. The goal is to detect arithmetic symbols in list, and construct final expression. E.G: โ€˜(+ 1 1 (* 2 1) (- 1 1)) -> 3 Was trying to parse list with next func:

(defn custom-matcher [x]
  (if (seq x)
    (clojure.core.match/match [x]
                   [([+ & r] :seq)] :plus
                   [([- & r] :seq)] :minus
                   [([* & r] :seq)] :multiplication
                   [([/ & r] :seq)] :division
                   :else nil))
  )
But, it does not work properly. If Iโ€™ll call func with next argument: (custom-matcher โ€™(- 1 1)), works first case (:plus keyword), not :minus. Appreciate for advises.

hiredman18:02:48

you may need to quote symbols or something in match patterns

hiredman18:02:09

to match + and r are both the same kind of thing

hiredman18:02:37

and the way it treats symbols is as names to bind to parts of things

hiredman18:02:21

so you need some way to tell it that you intend it to check that the first thing is the literal symbol + and not just bind the first thing to the name +

hiredman18:02:59

so really it isn't working correctly for the plus case either

hiredman18:02:48

I don't know enough core.match to know what you need to do to make it match literal symbols, but I would just try quoting

dpsutton18:02:19

(match '(- 1 2)
  (('+ & r) :seq) :plus
  (('- & r) :seq) :minus
  :else :nope)
:minus

Sergiy Shevchuk18:02:31

Yeeees, thanks! Quote helps!

dpsutton18:02:47

the docstring mentions you can omit the vectors if you're matching on a single entity

Sergiy Shevchuk18:02:20

Thanks to all! Now I can continue my tests!