如何用类型类实现兼容裸值与Signal包裹值的Haskell函数?
解决Haskell中同时支持裸值与Signal包裹值的函数定义问题
可以通过自定义类型类实现你的需求,让函数自动处理裸值到Signal的转换,无需编写重载版本或要求用户手动调用pure。
步骤1:定义转换类型类
需要启用FlexibleInstances和MultiParamTypeClasses扩展(通用版本),定义一个用于将输入类型转换为目标Signal类型的类:
{-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE MultiParamTypeClasses #-} class ToSignal dom target a where toSignal :: a -> Signal dom target -- 裸值Foo实例:自动用pure包裹成Signal instance Applicative (Signal dom) => ToSignal dom (Foo a b) (Foo a b) where toSignal = pure -- 已包裹的Signal实例:直接返回自身 instance ToSignal dom (Foo a b) (Signal dom (Foo a b)) where toSignal = id
如果只想针对Foo类型做转换,也可以用更专一的类型类,减少扩展依赖:
{-# LANGUAGE FlexibleInstances #-} class ToFooSignal dom a where toFooSignal :: a -> Signal dom (Foo x y) instance Applicative (Signal dom) => ToFooSignal dom (Foo x y) where toFooSignal = pure instance ToFooSignal dom (Signal dom (Foo x y)) where toFooSignal = id
步骤2:编写统一的函数
基于上面的类型类,编写单一版本的函数,内部自动处理转换:
-- 基于通用ToSignal类的版本 f :: (ToSignal dom (Foo a b) input) => input -> Signal dom Baz -> Signal dom Bar f input bazSig = f' (toSignal input) bazSig where f' :: Signal dom (Foo a b) -> Signal dom Baz -> Signal dom Bar f' fooSig bazSig = ... -- 这里写原有的核心逻辑 -- 基于专一ToFooSignal类的版本 f :: (ToFooSignal dom input) => input -> Signal dom Baz -> Signal dom Bar f input bazSig = f' (toFooSignal input) bazSig where f' = ... -- 核心逻辑
调用方式
现在调用f时,无论是传入Foo a b裸值还是Signal dom (Foo a b),都不需要额外处理,类型推导会自动匹配对应的实例:
-- 传入裸值 myFoo :: Foo Int String myFoo = ... result1 = f myFoo bazSignal -- 传入已包裹的Signal myFooSig :: Signal dom (Foo Int String) myFooSig = ... result2 = f myFooSig bazSignal
替代方案
如果不想引入自定义类型类,也可以利用Applicative的特性,让用户通过pure手动转换,但这就是你提到的第二种不够优雅的方式。相比之下,类型类的方案更透明,对用户更友好。
内容的提问来源于stack exchange,提问作者Jonathan McDoe
相关产品推荐
相关产品推荐

