funcool

A channel for discussing and asking questions about Funcool libraries https://github.com/funcool/
niwinz 2016-01-27T17:31:48.000007Z

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

niwinz 2016-01-27T17:32:21.000010Z

Is still WIP but promising 😄

jaen 2016-01-27T17:48:01.000011Z

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

niwinz 2016-01-27T17:48:41.000012Z

What do you mean with type alias?

jaen 2016-01-27T17:49:38.000013Z

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

jaen 2016-01-27T17:49:40.000014Z

Anyway

jaen 2016-01-27T17:50:17.000015Z

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.

jaen 2016-01-27T17:50:23.000016Z

So they are the same structurally

niwinz 2016-01-27T17:50:31.000017Z

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

jaen 2016-01-27T17:50:50.000018Z

But you can give different implementations of typeclasses to them

jaen 2016-01-27T17:50:58.000019Z

Maybe I'll explain my use case

niwinz 2016-01-27T17:52:00.000020Z

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

niwinz 2016-01-27T17:52:08.000023Z

this is a clear example of that

niwinz 2016-01-27T17:52:16.000024Z

extending the nil type with maybe context

jaen 2016-01-27T17:53:01.000025Z

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.

jaen 2016-01-27T17:53:37.000026Z

So how would you approach something like this?

jaen 2016-01-27T17:54:15.000027Z

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

jaen 2016-01-27T17:54:29.000028Z

That way I won't have to pollute PersistentMap.

jaen 2016-01-27T17:54:42.000029Z

Is this a good solution or is there another?

niwinz 2016-01-27T17:54:58.000030Z

Give me 1 min

jaen 2016-01-27T17:55:58.000031Z

Sure

jaen 2016-01-27T17:57:19.000032Z

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

jaen 2016-01-27T17:57:35.000033Z

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

jaen 2016-01-27T17:57:43.000034Z

Just in the context of my validation function

jaen 2016-01-27T17:58:30.000035Z

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

niwinz 2016-01-27T18:01:10.000036Z

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

niwinz 2016-01-27T18:01:57.000037Z

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

jaen 2016-01-27T18:02:06.000038Z

Yes, I am using it

jaen 2016-01-27T18:02:13.000039Z

But the thing is I want to merge errors

jaen 2016-01-27T18:02:17.000040Z

And that's what the semigroup is for

jaen 2016-01-27T18:02:45.000041Z

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

jaen 2016-01-27T18:02:50.000042Z

I might be using it wrong though.

niwinz 2016-01-27T18:08:43.000043Z

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

jaen 2016-01-27T18:09:51.000044Z

Yes

niwinz 2016-01-27T18:10:20.000045Z

One other possible solution

niwinz 2016-01-27T18:10:42.000046Z

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

niwinz 2016-01-27T18:11:07.000048Z

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

jaen 2016-01-27T18:11:31.000049Z

Oh, that sounds sensible, let me check

jaen 2016-01-27T18:14:11.000050Z

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

niwinz 2016-01-27T18:16:36.000051Z

I guess yes

niwinz 2016-01-27T18:16:49.000052Z

the validation type at this moment is not parametrizable

niwinz 2016-01-27T18:17:20.000053Z

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

jaen 2016-01-27T18:17:38.000054Z

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

jaen 2016-01-27T18:17:45.000055Z

I'll see if wrapping with a record will work

jaen 2016-01-27T18:17:48.000056Z

And let you know

niwinz 2016-01-27T18:20:05.000057Z

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

niwinz 2016-01-27T18:20:25.000058Z

the current validation is implemented thinking in one unique error

niwinz 2016-01-27T18:20:53.000059Z

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

jaen 2016-01-27T18:21:57.000060Z

Hmm

jaen 2016-01-27T18:22:45.000061Z

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

jaen 2016-01-27T18:22:48.000062Z

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

jaen 2016-01-27T18:23:12.000063Z

Because it mappends both fails' content.

niwinz 2016-01-27T18:23:49.000064Z

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

niwinz 2016-01-27T18:24:16.000065Z

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

niwinz 2016-01-27T18:32:04.000066Z

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

niwinz 2016-01-27T18:33:00.000068Z

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

jaen 2016-01-27T18:36:58.000070Z

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!

niwinz 2016-01-27T18:42:56.000071Z

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

niwinz 2016-01-27T18:43:31.000072Z

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

niwinz 2016-01-27T18:44:04.000073Z

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

niwinz 2016-01-27T18:44:27.000074Z

now I'm go away for diner and rest!

niwinz 2016-01-27T18:44:30.000075Z

😛

jaen 2016-01-27T18:45:19.000076Z

Bon appetit then!

jaen 2016-01-27T18:45:27.000077Z

And thanks again for your input : )