简体   繁体   中英

Inferred conversion to applicative

I am using the hardware description tool Clash. Though this is a hardware description tool, my question is purely about Haskell.

There is a datatype of the form data Signal dom a =... This datatype has an Applicative instance as follows:

instance Applicative (Signal domain) where
  pure  = signal#
  (<*>) = appSignal#

I have defined some functions which can sensibly take either Signal dom (Foo ab) or Foo ab . That is, they can either take a value wrapped in an Applicative, or they can take the value and then call pure to wrap it themselves.

I see two (ugly) ways to implement such a function:

  1. Create two versions of the function, one taking the "naked" value and the other taking the Applicative. Inside the first function, I call pure then delegate to the second function. These functions look something like the following:

     f:: (Foo ab) -> Signal dom Baz -> Signal dom Bar f = f'. pure f':: Signal dom (Foo ab) -> Signal dom Baz -> Signal dom Bar f' =...
  2. Create only one version of the function, then expect the user to call pure as needed.

I would like to create a typeclass to solve this, wherein the instance for Foo ab is pure and the instance for Signal dom (Foo ab) is id . But I cannot see any way to define such a thing.

Is there a way to define a typeclass, or is there another solution I have overlooked?

Of course if they can take an applicative then they can take a pure value. That's why we have pure . It may not be the most "fluent" thing but putting pure at the call site does have its advantages -- namely that the reader of the code knows that this value doesn't vary, and also that it is permitted to.

In any case, if the argument types are some fixed data type like Foo then you could:

class IsSignal dom a where
    type Arg dom a :: *
    toSignal :: a -> Signal dom (Arg dom a)

instance IsSignal dom (Foo a b) where
    type Arg dom (Foo a b) = Foo a b
    toSignal = pure
-- repeat this instance for every concrete type you expect

instance IsSignal dom (Signal dom a) where
    type Arg dom (Signal dom a) = a
    toSignal = id

f :: (IsSignal dom s, Arg dom s ~ Foo a b) => s -> Signal dom Baz -> Signal dom b
f a b = -- ... toSignal a ...

Whether that's worth it is up to you.

And if you want it to work on polymorphic types like

g :: a -> Signal dom Baz -> Signal dom a

There is, as far as I know, no good solution. There are things involving overlapping instances that may appear to work for the simplest examples, but they break very easily.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM