简体   繁体   中英

Passing a data constructor instead of a Type constructor on a function

data GsdCommand =  CreateWorkspace { commandId :: CommandId , workspaceId ::WorkspaceId , workspaceName :: Text }
             | RenameWorkspace { commandId :: CommandId , workspaceId ::WorkspaceId , workspaceNewName :: Text }
             | SetGoal  { commandId :: CommandId ,
                          workspaceId ::WorkspaceId ,
                          goalId :: GoalId ,
                          goalDescription :: Text} deriving Show

I would like to handle a specific command into one function, eg in pseudo code :

{-# LANGUAGE DataKinds #-}
handle :: 'CreateWorkspace -> CommandDirective GsdState

I was thinking that I could lift value constructors into types, but I don't know how to do it actually...

Solution 1: Don't get fancy, use Haskell 98 compatible types

The first solution I recommend is using a separate record type. This doesn't require any fancy type system hackery and avoids the partial functions implicit in your record field names.

type CommandId = ()
type WorkspaceId = ()
type Text = ()

data GsdCreate = GsdCreate { gsdcCommandId     :: CommandId
                           , gsdcWorkspaceId   :: WorkspaceId
                           , gsdcWorkspaceName :: Text
                           }
        deriving (Show)

data GsdCommand =  CreateWorkspace GsdCreate
             | RenameWorkspace { commandId :: CommandId
                               , workspaceId ::WorkspaceId
                               , workspaceNewName :: Text }
             | SetGoal  { commandId :: CommandId ,
                          workspaceId ::WorkspaceId ,
                          goalDescription :: Text }
        deriving (Show)

type CommandDirective a = Maybe a
type GsdState = ()

handle :: GsdCreate -> CommandDirective GsdState
handle = undefined

Aside: I'm actually in favor of Haskell eliminating data as we know it and instead having:

  • Record for single constructor types with field names
  • data without field names but with multiple constructors.

Solution 2: Use GADTs and DataKinds

This solution is a bit overkill for your example, but perhaps your real problem has a need that isn't presented in the snippet. The idea is to create a new data type that defines tags for your constructors and to use those constructors as types for a GADT:

{-# LANGUAGE GADTs #-}
{-# LANGUAGE DataKinds #-}

type CommandId = ()
type WorkspaceId = ()
type Text = ()

data Ty = Create | Rename | Set

data GsdCommand a where
  CreateWorkspace :: CommandId -> WorkspaceId -> Text -> GsdCommand Create
  RenameWorkspace :: CommandId -> WorkspaceId -> Text -> GsdCommand Rename
  SetGoal         :: CommandId -> WorkspaceId -> Text -> GsdCommand Set

type CommandDirective a = Maybe a
type GsdState = ()

handle :: GsdCommand Create -> CommandDirective GsdState
handle = undefined

Maybe this approach is enough for what you want. Below is a variant of the Either type, but tagged with L (left) and R (right) annotations, and with projections l and r .

{-# LANGUAGE DataKinds #-}
{-# LANGUAGE GADTs #-}

module G where

data L
data R

data E d a b where
  L :: {l :: a} -> E L a b 
  R :: {r :: b} -> E R a b 

foo :: E L a b -> a
foo (L x) = x

Function foo and the projection l work only on left values, and r works only on right values.

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