Haskell匹配构造类似于F#类型测试模式?

Kei*_*son 10 f# haskell types casting pattern-matching

TL;博士

如果我理解正确,Haskell没有像F#那样的子类型.因此我希望它没有匹配的类型测试模式,比如F#.它是否有任何可用于元编程的类似结构?

案例分析

我有一个F#项目,我在其中解耦了通过消息传递机制进行通信的模块.最近我一直想知道这个代码会是什么样子,或者如果我将它移植到Haskell,它甚至可以用这种方式编写它.

基本的想法是这样的.消息是从消息接口继承的类型:

type Message = interface end
Run Code Online (Sandbox Code Playgroud)

处理程序是一个获取特定子类型的函数,Message并返回Message:

type Handle<'TMsg when 'TMsg :> Message> = 'TMsg -> Message
Run Code Online (Sandbox Code Playgroud)

Bus一种publish方法可以将消息分发到其内部通道.有一个Channel<'TMsg>每个消息类型,并且这些被加入时动态处理程序被登记.总线将所有消息发布到所有通道,如果类型错误,则通道只返回一个空序列:

type Channel<'TIn when 'TIn :> Message>(...) = ...
    interface Channel with
        member x.publish (message : Message) =
            match message with
            | :? 'TIn as msg ->
                Seq.map (fun handle -> handle msg) _handlers
                |> Seq.filter (fun msg -> msg <> noMessage)
            | _ -> Seq.empty
Run Code Online (Sandbox Code Playgroud)

最终我在这里做的是动态元编程,它允许我有强类型的消息,仍然通过中间的相同机器.我不像H#那样依赖Haskell,我无法弄清楚如何在Haskell中做到这一点.

我是对的Haskell没有match... with... :?... as...构造吗?它是否有类似的构造或其他方法来进行这种元编程,你可以解决这类问题吗?是否有某种盒子/拆箱机制?

Mar*_*ann 8

抱歉用Mu来回答你的问题,但我也不会像在F#那样做.

这个Message接口就是所谓的标记接口,虽然这是有争议的,但我认为它是任何支持注释的语言(.NET中的属性)的代码味道.

标记接口不执行任何操作,因此您可以在没有它的情况下实现相同的行为.

我不会在C#中设计类似的消息传递系统,因为标记接口没有添加任何内容,除了错误消息以某种方式"键入".

我不会在F#中设计类似的消息传递系统,因为在F#中有一个更好,更安全,强类型的替代方案:

type UserEvent =
    | UserCreated of UserCreated
    | EmailVerified of EmailVerified
    | EmailChanged of EmailChanged
Run Code Online (Sandbox Code Playgroud)

这清楚地说明了事件集是有限的,并且是众所周知的.有了它,您将获得编译时检查而不是运行时检查.

(有关完整示例,请参见此处.)

这样一个Discriminated Union很容易转换为Haskell:

data UserEvent = UserCreated UserCreatedData
               | EmailVerified EmailVerifiedData
               | EmailChanged EmailChangedData
Run Code Online (Sandbox Code Playgroud)

  • @KeithPinson你并没有误解我的建议,但是如果你想制作一组消息(例如处理各种有界的上下文),你可以为每个组定义一个有区别的联合. (2认同)

chi*_*chi 8

Haskell的设计使得类型可以在运行时完全擦除.也就是说,当一个实现T在内存中存储一个类型的值时,不需要用一些标签来标记它"这是类型T".

这给出了很好的参数保证,也被称为"自由定理".例如,具有多态类型的函数

f :: a -> a
Run Code Online (Sandbox Code Playgroud)

必须返回其参数或未能终止.也就是说,我们知道f=id(如果我们知道它终止).

具体地说,这意味着没有办法写出类似的东西

f :: a -> a
f x = if a == Int then x+1 else x
Run Code Online (Sandbox Code Playgroud)

如果有人想做这样的事情,可以通过Data.Typeable类手动添加类型标签:

{-# LANGUAGE ScopedTypeVariables, GADTs, TypeOperators #-}
import Data.Typeable

f :: forall a . Typeable a => a -> a
f x = case eqT :: Maybe (a :~: Int) of
      Just Refl -> x+1
      Nothing   -> x
Run Code Online (Sandbox Code Playgroud)

注意类型是如何更改的,现在有一个约束.它必须,因为函数的效果违反了无约束多态类型的自由定理.

因此,如果您确实需要执行运行时类型检查,请执行Typeable约束.请注意,这可能过于笼统.如果你知道你有少量的类型,可能你可以使用sum类型,并在构造函数上使用普通模式匹配来测试类型:

data T = TInt Int
       | TChar Char

f :: T -> T
f (TInt i) = TInt (i+1)
f (TChar c) = TChar c
Run Code Online (Sandbox Code Playgroud)