Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Haskell match construct analogous to F# type-test pattern?

tl;dr

If I understand correctly, Haskell does not have subtypes in the way that F# does. Thus I expect that it does not have a type-test pattern for matching, like F#. Does it have any analogous constructs that can be used for metaprogramming?

Case Study

I have an F# project in which I have decoupled modules that talk through a messaging mechanism. Recently I have been wondering what this code would look like, or if it would even be possible to write it this way, if I were to port it to Haskell.

The basic idea is this. A message is a type that inherits from the message interface:

type Message = interface end

A handler is a function that takes a specific subtype of Message and returns a Message:

type Handle<'TMsg when 'TMsg :> Message> = 'TMsg -> Message

There is a Bus with a publish method that distributes a message to its internal channels. There is one Channel<'TMsg> per message type, and these are added dynamically when a handler is registered. The bus publishes all messages to all channels, and if it is of the wrong type, the channel simply returns an empty sequence:

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

Ultimately what I'm doing here is dynamic metaprogramming which allows me to have strongly typed messages that still get passed through the same machinery in the middle. I am not as up on Haskell as F#, and I can't figure out how to do this in Haskell.

Am I right that Haskell has no match... with... :?... as... construct? Does it have a similar construct or another approach to this sort of metaprogramming that you can solve this sort of problem with? Is there some sort of box/unbox mechanism?

like image 947
Keith Pinson Avatar asked Sep 02 '15 01:09

Keith Pinson


2 Answers

Haskell is designed so that types can be completely erased at runtime. That is, when an implementation stores a value of a type T in memory, there is no need to tag it with some label saying "this is of type T".

This gives nice parametricity guarantees in return, also referred to as "free theorems". For instance, a function having a polymorphic type

f :: a -> a

must return its argument or fail to terminate. That is, we know that f=id (if we know it terminates).

Concretely, this implies that there's no way to write something like

f :: a -> a
f x = if a == Int then x+1 else x

If one wishes to do such a thing, one can manually add type tags through the Data.Typeable class:

{-# 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

Note how the type changed, and now has a constraint. It has to, since the effect of the function violates the free theorem of the unconstrained polymorphic type.

So, if you really need to perform runtime type checks, carry around a Typeable constraint. Note that this is possibly too much general. If you know you have a small amount of types, probably you can use a sum type instead, and use plain pattern matching on constructors to test for the type:

data T = TInt Int
       | TChar Char

f :: T -> T
f (TInt i) = TInt (i+1)
f (TChar c) = TChar c
like image 102
chi Avatar answered Nov 02 '22 15:11

chi


Apologies for answering your question with Mu, but I wouldn't be doing it like that in F#, either.

That Message interface is what's known as a Marker Interface, and while this is contentious, I consider it a code smell in any language that supports annotations (attributes, in .NET).

A Marker Interface doesn't do anything, so you can achieve the same behaviour without it.

I wouldn't design a messaging system like that in C#, because the Marker Interface adds nothing, except the illusion that a message is somehow 'typed'.

I wouldn't design a messaging system like that in F#, because there's a better, safer, strongly typed alternative in F#:

type UserEvent =
    | UserCreated of UserCreated
    | EmailVerified of EmailVerified
    | EmailChanged of EmailChanged

This specifies clearly that the set of events is finite, and well-known. With that, you get compile-time checking instead of run-time checking.

(See here for the full example.)

Such a Discriminated Union is easy to translate to Haskell:

data UserEvent = UserCreated UserCreatedData
               | EmailVerified EmailVerifiedData
               | EmailChanged EmailChangedData
like image 8
Mark Seemann Avatar answered Nov 02 '22 15:11

Mark Seemann