Hacker News new | ask | show | jobs
by stcredzero 2508 days ago
How do you deal with state in a language that would prefer to be stateless?

Encapsulation? No one gets direct access to the state. Instead, there are methods or functions for dealing with the state indirectly, crafted to protect those outside.

Answer: wrap the entire external universe and all its messy state up into an object, then pass that down a chain of functions which can return "universe, but with some bytes written to the network" (IO monad)

Sounds like "Outside the Asylum" from the Hitchhiker's Guide to the Galaxy universe. Basically, someone decided the world had gone mad, so he constructed an inside-out house to be an asylum for it.

http://outside-the-asylum.net/

"A monad is a type that wraps an object of another type. There is no direct way to get that ‘inside’ object. Instead you ask the monad to act on it for you." How is a Monad anything different than a particular kind of object wrapper?

https://www.infoq.com/presentations/functional-pros-cons/

5 comments

1) Adding getters and setters does not make a program stateless. Your race-conditions and side-effects just take more steps.

2) "A monad is a type that wraps an object of another type. There is no direct way to get that ‘inside’ object. Instead you ask the monad to act on it for you." Your objection to this quote is right, because the quote is wrong.

>How is a Monad anything different than a particular kind of object wrapper?

It _is_ a particular type of wrapper object. That's where the whole "in the category of endofunctors" comes in. An endofunctor being a functor from something to itself.

You have IEnumberable<SomeObject> and that lets you do SelectMany to flatmap some internal nested set of objects down to IEnumerable<SomeOtherObject>.

The shape of what you get back doesn't change. You get back an IEnumerable of something. That has a specific contract on which you can do specific operations regardless of the object it is wrapping.

The other piece of the puzzle is that it is monoidal. A monoid is just a collection of objects + a way of combining them + a 'no-op'. This is usually worded something like "a set of objects with an associative binary operator and an identity".

The classic definition "a monad is just a monoid in the category of endofunctors" is worth picking apart piece by piece. But it's also utterly useless because you have to spend quite awhile picking it apart and then looking at concretions of every one of the individual abstractions to understand what the hell each part individually looks like and then put it back together in your mind.

That definition is classically used as a joke because it's so terse you have no hope of understanding it without a lot of study, yet at the same time it's so precise it's all the information you need!

What exactly do you mean by stateless? Encapsulation in the OOP sense is not stateless, because two identical method-calls may not return the same value. Example: if you have an ArrayList, calling `size` at one point in time might have a different result than calling `size` now. That's why I say the list 'remembers' its 'state'.

An object wrapper has to have the object somewhere in memory, you just can't touch it. With a monad, the object might not even exist yet (example: Promise) or there might be more than one (example: Collections).

The person you're replying to is talking about handling mutable state in a "stateless" way. That's a big distinction.
The person you're replying to is talking about handling mutable state in a "stateless" way. That's a big distinction.

How so? Why is it such a big distinction? Why isn't that just encapsulation? "Handling mutable state in a 'stateless' way" is basically just Smalltalk style Object Oriented programming. (As opposed to Java or C++, which has some differences.)

The value of monads is that they fold side-effects into the return values.

  dirty languages: 

  input -> function_a -> output/input -> function_b -> output 
               ^                             ^
               |                             |
          side_effect_a                 side_effect_b
               |                             |
               v                             v
          lovecraftian_primordial_soup_of_global_state

  pure languages: 

  input -> function_a -> output/input -> function_b -> output 
               ^                             ^
               |                             |
          side_effect_a                 side_effect_b ------>
               |
               +-------------------------------------------->
If C++ were pure, the type-signatures would look like

  (output, side_effect_a) function_a(input);

  (output, side_effect_b) function_b(input);
The drawback is that the type-signature of function_b(function_a()) becomes complex. Now, function_b needs to accept and pass-on the upstream side-effects. To compose function_a and function_b, we need to convert the type-signature of function_b to

  (output, side_effect_b, side_effect_a) function_b(input, side_effect_a); 
Fortunately, ">>=" converts function_b under the hood. Which allows us to write

  function_a() >>= function_b() >>= function_c >>= function_d
and pretend that each ">>=" is just a ";" without wrestling with compound inputs and compound returns.
It is encapsulation with the mandatory law stating that an encapsulation of an encapsulation must be as deep as a single encapsulation.

Note that this informal statement doesn't necessarily mean you have to be encapsulating data. A behavior, a contract, an assertion, compositions of all of these etc can also be encapsulated.

Fun ideas (not necessarily true but fun to think about):

* Monads kinda convert nesting to concatenations.

* A monad is the leakiest of the abstractions. You are always a single level of indirection away from the deepest entity encapsulated within.

* What's common among brackets, curly braces and paranthesises(?) is them being monadic if you remove them from their final construction while keeping the commas or semicolons.

Very important note: You should have already stopped reading by now if you got angry to due these false analogies.

A few observations which might also be false.

We can have arbitrarily nested monads:

  monadicObj.bind((T value) => monad.wrap(monad.wrap(value)));
Remember, `bind` only unwraps one layer. Without it unwrapping one layer, programs would continue accumulating large stacks of monads in monads.

I would also point out that it only collapses abstractions of the same kind; Maybe's bind only unwraps one layer of Maybe's. If you have a Promise<Maybe<Foo>> where Foo contains potentially more monads as instance-variables, those don't all get collapsed.

I like the 'converting nesting to concatenating' observation.

Sometimes we do need parentheses though, because most languages are not associative 5 - 2 - 1 is not the same as 5 - (2 - 1). Basically minus does not form a monoid, so the parens matter.

Any references that explain the "converts nesting to concatenation" idea? I find it fascinating, in particular because I write a lot of code that works on deeply nested data structures -- structs of values (which can be structs) or lists of values. The distinction between struct, list and value and the need to treat them differently in code is interesting and annoying, and goes beyond merely working with functors and applicables. I don't understand lenses at all, but I understand monads.
Do ctrl-f for "Nested Operator Expressions" in this piece: https://martinfowler.com/articles/collection-pipeline/

Some other references helped me along the way:

* http://www.lihaoyi.com/post/WhatsFunctionalProgrammingAllAbo...

* http://learnyouahaskell.com/chapters

Reading a little more about it, I think the concatenation idea more properly fits with the join operator, which acts like a flatten function.
>A monad is a type that wraps an object of another type.

So, the Adapter Pattern, but for types?

Monadic is a type-class. Like how Equatable is a type-class. Adapters essentially add a specific type to the type-class the client is looking for.