Giter Site home page Giter Site logo

failjure's Introduction

Failjure

Run Tests Clojars Project bb compatible

Failjure is a utility library for working with failed computations in Clojure(Script). It provides an alternative to exception-based error handling for applications where functional purity is more important.

It was inspired by Andrew Brehaut's error monad implementation.

Installation

Add the following to your build dependencies:

Clojars Project

You can also include the specs via the failjure-spec project, if you're into that sort of thing:

Clojars Project

Example

(require '[failjure.core :as f])

;; Write functions that return failures
(defn validate-email [email]
    (if (re-matches #".+@.+\..+" email)
      email
      (f/fail "Please enter a valid email address (got %s)" email)))

(defn validate-not-empty [s]
  (if (empty? s)
    (f/fail "Please enter a value")
    s))

;; Use attempt-all to handle failures
(defn validate-data [data]
  (f/attempt-all [email (validate-email (:email data))
                  username (validate-not-empty (:username data))
                  id (f/try* (Integer/parseInt (:id data)))]
    {:email email
     :username username}
    (f/when-failed [e]
      (log-error (f/message e))
      (handle-error e))))

Quick Reference

HasFailed

The cornerstone of this library, HasFailed is the protocol that describes a failed result. Failjure implements HasFailed for Object (the catch-all not-failed implementation), Exception, and the built-in Failure record type, but you can add your own very easily:

(defrecord AnnotatedFailure [message data]
  f/HasFailed
  (failed? [self] true)
  (message [self] (:message self)))

fail

fail is the basis of this library. It accepts an error message with optional formatting arguments (formatted with Clojure's format function) and creates a Failure object.

(f/fail "Message here") ; => #Failure{:message "Message here"}
(f/fail "Hello, %s" "Failjure") ; => #Failure{:message "Hello, Failjure"}

failed? and message

These two functions are part of the HasFailed protocol underpinning failjure. failed? will tell you if a value is a failure (that is, a Failure, a java Exception or a JavaScript Error.

attempt

Added in 2.1

Accepts a value and a function. If the value is a failure, it is passed to the function and the result is returned. Otherwise, value is returned.

(defn handle-error [e] (str "Error: " (f/message e)))
(f/attempt handle-error "Ok")  ;=> "Ok"
(f/attempt handle-error (f/fail "failure"))  ;=> "Error: failure"

Try it with partial!

attempt-all

attempt-all wraps an error monad for easy use with failure-returning functions. You can add any number of bindings and it will short-circuit on the first error, returning the failure.

(f/attempt-all [x "Ok"] x)  ; => "Ok"
(f/attempt-all [x "Ok"
              y (fail "Fail")] x) ; => #Failure{:message "Fail"}

You can use when-failed to provide a function that will handle an error:

(f/attempt-all [x "Ok"
                y (fail "Fail")]
  x
  (f/when-failed [e]
    (f/message e))) ; => "Fail"

ok-> and ok->>

If you're on-the-ball enough that you can represent your problem as a series of compositions, you can use these threading macros instead. Each form is applied to the output of the previous as in -> and ->> (or, more accurately, some-> and some->>), except that a failure value is short-circuited and returned immediately.

Previous versions of failjure used attempt-> and attempt->>, which do not short-circuit if the starting value is a failure. ok-> and ok->> correct this shortcoming

(defn validate-non-blank [data field]
  (if (empty? (get data field))
    (f/fail "Value required for %s" field)
    data))

(let [result (f/ok->
              data
              (validate-non-blank :username)
              (validate-non-blank :password)
              (save-data))]
  (when (f/failed? result)
    (log (f/message result))
    (handle-failure result)))

as-ok->

Added in 2.1

Like clojure's built-in as->, but short-circuits on failures.

(f/as-ok-> "k" $
  (str $ "!")
  (str "O" $))) ; => Ok!

(f/as-ok-> "k" $
  (str $ "!")
  (f/try* (Integer/parseInt $))
  (str "O" $))) ; => Returns (does not throw) a NumberFormatException

try*

This library does not handle exceptions by default. However, you can wrap any form or forms in the try* macro, which is shorthand for:

(try
  (do whatever)
  (catch Exception e e))

Since failjure treats returned exceptions as failures, this can be used to adapt exception-throwing functions to failjure-style workflows.

try-all

A version of attempt-all which automatically wraps each right side of its bindings in a try* is available as try-all (thanks @lispyclouds):

(try-all [x (/ 1 0)
          y (* 2 3)]
  y)  ; => java.lang.ArithmeticException (returned, not thrown)

(if-|when-)-let-(ok?|failed?)

Failjure provides the helpers if-let-ok?, if-let-failed?, when-let-ok? and when-let-failed? to help with branching. Each has the same basic structure:

(f/if-let-failed? [x (something-which-may-fail)]
  (handle-failure x)
  (handle-success x))
  • If no else is provided, the if- variants will return the value of x
  • The when- variants will always return the value of x

assert-with

The assert-with helper is a very basic way of adapting non-failjure-aware functions/values to a failure context. The source is simply:

(defn assert-with
  "If (pred v) is true, return v
   otherwise, return (f/fail msg)"
  [pred v msg]
  (if (pred v) v (fail msg)))

The usage looks like this:

(f/attempt-all
  [x (f/assert-with some? (some-fn) "some-fn failed!")
   y (f/assert-with integer? (some-integer-returning-fn) "Not an integer.")]
  (handle-success x)
  (f/when-failed [e] (handle-failure e)))

The pre-packaged helpers assert-some?, assert-nil?, assert-not-nil?, assert-not-empty?, and assert-number? are provided, but if you like, adding your own is as easy as (def assert-my-pred? (partial f/assert-with my-pred?)).

Changelog

2.3.0

Added clj-kondo support and indent annotations.

2.2.0

(Re-)added AOT compilation to the new leiningen project. This may help resolve errors with some project configurations.

2.1.1

Fix a deployment whoopsie causing attempt to have reversed argument order from what is documented here. It was fine in my REPL, I swear!

2.1.0

USE 2.1.1 INSTEAD

Added attempt and as-ok->. Changed from boot to leiningen for builds.

2.0.0

Added ClojureScript support. Since the jar now includes .cljc instead of .clj files, which could break older builds, I've decided this should be a major version. It should in general be totally backwards-compatible though.

Notable changes:

  • ClojureScript support (thanks @snorremd)
  • *try now wraps its inputs in a function and returns (try-fn *wrapped-fn*). This was necessary to keep the clj and cljs APIs consistent, but could break some existing use cases (probably).

1.5.0

Added try-all feature

1.4.0

Resolved issues caused by attempting to destructure failed results.

1.3.0

Fix bug where ok->/> would sometimes double-eval initial argument.

1.2.0

Refactored attempt-all, attempt->, and attempt->> to remove dependency on monads

1.1.0

Added assert helpers

1.0.1

This version is fully backwards-compatible with 0.1.4, but failjure has been in use long enough to be considered stable. Also I added a .1 because nobody trusts v1.0.0.

  • Added ok?, ok->, ok->>, if-let-ok?, when-let-ok?, if-let-failed? and when-let-failed?

0.1.4

  • Added changelog.

License

Copyright 2016 Adam Bard and Andrew Brehaut

Distributed under the Eclipse Public License v1.0 (same as Clojure).

failjure's People

Contributors

adambard avatar ashwinbhaskar avatar lispyclouds avatar pratik97 avatar vendethiel avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

failjure's Issues

Proposal: failure handling without binding

I don't see any way to handle failure without binding it to a symbol. attempt-all is great but if we have only one binding then there's a lot of boilerplate and nesting. My proposal is to add attempt macro which will let us handle failure without having to bind it manually.

Example with attempt-all:

(f/attempt-all [result (f/fail "Failed")]
  result
  (f/when-failed [e] (handle-error e)))

Boilerplate. We don't do anything with the result if there's no error.


Example with when-let-failed?:

(f/when-let-failed? [result (f/fail "Failed")]
  (handle-error result))

Less boilerplate, but we have to customize auto-indent and our code is less linear + we still have to name result

"There are only two hard things in Computer Science: cache invalidation and naming things."
Phil Karlton


Example with attempt:

(f/attempt (f/fail "Failed") handle-error)

0 boilerplate, elegant one-liner.


Example of real world use-case with when-let-failed?:

(defn create-todo [input]
  (f/when-let-failed? [result (f/ok->input
                               (validate)
                               (prepare)
                               (insert-into-db!)
                               (result-json))]
    (create-todo-error result)))

Example of real world use-case with attempt:

(defn create-todo [input]
  (f/attempt
    (f/ok-> input
            (validate)
            (prepare)
            (insert-into-db!)
            (result-json))
    create-todo-error)))

Ideal name imo would be when-failed but there's already macro with this name so attempt seams reasonable. It's almost like attempt-all but you attempt to get only one value and if there's only one value there's no point to bind it to anything

Plans for clojurescript support?

@adambard Have you tried porting Failjure to clojurescript? Failjure uses algo.monads, which just seems to use one function name-with-attributes, from clojure.tools.macro. Ie, it doesn't support clojurescript. But Cats does.

Anyways, I thought it would be really nice to reuse tha same Either / Error monadic semantics, on both layers of our application.

Thoughts?

Maybe you should get rid of the when-failed macro...

I think it would be much better, if attempt-all would accept a plain function as a failure-handler instead of a when-failed macro - remember the first rule of the macro-club ;-)

And it just takes a slight modification:

(defmacro attempt-all
  "Used like `let`, but short-circuits in case of
  a failed binding. If a `failure-handler` fn is provided, it is called to handle the failure.
  Unlike `let`, only accepts a single form to execute after the bindings.
    (attempt-all [x \"Ok\"
                  y (fail \"Fail\")]
      x
      (fn handle-failure [e]
        (message e))) ; => \"Fail\"
  "
  ([bindings return]
   `(domonad error-m ~bindings ~return))
  ([bindings return failure-handler]
   `(let [result# (attempt-all ~bindings ~return)]
      (if (failed? result#)
        (~failure-handler result#)
        result#))))

What do you think?

Regards, Jan

Monads dep was removed

Hello @adambard !
You removed monads dependency from failjure lib.
I am using previous failjure version in my project and i am using it as a monad in several places.
For example, m-map over collection where projection function can return error.

Can you enlighten me why did you removed monad dependency?
Is it Occam Razor application?

Thanks!

Destructuring inside attempt-all causes an exception/failjure that would normally be short-circuited not to be

De-structuring inside attempt-all causes a returned java Exception or failjure Failure that would normally be short-circuited to not be short-circuited.

(f/attempt-all [m (f :a)
                {:keys [a b c]} m]
          3)

The above code gets around the problem. The problem would be evident if you avoided having m, so directly de-structured. Obviously f is returning something that f/attempt-all ought to be catching (a java Exception or failjure Failure).

clj-kondo config broken for `try*` (triggers: `missing-clause-in-try`)

The current clj-kondo config shipped with the project maps failjure.core/try* to clojure.core/try. This causes clj-kondo to complain about the the expression Missing catch or finally in try.

current workaround is to set .clj-kondo/config.edn to be:

{:lint-as {failjure.core/try* clj-kondo.lint-as/def-catch-all}}

Introduce as-ok->

Introduce the failjure version of as-> which short circuits if any of the forms returns a failjure

Failure spec checking fails for functions returning collections

examples use defn-spec from orchestra
This works:

(defn-spec test-failure-works (s/or :ok string? :fail ::fs/failure)
  []
  (f/fail "something wrong"))

This does not work --looks like the invocation of 'empty' on Failure record from the clojure.spec code is the problem:

(defn-spec test-failure-doesnt-work (s/or :ok (s/coll-of string?) :fail ::fs/failure)
  []
  (f/fail "something wrong"))

Unhandled java.lang.UnsupportedOperationException
Can't create empty: failjure.core.Failure

            core.clj:   36  failjure.core.Failure/empty
            core.clj: 5187  clojure.core/empty
            core.clj: 5181  clojure.core/empty
           alpha.clj: 1229  clojure.spec.alpha/every-impl/cfns/fn
           alpha.clj: 1243  clojure.spec.alpha/every-impl/reify
           alpha.clj: 1017  clojure.spec.alpha/or-spec-impl/fn
           alpha.clj: 1052  clojure.spec.alpha/or-spec-impl/reify
           alpha.clj:  150  clojure.spec.alpha/conform
           alpha.clj:  146  clojure.spec.alpha/conform
            test.clj:   97  orchestra.spec.test/spec-checking-fn/conform!
            test.clj:  125  orchestra.spec.test/spec-checking-fn/fn
         RestFn.java:  397  clojure.lang.RestFn/invoke

Allow arbitrary data to be associated with failure?

My code often fails in varying and spectacular ways :)

What would you think about allowing arbitrary maps to be associated with failures as well as strings?

My database might fail and my Validateur validations might fail, and I need to be able to render those in different ways.

If I could add a map to a failure then I could put the error map from Validateur into my failure and render that on my front end.

What do you think? Looking at your library and learning about monads has really changed the way I think about error handling, thanks!

Missing the stack traces on errors

Hi,

I'm using this library in a project and I kind of like the flow.
However I'm missing the stack traces when an a failure is reached.

Would love to have a way to capture the stack-trace on fail so to have an easier experience when debugging.
Is this feasible?

Thanks,
Euge

Feature request: `when-ok` macro

For the occasions when you only care that the form passes and do not need a value from it, I find myself using when-let-ok?:

(f/when-let-ok? [_ (some-fn-that-returns-a-failure-or-a-value...)]
  ::woohoo)

I propose to add the macro when-ok:

(defmacro when-ok [form & body]
  (when-let-ok? [_# ~form]
    ~@body))

So we can write:

(f/when-ok (some-fn-that-returns-a-failure-or-a-value...)
  ::woohoo)

Happy to do a PR for this with tests if you're interested in adding it.

ps. Great library by the way, thank you for your efforts!

Document the using with with-open

Hello!

First of all, thanks for failjure

I would be glad if you write an example how to use failjure for cases, where a user needs to close opened resources in any case. E.g. Iโ€™m doing the next:

(f/attempt-all [reader (pdf-reader source)
                document (pdf-document reader)
                fields (fields document)]
  (do
    (.close document)
    (.close reader)
    fields)
  (f/when-fail [e]
    ; Here I do not have an access to reader and document
    ; to close them
  ))

I wonder if there any mechanism to close the document and the reader in any case, like with with-open

Oposite of try-all: try-all-failed and similar

I think try-all-failed and similar might also be useful.

I am trying to parse some input from user (a time Duration) and trying to be forgiving.
In my case I need to try out all options and stop when I get a non-failure.

So my algorithm is :

  • try to parse user input
  • if failed try to parse user input with "PT" pre-pended
  • if failed try to parse user input with "P" pre-pended
  • give up with parsing
  • or if one did not fail, return that value

I think code would look like this:
(beginner with Failjure, code might not be correct)

(try-all-failed [duration (try-parse s)
                         duration (try-parse (str "PT" s))
                         duration (try-parse (str "P" s))]
duration
(f/fail "Unable to parse duration"))

Some context
https://docs.oracle.com/javase/8/docs/api/java/time/Duration.html#parse-java.lang.CharSequence-

 Examples:

    "PT20.345S" -- parses as "20.345 seconds"
    "PT15M"     -- parses as "15 minutes" (where a minute is 60 seconds)
    "PT10H"     -- parses as "10 hours" (where an hour is 3600 seconds)
    "P2D"       -- parses as "2 days" (where a day is 24 hours or 86400 seconds)
    "P2DT3H4M"  -- parses as "2 days, 3 hours and 4 minutes"
    "P-6H3M"    -- parses as "-6 hours and +3 minutes"
    "-P6H3M"    -- parses as "-6 hours and -3 minutes"
    "-P-6H+3M"  -- parses as "+6 hours and -3 minutes"

Please aot compile core.cljs

Problem

We have the following issue/exception when we're importing failjure during dev-time (repl-based workflow):

java.lang.NoClassDefFoundError: failjure/core/HasFailed
	at bulk_gateway.rest.accounts$get_acccount_balance.invokeStatic(accounts.clj:68)
	at bulk_gateway.rest.accounts$get_acccount_balance.invoke(accounts.clj:52)
...

Background

  • Our project setups are "interesting", but basically what we do is to load our own namespaces using dynamic (require ...) calls after finding a list of namespaces in our integrant configuration file. These namespaces then have (ns bulk-gateway.rest.accounts (require [failjure.core]... dependencies on failjure.
  • One work-around we have is after the repl starts, we can (load-file "src/bulk_gateway/rest/accounts.clj") after which the NoClassDefFoundError exceptions go away.
  • We do NOT have this problem in our deployed artifacts (created using lein uberjar).

We've had similar issues with our own shared libraries that have (defprotocol ... in them and solved it by adding an :out [...] in the project.clj of the client lib project.

Solution

I cloned your repo locally and added an :aot [failjure.core] to failjure's project.clj and our issue goes away after lein install.

As far as I know this should be no-impact change for clients.

(defproject failjure "2.1.1"
  :description "Simple helpers for treating failures as values"
  :url         "https://github.com/adambard/failjure"
  :license     {:name "Eclipse Public License"
                :url "http://www.eclipse.org/legal/epl-v10.html"}

  :dependencies []

  :repl-options {:init-ns failjure.core}

  :plugins [[lein-cljsbuild "1.1.8"]
            [lein-doo "0.1.10"]]

  :aot [failjure.core]

  :profiles
  {:provided {:dependencies [[org.clojure/clojure       "1.10.1"]
                             [org.clojure/clojurescript "1.10.764"]]}}
  :cljsbuild
  {:builds [{:id "test"
             :source-paths ["src" "test"]
             :compiler {:output-to "target/testable.js"
                        :main failjure.runner
                        :target :nodejs
                        :optimizations :none}}]})

Threading macro for handling of nil and exceptions along with explicit failures

Using the f/ok->> macro allows short circuiting only on explicit failures, but since the regular Clojure idiom is to use nil punning, it would be useful to have a threading macro that provides the benefit of some->> as well. The modified macro below allows me to unify nils, exceptions as well explicit fails into the failjure interface. If the code below looks ok, could you please include it as part of failjure?

(defmacro check_attempt->>
  ([start] start)
  ([start form] `(domonad f/error-m [x# (->> ~start ~form)] x#))
  ([start form & forms]
   `(let [new-start# (check_attempt->> ~start ~form)]
      (cond
        (f/failed? new-start#) new-start#
        (nil? new-start#) (f/fail (str "got nil at " ~form))
        :else (check_attempt->> new-start# ~@forms)))))


(defmacro check->>
  ([start & forms]
   `(if (f/failed? ~start)
      ~start
      (f/try*
       (check_attempt->> ~start ~@forms)))))

Compile blows up on 1.9.0-alpha when failjure is 'required'

Hi, On 1.9-alpha16, just including the failjure declaration in a namespaces causes the following error

Exception in thread "main" java.lang.ExceptionInInitializerError
	at clojure.main.<clinit>(main.java:20)
Caused by: clojure.lang.ExceptionInfo: Call to clojure.core/defn did not conform to spec:
In: [0] val: clojure.algo.monads/m+m-join+m fails spec: :clojure.core.specs/defn-args at: [:args :name] predicate: simple-symbol?
:clojure.spec/args  (clojure.algo.monads/m+m-join+m [m-bind m-result m-zero m-plus m] (clojure.tools.macro/with-symbol-macros (m-bind m identity)))
 {:clojure.spec/problems [{:path [:args :name], :pred simple-symbol?, :val clojure.algo.monads/m+m-join+m, :via [:clojure.core.specs/defn-args :clojure.core.specs/defn-args], :in [0]}], :clojure.spec/args (clojure.algo.monads/m+m-join+m [m-bind m-result m-zero m-plus m] (clojure.tools.macro/with-symbol-macros (m-bind m identity)))}, compiling:(clojure/algo/monads.clj:248:1)
	at clojure.lang.Compiler.load(Compiler.java:7442)
	at clojure.lang.RT.loadResourceScript(RT.java:374)
	at clojure.lang.RT.loadResourceScript(RT.java:365)
	at clojure.lang.RT.load(RT.java:455)
	at clojure.lang.RT.load(RT.java:421)
	at clojure.core$load$fn__7831.invoke(core.clj:6008)
	at clojure.core$load.invokeStatic(core.clj:6007)
	at clojure.core$load.doInvoke(core.clj:5991)
	at clojure.lang.RestFn.invoke(RestFn.java:408)
	at clojure.core$load_one.invokeStatic(core.clj:5812)
	at clojure.core$load_one.invoke(core.clj:5807)
	at clojure.core$load_lib$fn__7776.invoke(core.clj:5852)
	at clojure.core$load_lib.invokeStatic(core.clj:5851)
	at clojure.core$load_lib.doInvoke(core.clj:5832)
	at clojure.lang.RestFn.applyTo(RestFn.java:142)
	at clojure.core$apply.invokeStatic(core.clj:659)
	at clojure.core$load_libs.invokeStatic(core.clj:5889)
	at clojure.core$load_libs.doInvoke(core.clj:5873)
	at clojure.lang.RestFn.applyTo(RestFn.java:137)
	at clojure.core$apply.invokeStatic(core.clj:659)
	at clojure.core$require.invokeStatic(core.clj:5911)
	at clojure.core$require.doInvoke(core.clj:5911)
	at clojure.lang.RestFn.invoke(RestFn.java:408)
	at failjure.core$eval16561$loading__7717__auto____16562.invoke(core.clj:1)
	at failjure.core$eval16561.invokeStatic(core.clj:1)
	at failjure.core$eval16561.invoke(core.clj:1)
	at clojure.lang.Compiler.eval(Compiler.java:6978)
	at clojure.lang.Compiler.eval(Compiler.java:6967)
	at clojure.lang.Compiler.load(Compiler.java:7430)
	at clojure.lang.RT.loadResourceScript(RT.java:374)
	at clojure.lang.RT.loadResourceScript(RT.java:365)
	at clojure.lang.RT.load(RT.java:455)
	at clojure.lang.RT.load(RT.java:421)
	at clojure.core$load$fn__7831.invoke(core.clj:6008)
	at clojure.core$load.invokeStatic(core.clj:6007)
	at clojure.core$load.doInvoke(core.clj:5991)
	at clojure.lang.RestFn.invoke(RestFn.java:408)
	at clojure.core$load_one.invokeStatic(core.clj:5812)
	at clojure.core$load_one.invoke(core.clj:5807)
	at clojure.core$load_lib$fn__7776.invoke(core.clj:5852)
	at clojure.core$load_lib.invokeStatic(core.clj:5851)
	at clojure.core$load_lib.doInvoke(core.clj:5832)
	at clojure.lang.RestFn.applyTo(RestFn.java:142)
	at clojure.core$apply.invokeStatic(core.clj:659)
	at clojure.core$load_libs.invokeStatic(core.clj:5889)
	at clojure.core$load_libs.doInvoke(core.clj:5873)
	at clojure.lang.RestFn.applyTo(RestFn.java:137)
	at clojure.core$apply.invokeStatic(core.clj:659)
	at clojure.core$require.invokeStatic(core.clj:5911)
	at clojure.core$require.doInvoke(core.clj:5911)
	at clojure.lang.RestFn.invoke(RestFn.java:436)
	at gs_authorization.routes.services.actions$eval16555$loading__7717__auto____16556.invoke(actions.clj:1)

Suggestion: Rename `if-failed` to `when-failed`

First of all, this is awesome! Thanks @adambard โœจ ๐Ÿ‘

As I read your introductory blog post, I had an initial reaction when I saw if-failed:

(f/attempt-all [,,,]
  ,,,
  (f/if-failed [e]
    (log-error (f/message e))
    (handle-error e)))

I saw two forms after if and my first intuition was that they would be "then" and "else" cases. This obviously doesn't make sense here, so this didn't hang me up for long.

...But, maybe we could avoid even the chance of momentary confusion by renaming to when-failed.

Feature Request: `f/attempt` with implicit `f/try*`

Think it would be handy to have a macro like f/attempt but wraps val-or-failed in an implicit f/try*.

Maybe something like f/try? or change f/attempt?:

(f/try
 #(f/fail "I failed!: %s" (f/message %))
 (throw (ex-info "I throw" {})))

ok? for multiple results

I was wondering if makes sense for change the ok? fn to also validate a list of results?

(defn ok?
  [v]
  (if (coll? v)
    (every? ok? v)
    (not (failed? v))))

I think would be nice to add a feature like this or on the other hand if you want to have a different function with it's own semantic we can just add a different fn.

your thoughts on this one @adambard

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.