4 ms·
You should probably mention in the introduction what the state monad is to those who don't use F#. At first blush it just seems redundant—what is a monad if not
by darby_nine 2y ago
You should probably mention in the introduction what the state monad is to those who don't use F#. At first blush it just seems redundant—what is a monad if not a representation of state?
- whateveracct 2y agomonads in no way are a representation of state tho
- darby_nine 2y agoHow do you figure? They're an abstract concept commonly used to represent values over a sequence of operations. How does that not describe state? Do you mean something very specific with the term "state" that might make it more meaningful?
- Izkata 2y agoThey are an abstraction of a sequence of operations, not the state used in those operations.
- darby_nine 2y agoThey're both, right? It's not like it specifically ignores the inputs and outputs of those operations. These are specifically entailed in the definition of the monad. I strongly, strongly suspect there's something about how F# processes monads in the context of a do expression that explains what is the focus here. Unfortunately I don't have the time to go through the steps now, hence why I specifically recommended modifying the introduction to give some clue about the terms involved.
- Izkata 2y agoAn "if" statement or "map" function also has inputs and outputs and probably some internal state due to implementation details, but we don't say they represent state either. They represent operations, or a sequence of operations.
- darby_nine 2y ago> An "if" statement or "map" function also has inputs and outputs and probably some internal state due to implementation details, but we don't say they represent state either. They absolutely are in the context of a program and not just abstract expression, which never matters. The distinction strikes me as meaningless. At the very least it's worth qualifying with "mutable state" if that's the attribute of state you care about. There's still "constant expressions" as other forms of values and "statically initialized values" as other forms of state.
- wavemode 2y agoI think "context" is a bit more all-encompassing. If I have an Int, then I just have an Int. If I have an Identity Int, then I really still just have an Int. If I have a [Int] then I have a list of Ints. If I have a Maybe Int, then I either have an Int or I have Nothing. If I have a Reader r Int, then I have a computation taking some input of type r and producing an Int. That computation can't modify the value of r. If I have a State s Int, I have a computation taking some initial state of type s and producing an Int. The value of s may change during the computation. All of these are monads, but calling all of them "state" is somewhat reductive. The Haskell wiki tutorial on monads refers to them vaguely as "strategies" - I mostly agree with that description.
- seanhunter 2y agoMonads can encode state but they don't have to. I tend to think of a monad in computer science as something which is constructed from a type and a computation and wraps the return values of that computation in that type. That allows you to bake in "context" into that type which could be state in the conventional sense but it could be something else. To make this practical, let's make a simple example of a simple somewhat useful monad with no state. Imagine you have all the regular trigonometric functions sine, cosine etc which accept radians[1] and you need versions which work in degrees. You could make a unit conversion monad which converts arguments on the way in and wraps the result in an inverse conversion monad such that if you passed it to arcsine arccosine etc on it it would know and return degrees on the way out. Neither of those monads would have any state in the usual computer science sense, the main conversion is just multiplying all the inputs by pi/180 before calling the wrapped function and constructing a result monad type so the inverse trig functions do the opposite of that. [1] ie the correct versions. Fight me physicists and engineers and you freaks who use gradians whoever you are[2]. [2] I think it's surveyors but I could be wrong.
- cryptonector 2y agoMonads can be used to attach all sorts of things besides state to data. For example, you can associate read-only data, such as configuration, authenticated identies, etc. You can associate I/O methods, which can be real or mocked. You can have a place in which to scribble logs/traces (this feels like state). And yes, you can associate mutable state, of course. The point of monads, from the point of view of programmer convenience, is that they let you have a bag of contextual stuff along with your values. Thus if you think of a server application that processes requests, you might want to carry all of: - configuration applicable to this request (or global configuration) - request metadata (time received, from where, from whom, authenticated how, etc.) - request processing state - logs/traces - I/O methods, so that - you can make all your code deterministic and thus easier to write tests for - so you can mock the world (see previous item) with each request. Thus each function that handles a request can get all of that context, and can be fully deterministic, yet it can function in a non-deterministic world. Besides this there's everything about the Maybe ("Optional") and Error/Either monads that just makes it easy to make sure all errors and "nulls" are handled. And what makes all of this possible (in Haskell) is the use of operators (functions) and syntax that lets one write "statements" that are actually combined into large expressions.
- 12_throw_away 2y agoyou might want to carry all of: - configuration applicable to this request (or global configuration) [...] Ok, this is super-interesting to me, as I'm currently dealing with the "configuration and interfaces as global state" pattern in a legacy application. Among other things, this makes it hell to test and refactor. Refactoring it into a functional abstraction like this seems like exactly what we need. But ... I'm kind of struggling to figure out what "global configuration as a monad" would look like (very much not a Haskell programmer). How would something like this work in practice?
- cryptonector 2y agoIn Java or C++ or other OOPish languages, say, you might make all your classes be Configurable (unless they are Configuration), which means their constructors would take a Configuration in some way, possibly with an explicit Configuration argument or with a Configurable argument whose configuration to copy. This way all your objects will know how to find configuration information.
- RandomThoughts3 2y agoIt's not about monads as an abstract concept. It's specifically about the State monad. The key idea is that you can have something ressembling states with pure functions if you pass the a representation of the initial state as an argument and have the modified state be part of what the function return. You can then use this modified state as an argument for the next function call. The State monad is just an handy way to do this chaining. But, yes, the actual state representation is in the way the argument is encoded, that's true. Edit: I find it hilarious that I’m the one downvoted here while all the comments saying complete non sense about monads - including the ones clearly having no clue of what a monad actually is - are not. Never change HN.
- trealira 2y agoYou can also use it for representing errors, for representing global read-only variables, for representing global write-only variables, for representing non-determinism, for representing continuations, and other things (since you can write your own). For example, this is a really minimalistic arithmetic syntax tree interpreter in Haskell. You could write errors using the Either type: data Term = Constant Int | Divide Term Term eval :: Term -> Either String Int eval (Constant n) = n eval (Divide left right) = case eval left of Left errorString -> Left errorString Right leftResult -> case eval right of Left errorString -> Left errorString Right rightResult -> if rightResult == 0 then Left "Division by zero" else Right (div leftResult rightResult) It's cumbersome, and there's a staircase growing. Because Either is a monad, you could rewrite the division op like this: eval (Divide left right) = do leftResult <- eval left rightResult <- eval right if rightResult == 0 then Left "Division by zero" else Right (div leftResult rightResult) This is a good article about the either monad: https://mmhaskell.com/blog/2022/3/3/using-either-as-a-monad https://mmhaskell.com/blog/2022/3/3/using-either-as-a-monad The state monad is basically a wrapper over a pure function that takes a state and returns a tuple with the result and a new state. In an imperative language, you'd just actually change state, rather than doing that. An example from this wiki page: https://en.wikibooks.org/wiki/Haskell/Understanding_monads/State#Pseudo-Random_Numbers https://en.wikibooks.org/wiki/Haskell/Understanding_monads/S... In an imperative language, for pseudo-random numbers, you could modify global variables every time you want a new random number. Haskell lets you do this in IO functions. But a pure way would be to return the result of the new global variable every time. -- randomR takes a range, so randomR (1,6) produces a random number between 1 and 6 rollPair :: StdGen -> ((Int, Int), StdGen) rollPair s0 = let (r1, s1) = randomR (1,6) s0 (r2, s2) = randomR (1,6) s1 in ((r1, r2), s2) It's annoying. You could use the state monad to simplify this: rollDieS :: State StdGen Int rollDieS = state (randomR (1,6)) rollPairS :: State StdGen (Int, Int) rollPairS = do r1 <- rollDieS r2 <- rollDieS return (r1, r2)
- darby_nine 2y ago> The state monad is basically a wrapper over a pure function that takes a state and returns a tuple with the result and a new state. So... it's a monad? I don't understand why this is worth calling State as distinct from non-State when that doesn't seem to add any meaning. Surely there must be something implied by a State monad that is not implied by a non-State monad. That seems relevant to call out in an introduction.
- marcosdumay 2y agoSome uses you'll find in Haskell: - representation of "the real world" where you can send data, control things, etc; - representation of early return error semantics, like you have with exceptions; - encapsulation of different execution threads, so you only interface with the group; - encapsulation of a computer architecture, so something compiled for your GPU runs on the GPU, and something compiled for your CPU run on your CPU; - encapsulation of transactions, so you can have a multi-threaded software changing whatever shared value you want and none of that leaks to the larger software; - data structure, like libraries that encode hardware description into them. That list is not in any way comprehensive. It's just stuff that I can remember right now.