Arc Forumnew | comments | leaders | submitlogin

Thanks for posting this akkartik!

T-expressions are my attempt at answering the question 'what's the minimal clean notation for expressing Prolog terms in S-expressions?'

PicoLisp's Pilog, for example just decides that 'a term is a list'. Which is very clean and simple - but unfortunately it means you can't distinguish between a term AND a list, and it's often very important to tell the two apart.

So the simplest solution is just to reserve one symbol (I use '/' because it's available and not special-syntax on most Lisps... though any symbol would do, and if I were writing from scratch, I might think about repurposing '.') to indicate 'the start of a Prolog-like term'.

The rest of that post series is really just about teasing out some of the implications of this, because I think it opens a lot of interesting possibilities for adding a very minimal notion of 'typed expression' to Lisp. Minimal in that there is no specification of what the meaning of a type is; just that a certain expression is more than just a raw list.

There's a number of applications of this: we can replace almost all special non-S-expression 'reader macro' syntax, for example.

Also having lists which are not Lisp functions/macros means we could implement 'implicit cons', which allows us to drop a lot of complexity in list-builder expressions. For example:

If x were (a b) and y were (c d e) then a standard Lisp expression might look like this:

(cons 1 (cons (car x) (cdr y)))

giving

(1 a d e)

But if we had a Lisp where function calls (or all forms) were term-expression terms, ie prefixed with '/', then we can write that expression as:

(1 (/ car x) / cdr y)

Suddenly we get a lot cleaner notation... fewer nested parens, less awkward syntax, and we can apply this technique to a lot of things.

For another example: say we had a list which was generated from a procedure (local or remote) and we wanted to show that it continues at a remote website:

(1 2 3 4 5 6 7 8 9 10 / more-at-URL "foo.com/bar/part2")

where "more-at-URL" is either a function that evaluates to a list, or just a piece of syntax that's parsed by some higher level.

This is a basic "lazy list" technique, well known to Lisp people for decades.... but the fact that we don't need to hide it behind lambdas, we can do this inline and mark ANY list up with a CDR that is an arbitrary expression, I think is an important capability that is worth thinking about.

I also think that we can then take this syntax and extend it down to the level of formal logic, because what we are essentially doing is writing (slightly extended) First-Order Predicate Logic expressions... and so we might want to think about exact mappings between FOPL and our Lisp expressions. And vice versa: I think we need to look hard at what Lisp, especially the S-expression 'dotted pair' allows us to express that standard vanilla FOPL does not. And ask what that extra piece of information might imply for logic - in the vein of, eg, what the HiLog researchers found, since this is a very similar kind of syntactic extension to FOPL as HiLog is. See, for example, the 1980s Sinclair Spectrum MicroProlog, which uses S-expressions internally to store Prolog expressions, and which discovered the dotted-pair technique for FOPL expressions and called it 'meta-variables'.

Final note: My syntax in the blog posts assumes that I have a custom reader macro and so I don't put a space after the / wherever it appears, in order to make the expressions look a but cleaner... but if you were embedding T-expressions on top of S-expressions in an existing Lisp, you would put a space there. And also you'd need to sort out some way of disambiguating in all cases between /-as-symbol and /-as-syntax. This last bit might be a little tricky.

Regards, Nate

2 points by i4cu 5 days ago | link | parent | on: How to handle Racket types?

> Really, #t and #f are not proper Arc booleans[0], so it makes sense that Arc can't tell what type they are.

Really, #t and #f are not proper Arc anything, but the language apparently handles them so IMHO Arc should also be able to know what type they are. Otherwise, I fear, this will become a Hodge Podge language that will lose appeal.

Personally I don't care if Arc supports booleans. I only care that it can translate booleans (when need be) to a meaningful Arc semantic. That said, if we're going to support booleans then let's not create partial support.

2 points by rocketnia 5 days ago | link | parent | on: How to handle Racket types?

That documentation may be wrong. On the other hand, it may be correct in the context of someone who is only using Arc, not Racket.

There are a lot of ways to conceive of what Arc "is" outside of the Racket implementations, but I think Arc implementations like Rainbow, Jarc, Arcueid, and so on tend to be inspired first by the rather small set of operations showcased in the tutorial and in arc.arc. (As the tutorial says, "The definitions in arc.arc are also an experiment in another way. They are the language spec.") Since #f isn't part of those, it's not something that an Arc implementation would necessarily focus on supporting, so there's a practical sense in which it's not a part of Arc our Arc code can rely on.

(Not that any other part of Arc is stable either.)

2 points by hjek 5 days ago | link | parent | on: How to handle Racket types?

Having nil represent both false and the empty list is also what Common Lisp does.

Really, #t and #f are not proper Arc booleans[0], so it makes sense that Arc can't tell what type they are.

You can configure the value Arc chooses for a JSON null with the $.json-null function, which I think is fine as JSON APIs might have differing semantics.

[0]: http://arclanguage.github.io/ref/combining.html

3 points by rocketnia 6 days ago | link | parent | on: How to handle Racket types?

I liked it when it was returning #f.

But now that I look closer at the ac.scm history (now ac.rkt in Anarki), I realize I was mistaken to believe Arc treated #f as a different value than nil. Turns out Arc has always equated #f and 'nil with `is`, counted them both as falsy, etc. So this library was already returning nil, from Arc's perspective.

There are some things that slip through the cracks. It looks like (type #f) has always given an "unknown type" error, as opposed to returning 'sym as it does for 'nil and '().

So with that in mind, I think it's a bug if an Arc JSON library returns 'nil or #f for JSON false, unless it returns something other than '() for JSON []. To avoid collision, we could represent JSON arrays using `annotate` values rather than plain Arc lists, but I think representing JSON false and null using Arc symbols like 'false and 'null is easier.

2 points by hjek 6 days ago | link | parent | on: How to handle Racket types?

> ; Branch on the symbols 'false and 'true.

We do already have booleans that work in Arc without any conversion. Please see http://arclanguage.org/item?id=20492

There's absolutely no need to convert the booleans to symbols and other hackery.


"Plaintiff has failed to allege how these numerous benefits outweigh the few seconds it takes to transcribe one word."

A few seconds is qualitatively different from 20 minutes, I'd think. :-p

2 points by rocketnia 6 days ago | link | parent | on: How to handle Racket types?

For round-tripping between JSON and Arc, I would expect the JSON values {}, {"foo": null}, {"foo": false}, and {"foo": []} to parse as four distinct Arc values.

I recommend (obj), (obj foo (list 'null)), (obj foo (list 'false)), and (obj foo (list (list))). Arc is good at representing optional values as subsingleton lists:

  ; Access a key that's known to be there.
  t!foo.0
  
  ; Access a key that isn't known to be there.
  (iflet (x) t!foo
    (do-something-then x)
    (do-something-else))
Using my `sobj` utility, you can write (obj foo (list 'false)) as (sobj foo 'false).

Meanwhile, I don't think there's any big deal when we don't have Arc-style booleans....

  ; Branch on the symbols 'false and 'true.
  (if (isnt 'false x)
    (do-something-then)
    (do-something-else))
  
  ; Alternative
  (case x true
    (do-something-then)
    (do-something-else))

Someone tried to take Google to court already, arguing exactly that :-)

https://arstechnica.com/tech-policy/2016/02/judge-tosses-pro...


Check the Racket docs on UDP[0]. Arc itself is very high-level, but you can do more low-level stuff via Racket interop.

[0]: https://docs.racket-lang.org/reference/udp.html

2 points by hjek 6 days ago | link | parent | on: How to handle Racket types?

Yes, looks like it's plain Arc 3.1.
2 points by hjek 6 days ago | link | parent | on: How to handle Racket types?

Nice example. (So much easier to tell what's going on when you have these brief examples)

Actually the Racket docs[0] clarifies the situation with null in JSON:

    (json-null) → any/c

    (json-null jsnull) → void?
      jsnull : any/c
> This parameter determines the default Racket value that corresponds to a JSON “null”. By default, it is the 'null symbol. In some cases a different value may better fit your needs, therefore all functions in this library accept a #:null keyword argument for the value that is used to represent a JSON “null”, and this argument defaults to (json-null).

If you set the JSON null to nil before running your example, it works as you'd expect:

    arc> ($.json-null nil)
    #<void>
    arc> (= x (w/instring in "{\"foo\": null}" ($.read-json in)))
    #hash((foo . nil))
    arc> (if x!foo (prn "error"))
    nil
I think we should get rid of json.rkt and use the Racket built-in. It's way better documented, and we should use that one. (But I'm not going to delete json.rkt myself, particularly when I know someone is working with it.)

[0]: https://docs.racket-lang.org/json/index.html

3 points by akkartik 6 days ago | link | parent | on: How to handle Racket types?

In fairness, tryarc.org it isn't always up lately. evanrmurphy has broached the subject of someone taking it over, but it hasn't happened yet.
2 points by akkartik 6 days ago | link | parent | on: How to handle Racket types?

Wow, that's kinda mind-blowing. I had no idea. Thanks!

Ah, here's perhaps the only special-case we need to handle:

    arc> (= x (w/instring in "{\"foo\": null}" ($.read-json in)))
    #hash((foo . null))  # expected: #hash((foo . nil)) or #hash()
    arc> (if x!foo (prn "error"))
    error
2 points by hjek 6 days ago | link | parent | on: How to handle Racket types?

> when I tried to do a boolean check on it,

Can I ask how you did a "boolean check" on #t?

2 points by krapp 6 days ago | link | parent | on: How to handle Racket types?

Sure... the if statement in verify-captcha is where the problem occurs.

It currently works but that's only because json.rkt has been modified.

    (def post-getjson (url params (o port stdin))
      (fromstring (post-url url params) 
        (read-json (port))))

    (def recaptcha-response (s r)
      (post-getjson "https://www.google.com/recaptcha/api/siteverify"
      (list 'secret s 'response r)))

    (def verify-captcha (req)
      (do
        (= resp (recaptcha-response recaptcha-secret* 
        (alref (req 'args) "g-recaptcha-response")))
      (if resp!success 
        (pr "success") 
        (pr "failure"))))
2 points by hjek 6 days ago | link | parent | on: How to handle Racket types?

Would you mind preparing a simple code example to show this issue? I'm not sure exactly what you are doing.
3 points by krapp 6 days ago | link | parent | on: How to handle Racket types?

I got an error saying #t was an unknown type when I tried to do a boolean check on it, as it was passed from a JSON response. It's the reason I started this thread to begin with.
1 point by hjek 6 days ago | link | parent | on: How to handle Racket types?

> sorry I don't have an install.

http://tryarc.org/

2 points by i4cu 6 days ago | link | parent | on: How to handle Racket types?

sorry I don't have an install. I've been assuming:

  (if #f 'foo 'bar)
 
would error.

edit: and now I have and install... :)

2 points by hjek 6 days ago | link | parent | on: How to handle Racket types?

Why do we need to explicitly translate #t to t? Arc has no problem handling Racket booleans.

I'd like to see perhaps a specific brief example, where using Racket booleans within Arc actually is a problem, because I haven't encountered any myself.

If I try to use the hash from your example in actual Arc code, it works fine:

    arc> (if ((w/instring in "{\"foo\": true}" ($.read-json in)) 'foo) 'yes 'no)
    yes
    arc> (if ((w/instring in "{\"foo\": false}" ($.read-json in)) 'foo) 'yes 'no)
    no
2 points by hjek 6 days ago | link | parent | on: How to handle Racket types?

Is the $ the handler you are referring to?

You don't need to add any $ handlers to handle Racket booleans. They can be passed directly to Arc functions.

The $ is just there to get some Racket booleans.

2 points by akkartik 6 days ago | link | parent | on: How to handle Racket types?

Thanks! I think you're right that the existing JSON parser is unnecessary. But there's still an open problem about translating to Arc:

    arc> (w/instring in "{\"foo\": true}" ($.read-json in))
    #hash((foo . #t))
We'd like this to translate to:

    #hash((foo . t))
Perhaps what we're looking for isn't related to JSON at all, but a general shim for translating the output of Racket functions. That could be broadly useful.
2 points by i4cu 6 days ago | link | parent | on: How to handle Racket types?

$.#f means you need to add a handler on a value, that's not handling the value as it's supposed to.

CAPTCHA sounds interesting.

Would be seriously cool if the CAPTCHA was implemented in Arc and generated the images locally instead of relying on some Google SASS.

(I twiddled a bit with Racket's image manipulation functions in img.arc in the Anarki repo, if that's any help.)

3 points by hjek 6 days ago | link | parent | on: How to handle Racket types?

Someone should get rid of json.rkt and json.arc from the Anarki repo.

Racket already has a built-in JSON parser[0] that works just fine with Arc.

[0]: https://docs.racket-lang.org/json/index.html

2 points by hjek 6 days ago | link | parent | on: How to handle Racket types?

Great, that looks fine. Then you can just access the `success` key like this:

    ($ (require json))
    
    (if
       ((w/instring json "{\"success\":false}"
                          ($.read-json json))
                          'success)
       'success ; proceed to login
       'fail    ; must be a robot then
     )
2 points by hjek 6 days ago | link | parent | on: How to handle Racket types?

Arc already supports Racket booleans:

    arc> (if $.#f 'foo 'bar)
    bar
    arc> (if $.#t 'foo 'bar)
    foo
They work, so what is the problem?

Also, I don't get why the Anarki repository contains a JSON parser implemented in Racket, when the built-in Racket JSON parser works just fine with Arc:

    arc> ($ (require json))
    arc> ($.write-json (obj foo "bar"))
    {"foo":"bar"}#<void>
    arc> (w/instring in "{\"foo\":\"bar\"}" ($.read-json in))
    #hash((foo . "bar"))
I fail to see the problem with the Racket built-in JSON parser here. Would anyone care to explain?

The JSON solution is a quick and dirty hack by a rank noob, and I'm sure something better will come along.

And in hindsight the problem with the (body) macro should probably have been obvious, considering HTML tables are built using (tab) and not (table). I'm starting to think everything other than (tag) should be done away with to avoid the issue in principle, but that would be a major undertaking and probably mostly just bikeshedding.


It's great to see a JSON API integrated in Arc. :)

I took a look and found fixes for the unit tests. Before I got into that debugging though, I noticed some problems with the JSON library that I'm not sure what to do with. It turns out those are unrelated to the test failures.

I left details about these in comments on the closed pull request, which might not have been the best place: https://github.com/arclanguage/anarki/pull/102

More