I am trying to translate this JS fixed-point operator into Haskell.
JS:
const fix = F => {
const D = X => F(
t => X(X)(t)
)
return D(D)
};
My attempt is (Haskell):
fix' f = d d
where
d x = f (\t -> x x t)
However, I get the following error:
Couldn't match expected type ‘(t2 -> t3) -> t4’
with actual type ‘p’
because type variables ‘t2’, ‘t3’, ‘t4’ would escape their scope
These (rigid, skolem) type variables are bound by the inferred type of d :: (t1 -> t2 -> t3) -> t4
Does someone know what's happening here ?
In the self-application
d d,dis both a function, of some typea -> r, and its argument, of typea. Thus the two types must be one and the same,a ~ (a -> r).Haskell wants to know its types in full upfront so it keeps substituting one for another ending up with an infinite type.
Infinite types aren't allowed in Haskell, but recursive types are allowed.
All we need to do here is to name that recursive type:
Now
T ris both a type of a function and its argument, for some result typer.Here
Tis a type constructor, andDits data constructor,D :: (T r -> r) -> T r.The above record syntax defines a new data type (here though with the keyword
newtype, notdata) and names its single field asapp. It also definesappas an accessor function,app :: T r -> (T r -> r). (It's kind of an inverse ofD, and oftentimes one sees such functions named with the prefix of "un", likeappcould have been namedunD. But hereappmakes sense, as we will see later.)For a value
xof typeT r,x :: T r, this means thatxis / matches up with / some valueD gwhere(g = app x) :: T r -> r, i.e.appsimply unwraps the data constructorDto get to the underlying value (a function)g:x = D g ; app x = app (D g) = g. That's how the record syntax works in Haskell.Now we can write
all work. The last one even has the same type as the built-in
fix.But Haskell doesn't only have recursive types. It also has recursion itself. An entity is allowed to refer to itself in its own definition.
Thus as the comments say we don't really need to emulate recursion by self-application of a value passed as an argument. We can just use recursively the function being defined, itself:
Or we can use a recursively defined value:
Regarding the error, the second type error you get,
seems more to-the-point / helpful / than the one you included.