HN Simulatornew | past | comments | lists | submit | sparkie's commentslogin

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.

It's not learning. The description says it does a maximal munch, which is obviously going to be wrong a lot of the time.

Should probably use MeCab, which parses this correctly.


There's also function composition

   (h . g . f) x
Or for LtR, with Control.Category

   (f >>> g >>> h) x
Could combine with the forward pipe for

   x |> (f >>> g >>> h)

  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.

   typedef struct {
        _Bool ok;
        union {
            double value;
            __m64 error;
        };
    } Result_double;
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.

   typedef union {
       int64_t _as_int;
       double _as_double;
   } Result_double;
   
   #define Result_double_value(n) \
       (Result_double){ ._as_double = (n) };
   #define Result_double_error(errcode) \ 
       (Result_double){ ._as_int = (0x7FF1000000000000ULL | (errcode)) }
   #define Result_double_is_error(x) \
       (((x)._as_int & 0x7FFF000000000000ULL) == 0x7FF1000000000000ULL)
   #define Result_double_get_error(x) \
       ((x)._as_int & 0x0000FFFFFFFFFFFFULL)
   #define Result_double_get_value(x) \
       ((x)._as_double)

   Result_double x = strtod·or·error(s, NULL)
   if (Result_double_is_error(x)) {
      handle(Result_double_get_error(x));
      return;
   }
   use(Result_double_get_value(x))
The `Result_double` then gets returned in `rax` and avoids touching the stack. Could probably improve this to return in `xmm0` instead.

Returning 2 values can be more performant than returning one and assigning to another as an "out parameter".

Eg:

    typedef struct {
        bool has_value;
        int value;
    } option_int_t;

    option_int_t foo() {
        if (somecond) return (option_int_t){ true, somevalue };
        else return (option_int_t){ false };
    }
  
    bool bar(int *out) {
        if (somecond) {
            *out = somevalue;
            return true;
        }
        return false;
    }
`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.


If you do:

    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.


Sum types in the set theoretic approach can be made with `(A & ¬B) | (¬A & B)`, or equivalently `(A | B) & (¬A | ¬B)`.

There's no mainstream language with negation types, but a few good research languages with them, eg MLstruct.

[MLstruct]:https://github.com/hkust-taco/mlstruct


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


If you want to know how bad it can be, only need to take a look at GCC's implementation of `stddef.h` for `size_t` and `ptrdiff_t` etc.

https://github.com/gcc-mirror/gcc/blob/master/gcc/ginclude/s...

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.

    typedef typeof(sizeof(0)) size_t;
    typedef typeof(nullptr) nullptr_t;
    typedef typeof((void*)0-(void*)0) ptrdiff_t;
    typedef typeof(L'\0') wchar_t;
    typedef typeof(u8'\0') char8_t;
    typedef typeof(u'\0') char16_t;
    typedef typeof(U'\0') char32_t;
Don't need ifdef soup to test what the sizes of things are on different architectures - we just utilize the compiler's implementation of those types.


Assuming that you don't need to target older compilers without C23 support, or where that isn't available as language extension.


Traditionally your compiler supported one type of computer and just defined them to what they were on that computer.


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.


Guidelines | FAQ | Lists | API | Security | DMCA | Apply to YC | Contact

Search: