Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

> very hard to understand why a function could be useful in the first place

So true.

https://hackage.haskell.org/package/base-4.9.1.0/docs/Contro...

> mfix :: (a -> m a) -> m a

> The fixed point of a monadic computation. mfix f executes the action f only once, with the eventual output fed back as the input. Hence f should not be strict, for then mfix f would diverge.

But why tho?



Ubiquitous single-letter symbols mapping to who-knows-what possible things, pnflly abvted fncn nms, and unclear motivations for code are what I've bounced off of with Haskell every time I've tried to dig in. The community seems to have adopted all the worst parts of mathematics culture, along with whichever good parts they've brought in.


Serious question. In the following code, what would be better function / type names? (or you can pick another bit of Haskell code that you know and/or have particular trouble understanding).

    class Foldable t where
        foldl :: (b -> a -> b) -> b -> t a -> b


Maybe something like:

  class Foldable myFoldable where
    foldl :: (summary -> element -> summary) 
          -> summary 
          -> myFoldable element 
          -> summary
This is the translation I do in my head when I read that type signature, at least.


Wow. That's much better.

Apparently-idiomatic Haskell reads (or, rather, doesn't) about like line-noise/code-golf style Perl to me.


The problem with this is that there are Foldables that do not behave in the way that is implied by the above re-write of the parameters.

I think this is kind of the problem when you are really high up in abstraction-land. The functions that you're using are so generic that it's hard to say that they operate on anything in particular, except that the arguments fulfill certain properties or laws.


I wonder if anyone has worked on a Prelude replacement which has these replacements (along with renaming functions where appropriate)...


To change the names you'd have to re-implement all the functions. Good luck with that, base is /large/.

This is a documentation issue, not an implementation issue.


Really? I find the single-letter version far easier to read.


The single letter version is far easier since I already know what foldable does. Not so much when I'm learning a new library.


I've always disliked mapM_. Something about it just looks ugly and it doesn't really hint what its for. I would like to see mapM as mapMonad and mapM_ as eachMonad. Of course, once I knew what it meant the abbreviations became rather nice.


Yeah, reading that shit drives me nuts.


Here's a practical application of mfix:

    mfix $ \threadId -> forkIO $ do
      -- computation in forked thread
forkIO creates a new thread and returns its thread id. However, in order to access the returned thread id inside the forked computation, normally one would have to store it in a variable and then read from it inside the thread.

mfix, by nature of its laziness, captures the return value of the function passed to it, and passes it to said function.


It's a very neat example, and I like it very much as an illustration of mfix, but wouldn't one just use Control.Concurrent.myThreadId for that? Seems a little overkill.


Ha, I guess this must be some kind of selective blindness then :) I've seen myThreadId many times, it makes perfect sense, and yet, whenever I've had to grab the spawned thread id I've always reached for mfix... Well, in this particular case there's of course no need for that.

But the general pattern applies to many things. One other example that immediately springs to mind is e.g. registering a callback while needing the ability to unregister the callback from within itself, using some sort of id returned from the registering function.


By analogy to fix:

fix f --> f (fix f)

That is, to compute the fixed point of some function f, we give f access to the fixed point (i.e. fix f) in order to compute the fixed point.

Consider f is strict, and that we try to run this:

    fix f --> f (fix f)
          --> f (f (fix f))
          --> f (f (f (...)))
Why? Because if f is strict it has to evaluate its argument before evaluating the body.

On the other hand, if f is non-strict, we can evaluate its body without evaluating its argument first.

mfix generalizes fix to monadic functions. When we have monadic functions, we're usually dealing with side-effectful computation, so we want to make sure that we only force the monadic action f once, so that the side effects only run once.

---

Another question to ask is "what is fix even useful for"? fix is how we introduce general recursion into a language. For example, the lambda calculus has fix, except it's called the Y combinator. Compare:

    Y f   --> f (Y f)
    fix f --> f (fix f)
The lambda calculus is Turing complete because it has this general recursion.

fix is often used in smaller toy languages to also make them Turing complete, because this form of recursion is straightforward.

While Haskell has recursive function definitions, fix (and mfix more generally) are sometimes useful in their own right.


I think you answered the wrong "why".


See: https://wiki.haskell.org/MonadFix

Basically it's the primitive that allows monadic computations to be written in the same lazy cyclic style as regular values in Haskell. (e.g. `ones = 1:ones` to create an infinite lazy list of 1.)

There isn't a single answer to "why?" any more than there is for monads in general, but as an example, I've been looking into using this abstraction to model circuit graphs.


Reflex is a frontend library for haskell.

    -- create an input text widget with auto clean on return and return an event firing on return
    -- containing the string before clean
    inputW :: MonadWidget t m => m (Event t T.Text)
    inputW = do
        rec
          -- fire send event when pressing enter
          let send = textInputGetEnter input

          -- textInput with content reset on send
          input <- textInput $ def & setValue .~ ("" <$ send)

         -- tag the send signal with the inputText value BEFORE resetting 
        return $ tag (current $ input ^. textInput_value) send


Ignoring the operator soup for a second (thanks lens) you might notice that the send event depends on the input field and the content of the input field depends on the send event. In, say, java you would first create the field and then mutate it separately. In haskell mutating wouldn't work so instead we usually tie the knot which means making the value dependent on itself and letting laziness figure it out.

However we are creating a text field here so if we make the value recursive on itself we are gonna be spawning text fields all over the place. MonadFix is variant of this that splices the effect out so it is only run once and then allows the value to be recursive.


It's useful if you want to e.g. traverse a custom tree structure with an effectful function. You can write the function that just does one level of the tree and pass it to mfix to get a version that does the whole tree recursively, composing the effects in the way that naturally makes sense for that kind of effect.


The standard version [ fix :: (a -> b) -> b ] is weird too - I'd suggest reading up on that first, there's a lot of tutorials. You can use fix to write recursive functions, so I'm guessing mfix is analogous, it just handles some >>= plumbing for you.


mfix is somewhat low-level. It’s used in the desugaring of Haskell’s “do rec” notation.

I find it useful because it lets me take a recursive structure[1] and a multi-pass algorithm[2] on that structure, which involves side effects[3], and express it in code as a single pass, without mutation.

This conversion of multi-pass algorithms to a single pass while retaining time/space complexity guarantees is one of the key benefits of laziness in general.

[1]: e.g., an AST

[2]: e.g., type inference, where pass 1 is generating type constraints, and pass 2 is solving the constraints and annotating the tree with the final inferred types

[3]: e.g., generating fresh type variables


I'd love to hear more detail about the example you give of type inference over an AST. I like the idea, but I'm having trouble envisioning how to write code in that style cleanly. Did you implement that in an open source project I could take a look at?


It’s not the prettiest code, but here:

https://github.com/evincarofautumn/kitten/blob/8a7e949f1af71...

The key line is:

    (term', t, tenvFinal) <- inferType dictionary tenvFinal' tenv term
In “inferType”, I just annotate each term with “Zonk.type_ tenvFinal type_” as I go, in the same pass as generating the type constraints. Since “tenvFinal” is the final type environment, and “zonking” substitutes all type variables with their solved types, everything gets lazily resolved when reading the annotated types later on, e.g., during analysis & codegen.


Thanks, that helped me understand better. That's really cool!




Consider applying for YC's Fall 2026 batch! Applications are open till July 27.

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

Search: