我正在尝试用 Haskell 定义一种编程语言。我希望使 AST 可扩展:AST 模块的用户(例如漂亮的打印机、解释器、编译器、类型系统、语言服务器等)应该能够通过添加新功能和新数据(用于扩展语法的新数据类型以及当前数据构造函数的新字段,以存储各个组件所需的数据)。
我尝试通过使用Trees That Grow (TTG)来实现这个目标。它有效,但会导致太多的样板文件。我的最小原型的代码行数增加了 10 倍,并且这个数字随着 AST 大小乘以扩展数量而增长。更改一些较小的内容需要更改 AST 模块的几行,而更改扩展性实现方式的某些内容则需要重写大部分内容。
有没有什么方法可以减少所需的样板数量,或者最好将其完全删除?
这只是 AST 的一小部分。它与 JSON 非常相似,因为我决定从一个小型原型开始。
module AST ( KeyValue(..), Data(..) ) where
data KeyValue = KV String Data deriving (Show, Eq, Ord)
data Data =
Null |
Int Int |
Num Double |
Bool Bool |
String String |
Array [Data] |
Object [KeyValue] deriving (Show, Eq, Ord)
Run Code Online (Sandbox Code Playgroud)
为了通过 TTG 扩展它,数据类型变成这样:
data KeyValueX x =
KVX …Run Code Online (Sandbox Code Playgroud) for...of当我中断循环时,是否可以循环迭代器的一部分而不关闭迭代器?
例子:
function* numbers(i=0){
while(true) yield i++;
}
let nums=numbers();
// this loop prints numbers from 0 to 3
for(const n of nums){
if(n>3) break;
console.log(n);
}
// this loop doesn't print anything because `nums` has already been closed
for(const n of nums){
if(n>10) break;
console.log(n);
}Run Code Online (Sandbox Code Playgroud)
我知道我可以通过自己的调用来遍历迭代器iterator.next()。但我想知道是否可以用for...of语法来做到这一点。
有时我会遇到充满不同运算符的表达式,但我不知道它们是如何解析的。我知道我可以使用 询问有关运算符的 GHCi 信息:i,但必须查看多个运算符并应用它们的优先级和关联性来在我的脑海中重建它感觉相当困难。
有没有更简单的理解方法?
\n例如,采用以下有效的 Haskell 表达式:
\n7 \xc2\xa4 2 \xe2\x8f\x86 4 \xe2\x8f\x86 3 \xc2\xa4 1 \xe2\x99\x82 3 \xe2\x99\x82 2\nRun Code Online (Sandbox Code Playgroud)\n并假设这些运算符的优先级是:
\n可以让GHCi这样打印出来吗?
\n(7 \xc2\xa4 ((2 \xe2\x8f\x86 4) \xe2\x8f\x86 3)) \xc2\xa4 (1 \xe2\x99\x82 (3 \xe2\x99\x82 2))\nRun Code Online (Sandbox Code Playgroud)\n有没有其他方法可以快速了解该表达式的工作原理?
\n我的 C 库,在 1.0.0 版本中,定义了一个结构体和一些用于分配和使用结构体的函数:
typedef struct { int x; int y; } MyStruct;
MyStruct *allocate( int, int );
void destroy( MyStruct* );
void print( MyStruct* );
Run Code Online (Sandbox Code Playgroud)
用户不应该自己分配结构,也不应该按值复制它。这是与语义版本控制问题的主要区别:次要还是主要变化?. 例如,程序应该像这样使用它:
void f(){
MyStruct *ms = allocate(0,0);
ms->x += 1;
print(ms);
destroy(ms);
}
Run Code Online (Sandbox Code Playgroud)
现在我需要向结构中添加一个新字段,而函数的签名不会改变。
typedef struct { int x; int y; int z; } MyStruct;
Run Code Online (Sandbox Code Playgroud)
新结构比旧结构占用更多内存:如果程序尝试MyStruct直接分配实例或按值复制它,如果它链接到与构建时使用的库版本不同的库版本,则它可能会中断。
然而,这不是程序使用的方式MyStruct:只要它们遵循文档,一切正常。但是代码中没有任何内容可以阻止他们滥用结构。
我正在使用语义版本控制来对我的库进行版本控制。在上述情况下,我应该增加次要版本(以向后兼容的方式添加的功能)还是主要版本(不兼容的 API 更改)?
拿这个代码片段(在各种评论和答案提到它之后,我将类型系列更改为封闭类型):
-- a closed type family for which I won't define any instances
type family EmptyTypeFamily t where
-- 1. this type doesn't exist, since there's no `instance EmptyTypeFamily ()`
type NotAType = EmptyTypeFamily ()
-- 2. this value has a type that doesn't exist
untypedValue :: NotAType
untypedValue = undefined
main :: IO ()
main = do
-- 3. the value with no type can be used in expressions
return untypedValue
return . id . fix $ \x -> …Run Code Online (Sandbox Code Playgroud) 作为练习,我尝试在 monad 内递归地构造一个无限的、惰性的列表。
基本上,相当于nat,但是是一元的,monat:
nat :: Int -> [Int]
nat n = n : nat (n+1)
monat :: Monad m => Int -> m [Int]
monat n = return n >>= \n -> (n:) <$> monat (n+1)
Run Code Online (Sandbox Code Playgroud)
但monat并不是真正懒惰地求值:如果不计算所有元素,就不可能获得第一个元素:
ghci> take 5 $ nat 0
[0,1,2,3,4]
ghci> take 5 <$> (monat 0) :: Maybe [Int]
... never ending computation ...
Run Code Online (Sandbox Code Playgroud)
我怀疑问题一定在于fmapor >>=,因为它们是我在那里使用但不在 中使用的唯一额外函数nat。然而,我查看了他们的实现,但不明白为什么或在哪里打破了懒惰:
instance Functor Maybe where
fmap _ Nothing …Run Code Online (Sandbox Code Playgroud) monads recursion haskell lazy-evaluation recursive-datastructures
我有一组封闭的值:
data Value = A | B | C | D | E ...
deriving (Eq, Ord, Show)
Run Code Online (Sandbox Code Playgroud)
以及代表它们顺序的数据结构:
order :: [[Value]]
order = [
[ B ],
[ A, D ],
[ C ],
...
]
Run Code Online (Sandbox Code Playgroud)
我需要将 Value 的订单转换为Int. 我可以这样做:
prec' :: [[Value]] -> Value -> Int
prec' [] _ = 0
prec' (vs : rest) v = if v `elem` vs
then 1 + length rest
else prec' rest v
prec :: Value -> Int
prec …Run Code Online (Sandbox Code Playgroud) haskell ×5
boilerplate ×1
c ×1
for-of-loop ×1
ghci ×1
iterator ×1
javascript ×1
monads ×1
recursion ×1