Fork me on GitHub
#funcool
<
2016-01-27
>
niwinz17:01:48

[ann] Initial work on arbitrary precision decimal type for clojurescript https://github.com/funcool/decimal

niwinz17:01:21

Is still WIP but promising 😄

jaen17:01:01

@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?

niwinz17:01:41

What do you mean with type alias?

jaen17:01:38

Oh, I figured you might be familiar with how Haskell names it since you implemented category theory library.

jaen17:01:17

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.

jaen17:01:23

So they are the same structurally

niwinz17:01:31

I familiar with it but I don't understand that you have said before some "context"...

jaen17:01:50

But you can give different implementations of typeclasses to them

jaen17:01:58

Maybe I'll explain my use case

niwinz17:01:00

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

niwinz17:01:08

this is a clear example of that

niwinz17:01:16

extending the nil type with maybe context

jaen17:01:01

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.

jaen17:01:37

So how would you approach something like this?

jaen17:01:15

My current idea is to have something like (ErrorContainer. {...}) that will implement a mappend that recursively merges the contained value.

jaen17:01:29

That way I won't have to pollute PersistentMap.

jaen17:01:42

Is this a good solution or is there another?

niwinz17:01:58

Give me 1 min

jaen17:01:19

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))

jaen17:01:35

But I just don't want this to be the monoid for all Clojure maps

jaen17:01:43

Just in the context of my validation function

jaen17:01:30

So that's why I wanted to "newtype" the maps, so there can be some specific "validation maps" I can implement that for

niwinz18:01:10

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...

niwinz18:01:57

Furthermore if you are doing validation stuf, have you considered the validation type that comes with cats? http://funcool.github.io/cats/latest/#validation

jaen18:01:06

Yes, I am using it

jaen18:01:13

But the thing is I want to merge errors

jaen18:01:17

And that's what the semigroup is for

jaen18:01:45

As far as I can tell without that you will get only one error from validation, not all of them.

jaen18:01:50

I might be using it wrong though.

niwinz18:01:43

with validation you can get only one error per "field" , I guess you want collect more than one

niwinz18:01:20

One other possible solution

niwinz18:01:42

is just use (ctx/with-context yourcontext (m/mappend {...} {...}))

niwinz18:01:07

With this way you don't need extend the map type

jaen18:01:31

Oh, that sounds sensible, let me check

jaen18:01:11

Ah, but it will override the context for the validation applicative, won't it?

niwinz18:01:36

I guess yes

niwinz18:01:49

the validation type at this moment is not parametrizable

niwinz18:01:20

so I think that if the current behavior does not fits for you needs, you should use something else

jaen18:01:38

Basically I want (m/<*> (av/fail {:a [:wrong]}) (av/fail {:b [:wrong]}) (av/fail {:b [:more-wrong]})) to "just work".

jaen18:01:45

I'll see if wrapping with a record will work

jaen18:01:48

And let you know

niwinz18:01:05

I just recommend take the validation code as initial code and modify it for you needs, it will result in much less hacks

niwinz18:01:25

the current validation is implemented thinking in one unique error

niwinz18:01:53

you just need different use case that validation type does not implement

jaen18:01:45

The way I look at it (maybe I'm misunderstanding something) but given right mappend definition validation would do what I expect:

jaen18:01:48

(fail (let [sv (p/-extract sv)
            sv' (p/-extract sv')]
        (p/-mappend (p/-get-context sv) sv sv'))

jaen18:01:12

Because it mappends both fails' content.

niwinz18:01:49

yes, the way is creating a wrapper type that works like a map and implements a context with proper mappend impl for you

niwinz18:01:16

that should work, but it looks a little bit hacky IMHO, but if that is ok for you, it should work

niwinz18:01:04

(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]}}>

niwinz18:01:00

@jaen: ^ this works, but using a wrapped type

jaen18:01:58

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!

niwinz18:01:56

Don't worry, simply I think that it there was some other way to do so... and I want to experiment also

niwinz18:01:31

we are working in a 2.x version of cats reimplementing the current approach for monad/whatever abstraction composition

niwinz18:01:04

because the current approach is a little bit limiting and implies dynamic vars

niwinz18:01:27

now I'm go away for diner and rest!

jaen18:01:19

Bon appetit then!

jaen18:01:27

And thanks again for your input : )