当 "factoring" 一个 Haskell 函数变成两个具有各种中介类型的函数时,如何避免重复?
How to avoid repetition when "factoring" a Haskell function into two functions with various intermediary types?
考虑以下 Haskell 代码:
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE TemplateHaskell #-}
import Data.Singletons.TH
singletons [d| data SimpleType = Aaa | Bbb | Ccc | Ddd
deriving (Read)
|]
-- each SimpleType value has an associated type
type family Parsed (t :: SimpleType) :: * where
Parsed Aaa = [Int]
Parsed Bbb = Maybe Int
Parsed Ccc = (Int, Int)
Parsed Ddd = Int
forth :: SSimpleType t -> Int -> Parsed t
forth SAaa x = [x,x*2,x*3]
forth SBbb x = Just x
forth SCcc x = (1337, x)
forth SDdd x = x
back :: SSimpleType t -> Parsed t -> Int
back SAaa [_, y, _] = y + 5
back SBbb (Just y) = y - 7
back SCcc (y1, y2) = y1 + y2
back SDdd y = y * 2
helper b = back b . forth b
go :: SimpleType -> Int -> Int
go Aaa = helper SAaa
go Bbb = helper SBbb
go Ccc = helper SCcc
go Ddd = helper SDdd
main = do
-- SimpleType value comes at run-time
val <- readLn
putStrLn $ show $ go val 100
是否可以在定义go
时避免重复?换句话说,有没有办法写这样的东西:
go val = helper (someMagicFunction val)
- 单身人士不必成为解决方案的一部分,
- ...但是应该保留
go
被分解为 forth
和 back
以及依赖于 Simple
的中间类型的想法。
您可以使用 toSing
from SingKind
将 SimpelType
的值转换为 SomeSing SimpleType
的值,这是 Sing SimpleType
周围的存在量化包装器。然后您可以解包该值以获得 Sing SimpleType
,然后您可以将其传递给 back
和 forth
:
go :: SimpleType -> Int -> Int
go val x =
case toSing val of
SomeSing s -> back s $ forth s x
SingKind
的实例由您正在使用的 singletons
拼接为您生成(以及许多其他内容)。
请注意,虽然单分支 case
要求成为 let
,但无法编译:
go val x =
let (SomeSing s) = toSing val
in back s $ forth s x
这是被禁止的,因为 let
可以递归,并且由于展开 GADT 可能会将新类型引入上下文,这可能会导致创建无限类型。另一方面,case
分支不能递归,所以这是可行的。 (此解释归功于@HTNW)
但是辅助函数也可以:
go val x = helper $ toSing val
where
helper (SomeSing s) = back s $ forth s x
考虑以下 Haskell 代码:
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE TemplateHaskell #-}
import Data.Singletons.TH
singletons [d| data SimpleType = Aaa | Bbb | Ccc | Ddd
deriving (Read)
|]
-- each SimpleType value has an associated type
type family Parsed (t :: SimpleType) :: * where
Parsed Aaa = [Int]
Parsed Bbb = Maybe Int
Parsed Ccc = (Int, Int)
Parsed Ddd = Int
forth :: SSimpleType t -> Int -> Parsed t
forth SAaa x = [x,x*2,x*3]
forth SBbb x = Just x
forth SCcc x = (1337, x)
forth SDdd x = x
back :: SSimpleType t -> Parsed t -> Int
back SAaa [_, y, _] = y + 5
back SBbb (Just y) = y - 7
back SCcc (y1, y2) = y1 + y2
back SDdd y = y * 2
helper b = back b . forth b
go :: SimpleType -> Int -> Int
go Aaa = helper SAaa
go Bbb = helper SBbb
go Ccc = helper SCcc
go Ddd = helper SDdd
main = do
-- SimpleType value comes at run-time
val <- readLn
putStrLn $ show $ go val 100
是否可以在定义go
时避免重复?换句话说,有没有办法写这样的东西:
go val = helper (someMagicFunction val)
- 单身人士不必成为解决方案的一部分,
- ...但是应该保留
go
被分解为forth
和back
以及依赖于Simple
的中间类型的想法。
您可以使用 toSing
from SingKind
将 SimpelType
的值转换为 SomeSing SimpleType
的值,这是 Sing SimpleType
周围的存在量化包装器。然后您可以解包该值以获得 Sing SimpleType
,然后您可以将其传递给 back
和 forth
:
go :: SimpleType -> Int -> Int
go val x =
case toSing val of
SomeSing s -> back s $ forth s x
SingKind
的实例由您正在使用的 singletons
拼接为您生成(以及许多其他内容)。
请注意,虽然单分支 case
要求成为 let
,但无法编译:
go val x =
let (SomeSing s) = toSing val
in back s $ forth s x
这是被禁止的,因为 let
可以递归,并且由于展开 GADT 可能会将新类型引入上下文,这可能会导致创建无限类型。另一方面,case
分支不能递归,所以这是可行的。 (此解释归功于@HTNW)
但是辅助函数也可以:
go val x = helper $ toSing val
where
helper (SomeSing s) = back s $ forth s x