随笔 - 10,  文章 - 1,  评论 - 4,  阅读 - 2059

初识体验

最开始是通过华为官方发布的cangjie白皮书熟悉了一下cangjie编程语言。通过白皮书指南,第一感受是cangjie几乎涵盖了我开发过程需要的特性。比较亮眼的是强制的Nullable类型处理,和静态编译技术(宏)。于是下意识的认为,动态特性上面也不会让人失望。可实际体验下来发现动态特性非常薄弱,随着开发需求和业务复杂性的提升,对动态特性的需求也变得越来越强烈。对cangjie在动态特性方面的薄弱也感到失望。以及对于aot,宏泛滥如果构建大型项目(有些模块无法编译成lib)在编译速度等方面的担忧。

虽然cangjie几乎涵盖了非常全面的语言特性、框架特性。但没有多少是做的非常突出或者说有亮点的。比如扩展语法、属性语法就非常鸡肋。语言特性方面虽然支持结构体但是并没有看到与之配套的指针特性,或者说安全的指针特性,这就导致结构体的存在变得非常尴尬,让开发者难以取舍。

万事开头难,cangjie拥有非常全面的语言特性,但假以时日完善形成自己特有的特色未来可期。我基于仓颉开源了aspnetcore基础开发实现。有一点可以承认的是,cangjie具有简洁安全快速表达能力,因为使用仓颉来实现aspnetcore代码量并不大:https://gitcode.com/soulsoft/asp。

asp4cj项目介绍

目前支持了aspnetcore中的controller-action。动态终结点,路由,多数据源配置,多日志提供架构,日志过滤器,请求管道-中间件,依赖注入ioc

@Route["/[controller]/[action]"]
public class TestController <: Controller {
    @HttpGet
    @HttpPut
    @HttpPost["create"]
    public func hello() {
        return json(###"{"msg": "asp"}"###)
    }

    @HttpGet["/index"]
    public func index() {
        context.responseBuilder.body("ednpoint:index")
    }
}

public class LoggingMiddleware <: Middleware {
    let _loggerFactory: LoggerFactory
    public init(loggerFactory: LoggerFactory) {
        _loggerFactory = loggerFactory
    }
    public func invoke(context: HttpContext, next: () -> Unit) {
        let logger = _loggerFactory.createLogger("asp.1test1.LoggingMiddleware")
        logger.info(context.request.url.toString())
        logger.warn(context.request.url.toString())
        next()
    }
}

main(args: Array<String>): Int64 {
    var builder = WebHost.createBuilder(args)
    builder.logging.addConsole()//添加控制台日志源
    let app = builder.build()//构建host
    app.useDefaultFiles()//使用默认文件处理
    app.useStaticFiles()//使用静态文件处理
    //注册EndpointMiddleware负责执行终结点,并同时注册终结点
    app.mapEndpoints { endpoints => 
        endpoints.mapGet("/asp") {
            context => context.responseBuilder.body("hello")
        }
        //把controller-action处理成endpoint
        endpoints.mapController<TestController>()
    }
    app.run()
    return 0
}

个人建议

下面就基于实现asp4cj这个项目来谈一下我对仓颉语言的实际感受和建议。cangjie存在很多迷惑性的语法陷阱,比如可变长参数我就不提出来了,迷惑性非常强很难排查。

关于模式匹配

public interface SomeService {
    prop name: ?String
}

public class SomeServiceImpl <: SomeService {
    private let _name: ?String

    init(name: ?String) {
        this._name = name
    }

    public prop name: ?String {
        get() {
            _name
        }
    }
}

main(): Int64 {
    let instance: ?SomeServiceImpl = SomeServiceImpl("cangjie")
    let name = instance?.name
    if (name == None) {
        println("name not null")
    }else {
        println("name is null")
    }
    return 0
}

这个demo让我感觉十分无语和不可理解。既然支持了?运算符,instance?.name推断出来的类型居然是Option<Option>。我们肯定是希望?能够帮我解构的。

这就给下面的模式解构带来了负担,下面的两种解构都存在嵌套,让人很难受。

main(): Int64 {
    let instance: ?SomeServiceImpl = SomeServiceImpl("cangjie")

    if (let Some(Some(name)) <- instance?.name) {
        println(name)
    }
    if (let Some(obj) <- instance) {
        if (let Some(name) <- obj.name) {
            println(name)
        }
    }
    return 0
}

类型转换

main(): Int64 {
    let any:Any = Object() 
    if (let Some(obj) <- (any as Object)) {
        println("success")
    }
    //简写
    if (let obj:Object <- any) {
        println("success")
    }
    return 0
}

通过这个案例,可以给人一种语法迷惑,就是if let语法可以实现两个类型之间的转换,于是会有人写出这样的代码。

//错误的认为if-let可以,完成Object <- ?Object之间的类型转换,但实际不会成功,需要我们区分你是在进行类型转换还是模式解构。即cangjie的if-let只能做模式解构,它的简化形式,会让人误导它能够进行类型转换
main(): Int64 {
    let any:?Object = Object() 
    if (let obj:Object <- any) {
        println("success")
    }
    return 0
}

个人建议应该对if-let进行增强使得它可以进行Object <- ?Object之间的转换。减少迷惑性代码的发生。而且同时简化了类型转换。if本来就应该能支持判定是否能转换到目标类型,在可以转换的情况下,转换到目标类型,减少解构路径。个人建议要么取消if-let的隐式问题,要么对其进行增强。

//对于这个场景路径就会变得很长,嵌套也会变得很多
main(): Int64 {
    let anyNullable:?Any = Object()
    if (let Some(any) <- anyNullable) {
        //注意if-let不支持类型转换,类型转换只有一种就是as,但是这里涉及到了一个隐式语法
        if (let obj:Object <- any) {
            println("success")
        }
    }
    return 0
}

关于扩展语法

目前扩展语法个人感觉非常鸡肋,有点画大饼的感觉,还不如Utilities来的方便,比如要给HttpContext扩展函数,使用起来和声明起来都很繁琐(需要定义一个接口+实现,导出的时候也需要导出接口和被扩展类型)。而且目前不能对接口直接扩展。比如我们希望给所有的实现了Iterable接口的派生类扩展一个打印函数,目前无法实现。只能通过扩展Iterator来实现,导致调用不够简洁,为此cangjie还吧Iterator变成了abstract class实现Iterable,这让人感觉非常混乱。实际情况是很多第三方框架暴露出来的都是一些接口。这使得我们无法面向接口设计api。std库的Iterator尚且如此。

关于属性

从上面的SomeService接口来看,你无法在接口上声明name是一个只读的属性。而且属性的语法过于啰嗦,必须要使用完整写法,即需要同时定义一个对应字段_name。

关于函数隐式返回和返回类型推断

func name() {
    0
}

这种写法如果不加以控制,会造成隐患,可能你一个不注意的改动,就会导致难以排查的风险,多个if-else的函数体你找不到退出函数的分支。其实声明类型和一个return,并不会让人觉得繁琐。

从这里可以看出设计师的初衷是好的简化书写,但是这一点和扩展语法设计初衷违背。这么繁琐的扩展语法为了安全,到了函数隐式类型推断和返回安全性就被忽略了。看起来这两个点不像是一个人设计的。

关于动态技术

由于cangjie的采用的aot首发,导致动态特性非常薄弱,而cangjie语言的定位又是应用开发。社区妄图通过静态技术来进行弥补,导致连std库连个json序列化,反序列化器都没能提供。而且将来的orm也难以做到像C#那样遍地开花。个人感觉像是定位不清晰,花那么大精力对静态编译技术投入,可是产出却不太客观。赋能也很有限,因为编写心智负担极大。反过来如果在动态技术上进行投入,那么产出肯定相对比较大,比如实现运行阶段的cangjie表达式解析等技术,为后来的orm以及其它的DSL场景提供了可能性。

总结

cangjie语言特性丰富,有很多的可能性,以及安全特性,但存在诸多不足之处,希望日后日益完善。建议应该往动态特性上面加大投入,以及引入安全的指针操作,比如C#的ref等等。静态编译技术不能作为语法不足的找补,只是一种工具和手段,实际赋能有限。

posted on   花间岛  阅读(75)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· .NET Core 中如何实现缓存的预热?
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 如何调用 DeepSeek 的自然语言处理 API 接口并集成到在线客服系统
· 【译】Visual Studio 中新的强大生产力特性

< 2025年3月 >
23 24 25 26 27 28 1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30 31 1 2 3 4 5
点击右上角即可分享
微信分享提示