Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

View don't handle exceptions #20

Closed
darkleaf opened this issue Oct 30, 2017 · 19 comments
Closed

View don't handle exceptions #20

darkleaf opened this issue Oct 30, 2017 · 19 comments

Comments

@darkleaf
Copy link

;; fails with  java.lang.IllegalArgumentException: Don't know how to create ISeq from: clojure.lang.Symbol
(match {:foo 0}
       {:foo (:view last foo)} foo)

;; working
(match {:foo [1]}
       {:foo (:view last foo)} foo)
@missingfaktor
Copy link
Owner

0 is not a sequence, so last would fail. View patterns require that the viewer function succeeds on a viewee.

@darkleaf
Copy link
Author

I understand. But I expect that first example fails with "no match".

@darkleaf
Copy link
Author

darkleaf commented Oct 30, 2017

For example I match a complex deep data structure.
I don't want write smth like this:

;; it's not compiled
(match {:foo 0}
       {:foo (:and x (seq? x)  ;; and a lots of checks for complex cases
                   (:view last foo))} foo)

@missingfaktor
Copy link
Owner

I believe the contract of all pattern combinators should be as minimal as possible. If we had types, !view function's current type would be (in Haskell-like notation) (a -> b) -> Pattern b c -> Pattern a c.

With your proposed changes, we make our semantics more complex. We will be making a choice of which exceptions to ignore on user's behalf without knowing their use case, which seems wrong. Additionally, we risk swallowing exceptions inadvertently.

I would suggest doing one of the following:

  • Define a function like fnil but one that looks for certain exceptions (ones you care about) rather than for nil.
  • Define a custom pattern function like following:
akar.try-out=> (defn !try-view [f]
          #_=>   (fn [x]
          #_=>      (try [(f x)]
          #_=>        (catch RuntimeException _ nil))))

#'akar.try-out/!try-view

akar.try-out=> (match 0
          #_=>        [(!try-view last) x] (println x))

RuntimeException Pattern match failed. None of the clauses applicable to the value: 0.  
akar.internal.utilities/fail-with (utilities.clj:24)

akar.try-out=> (match [1 0 9]
          #_=>        [(!try-view last) x] (println x))

9
nil

@darkleaf
Copy link
Author

I completely agree! 👍

@darkleaf
Copy link
Author

(defmacro fn-silent-> [& xs]
  `(fn [x#]
     (try
       (-> x# ~@xs)
       (catch RuntimeException _# nil))))

{:via (:view (fn-silent-> peek s/form  first) [(!constant `s/keys)])
 :pred (:view (fn-silent-> last last) k)
 :in in}

May be I'll find a better solution.

@missingfaktor
Copy link
Owner

Why not use !try-view that I defined earlier?

{:via [(!try-view (comp first s/form peek)) [(!constant `s/keys)]] 
 :pred [(!try-view (comp last last)) k]
 :in in}

@darkleaf
Copy link
Author

Probably you are right.
!try-view returns nil when fn fails and clause not match.
{:pred (:view (fn-silent-> last last) k)} match all possible data. This is wrong.

I am also confused by the mixture of syntax (:view ...) and [(!try-view ...)...]

@missingfaktor
Copy link
Owner

If you run syndoc match at the REPL, you will see the following output:

screen shot 2017-10-31 at 07 54 55

I will draw your attention to three of those lines:

pattern' => any' | literal' | constant' | bind' | guard-pattern' | view-pattern' | or-pattern' | and-pattern' | seq-pattern' | map-pattern' | variant-pattern' | record-pattern' | type-pattern' | arbitrary-pattern'
...
view-pattern' => (:view form pattern')
...
arbitrary-pattern' => [form pattern'*]

Akar provides a dedicated syntactic support for a bunch of things, including view patterns. These special syntactic constructs always look like (:this ...). If your needs fall outside of it, you can define custom pattern functions like !try-match above, and use the arbitrary pattern syntax. Arbitrary patterns look like [!this ...].

@missingfaktor
Copy link
Owner

If you haven't done so already, I highly recommend working through the tutorial.

@darkleaf
Copy link
Author

You don't understand me =)
I understand arbitrary-pattern and read the tutorial.
The arbitrary pattern looks unusually/unnatural for me.
It's was added for rare cases I think.

@missingfaktor
Copy link
Owner

missingfaktor commented Oct 31, 2017

I see. Its different syntactic appearance is intentional, to make it stand out from dedicated syntactic patterns. :) It's not supposed to be rare at all! With Lisp, we are limited to very few syntactic tools, and have to make the best of it. Another alternative I thought of, at the time I built this, was (:arb (!try-view f) out). But it looks too verbose for my taste.

@missingfaktor
Copy link
Owner

missingfaktor commented Oct 31, 2017

I had another idea too: [!try-view f | out] instead of [(!try-view f) out]. What do you think about these alternatives?

@darkleaf
Copy link
Author

It's complex task I think. It is necessary to consider approaches in other languages. Custom patterns should be looks like library patterns.

[(!try-view f) out] is cleaner than [!try-view f | out].

@darkleaf
Copy link
Author

Another case. It's about clojure lists.

Data example:

    {:path [], :val {}, :in []
     :pred (clojure.core/fn [%] (clojure.core/contains? % :SOME/K))
     :via [:SOME/SPEC-1 :SOME/SPEC-2]})

I can't describe this pattern in terms of data patterns because it's too complex for lists.

(match {:foo 1}
    {:foo k} k) ;; easy for maps

I can only understand approach with view:

;; todo: change fn-silent-> to !try-view
         {:via (:view (fn-silent-> peek s/form first) [(!constant `s/keys)])
          :pred (:and (:view (fn-silent-> last first) [(!constant `contains?)])
                             (:view (fn-silent-> last last) k))

@missingfaktor
Copy link
Owner

You can use (:seq ...) to match sequences.

As for symbols, I wonder if we should add support for symbol literals at the syntax level, so you can say (match some-symbol `s/keys nil) instead of (match some-symbol [(!constant `s/keys)] nil).

@missingfaktor
Copy link
Owner

You could define some helper pattern functions too. Like !fn that extracts argument list and body separately.

@darkleaf
Copy link
Author

darkleaf commented Nov 1, 2017

Thanks for your answer.
I send email to you.


Hi!
Today we talked in github issues. My nickname is @darkleaf.

I rewrite my clojure module without Akar.
https://gist.github.com/darkleaf/92773deb87da7f57930ee40f1c44b313

Old solution https://github.com/darkleaf/publicator/blob/7d52ca2/src/publicator/web/problem_presenter.clj

Is Akar suitable for my case?

Thanks!

@missingfaktor
Copy link
Owner

You could, of course, rewrite it without Akar. But your problem seems like it could benefit from pattern matching. You are inspecting Spec outputs which are very structured.

Akar supports view patterns for completeness' sake, but I would normally just define lots of small pattern functions, and then use those. That could make your code easier to read.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants