[ann] Initial work on arbitrary precision decimal type for clojurescript https://github.com/funcool/decimal
Is still WIP but promising 😄
@niwinz: how do you deal when you need a type alias to implement some cats type abstraction on it? Do you just wrap that thing in a single field defrecord wrapper and implement it on that, or is there another way?
What do you mean with type alias?
Oh, I figured you might be familiar with how Haskell names it since you implemented category theory library.
Anyway
In Haskell you can take a type, say Map
and add a new type that is basically Map
but is nominally different from it NewMap
.
So they are the same structurally
I familiar with it but I don't understand that you have said before some "context"...
But you can give different implementations of typeclasses to them
Maybe I'll explain my use case
Yes, in this case, cats works in very similar way. It implements the abstraction as separated object that is late referenced to the final type
https://github.com/funcool/cats/blob/master/src/cats/builtin.cljc#L39
this is a clear example of that
extending the nil type with maybe context
Basically I want to have a merging map semigroup - that is (mappend {:a {:b [1 2]}} {:a {:c "test" :b [3 2]}})
should result in {:a {:b [1 2 3 2] :c "test"}}
but I don't want to pollute Clojure types with this implementation, since it might be undesirable in other contexts.
So how would you approach something like this?
My current idea is to have something like (ErrorContainer. {...})
that will implement a mappend
that recursively merges the contained value.
That way I won't have to pollute PersistentMap
.
Is this a good solution or is there another?
Give me 1 min
Sure
Before I was doing something like this:
(def alt-map-monoid
(reify
p/Context
(-get-level [_] ctx/+level-default+)
p/Semigroup
(-mappend [_ sv sv']
(merge-with
(fn [sv sv']
(if (clojure.core/and
(clojure.core/and (satisfies? p/Contextual sv)
(when-let [context (p/-get-context sv)]
(satisfies? p/Semigroup context)))
(clojure.core/and (satisfies? p/Contextual sv')
(when-let [context (p/-get-context sv')]
(satisfies? p/Semigroup context))))
(m/mappend sv sv')
sv'))
sv sv'))
p/Monoid
(-mempty [_]
{})))
(extend-type #?(:clj clojure.lang.PersistentHashMap
:cljs cljs.core.PersistentHashMap)
p/Contextual
(-get-context [_] alt-map-monoid))
But I just don't want this to be the monoid for all Clojure maps
Just in the context of my validation function
So that's why I wanted to "newtype" the maps, so there can be some specific "validation maps" I can implement that for
I don't remember now, but I think that can be done in more simple way just parametrizing the current map context, without touching or implementing new context. Just let me remember that...
Furthermore if you are doing validation stuf, have you considered the validation type that comes with cats? http://funcool.github.io/cats/latest/#validation
Yes, I am using it
But the thing is I want to merge errors
And that's what the semigroup is for
As far as I can tell without that you will get only one error from validation, not all of them.
I might be using it wrong though.
with validation you can get only one error per "field" , I guess you want collect more than one
Yes
One other possible solution
is just use (ctx/with-context yourcontext (m/mappend {...} {...}))
With this way you don't need extend the map type
Oh, that sounds sensible, let me check
Ah, but it will override the context for the validation applicative, won't it?
I guess yes
the validation type at this moment is not parametrizable
so I think that if the current behavior does not fits for you needs, you should use something else
Basically I want (m/<*> (av/fail {:a [:wrong]}) (av/fail {:b [:wrong]}) (av/fail {:b [:more-wrong]}))
to "just work".
I'll see if wrapping with a record will work
And let you know
I just recommend take the validation code as initial code and modify it for you needs, it will result in much less hacks
the current validation is implemented thinking in one unique error
you just need different use case that validation type does not implement
Hmm
The way I look at it (maybe I'm misunderstanding something) but given right mappend
definition validation would do what I expect:
(fail (let [sv (p/-extract sv)
sv' (p/-extract sv')]
(p/-mappend (p/-get-context sv) sv sv'))
Because it mappend
s both fails' content.
yes, the way is creating a wrapper type that works like a map and implements a context with proper mappend impl for you
that should work, but it looks a little bit hacky IMHO, but if that is ok for you, it should work
(require '[cats.core :as m])
(require '[cats.builtin :as b])
(require '[cats.context :as ctx])
(require '[cats.protocols :as p])
(require '[cats.applicative.validation :as av])
(require '[clojure.set :as set])
(defrecord MyMap [v])
(def merge-context
(reify
p/Context
(-get-level [_] ctx/+level-default+)
p/Semigroup
(-mappend [_ sv sv']
(->MyMap
(merge-with set/union (:v sv) (:v sv'))))
p/Monoid
(-mempty [_]
{})))
(extend-type MyMap
p/Contextual
(-get-context [_]
merge-context))
(m/<*> (av/fail (->MyMap {:a [:wrong]})) (av/fail (->MyMap {:b [:wrong]})) (av/fail (->MyMap {:b [:more-wrong]})))
;; => #<Fail #user.MyMap{:v {:a [:wrong], :b [:wrong :more-wrong]}}>
@jaen: ^ this works, but using a wrapped type
Yeah, exactly what I wanted to implement, just wanted to know if you think it made sense. You didn't have to waste time writing that for me, I would have known how to do that - sorry. And thanks!
Don't worry, simply I think that it there was some other way to do so... and I want to experiment also
we are working in a 2.x version of cats reimplementing the current approach for monad/whatever abstraction composition
because the current approach is a little bit limiting and implies dynamic vars
now I'm go away for diner and rest!
😛
Bon appetit then!
And thanks again for your input : )