monads (again? ikr) - General

Welcome to the Functional Programming Zulip Chat Archive. You can join the chat here.

drew verlee

Do we say a function is monadic? Or do we say the context in which a function is _used_ was monadic?

The distinction seems fuzzy so let me give an example in clojure (because its the language i know)

;; i define my best interpretation of a monad. Conceptually a function that takes any number of functions and returns a function.
(defn monad
  [& fns]
  (apply comp (reverse fns)))

;; would we say this is monadic?
((monad inc dec) 0)
;; => 0

;; would we say this isn't monadic?
((monad inc split-with) 0)
;; error

If the answer is yes then no, then the context matters. If the answer is universally no, then the context doesn't, which implies there cant be an error. I believe the later is the case.

TheMatten

Definition of monads isn't really about composition - it's about what you do "after" you compose things
If we were to specialize formal definition of monad to what we're doing with them, it would look something like this in Haskell:

-- It's not about typeclasses, it's about tuple of operations that satisfy some laws, so let's define it as simple datatype instead
data Monad m = Monad (a -> m a) (m (m a) -> a)
-- This is really equivalent to `Monad` typeclass, it's just defined more "directly" and doesn't require single implementation per `m`

that is, Monad is some specific m, together with function that lifts pure value into context a -> m a and function that can "append contexts" m (m a) -> m a

You could make such tuple in any language that doesn't have problem with higher-kinded types (m takes type and returns type, it isn't inhabitated by itself), but it's hard to fit your definition monad into this specification - written in Haskell:

monad :: [a -> a] -> a -> a
monad = foldr (.) id

we could draw some connections to State:

newtype State s a = State (s -> (a, s))

where we can define

execState :: State s a -> s -> s
execState (State f) s = snd (f s)

and thus your monad looks like composition of State s a actions where we ignore as - it's just possible operation on one specific instance (Monad (State s))

TheMatten

Back to your original question, I would say function is monadic if it's return type is m a for some m, a where exists Monad m

TheMatten

Such function f :: x -> m a can be composed with others (e.g g :: a -> m b) using join :: m (m a) -> m a by doing join (fmap (fmap g) f) (I forgot to mention that Monad m requires Functor m too)

TheMatten

And that (>>=) thing is just more direct definition of that pattern which is equivalent in power to join + fmap

Joel McCracken

I am extemely confused by your explanation @TheMatten lol

drew verlee

Thanks for responding,

So i take it the answer was actually (at best) Yes followed by No based off the understanding that the context defines the meaning. In the later example, the context, which is just the set of integers, doesn't allow for an operation specific to strings (which additionally is over two arguments, not 1).

The mind F*$# being that haskell forces that context be established at compile-time and clojure allows for it to trickle to run time. This isn't the burden people assume due to the repl... but that's not the topic at hand.

Albert ten Napel

How I see it: a monad for M is a way to compose functions of type A -> M<B> (for some A and B).

If you have a partial function A -> Option<B> then you cannot compose it with another partial function B -> Option<C> using normal function composition. Luckily because you can define a monad for Option we can use that to compose them anyway (as TheMatten showed).

drew verlee

@Albert ten Napel and if your composition failed (you used the wrong types) then you didn't make a monad. Similarly, in the second example, i get an exception, and so that wasn't a monad. While understanding, not every working program is an example of a monad.

;;monads
((comp dec inc) 0)
;; => 0

((comp str inc) 0)
;; => "1"

((comp conj inc) 0)
;; => 1

((comp * inc) 0)
;; => 1

((comp map inc) 0)
;; => #function[clojure.core/map/fn--5862]

;; man its really hard to not write a monad i clojure...

Albert ten Napel

I would say that a type constructor M is monadic if you can define a monad for it, meaning that you can define an identity function A -> M<A> (for any A) and you can define a composition function (A -> M<B>, B -> M<C>) -> (A -> M<C>) (for any A, B, C). So as TheMatten showed a monad structure is a pair of two functions (identity and composition), but TheMatten used a different style of composition (which is equivalent). Given that definition I don't see how your monad definition actually implements a monad structure for some M.

Your monad function composes 0 or more functions and that may fail because Clojure is not statically typed (I think). I would not classify the result of your examples as being either a "monad" or not a "monad", but I would just say your examples fail or do not fail based on the specific functions your are trying to compose.

drew verlee

@Albert ten Napel

The category of integers and addition has an established identify function. (+ 0). My example stays within the same set (integers) and potentially doesn't have the wrapped context that gives the monad its meaning. Maybe this is closer:

(into []
  (comp
    (map inc)
    (map dec))
  [0])
;; => [0]

or if its necessary to change types (e.g B->C) then...

(into []
  (comp
    (map inc)
    (map str))
  [0])
;; => ["1"]
drew verlee

I would just say your examples fail or do not fail based on the specific functions your are trying to compose.

That't true. But that's also how i feel were defining a monad. If you give me:

(A -> M<B>, B -> M<D>) -> (A -> M<C>)

The compiler will reject it because of the specific Types you picked. But you wouldn't claim haskell couldn't express a monad, just that you had failed to do so in this case.

Albert ten Napel

with (A -> M<B>, B -> M<C>) -> (A -> M<C>) I meant that I do not pick specific A, B or Cs. In Haskell (+extensions) I would write
forall a b c. (a -> m b) -> (b -> m c) -> (a -> m c), meaning that it's up to you to pick whatever a, b, c you want. I think we may be misunderstanding each other :sweat_smile:

drew verlee

I feel i understand what your saying, I think the language is inverted between the communities so its hard to communicate because you have to establish meaning through smaller/broader categories than are precisely defined in either place.

Here is an example question, does a monad exist at "run time"?

Joel McCracken

Yes and no, it depends what you mean

Joel McCracken

Background: the notion of a monad is extremely abstract. In order to apply this idea to programming, we have to make certain choices in order how to apply these ideas.

For example, an integer is an abstract idea. We might talk about integers in terms of programming, but really these are not the same thing. For example, there are infinite integers. But as no computer has infinite memory, any integer type within a computer may not actually "reflect" the reality of integers. And I might say "there are infinite integers", but in such a statement I am assuming it is understood that I am not necessarily talking about this specific implementation choices in programs.

Why do I bring this up? Well, part of what makes monads confusing is the difference between:

  • abstract, category-theoretic idea of a monad (do not worry about this right now)
  • how monads are implemented in Haskell.
  • how monads may be implemented in other languages besides Haskell.
  • why these can be useful at times (for example, handling along multiple possible failures with the Maybe monad, or representing non-determinism with the List monad).
  • The different kinds of monads that exist, how these behave, and why it matters. (For example, the List monad behaves very differently from the Maybe monad).
  • annoyingly, "monads" usually get mixed up with the idea of pure functional programming and effect systems.

...etc. I am sure this list is not exhaustive, this is just off the top of my head. Since is hard to talk about things unambiguously with human languages, these ideas are hard to suss apart as a beginner. People also tend to refer to these ideas without explicitly denoting when a statement is being made in one context vs another context. in fact, you allude to this very thing when you said "its hard to communicate because you have to establish meaning through broader categories than are precisely defined in either place."

drew verlee said:

Here is an example question, does a monad exist at "run time"?

Before answering, we need an illustration.

In terms of programming and intuition, IMO the best way to think of a monad as as a "pattern" or an "interface" from OO. A type having an interface indicates that it supports the methods/operations/functions that interface requires.

So, lets give an example to help clarify -- imagine you have a ToJSONable interface with a method toJSON. If we then have a type User which we say implements the ToJSONable interface, we can be sure that this has toJSON function.

Ok; so lets apply this to your question. Would you say that ToJSONable exists at runtime? The data for the type User exists at runtime, and the compiled function toJSON exists at runtime, so many things about the interface exist at runtime. But as for ToJSONable existing? Hard to say, depends upon how you look at it. If you never actually call toJSON anywhere, would the compiler elide the compiled function? Does that somehow change the answer? When you ask if a monad exists at runtime, my brain thinks the same thing.

(This is all I have time to finish at the moment; I really need to finish my monad tutorial that goes into depth. I'll try to revisit/finish if this doesn't clarify)

drew verlee

Thanks for this,

Thinking out load, if i want to teach myself a concept like monads, it would be wise to raise the level of abstraction above the Haskell implementation.

I would tell myself:

this function is monadic

If my run time program contained an example of a monadic function (that wasn't removed by compiler optimizations) i would say i have

"a monadic runtime function".

It's not often i feel the need to explain this, so a long description is ok.

It might be possible to describe a monad using clojure spec, in which case you could say:

this spec is a monad

In which case you should probably give it the name monad.

I wonder if its easier to create monadic functions in clojure, but harder to know they are monads. As where in haskell its harder to create monadic functions, but easier to identify them.

This would seem to describe the common grumblings both communities have.

i can't get anything done!

i don't know what anything is!

Joel McCracken

If you want to understand monads, my strong, strong advice is to just learn haskell until you get to that point. There are many reasons for this

Joel McCracken

but largely they all come down to that there are a number of ideas that you should have a good understanding of before tackling this concept

Joel McCracken

but if your primary goal is to program better, I would say going to the "level above" haskell, which i believe you mean category theory, is going to be a long, long, long journey

Joel McCracken

(unless perhaps you have a very strong grounding in math in general, but not category theory, in which case it might be faster?)

Vladimir Ciobanu

I mostly agree with Joel. The main reason, IMO, is that you don't really need a monad as much until you're faced with pure FP. And for most people, it's hard to accept this level of abstraction without relatively clear use cases.

drew verlee

@Joel McCracken What your suggesting has value, and i have done a bit of it. But its also useful to translate to a language you know, so communicate between groups and see similarities.

@Vladimir Ciobanu I would argue I create monadic functions all the time in my work and find them very useful. That i previously didn't care to describe them as monadic didn't lessen there usefulness, but it might limit my ability to communicate intent with others that did understand that relationship.

I feel very enlightened by this conversation. I now feel I can correctly identify monads or the intent to want to create them. More generally though, i feel i understand more about the language mismatch that often causes confusion.

Joel McCracken

i mean, i am certailny not the smartest person in the world, but I tried really hard to make an IO monad implementation in Ruby without learning Haskell and I got so confused I just gave up and learned haskell

Joel McCracken

i'd just like to point out that translating an idea while you understand it is much easier than trying to translate an idea without understanding it. Like, imagine trying to translate don quixote while studying spanish. Gonna probably be harder than learning spanish and then translating, I would think.

Joel McCracken

but yes, bridging communities is a great goal!

drew verlee

@Joel McCracken

Based off reading: https://en.wikibooks.org/wiki/Haskell/Understanding_monads/IO, here is how i would describe them in clojure:

;; this function is IO monadic
(->> [1 2 3]
  (filter odd?)
  (run! println))
;; => nil
;; prints 1 3

;; this function is also IO monadic
(->> [1 2 3]
  (run! println))
;; => nil
;; prints 1 2 3

;; this function is not monadic
(->> [1 2 3]
  (filter odd?))
;; => (1 3)

;; this function is just monadic
(->> [1 2 3]
  (map odd?))
;; => (true false true)

It might help to peek at the definition for run!

= clojure.core/run!

 [proc coll]

Runs the supplied procedure (via reduce), for purposes of side
  effects, on successive items in the collection. Returns nil

I think its easy to capture these concepts because Clojure puts a high emphasis on composability around the collections.

i mean, i am certainly not the smartest person in the world

A healthy dose of constant failure has taught me I'm as smart as I'm ever going to be so i might as well just deal with it rather than hoping enlightenment will land on my head one day.

Joel McCracken

haha yeah, i am just tryign to say that you might succeed where I failed!

Kim-Ee Yeoh

drew verlee said:

A healthy dose of constant failure has taught me I'm as smart as I'm ever going to be so i might as well just deal with it rather than hoping enlightenment will land on my head one day.

Someone famous once said, "The man who never made a mistake never tried anything new, never tried to learn anything new."

Especially with the material on monads, one must be careful with what one takes as gospel. Firstly, it's hard to distinguish what's signal and what's bunk, and secondly, the time it takes to do so could extend beyond one's working life. Learning monads could well be on someone's bucket list that might NEVER be crossed out.

Unless.

Kim-Ee Yeoh

drew verlee said:

;; man its really hard to not write a monad i clojure...

Absolutely true (and I don't need to know much clojure to see the truth of the statement). If by a monad we mean a context, and since everything can be a context, then everything one writes in clojure is monadic.

In which case, what use is there of the word monadic?

Something similar threatens the word "pure." There are haskell proselytizers who insist that everything in Haskell is pure.

In which case, what use is there of the word "pure"?

Also, canonical works in the literature actually use the word "pure" on some code and "impure" on other code. Could those academics have been misled all along?

"Context" is a word that doesn't have a clear and precise meaning. To understand monads, maybe we should skip that word and look elsewhere?

drew verlee

maybe? I'm not sure what your suggesting. Anything I seem to come up with goes straight into philosophizing at this point.

Kim-Ee Yeoh

@drew verlee You're doing good figuring out wth are monads. I'm suggesting that you take a closer look at the meaning of words people use to distinguish from reliable sources vs not-so-reliable ones.

When evaluating a budget proposal, you might call out its bogosity if the numbers don't add up.

When confronted with a document with words but without numbers, you can do the same thing if the word meanings and the logical sequence don't add up either.

drew verlee

Refining my previous guesses, here is monads in clojure as i understand them now.

;; state monad
;; inc left dec right
;; Left:l
;; left value: lv
;; right: r
;; right value : rv
(letfn [(m [l] (fn [r] [l r]))
        (b [mv f] (fn [r] (let [[l rv] (mv r)] ((f l) rv))))
        ;; left right functions
        (lf [mv] (b mv (fn [l] (fn [r] [(inc l) r]))))
        (rf [mv] (b mv (fn [l] (fn [r] [l (dec r)]) )))
        ]
  (
   ;; left
   (-> (m 0) (lf) (rf) (lf) (rf))
   ;; right state
   2)
  )
;; => [2 0]
Albert ten Napel

Here's how I would write the State monad in Javascript:

const Return = val => state => [state, val];
const Bind = (prog, fn) => state => {
  const [newstate, result] = prog(state);
  return fn(result)(newstate)
};

const Get = state => [state, state];
const Put = val => state => [val, null];

const runState = (prog, state) => prog(state)[1];

const example =
  Bind(Get, x =>
  Bind(Put(x + 1), _ =>
  Bind(Get, y =>
  Return(y))));

const result = runState(example, 10); // 11