动态语言能否实现《Type-Driven Development with Idris》第6章数据存储功能?
动态语言(Ruby/Python)能否实现Idris的DataStore功能?
背景说明
以下是《Type-Driven Development with Idris》第6章的DataStore实现代码,它通过类型驱动的方式在运行时定义支持String和Int类型的数据库Schema,并提供添加数据、查询数据、设置Schema等功能:
module Main import Data.Vect infixr 5 .+. data Schema = SString | SInt | (.+.) Schema Schema SchemaType : Schema -> Type SchemaType SString = String SchemaType SInt = Int SchemaType (x .+. y) = (SchemaType x, SchemaType y) record DataStore where constructor MkData schema : Schema size : Nat items : Vect size (SchemaType schema) addToStore : (store : DataStore) -> SchemaType (schema store) -> DataStore addToStore (MkData schema size store) newitem = MkData schema _ (addToData store) where addToData : Vect oldsize (SchemaType schema) -> Vect (S oldsize) (SchemaType schema) addToData [] = [newitem] addToData (x :: xs) = x :: addToData xs setSchema : (store : DataStore) -> Schema -> Maybe DataStore setSchema store schema = case size store of Z => Just (MkData schema _ []) S k => Nothing data Command : Schema -> Type where SetSchema : Schema -> Command schema Add : SchemaType schema -> Command schema Get : Integer -> Command schema Quit : Command schema parsePrefix : (schema : Schema) -> String -> Maybe (SchemaType schema, String) parsePrefix SString input = getQuoted (unpack input) where getQuoted : List Char -> Maybe (String, String) getQuoted ('"' :: xs) = case span (/= '"') xs of (quoted, '"' :: rest) => Just (pack quoted, ltrim (pack rest)) _ => Nothing getQuoted _ = Nothing parsePrefix SInt input = case span isDigit input of ("", rest) => Nothing (num, rest) => Just (cast num, ltrim rest) parsePrefix (schemal .+. schemar) input = case parsePrefix schemal input of Nothing => Nothing Just (l_val, input') => case parsePrefix schemar input' of Nothing => Nothing Just (r_val, input'') => Just ((l_val, r_val), input'') parseBySchema : (schema : Schema) -> String -> Maybe (SchemaType schema) parseBySchema schema x = case parsePrefix schema x of Nothing => Nothing Just (res, "") => Just res Just _ => Nothing parseSchema : List String -> Maybe Schema parseSchema ("String" :: xs) = case xs of [] => Just SString _ => case parseSchema xs of Nothing => Nothing Just xs_sch => Just (SString .+. xs_sch) parseSchema ("Int" :: xs) = case xs of [] => Just SInt _ => case parseSchema xs of Nothing => Nothing Just xs_sch => Just (SInt .+. xs_sch) parseSchema _ = Nothing parseCommand : (schema : Schema) -> String -> String -> Maybe (Command schema) parseCommand schema "add" rest = case parseBySchema schema rest of Nothing => Nothing Just restok => Just (Add restok) parseCommand schema "get" val = case all isDigit (unpack val) of False => Nothing True => Just (Get (cast val)) parseCommand schema "quit" "" = Just Quit parseCommand schema "schema" rest = case parseSchema (words rest) of Nothing => Nothing Just schemaok => Just (SetSchema schemaok) parseCommand _ _ _ = Nothing parse : (schema : Schema) -> (input : String) -> Maybe (Command schema) parse schema input = case span (/= ' ') input of (cmd, args) => parseCommand schema cmd (ltrim args) display : SchemaType schema -> String display {schema = SString} item = show item display {schema = SInt} item = show item display {schema = (y .+. z)} (iteml, itemr) = display iteml ++ ", " ++ display itemr getEntry : (pos : Integer) -> (store : DataStore) -> Maybe (String, DataStore) getEntry pos store = let store_items = items store in case integerToFin pos (size store) of Nothing => Just ("Out of range\n", store) Just id => Just (display (index id (items store)) ++ "\n", store) processInput : DataStore -> String -> Maybe (String, DataStore) processInput store input = case parse (schema store) input of Nothing => Just ("Invalid command\n", store) Just (Add item) => Just ("ID " ++ show (size store) ++ "\n", addToStore store item) Just (SetSchema schema') => case setSchema store schema' of Nothing => Just ("Can't update schema when entries in store\n", store) Just store' => Just ("OK\n", store') Just (Get pos) => getEntry pos store Just Quit => Nothing main : IO () main = replWith (MkData (SString .+. SString .+. SInt) _ []) "Command: " processInput
示例交互
Command: schema String String Int OK Command: add "Rain Dogs" "Tom Waits" 1985 ID 0 Command: add "Fog on the Tyne" "Lindisfarne" 1971 ID 1 Command: get 1 "Fog on the Tyne", "Lindisfarne", 1971 Command: quit
提问
由于静态语言通常需要在编译前定义Schema,那么Ruby、Python这类动态语言能否实现上述完全相同的功能?
回答
当然可以实现,而且动态语言的特性让实现过程更灵活、代码更简洁。
核心实现思路如下:
- 用基础数据结构(比如Python的列表/字典、Ruby的数组/哈希)存储当前Schema的类型定义,例如用
["String", "Int"]表示包含字符串和整数的Schema - 解析
schema命令时,验证输入的类型列表合法性,并且遵循只有空存储时才能修改Schema的规则 - 处理
add命令时,按照当前Schema的类型规则解析输入字符串:提取引号包裹的内容作为字符串、将数字转换为整数,同时验证输入格式是否匹配Schema要求 - 处理
get命令时,根据索引取出对应数据,再按照Schema格式格式化输出 - 维护存储的数据列表,确保每个条目都符合当前Schema的结构
和Idris版本的核心区别在于:Idris依靠编译时的类型检查来保证数据与Schema的一致性,而动态语言是在运行时完成格式验证。但从功能层面来说,完全可以实现和示例中一模一样的交互逻辑,包括Schema定义、数据添加、查询、Schema修改限制等所有功能。
比如在Python中,可以用类封装DataStore的状态(当前Schema、数据列表),再实现对应的命令解析、数据处理方法;Ruby同理,用类或模块都能轻松实现。
内容的提问来源于stack exchange,提问作者wang kai
相关产品推荐
相关产品推荐

