I find it easier to think of them as transforms which turn "a function which accepts/returns plain values" into a "function which accepts and returns a type which embeds plain values".
map :: (a -> b) -> (t a -> t b) -- Functor
bind :: (a -> t b) -> (t a -> t b) -- Monad
cobind :: (t a -> b) -> (t a -> t b) -- Comonad
ap :: t (a -> b) -> (t a -> t b) -- Applicative
The resulting function type is the same in all these cases - the difference is the type of the input function. There are other similar functions which return a `(t a -> t b)` too.
The "ordered computation" isn't unique to monads, it applies to all of these when you chain them. For monads particularly though, you can't begin the continuation until the previous result has been generated - whereas with a comonad, you can begin the continuation right away and extract the value afterwards (inside the continuation).
Can also think of it like a producer/consumer or push/pull thing. Monads push values into some type `t`, and comonads pull values out of `t`.
The naming of `return` is perhaps unfortunate as it gives the impression you're returning a value (done to make do-notation look like imperative code) - but monads don't return values. `return` produces a computation.
`coreturn` is usually called `extract`, which is better naming - it extracts a value from a computation.
The order of arguments is also unfortunate because it makes the above pattern less obvious - they're usually written as:
fmap :: f a -> (a -> b) -> f b
bind :: m a -> (a -> m b) -> m b
So it's not obvious that `fmap` is transforming a function `(a -> b)` into one `(f a -> f b)`, etc.
typedef struct {
double value;
int error;
} maybe_double;
Is most efficient on x86-64 SYSV, because the ABI supports the `error` being returned in a GP register, and the `value` in a vector register. The struct is returned in `rax:xmm0` - the double is where it is meant to be.
When you use a union containing an int and a double, the double has to be moved to a GP register, and back to the vector register when read as a double (via movq), so there's a small hidden cost compared to the former approach. Your `Result_double` is returned in `rax:rdx`.
There's a trick you can use to avoid this - keep the `double` in the vector register and move the `int` to the vector register instead - so you only pay the cost to move between GP and vector registers when there's an error but not otherwise. To do this, make the union use `__m64` instead of `int` - the whole union is then in a vector register, and the `Result_double` struct is returned in `rax:xmm0`, as with `maybe_double` struct.
Of course, this is x86-64 SYSV only. On AARCH64, the double will always be moved to a GP register in either example. Other conventions may also differ - MSVC in particular is poor because it converts to pass-by-pointer to a stack allocated structure for any struct greater than 64-bits.
On MSVC we could maybe get maximum efficiency by using NaN-boxing - store the error code in the signalling NaN space - eg, treat NaNs beginning 0x7FF1 as error codes.
`foo` is slightly more efficient than `bar` on 64-bit SYSV, whose ABI supports 128-bit ("two eightbytes") returns in hardware registers. `bar` requires at least one dereference to assign the result.
On other compilers this isn't the case. MSVC only supports 64-bit returns and would use the stack for `option_int_t`. The caller allocates the space and an implicit pointer is passed in the first argument register (eg, `rcx`), and the same pointer is returned as the result.
typedef struct {
int status;
float value;
} div_result_t;
div_result_t divide(float a, float b) {
if (b = 0.0) return (div_result_t){ E_DIVZERO };
return (div_result_t){ 0, a / b };
}
On SYSV: The `status` gets returned in x0/rax, and `value` gets returned in x1/xmm0.
On MSVC: The caller allocates `sizeof(div_result_t)` on the stack, passes the the pointer to that space as an implicit first argument to `divide` in register x0/rcx. The function returns the same pointer as its result in x0/rax.
The latter is almost equivalent to you passing a pointer manually - but the compiler does it for you. In the former case, it's actually more efficient to use the struct as we don't even need to touch the stack - all arguments and returns are in registers (for structs up to two "eightbytes" - ie, 16-bytes).
IMO, if it intended to use automatic storage duration, and the size of the struct is small enough, returning by value is preferable - it performs better on SYSV, and basically automates what you would do manually on MSVC. If it is intended for manual storage duration, then yes, use pass and return by pointer explicitly.
> MLstruct supports union, intersection, and complement (or negation) connectives. We make sure they form a Boolean algebra and add enough structure to derive a sound and complete type inference algorithm. For more details, please see the paper mentioned above.
This is cool. Unfortunately it's also too late now. The machines have taken over programming.
Partial register stalls occurred when you assigned something to say `eax`, then read `ax`, or vice versa - if you wrote `ax` then read `eax`. If you wrote to `ax` then read `ax`, there was no stall. This was due to the register renaming implementation.
AFAIK, this was only an issue in some older CPUs and isn't a problem today.
32-bit `int` is still cheaper than 16-bits though, because 16-bit instructions require an operand size override prefix (0x66), or address size override prefix (0x67), or both. Technically, these aren't "16 bit prefixes" - if the machine was running in 16-bit protected mode, then you would need those prefixes to use 32-bit instructions and the non-prefixed ones would be 16-bit and thus cheaper, so `int` would be better as 16-bits in 16-bit protected mode - though this mode is essentially unused today, so for all intents and purposes the prefixes are used to issue 16-bit instructions and 16-bits is more expensive (in code size, i-cache usage, which may impact performance).
There's a simpler way to define these types in newer versions of C which have `typeof`, we can take advantage of the fact that `sizeof()` always returns a `size_t`, `ptr - ptr` always returns a `ptrdiff_t`, and a literal L'c' has type `wchar_t`, etc.
Maybe you are talking past grandparent, but I think they meant "it's safe to assume float and double are IEEE-754," which is not mandated by the C standard.
The "ordered computation" isn't unique to monads, it applies to all of these when you chain them. For monads particularly though, you can't begin the continuation until the previous result has been generated - whereas with a comonad, you can begin the continuation right away and extract the value afterwards (inside the continuation).
Can also think of it like a producer/consumer or push/pull thing. Monads push values into some type `t`, and comonads pull values out of `t`.
The naming of `return` is perhaps unfortunate as it gives the impression you're returning a value (done to make do-notation look like imperative code) - but monads don't return values. `return` produces a computation.
`coreturn` is usually called `extract`, which is better naming - it extracts a value from a computation.
The order of arguments is also unfortunate because it makes the above pattern less obvious - they're usually written as:
So it's not obvious that `fmap` is transforming a function `(a -> b)` into one `(f a -> f b)`, etc.reply