gpt4 book ai didi

haskell - 如何在递归结构中存储任意值或如何构建可扩展的软件架构?

转载 作者:行者123 更新时间:2023-12-02 03:31:47 25 4
gpt4 key购买 nike

我正在开发一个基本的 UI 工具包,并试图找出整体架构。

我正在考虑使用 WAI's structure for extensibility .我的 UI 核心结构的简化示例:

run :: Application -> IO ()
type Application = Event -> UI -> (Picture, UI)
type Middleware = Application -> Application

在 WAI 中,中间件的任意值被保存 in the vault .我认为这是保存任意值的糟糕 hack,因为它不透明,但我想不出一个足够简单的结构来替换这个 vault 来为每个中间件提供一个保存任意值的地方。

我考虑过以递归方式将元组存储在元组中:

run :: (Application, x) -> IO ()
type Application = Event -> UI -> (Picture, UI)
type Middleware y x = (Application, x) -> (Application, (y,x))

或者只使用惰性列表来提供不需要分隔值的级别(这提供了更多的自由,但也有更多的问题):

run :: Application -> IO ()
type Application = [Event -> UI -> (Picture, UI)]
type Middleware = Application -> Application

实际上,我会使用经过修改的惰性列表解决方案。还有哪些其他解决方案可能有效?

注意:

  • 我根本不喜欢使用镜头。
  • 我知道 UI -> (Picture, UI) 可以定义为 State UI Picture
  • 我不知道关于 monad、transformers 或 FRP 的解决方案。很高兴看到一个。

最佳答案

Lenses 提供了一种引用数据类型字段的通用方法,这样您就可以在不破坏向后兼容性的情况下扩展或重构数据集。我将使用 lens-familylens-family-th库来说明这一点,因为它们比 lens 的依赖性更轻.

让我们从一个包含两个字段的简单记录开始:

{-# LANGUAGE Template Haskell #-}

import Lens.Family2
import Lens.Family2.TH

data Example = Example
{ _int :: Int
, _str :: String
}

makeLenses ''Example
-- This creates these lenses:
int :: Lens' Example Int
str :: Lens' Example String

现在你可以写State引用数据结构字段的完整代码。您可以使用 Lens.Family2.State.Strict为此目的:

import Lens.Family2.State.Strict

-- Everything here also works for `StateT Example IO`
example :: State Example Bool
example = do
s <- use str -- Read the `String`
str .= s ++ "!" -- Set the `String`
int += 2 -- Modify the `Int`
zoom int $ do -- This sub-`do` block has type: `State Int Int`
m <- get
return (m + 1)

要注意的关键是我可以更新我的数据类型,上面的代码仍然可以编译。向 Example 添加新字段一切仍然有效:

data Example = Example
{ _int :: Int
, _str :: String
, _char :: Char
}

makeLenses ''Example
int :: Lens' Example Int
str :: Lens' Example String
char :: Lens' Example Char

然而,我们实际上可以更进一步,完全重构我们的 Example像这样输入:

data Example = Example
{ _example2 :: Example
, _char :: Char
}

data Example2 = Example2
{ _int2 :: Int
, _str2 :: String
}

makeLenses ''Example
char :: Lens' Example Char
example2 :: Lens' Example Example2

makeLenses ''Example2
int2 :: Lens' Example2 Int
str2 :: Lens' Example2 String

我们必须打破旧代码吗?不!我们所要做的就是添加以下两个镜头以支持向后兼容:

int :: Lens' Example Int
int = example2 . int2

str :: Lens' Example Char
str = example2 . str2

现在,尽管对我们的 Example 进行了侵入式重构,但所有旧代码仍然可以正常工作,无需任何更改。类型。

事实上,这不仅仅适用于记录。您也可以对求和类型(又名代数数据类型或枚举)执行完全相同的操作。例如,假设我们有这种类型:

data Example3 = A String | B Int

makeTraversals ''Example3
-- This creates these `Traversals'`:
_A :: Traversal' Example3 String
_B :: Traversal' Example3 Int

我们用求和类型做的很多事情都可以类似地用 Traversal' 重新表达。秒。模式匹配有一个明显的异常(exception):实际上可以通过 Traversal 的完整性检查来实现模式匹配。 s,但它目前很冗长。

但是,相同的观点成立:如果您用 Traversal' 表示所有求和类型运算s,那么你可以极大地重构你的总和类型,只需更新适当的 Traversal' s 以保持向后兼容性。

最后:注意 sum 类型构造函数的真正模拟是 Prism s(除了模式匹配之外,它还允许您使用构造函数构建值)。 lens-family 不支持这些库系列,但它们由 lens 提供你可以自己使用 profunctors 实现它们如果需要,可以依赖。

此外,如果您想知道 lens 是什么新类型的模拟是,它是一个 Iso' ,这也最低限度地需要一个 profunctors依赖。

此外,我所说的一切都适用于引用多个递归类型字段(使用 Fold s)。从字面上看,您可以想象的任何想要以向后兼容的方式在数据类型中引用的内容都包含在 lens 中。图书馆。

关于haskell - 如何在递归结构中存储任意值或如何构建可扩展的软件架构?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/26224312/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com