What makes F# work so well for JS interop is the support for objects and OOP while still offering up all of the syntactic niceties of an ML language like pattern matching, monad-like computation expressions, etc.
Haskell wouldn't work in this context.
I've had a very easy time debugging Fable compiled projects.
Again, I have extensive node and JS experience which is absolutely required for working on a Fable project.
I would argue that the hardest parts of TypeScript aren't that much less of a leap than F#.
I would argue that this code is easier to debug than the equivalent .tsx:
[<ReactComponent>]
let Counter() =
let (count, setCount) = React.useState(0)
Html.div [
Html.button [
prop.onClick (fun _ -> setCount(count + 1))
prop.text "Increment"
]
Html.button [
prop.onClick (fun _ -> setCount(count - 1))
prop.text "Decrement"
]
Html.h1 count
]
And this basically looks like JS to begin with:
[<ReactComponent>]
let ShowGreeting(props: {| greeting : Greeting |}) =
React.fragment [
Html.h2 props.greeting.heading
Html.h4 props.greeting.subheading
Html.p props.greeting.content
]
app.get("/", fun req res next ->
promise {
let! response =
req
|> gql "query { greeting { heading subheading content } }" {||}
match response with
| Ok response ->
let greeting : Greeting = response?greeting
ShowGreeting ({| greeting = greeting |})
|> res.renderComponent
| Error message -> next()
}
)
Again, Result types! Computation expressions! Error handling with these constructs is well worth the price of admission.
Compare this to the try/catch approach you'd be using in TS:
https://fex-template.fly.dev/form-validation