【问题标题】:Writing a function (a -> b -> ... -> t) -> (Monad m => m a -> m b -> ... -> m t)编写一个函数 (a -> b -> ... -> t) -> (Monad m => m a -> m b -> ... -> m t)
【发布时间】:2012-04-07 09:00:53
【问题描述】:

有没有办法为任何n编写函数f :: (a -> b -> ... -> t) -> (Monad m => m a -> m b -> ... -> m t),基本上是liftMn

(编辑:固定无意义的例子。)

我正在编写一个 FRP 库,并认为如果我可以有类似这样的代码会很整洁:

main = do
  input1 <- signalFromTextBoxTheUserMayTypeANumberInto
  input2 <- signalFromAnotherTextBox
  divided <- signal div input1 input2
  signal (\x -> showTextAlert ("input1 `div` input2 is " ++ show x)) divided

我一直在摆弄类型族以使其正常工作,但我开始认为这实际上是不可行的。我目前正在做这样的事情:

type Action a = IORef a -> IO ()
type Listener = IO ()
newtype Signal a = Signal (IORef (SigData a))

data SigData a = SigData {
    trigger   :: Action a,
    output    :: IORef a,
    listeners :: [Listener]
  }

class Sig a where
  type S a
  mkSig :: [AnySignal] -> a -> S a

instance Sig b => Sig (a -> b) where
  type S (a -> b) = Signal a -> S b
  mkSig dependencies f =
    \s@(Signal sig) ->
      let x  = unsafePerformIO $ readIORef sig >>= readIORef . output
      in mkSig (AnySignal s : dependencies) (f x)

instance Sig Int where
  type S Int = IO (Signal Int)
  out <- newIORef x
  self <- Signal <$> (newIORef $ SigData {
      trigger   = \ref -> writeIORef ref $! x,
      output    = out,
      listeners = []
    })
  mapM_ (self `listensTo`) deps
  return self

这显然是行不通的,因为 unsafePerformIO 被评估一次,然后保持该值,如果确实有效,它仍然是丑陋的,hacky 并且通常是邪恶的。有没有办法做到这一点,还是我只能放弃这个想法?

【问题讨论】:

标签: haskell types monads type-families


【解决方案1】:

我对这一切有点陌生,如果这是一个愚蠢的答案,请原谅我,但这不正是应用函子的用途吗?

Applicatives 让您可以执行以下操作:

f :: a -> b -> ... -> c

f2 :: Applicative p => p a -> p b ... -> p c
f2 x ... y = f <$> x <*> ... <*> y

如果我没记错的话。 (省略号是任意数量的类型/参数)

【讨论】:

  • 确实如此。给出的示例是divided &lt;- div &lt;$&gt; input1 &lt;*&gt; input2。应用函子是 FRP 的理想选择; reactive-bananasodium 库都采用这种方法。
  • 是的,如果我不能让这种类型的诡计发挥作用,那就是备用计划。虽然它不那么漂亮。 :(
  • @valderman,类型诡计只会给你带来麻烦。它已经做过很多次了,除了最具体的情况外,所有情况都失败了(即它破坏了你的抽象能力)。这就是应用函子的用途;我推荐它们(一旦你对代数结构有了感觉,你可能会发现它比你建议的更漂亮)
【解决方案2】:

Strathclyde Haskell Environment 预处理器怎么样,它可以让你使用idiom brackets,应用函子的原始符号?这使您可以将(| f a b c |) 用于f &lt;$&gt; a &lt;*&gt; b &lt;*&gt; c。你的例子是(| input1 `div` input2 |)

顺便说一句,你的Signal 类型有一个Monad 实例可能是个坏主意;这导致了众所周知的(在 FRP 社区中)时间泄漏的问题;请参阅this blog post 了解更多信息。 Applicative 接口可以,但Monad 接口不行。有几种解决方案可以防止时间泄漏,同时仍然允许相同的动态事件切换行为,包括 an additional type parameter 或其他 monad(如 sodium 库中所示)。

【讨论】:

  • 这确实是对 处处 表示法的改进。另外,感谢有关 Monad 实例的建议!
猜你喜欢
  • 2018-01-12
  • 2022-10-13
  • 2015-01-31
  • 2014-03-23
  • 1970-01-01
  • 1970-01-01
  • 2021-10-15
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多