2

Anyone knows how I could deal with errors like `Could not deduce (Reflex t0) arising from a use of ‘never’?

I am refactoring a portion of an app, which means a few never events don't get used, but since they are never to begin with, I don't care.

Can I make GHC not care somehow as well?

I know about -XExtendedDefaultRules which likely at least helps to specialize the type from forall t a. Event t a to forall t. Event t ()

And I want to specialize t as well to whatever value GHC would accept, since it results in dead code anyway.

Is there something I can write in the default (...) statement that could work? Or is that similarly impossible to writing default (IO) to specialize non-fully-specified monads to IO?

Edit: On #reflex-frp @dalaing asked for a code example, and this is what I put together for him: https://gist.github.com/Wizek/d14aada2d75637cb4f424f8a324c7ad7

Section 1 and 2 compile, 3 doesn't. But I want to make 3 compile as well since the compiler is complaining about ambiguity in something that can only be dead code.

Wizek
  • 4,854
  • 2
  • 25
  • 52
  • 2
    GHC shouldn’t care/complain about `t` being unspecified unless it’s actually relevant for selecting a typeclass instance. Without seeing more of the code, it’s hard to know why a typeclass instance is being selected using that type. – Alexis King Feb 13 '19 at 06:47
  • @AlexisKing, I've added a code example. Is it easier to see what I meant with that? In the meantime I've also found an answer to my question with the help of dalaing. But perhaps you know another solution to this situation? I'm still open to other options, especially if they work more generally in non-reflex Haskell code too. (See: "I just still wish GHC could be asked to be more lenient in type checking dead code in general, not just when it comes to reflex, but perhaps that is impossible for now.") – Wizek Feb 13 '19 at 08:06

2 Answers2

3

Together with @dalaing on #reflex-frp we found that never :: Event t () works if -XScopedTypeVariables is enabled and the parent widget has a forall t. Reflex t => constraint or similar.

For example, section 3 in the linked code example could be modified as such:

{-# language ScopedTypeVariables #-}

main = run 3000 $ mainWidget foo

foo :: forall t m. MonadWidget t m => m ()
foo = do
  let buttonEv = never :: Event t ()
  buttonEv <- button "click me"
  clicksCountDy <- count buttonEv
  display clicksCountDy

Which compiles. But it is inconvenient to have to specify the event type everywhere, and it may also not be as DRY as we want, so -XPartialTypeSignatures could help via never :: Event t _

Or even better, I find, we can do never @t with -XTypeApplications:

{-# language ScopedTypeVariables #-}
{-# language TypeApplications #-}

main = run 3000 $ mainWidget foo

foo :: forall t m. MonadWidget t m => m ()
foo = do
  let buttonEv = never @t
  buttonEv <- button "click me"
  clicksCountDy <- count buttonEv
  display clicksCountDy

So from this point onwards, I might just make it a policy that in my reflex related code portions I never write never, and always write (never @t), which solves this problem perfectly for the most part.

I just still wish GHC could be asked to be more lenient in type checking dead code in general, not just when it comes to reflex, but perhaps that is impossible for now.

Wizek
  • 4,854
  • 2
  • 25
  • 52
0

Using

foo :: forall t m. MonadWidget t m => m ()
foo = do
  let buttonEv = never @t
  buttonEv <- button "click me"
  clicksCountDy <- count buttonEv
  display clicksCountDy

It's really not a problem with never here. Its a problem of not binding the action to the current monad. The line in question could have been anything:

let buttonEv = button "never click me"

This would create a button widget action, but never "connect" it to the current widget. You wouldn't see the "never click me" button in your app.

On the other hand, if you connected the never event to your widget,

buttonEv <- return never

you would no longer need the @t annotation.

Ultimately though, I find that the -- annotation works well in instances of dead code.

-- let buttonEv = never

Keep the code around if you must, but this is the best way to tell the compiler that the code is unimportant.

trevor cook
  • 1,531
  • 8
  • 22
  • There are a few reasons why the `--` suggestion wouldn't work unfortunately. (1) `let buttonEv = never` was used as a simple example. In reality, there exist lines like: `fooEv <- phi $ (id &&& pure never) <$> (fmap snd <$> barWidget baz)` and like `let qux = fmap ((,,) <$> view _1 <*> view _2 <*> pure never)`. Even if I could use block comments, the expected underlying structure is a 2-tuple and a 3-tuple respectively. Not so simple to just comment out. – Wizek Feb 14 '19 at 06:20
  • (2) Like I mentioned, this question is mainly for refactoring. It is quite frustrating to be yelled at by the compiler when I am moving around code that handles events that some `never` events aren't used. It's useless noise that I have to weed through to hunt for the relevant type error information. (3) Some, if not all, of those `never`s might get reconnected as the refactoring eventually concludes, making it to be a useless exercise to having to comment them out just to do the reverse later on. Especially when small changes can mean having to comment out dozens of them. – Wizek Feb 14 '19 at 06:20
  • (4) I find it to be bad form from a compiler in general to complain about code that can never be reached. Makes refactoring that much more noisy, and I have to put in considerable amount more work even just to find out whether the main reason for the refactor even works. I see value in the compiler being able to say these, but I also want to be able to turn off type errors in dead code. That could solve a class of refactoring-related annoyances even beyond reflex-frp. – Wizek Feb 14 '19 at 06:21
  • @wizek. I made an edit. Binding a pure event instead of the "let" should let the "Reflex t" be inherited from your widget. – trevor cook Feb 15 '19 at 12:43
  • 1
    As for the commenting, you'll have to do what makes the most sense for you, but I believe that it is the best solution. If you decide that you need the commented code then you'll have to refactor, so no additional work over refactoring initially would be done. OTOH, refactoring something which proves unnecessary is additional work, and there is a possibility of introducing a bug which does something when you thought it would do nothing. Anyway, its my advice to you, you don't need to take it. You've done enough by even considering it. Thanks. – trevor cook Feb 15 '19 at 12:43
  • And thank you for offering your suggestions for consideration. Your other one could work, with a bit of tweaking indeed. In its current form, I think the types don't unify, as I would guess `return never` is `:: (MonadWidget t1 m, Reflex t1, Reflex t2) => m (Event t2 a)`, and we want `return never :: (MonadWidget t1 m, Reflex t1) => m (Event t1 a)`. Which could likely be achieved with an explicit definition like `boundNever :: (MonadWidget t m) => m (Event t a); boundNever = pure never`. I have considered this briefly prior, but I see `never @t` to be mostly superior for its purity. – Wizek Feb 15 '19 at 18:48