gpt4 book ai didi

haskell - 使用类型来防止列表中的端口号冲突

转载 作者:行者123 更新时间:2023-12-03 15:23:51 27 4
gpt4 key购买 nike

Propellor 表示它部署为 [Property] 的系统,为了简单起见,假设 data Property = Property (Set Port) SatisfyProperty
所以,可能有 apacheInstalled使用端口 80 和 443 以及 torBridge 的属性属性,它使用端口 443。系统同时具有这两个属性是没有意义的,因为它们使用相同的端口 443。

我想知道类型检查器是否有可行的方法来防止系统同时被分配?然后可以在构建时捕获端口冲突。我想类型级别 Ints 将是第一步,但我对第二步一无所知..

最佳答案

这非常棘手,但完全可以使用最新的 ghc 版本(最新的 haskell 平台可以工作)。关于这个的例子不多(因为它都是很新的),所以我希望这对你有帮助。

你是对的,使用类型级别的自然会起作用。您将需要两个模块 - 一个用于定义结构并提供安全接口(interface),另一个用于定义实际服务。

这是正在使用的代码:

{-# LANGUAGE TypeOperators, PolyKinds, RankNTypes #-}
{-# LANGUAGE KindSignatures, DataKinds, TypeFamilies, UndecidableInstances #-}
module DefinedServices where
import ServiceTypes
import Control.Monad

apacheInstalled :: Service '[443] ServiceDetails
apacheInstalled = makeService "apache" $ putStrLn "Apache service"

torBridge :: Service [80,443] ServiceDetails
torBridge = makeService "tor" $ putStrLn "Tor service"

httpService :: Service [80, 8080] ServiceDetails
httpService = makeService "http" $ putStrLn "Http service"

serviceList1 :: [ServiceDetails]
serviceList1 = getServices $
noServices `addService` httpService `addService` apacheInstalled

-- serviceList2 :: [ServiceDetails]
-- serviceList2 = getServices $
-- noServices `addService` apacheInstalled `addService` torBridge


main = startServices serviceList1

请注意每个服务的端口是如何在类型中定义的。 serviceList1使用 httpServiceapacheInstalled服务。这可以编译,因为它们的端口不冲突。 serviceList2被注释掉,如果不注释会导致这个编译错误:
DefinedServices.hs:22:56:
Couldn't match type 'False with 'True
Expected type: 'True
Actual type: ServiceTypes.UniquePorts '[443, 80, 443]
In the second argument of `($)', namely
`noServices `addService` apacheInstalled `addService` torBridge'
In the expression:
getServices
$ noServices `addService` apacheInstalled `addService` torBridge
In an equation for `serviceList2':
serviceList2
= getServices
$ noServices `addService` apacheInstalled `addService` torBridge
Failed, modules loaded: ServiceTypes.

这很好地描述了这个问题:UniquePorts 最终为假,因为 443 被使用了两次。

下面是 ServiceTypes.hs 中的操作方法:
{-# LANGUAGE TypeOperators, PolyKinds, RankNTypes #-}
{-# LANGUAGE KindSignatures, DataKinds, TypeFamilies, UndecidableInstances #-}
module ServiceTypes (
makeService, noServices, addService, Service, ServiceDetails(..)
, getServices, startServices) where
import GHC.TypeLits
import Control.Monad
import Data.Type.Equality
import Data.Type.Bool

我们需要一大堆语言扩展来让它工作。此外,还定义了安全接口(interface)。

首先,需要一个类型级别的函数来检查列表是否唯一。这利用了 Data.Type.Equality 中的类型族运算符和 Data.Type.Bool .请注意,以下代码仅由类型检查器执行。
type family UniquePorts (list1 :: [Nat]) :: Bool
type instance UniquePorts '[] = True
type instance UniquePorts (a ': '[]) = True
type instance UniquePorts (a ': b ': rest) = Not (a == b) && UniquePorts (a ': rest) && UniquePorts (b ': rest)

它只是唯一性的递归定义。

接下来,由于我们将同时使用多个服务,因此需要一种方法将两个列表合并为一个:
type family Concat (list1 :: [a]) (list2 :: [a]) :: [a]
type instance Concat '[] list2 = list2
type instance Concat (a ': rest) list2 = a ': Concat rest list2

这就是我们需要的所有类型级函数!

接下来,我将定义一个 Service类型,用所需的端口包装另一种类型:
data Service (ports :: [Nat]) service = Service service

接下来,为单个服务的实际细节。您应该根据需要对其进行自定义:
data ServiceDetails = ServiceDetails {
serviceName :: String
, runService :: IO ()
}

我还添加了一个辅助函数来将服务包装在 Service 中。类型与定义的端口:
makeService :: String -> IO () -> Service ports ServiceDetails
makeService name action = Service $ ServiceDetails name action

现在终于为多个服务列表。 `noServices 只是定义了一个空的服务列表,显然不使用端口:
noServices :: Service '[] [ServiceDetails]
noServices = Service []
addService是所有东西的结合点:
addService :: (finalPorts ~ Concat ports newPorts, UniquePorts finalPorts ~ True)
=> Service ports [ServiceDetails]
-> Service newPorts ServiceDetails
-> Service finalPorts [ServiceDetails]
addService (Service serviceList) (Service newService) =
Service $ (newService : serviceList)
finalPorts ~ Concat ports newPorts只做 finalPorts服务列表中的端口和新服务的组合。 UniquePorts finalPorts ~ True确保最终端口不包含任何重复的端口。该功能的其余部分完全是微不足道的。
getServices展开 [ServiceDetails]来自 Service ports [ServiceDetails] .作为 Service构造函数不公开,创建 Service ports [ServiceDetails] 的唯一方法类型是通过 noServicesaddService保证安全的功能。
getServices :: Service ports [ServiceDetails] -> [ServiceDetails]
getServices (Service details) = details

最后是运行服务的测试功能:
startServices :: [ServiceDetails] -> IO ()
startServices services = forM_ services $ \service -> do
putStrLn $ "Starting service " ++ (serviceName service)
runService service
putStrLn "------"

这个新功能的可能性几乎是无穷无尽的,并且是对以前的 ghc 版本的巨大改进(它仍然是可能的,但要困难得多)。一旦您开始使用类型中的值,此代码就非常简单。

关于haskell - 使用类型来防止列表中的端口号冲突,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/26027765/

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