如何在 Haskell 中创建函数的字符串表示形式。如何以一种奇特的方式打印一个函数?
How to create a string representation of a function in Haskell. How to print a function in a fancy way?
我的目标是能够将布尔表达式表示为字符串,例如 "True or False is True"
。为了让它成为可能,我首先做了一些布尔谓词:
and' :: Bool -> Bool -> Bool
and' p q = p && q
or' :: Bool -> Bool -> Bool
or' p q = p || q
-- ... same for nor, nand, xor, imply and equivalent
equ' :: Bool -> Bool -> Bool
equ' p q = p == q
在那之后,我决定制作一个将函数映射到字符串的函数。我依赖 Haskell 的模式匹配功能,但我的技巧没有奏效。
-- representation function, a.k. "show" for functions
repr :: (Bool -> Bool -> Bool) -> [Char]
repr and' = "and"
repr or' = "or"
repr nand' = "nand"
repr nor' = "nor"
repr xor' = "xor'"
repr impl' = "implies"
repr equ' = "equivalent to"
repr other = error "No representation for the given predicate"
GHC认为函数名是参数名,一般情况下只考虑第一个定义。对于剩余的行,GHC 会发出警告,指出“模式匹配是多余的”。这是 运行 repr
函数的示例:
*LogicH99> repr equ'
"and"
预计"equivalent to"
是否可以在 Haskell 中以奇特的方式打印函数?
对于一般的功能,不,没有。但是对于 Bool -> Bool -> Bool
类型的函数来说,可能性太小以至于通过做这样的事情来穷举所有输入是可行的:
repr f = case (f False False, f False True, f True False, f True True) of
(False, False, False, True) -> "and"
(False, True, True, True) -> "or"
-- ...
(True, False, False, True) -> "equivalent to"
_ -> error "No representation for the given predicate"
对于您的目标,可能还有另一种方法可以将布尔表达式更改为字符串。下面是一个例子:
toString :: Bool -> Bool -> Bool -> String -> String
toString p q r op = show p ++ op ++ show q ++ " is " ++ show r
and', or' :: Bool -> Bool -> (Bool, String)
and' p q = let r = p && q
in (r, toString p q r " and ")
or' p q = let r = p || q
in (r, toString p q r " or ")
所以如果要得到布尔结果,就得到结果元组的第一个元素,如果是字符串表达式,就得到第二个元素。
λ> snd $ and' True False
"True and False is False"
λ> fst $ and' True False
False
您只需测试 4 个输入,这样您就可以通过对输入进行详尽评估来定义模式,如前所述。然后,您可以定义一个仅匹配 (&&)
:
的模式同义词
{-# LANGUAGE PatternSynonyms, ViewPatterns #-}
isAnd :: (Bool -> Bool -> Bool) -> Bool
isAnd (·) = and
[ True · True == True
, True · False == False
, False · True == False
, False · False == False
]
-- case (&&) of
-- IsAnd -> "and"
pattern IsAnd :: Bool -> Bool -> Bool
pattern IsAnd <- (isAnd -> True)
where IsAnd = (&&)
describe :: (Bool -> Bool -> Bool) -> String
describe IsAnd = "and"
describe ..
这很有趣,但您应该只创建一个数据类型 data Op = And | ..
或通过其签名对其进行索引
type Op :: Type -> Type
data Op sig where
OpAnd :: Op (Bool -> Bool -> Bool)
OpNot :: Op (Bool -> Bool)
OpTrue :: Op Bool
OpFalse :: Op Bool
在这种情况下,常规的解决方案是引入数据类型(“初始”编码)或类型类(“最终”编码),然后您可以将函数和漂亮打印的形式定义为两种不同的解释。例如,对于普通数据类型,您可以只进行模式匹配:
data Exp
= Lit Bool
| And Exp Exp
| Or Exp Exp
| Not Exp
| Equ Exp Exp
deriving (Read, Show)
-- Roundtrip to a debugging representation string.
-- (Plus whatever other standard classes you need.)
-- Evaluate an expression to a Boolean.
eval :: Exp -> Bool
eval (Lit b) = b
eval (And e1 e2) = eval e1 && eval e2
eval (Or e1 e2) = eval e1 || eval e2
eval (Not e) = not (eval e)
eval (Equ e1 e2) = eval e1 == eval e2
-- Render an expression to a pretty-printed string.
render :: Exp -> String
render (Lit b) = show b
render (And e1 e2) = concat ["(", render e1, " and ", render e2, ")"]
render (Or e1 e2) = concat ["(", render e1, " or ", render e2, ")"]
render (Not e) = concat ["not ", render e]
render (Equ e1 e2) = concat ["(", render e1, " equivalent to ", render e2, ")"]
使用 GADT,您可以添加一些更具体的静态类型:
{-# Language GADTs #-}
data Exp t where
Lit :: Bool -> Exp Bool
And, Or, Equ :: Exp (Bool -> Bool -> Bool)
Not :: Exp (Bool -> Bool)
(:$) :: Exp (a -> b) -> Exp a -> Exp b
eval :: Exp t -> t
eval (Lit b) = b
eval And = (&&)
eval Or = (||)
eval Equ = (==)
eval Not = not
eval (f :$ x) = eval f $ eval x
render :: Exp t -> String
render (Lit b) = show b
render And = "and"
render Or = "or"
render Equ = "equivalent to"
render Not = "not"
render (f :$ x :$ y) = concat [render x, " ", render f, " ", render y]
render (f :$ x) = concat [render f, " ", render x]
或者最后使用类型类,结果类似:
-- The set of types that can be used as
-- /interpretations/ of expressions.
class Exp r where
lit' :: Bool -> r
and', or', equ' :: r -> r -> r
not' :: r -> r
-- Expressions can be interpreted by evaluation.
instance Exp Bool where
lit' = id
and' = (&&)
or' = (||)
equ' = (==)
not' = not
-- A pretty-printed string.
newtype Pretty = Pretty String
-- They can also be interpreted by pretty-printing.
instance Exp Pretty where
lit' b = Pretty $ show b
and' r1 r2 = Pretty $ concat ["(", r1, " and ", r2, ")"]
or' r1 r2 = Pretty $ concat ["(", r1, " or ", r2, ")"]
equ' r1 r2 = Pretty $ concat ["(", r1, " equivalent to ", r2, ")"]
not' r = Pretty $ concat ["not ", r]
这增加了您在这里可能不需要的灵活性和复杂性,但我提到它是因为这种设计模式可以用于解决更大的问题。 (有关更多信息,请参阅 Tagless-final Style。)
我想补充 Joseph 的回答。它执行相同的计算(即 运行 在最坏情况下所有可能的输入上的给定函数),但可能以更具可读性的方式。它使用 universe and containers 包。
import Data.Maybe
import Data.Universe.Instances.Reverse
import qualified Data.Map as M
repr :: (Bool -> Bool -> Bool) -> String
repr f = fromMaybe noName (M.lookup f names) where
noName = "No short name for " ++ show f
names = M.fromList
[ ((&&), "and")
, ((||), "or")
, ((==), "equivalent to")
]
这样做的好处是,您不必查看输出列表,也不必像 Joseph 的回答中必须做的那样对描述的函数进行逆向工程;相反,实际的 Haskell 函数(例如 (&&)
)和我们想要给它的名称(例如 "and"
)之间存在直接的视觉联系。这是在 ghci 中使用它的一个简单示例:
> repr (&&)
"and"
> repr (/=)
"No short name for [(False,[(False,False),(True,True)]),(True,[(False,True),(True,False)])]"
> repr (\x -> if x then id else not)
"equivalent to"
我的目标是能够将布尔表达式表示为字符串,例如 "True or False is True"
。为了让它成为可能,我首先做了一些布尔谓词:
and' :: Bool -> Bool -> Bool
and' p q = p && q
or' :: Bool -> Bool -> Bool
or' p q = p || q
-- ... same for nor, nand, xor, imply and equivalent
equ' :: Bool -> Bool -> Bool
equ' p q = p == q
在那之后,我决定制作一个将函数映射到字符串的函数。我依赖 Haskell 的模式匹配功能,但我的技巧没有奏效。
-- representation function, a.k. "show" for functions
repr :: (Bool -> Bool -> Bool) -> [Char]
repr and' = "and"
repr or' = "or"
repr nand' = "nand"
repr nor' = "nor"
repr xor' = "xor'"
repr impl' = "implies"
repr equ' = "equivalent to"
repr other = error "No representation for the given predicate"
GHC认为函数名是参数名,一般情况下只考虑第一个定义。对于剩余的行,GHC 会发出警告,指出“模式匹配是多余的”。这是 运行 repr
函数的示例:
*LogicH99> repr equ'
"and"
预计"equivalent to"
是否可以在 Haskell 中以奇特的方式打印函数?
对于一般的功能,不,没有。但是对于 Bool -> Bool -> Bool
类型的函数来说,可能性太小以至于通过做这样的事情来穷举所有输入是可行的:
repr f = case (f False False, f False True, f True False, f True True) of
(False, False, False, True) -> "and"
(False, True, True, True) -> "or"
-- ...
(True, False, False, True) -> "equivalent to"
_ -> error "No representation for the given predicate"
对于您的目标,可能还有另一种方法可以将布尔表达式更改为字符串。下面是一个例子:
toString :: Bool -> Bool -> Bool -> String -> String
toString p q r op = show p ++ op ++ show q ++ " is " ++ show r
and', or' :: Bool -> Bool -> (Bool, String)
and' p q = let r = p && q
in (r, toString p q r " and ")
or' p q = let r = p || q
in (r, toString p q r " or ")
所以如果要得到布尔结果,就得到结果元组的第一个元素,如果是字符串表达式,就得到第二个元素。
λ> snd $ and' True False
"True and False is False"
λ> fst $ and' True False
False
您只需测试 4 个输入,这样您就可以通过对输入进行详尽评估来定义模式,如前所述。然后,您可以定义一个仅匹配 (&&)
:
{-# LANGUAGE PatternSynonyms, ViewPatterns #-}
isAnd :: (Bool -> Bool -> Bool) -> Bool
isAnd (·) = and
[ True · True == True
, True · False == False
, False · True == False
, False · False == False
]
-- case (&&) of
-- IsAnd -> "and"
pattern IsAnd :: Bool -> Bool -> Bool
pattern IsAnd <- (isAnd -> True)
where IsAnd = (&&)
describe :: (Bool -> Bool -> Bool) -> String
describe IsAnd = "and"
describe ..
这很有趣,但您应该只创建一个数据类型 data Op = And | ..
或通过其签名对其进行索引
type Op :: Type -> Type
data Op sig where
OpAnd :: Op (Bool -> Bool -> Bool)
OpNot :: Op (Bool -> Bool)
OpTrue :: Op Bool
OpFalse :: Op Bool
在这种情况下,常规的解决方案是引入数据类型(“初始”编码)或类型类(“最终”编码),然后您可以将函数和漂亮打印的形式定义为两种不同的解释。例如,对于普通数据类型,您可以只进行模式匹配:
data Exp
= Lit Bool
| And Exp Exp
| Or Exp Exp
| Not Exp
| Equ Exp Exp
deriving (Read, Show)
-- Roundtrip to a debugging representation string.
-- (Plus whatever other standard classes you need.)
-- Evaluate an expression to a Boolean.
eval :: Exp -> Bool
eval (Lit b) = b
eval (And e1 e2) = eval e1 && eval e2
eval (Or e1 e2) = eval e1 || eval e2
eval (Not e) = not (eval e)
eval (Equ e1 e2) = eval e1 == eval e2
-- Render an expression to a pretty-printed string.
render :: Exp -> String
render (Lit b) = show b
render (And e1 e2) = concat ["(", render e1, " and ", render e2, ")"]
render (Or e1 e2) = concat ["(", render e1, " or ", render e2, ")"]
render (Not e) = concat ["not ", render e]
render (Equ e1 e2) = concat ["(", render e1, " equivalent to ", render e2, ")"]
使用 GADT,您可以添加一些更具体的静态类型:
{-# Language GADTs #-}
data Exp t where
Lit :: Bool -> Exp Bool
And, Or, Equ :: Exp (Bool -> Bool -> Bool)
Not :: Exp (Bool -> Bool)
(:$) :: Exp (a -> b) -> Exp a -> Exp b
eval :: Exp t -> t
eval (Lit b) = b
eval And = (&&)
eval Or = (||)
eval Equ = (==)
eval Not = not
eval (f :$ x) = eval f $ eval x
render :: Exp t -> String
render (Lit b) = show b
render And = "and"
render Or = "or"
render Equ = "equivalent to"
render Not = "not"
render (f :$ x :$ y) = concat [render x, " ", render f, " ", render y]
render (f :$ x) = concat [render f, " ", render x]
或者最后使用类型类,结果类似:
-- The set of types that can be used as
-- /interpretations/ of expressions.
class Exp r where
lit' :: Bool -> r
and', or', equ' :: r -> r -> r
not' :: r -> r
-- Expressions can be interpreted by evaluation.
instance Exp Bool where
lit' = id
and' = (&&)
or' = (||)
equ' = (==)
not' = not
-- A pretty-printed string.
newtype Pretty = Pretty String
-- They can also be interpreted by pretty-printing.
instance Exp Pretty where
lit' b = Pretty $ show b
and' r1 r2 = Pretty $ concat ["(", r1, " and ", r2, ")"]
or' r1 r2 = Pretty $ concat ["(", r1, " or ", r2, ")"]
equ' r1 r2 = Pretty $ concat ["(", r1, " equivalent to ", r2, ")"]
not' r = Pretty $ concat ["not ", r]
这增加了您在这里可能不需要的灵活性和复杂性,但我提到它是因为这种设计模式可以用于解决更大的问题。 (有关更多信息,请参阅 Tagless-final Style。)
我想补充 Joseph 的回答。它执行相同的计算(即 运行 在最坏情况下所有可能的输入上的给定函数),但可能以更具可读性的方式。它使用 universe and containers 包。
import Data.Maybe
import Data.Universe.Instances.Reverse
import qualified Data.Map as M
repr :: (Bool -> Bool -> Bool) -> String
repr f = fromMaybe noName (M.lookup f names) where
noName = "No short name for " ++ show f
names = M.fromList
[ ((&&), "and")
, ((||), "or")
, ((==), "equivalent to")
]
这样做的好处是,您不必查看输出列表,也不必像 Joseph 的回答中必须做的那样对描述的函数进行逆向工程;相反,实际的 Haskell 函数(例如 (&&)
)和我们想要给它的名称(例如 "and"
)之间存在直接的视觉联系。这是在 ghci 中使用它的一个简单示例:
> repr (&&)
"and"
> repr (/=)
"No short name for [(False,[(False,False),(True,True)]),(True,[(False,True),(True,False)])]"
> repr (\x -> if x then id else not)
"equivalent to"