From d5de65fdb1802cdf498d65d93397f290813c377c Mon Sep 17 00:00:00 2001 From: muqiuhan Date: Tue, 9 Sep 2025 06:17:00 +0000 Subject: deploy: 9665097f0fa0dae9f92123fac54d26c0818758a5 --- 2024/01/08/OCaml-News-2024-1/index.html | 19 +- 2024/01/18/OCaml-News-2024-2/index.html | 40 ++-- 2024/01/22/Rescript-React-Native/index.html | 5 +- 2024/01/23/G-Machine/index.html | 4 +- .../index.html" | 5 +- .../index.html" | 50 +++-- .../index.html" | 42 ++-- .../index.html" | 12 +- 2024/02/26/OCaml-News-2024-3/index.html | 135 ++++++++----- .../index.html" | 12 +- .../index.html" | 6 +- .../index.html" | 18 +- .../index.html" | 5 +- .../index.html" | 15 +- .../index.html" | 9 +- 2024/06/27/OCaml-News-2024-5/index.html | 94 +++++---- .../index.html" | 6 +- 2024/07/01/OCaml-News-2024-4/index.html | 162 ++++++++++------ .../index.html" | 156 +++++++++------ 2024/07/23/OCaml-News-2024-6/index.html | 130 ++++++++----- .../index.html" | 5 +- .../index.html | 37 +--- .../index.html | 56 +++--- .../index.html" | 15 +- .../Turborepo-\347\256\200\350\277\260/index.html" | 129 +++++++------ .../index.html" | 27 +-- .../index.html | 56 +++--- .../index.html | 95 ++++----- .../index.html | 51 ++--- .../index.html" | 3 +- .../index.html" | 30 ++- 2024/12/15/OCaml-News-2024-7/index.html | 213 +++++++++++++-------- .../index.html" | 11 +- 33 files changed, 975 insertions(+), 678 deletions(-) (limited to '2024') diff --git a/2024/01/08/OCaml-News-2024-1/index.html b/2024/01/08/OCaml-News-2024-1/index.html index 7dda21b9..6f9d1b9a 100644 --- a/2024/01/08/OCaml-News-2024-1/index.html +++ b/2024/01/08/OCaml-News-2024-1/index.html @@ -192,7 +192,8 @@
-

News

-

News

-

瀑布的水逆流而上,
蒲公英种子从远处飘回,聚成伞的模样,
太阳从西边升起,落向东方。

-

子弹退回枪膛,
运动员回到起跑线上,
我交回录取通知书,忘了十年寒窗。

-

厨房里飘来饭菜的香,
你把我的卷子签好名字,
关掉电视,帮我把书包背上。

+

瀑布的水逆流而上,
+蒲公英种子从远处飘回,聚成伞的模样,
+太阳从西边升起,落向东方。

+

子弹退回枪膛,
+运动员回到起跑线上,
+我交回录取通知书,忘了十年寒窗。

+

厨房里飘来饭菜的香,
+你把我的卷子签好名字,
+关掉电视,帮我把书包背上。

你还在我身旁。


功利性主义总会引导我们思考结局,可生活本质上就是一场旅途。

diff --git a/2024/02/26/OCaml-News-2024-3/index.html b/2024/02/26/OCaml-News-2024-3/index.html index 4c44f48a..22dc4af2 100644 --- a/2024/02/26/OCaml-News-2024-3/index.html +++ b/2024/02/26/OCaml-News-2024-3/index.html @@ -192,99 +192,144 @@
-

o-新鲜事儿

+

+

o-新鲜事儿

-

o-视频

+

+

o-视频

-

o-博客 / 文章 / 帖子

+

+

o-博客 / 文章 / 帖子

-

o-未来

+

+

o-未来

-

o-值得被注意的项目

+

+

o-值得被注意的项目

diff --git "a/2024/03/22/\344\272\214\343\200\207\344\272\214\345\233\233\345\271\264\344\270\211\346\234\210\344\272\214\345\215\201\344\272\214\346\227\245/index.html" "b/2024/03/22/\344\272\214\343\200\207\344\272\214\345\233\233\345\271\264\344\270\211\346\234\210\344\272\214\345\215\201\344\272\214\346\227\245/index.html" index 9f9b451e..c0f0a672 100644 --- "a/2024/03/22/\344\272\214\343\200\207\344\272\214\345\233\233\345\271\264\344\270\211\346\234\210\344\272\214\345\215\201\344\272\214\346\227\245/index.html" +++ "b/2024/03/22/\344\272\214\343\200\207\344\272\214\345\233\233\345\271\264\344\270\211\346\234\210\344\272\214\345\215\201\344\272\214\346\227\245/index.html" @@ -184,12 +184,18 @@
-

毛毛在人群中牵着我,却很难融入人群,我在林中面对着毛毛,树木的思想却千姿百态地向我涌来。
它说,它要将灵魂撕裂,让它暴露在最灼热的阳光下灼烧,要让风刺进来,硬生生扯开一道口子,要让原有的赤色消失殆尽,只留下一堆松散的灰色颗粒,任雨打浮萍。

-

毛毛和我看到了宇宙熵增,和我见到了熙攘的街道,现在变成了我耳边的风声。
是啊,是啊,你不再身陷囹圄,自由不再是攀在高墙上爬山虎的叶子,不再是吹进风间的花粉,不再是藏躲进阴暗土地里的水,不再是破碎割手的杯子片块。
但是这个世界还是越来越美了,我独自一人,却很自在,别无所求,只想被阳光晒透,被狂风穿过。渴望成熟,要准备好死去,也准备好重生。

+

毛毛在人群中牵着我,却很难融入人群,我在林中面对着毛毛,树木的思想却千姿百态地向我涌来。
+它说,它要将灵魂撕裂,让它暴露在最灼热的阳光下灼烧,要让风刺进来,硬生生扯开一道口子,要让原有的赤色消失殆尽,只留下一堆松散的灰色颗粒,任雨打浮萍。

+

毛毛和我看到了宇宙熵增,和我见到了熙攘的街道,现在变成了我耳边的风声。
+是啊,是啊,你不再身陷囹圄,自由不再是攀在高墙上爬山虎的叶子,不再是吹进风间的花粉,不再是藏躲进阴暗土地里的水,不再是破碎割手的杯子片块。
+但是这个世界还是越来越美了,我独自一人,却很自在,别无所求,只想被阳光晒透,被狂风穿过。渴望成熟,要准备好死去,也准备好重生。


小土堆上长出了狗尾巴草,当我靠近时,狗尾巴草会向我轻轻晃动,就像它小时候向我跑来时那样,不同的是,当我向前跑的时候,背后没有一个小小的身影再追过来了,但当我回望小土堆,狗尾巴草依然在摇晃,就像当初给予我回应那样。


-

狗儿要听狗儿歌,
毛毛下雨要回家,
小狗小狗画梅花,
直走就是我们家。

+

狗儿要听狗儿歌,
+毛毛下雨要回家,
+小狗小狗画梅花,
+直走就是我们家。

diff --git "a/2024/04/08/\344\272\214\343\200\207\344\272\214\345\233\233\345\271\264\345\233\233\346\234\210\345\205\253\346\227\245/index.html" "b/2024/04/08/\344\272\214\343\200\207\344\272\214\345\233\233\345\271\264\345\233\233\346\234\210\345\205\253\346\227\245/index.html" index 74a86016..cbcf68be 100644 --- "a/2024/04/08/\344\272\214\343\200\207\344\272\214\345\233\233\345\271\264\345\233\233\346\234\210\345\205\253\346\227\245/index.html" +++ "b/2024/04/08/\344\272\214\343\200\207\344\272\214\345\233\233\345\271\264\345\233\233\346\234\210\345\205\253\346\227\245/index.html" @@ -184,7 +184,11 @@
-

我不预设和任何人事物共度一生,
就自然的生活,
命运把我带到哪里就到哪里,
有缘的话,我会在世界中看见你,
无缘的话,我会看见你在世界中。

+

我不预设和任何人事物共度一生,
+就自然的生活,
+命运把我带到哪里就到哪里,
+有缘的话,我会在世界中看见你,
+无缘的话,我会看见你在世界中。

我为人们的开心而开心,诚恳,真诚的请求看到这里的人们每天都要笑一笑哦,你们真的真的对我很重要。

diff --git "a/2024/05/07/\344\272\214\343\200\207\344\272\214\345\233\233\345\271\264\344\272\224\346\234\210\344\270\203\346\227\245/index.html" "b/2024/05/07/\344\272\214\343\200\207\344\272\214\345\233\233\345\271\264\344\272\224\346\234\210\344\270\203\346\227\245/index.html" index e241a2f3..ef899859 100644 --- "a/2024/05/07/\344\272\214\343\200\207\344\272\214\345\233\233\345\271\264\344\272\224\346\234\210\344\270\203\346\227\245/index.html" +++ "b/2024/05/07/\344\272\214\343\200\207\344\272\214\345\233\233\345\271\264\344\272\224\346\234\210\344\270\203\346\227\245/index.html" @@ -184,9 +184,23 @@
-

我在写信
寄给羊水
信里提到
宇宙称呼它为灰
我们叫地球
还提到
风车不能骑
但是石头可以打水漂
你呀
到时别忘了
用小小的哭声款待我

+

我在写信
+寄给羊水
+信里提到
+宇宙称呼它为灰
+我们叫地球
+还提到
+风车不能骑
+但是石头可以打水漂
+你呀
+到时别忘了
+用小小的哭声款待我


-

我们应该坐在一起发呆
发很久的呆
然后我说人类好无聊啊
这个地球完蛋了
你点点头

+

我们应该坐在一起发呆
+发很久的呆
+然后我说人类好无聊啊
+这个地球完蛋了
+你点点头

diff --git "a/2024/05/16/\344\272\214\343\200\207\344\272\214\345\233\233\345\271\264\344\272\224\346\234\210\345\215\201\345\205\255\346\227\245/index.html" "b/2024/05/16/\344\272\214\343\200\207\344\272\214\345\233\233\345\271\264\344\272\224\346\234\210\345\215\201\345\205\255\346\227\245/index.html" index 4abe50cf..25ef30a4 100644 --- "a/2024/05/16/\344\272\214\343\200\207\344\272\214\345\233\233\345\271\264\344\272\224\346\234\210\345\215\201\345\205\255\346\227\245/index.html" +++ "b/2024/05/16/\344\272\214\343\200\207\344\272\214\345\233\233\345\271\264\344\272\224\346\234\210\345\215\201\345\205\255\346\227\245/index.html" @@ -190,10 +190,11 @@

多尝试点事情吧,试得越多,就能越快判断出自己是不是这块料。如果是,很好;如果不是,过,下一个。在这期间不要有任何心态波澜,行就是行,不行就是不行。反正不是非行不可。

人自信不是样样精通、事事皆成,凡事都做得比别人更好;自信是一个高级陷阱,仰赖自信的人终有一日会崩塌于自信的毁灭。

用生命力来取代往日的自信心,用绝对强大的权力意志来取代往日相对成功的小胜小利。

-

不放弃的理由不是因为自信,而是因为知道这事必须得干/行得通;放弃的理由也不是因为不自信,而是因为知道这事不能去干/行不通。

+

不放弃的理由不是因为自信,而是因为知道这事必须得干/行得通;放弃的理由也不是因为不自信,而是因为知道这事不能去干/行不通。

而一旦在理性和感性层面都认为必须办成某件事,那就不顾一切去办成。哪怕绕再多的路,要办的事就是要办。

当不再局限于自信或自卑,而是立足于实事求是去布局和行动,一切都会变得更加清晰明了。若想成事,首先要花时间去搞明白自己的长处与短板,然后学会在面对具体事项之际快速判断自己行不行、是不是非做不可、要做的话又有几分把握、失败了要如何应对、要不要改变其中某些变量再多试几次。

-

当有了一个目标,需要的不是豪言壮志,不是任何心理建设,而是尽快上手去做。
做一件事,无关心态,只要去做就是了。发挥最大的主观能动性,遇到问题就解决,碰到障碍就破开,需要帮助就求助。别因为所谓的自信就盲目冒进,也别因为所谓的自卑就胆小退缩,那俩玩意都不存在。

+

当有了一个目标,需要的不是豪言壮志,不是任何心理建设,而是尽快上手去做。
+做一件事,无关心态,只要去做就是了。发挥最大的主观能动性,遇到问题就解决,碰到障碍就破开,需要帮助就求助。别因为所谓的自信就盲目冒进,也别因为所谓的自卑就胆小退缩,那俩玩意都不存在。

尊重事情本身的客观发展规律,什么样的人匹配什么样的事,什么样的条件匹配什么样的理想。别把心态想得太重要,更别花太多时间精力去建设心态,客观规律不会因为心态就发生奇迹般地转变。

你最好趁早学会尊重客观规律。

diff --git "a/2024/05/20/Rescript-genType-import-\351\227\256\351\242\230/index.html" "b/2024/05/20/Rescript-genType-import-\351\227\256\351\242\230/index.html" index 060d77b2..607c75e2 100644 --- "a/2024/05/20/Rescript-genType-import-\351\227\256\351\242\230/index.html" +++ "b/2024/05/20/Rescript-genType-import-\351\227\256\351\242\230/index.html" @@ -194,20 +194,19 @@

Rescript 11 之后,@genType 被合并进编译器,无需任何依赖就能使用,当在 Rescript 中 @genType 了使用某些 Rescript built-in 的基本类型时,可能会生成有问题的 import 相关代码,例如:

1
2
3
4
5
6
7
8
9
10
@genType
module LoginResponse = {
let status = response => {
response
->Js.Json.decodeObject
->Option.flatMap(response => {
response->Js_dict.get("status")
})
}
}
-

status 函数具有 Js.Json.t => option<Js.Json.t> 类型,那么在生成的 TypeScript 文件中,会出现这样的 import:

1
import type { Json_t as Js_Json_t } from "./Js.gen.tsx"
-

而 Js.gen.tsx 这个文件是不存在的,解决方案是使用 @genType 的 shim:

1
2
3
4
5
6
7
8
9
...
"gentypeconfig": {
...
"shims": {
"Js": "Js"
},
...
}
...
- -
  • 然后新建 Js.shim.ts:

    +
      +
    • 然后新建 Js.shim.ts:
    • +
    1
    2
    3
    4
    5
    export type Json_t = unknown;

    export type t = unknown;

    export type Exn_t = Error;
    -
  • -
  • 删除原来由 @genType 生成的 TypeScript 文件并重新生成

    -
  • +

    现在生成的 TypeScript 文件将会从 Js.shim.ts import 类型:

    1
    import type {Json_t as Js_Json_t} from '../../src/model/Js.shim.ts';
    diff --git "a/2024/06/06/NET-AOT-\344\270\213\347\232\204-F-\345\221\275\344\273\244\350\241\214\345\217\202\346\225\260\350\247\243\346\236\220\345\272\223\351\200\211\346\213\251/index.html" "b/2024/06/06/NET-AOT-\344\270\213\347\232\204-F-\345\221\275\344\273\244\350\241\214\345\217\202\346\225\260\350\247\243\346\236\220\345\272\223\351\200\211\346\213\251/index.html" index 68a607bf..1c605526 100644 --- "a/2024/06/06/NET-AOT-\344\270\213\347\232\204-F-\345\221\275\344\273\244\350\241\214\345\217\202\346\225\260\350\247\243\346\236\220\345\272\223\351\200\211\346\213\251/index.html" +++ "b/2024/06/06/NET-AOT-\344\270\213\347\232\204-F-\345\221\275\344\273\244\350\241\214\345\217\202\346\225\260\350\247\243\346\236\220\345\272\223\351\200\211\346\213\251/index.html" @@ -192,9 +192,12 @@
    -

    Argu 不支持 AOT,不过用 F# 的话可以看整个 .NET 的生态,我看了一下 C# 的 CommandLineParser:
    https://github.com/commandlineparser

    -

    在这个 PR 中支持了 Native AOT
    https://github.com/commandlineparser/commandline/pull/913

    -

    除此之外,还有一个更加精巧的 F# 库可以用,只有两百多行:
    https://github.com/B2R2-org/FsOptParse/

    +

    Argu 不支持 AOT,不过用 F# 的话可以看整个 .NET 的生态,我看了一下 C# 的 CommandLineParser:
    +https://github.com/commandlineparser

    +

    在这个 PR 中支持了 Native AOT
    +https://github.com/commandlineparser/commandline/pull/913

    +

    除此之外,还有一个更加精巧的 F# 库可以用,只有两百多行:
    +https://github.com/B2R2-org/FsOptParse/

    AOT 后的大小很可观,并且支持 full trim.

    用例:

    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
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    (** defines a state to pass to the option parser *)
    type opts =
    {
    optX : int;
    optY : bool;
    optZ : string;
    }

    (** default option state *)
    let defaultOpts =
    {
    optX = 0;
    optY = false;
    optZ = "";
    }

    (*
    An example command line specification, which is a list of Options.
    Each Option describes a command line option (switch) that is specified with
    either a short (a single-dash option) or long option (a double-dash option).
    *)
    let spec =
    [
    (* This option can be specified with -x <NUM>. There is an extra argument to
    specify a value in integer. *)
    Option ((* description of the option *)
    descr="this is a testing param X",
    (* how many extra argument must be provided by a user? *)
    extra=1,
    (* callback sets up the option and returns it *)
    callback=(fun opts arg -> {opts with optX=(int) arg.[0]}),
    (* use a short option style -x *)
    short="-x"
    );

    (* This option can be specified with -y. There is no extra argument. This
    option just sets a flag, optY. *)
    Option ((* description of the option *)
    descr="this is a testing param Y",
    (* set the option to be true *)
    callback=(fun opts _ -> {opts with optY=true}),
    (* use a short option style (-y) *)
    short="-y",
    (* also use a long option style (--yoohoo) *)
    long="--yoohoo"
    );

    (* A dummy option to pretty-print the usage *)
    Option ((* description of the option *)
    descr="",
    dummy=true
    );
    Option ((* description of the option *)
    descr="[Required Options]",
    descrColor=System.ConsoleColor.DarkCyan,
    dummy=true
    );

    (* The third option is a required option. In other words, option parsing
    will raise an exception if this option is not given by a user. This
    option takes in an additional integer argument, and set it to the global
    variable z. *)
    Option ((* description of the option *)
    descr="required parameter <STRING> with an integer option",
    (* callback to set the optZ value *)
    callback=(fun opts arg -> {opts with optZ=arg.[0]}),
    (* specifying this is a required option *)
    required=true,
    (* one additional argument to specify an integer value *)
    extra=1,
    (* use only a long option style *)
    long="--req"
    );
    ]

    let _ =
    let prog = "opttest.fsx"
    let args = System.Environment.GetCommandLineArgs ()
    let usageGetter () = "[Usage]\n %p %o"
    try
    let left, opts = optParse spec usageGetter prog args defaultOpts
    printfn "Rest args: %A, x: %d, y: %b, z: %s"
    left opts.optX opts.optY opts.optZ
    0
    with
    | SpecErr msg ->
    eprintfn "Invalid spec: %s" msg
    exit 1
    | RuntimeErr msg ->
    eprintfn "Invalid args given by user: %s" msg
    usagePrint spec prog usageGetter (fun () -> exit 1)
    diff --git a/2024/06/27/OCaml-News-2024-5/index.html b/2024/06/27/OCaml-News-2024-5/index.html index 198d695e..6b45e5ad 100644 --- a/2024/06/27/OCaml-News-2024-5/index.html +++ b/2024/06/27/OCaml-News-2024-5/index.html @@ -192,58 +192,70 @@
    -

    语言的发展

    diff --git "a/2024/06/27/\344\272\214\343\200\207\344\272\214\345\233\233\345\271\264\345\205\255\346\234\210\344\272\214\345\215\201\344\270\203\346\227\245/index.html" "b/2024/06/27/\344\272\214\343\200\207\344\272\214\345\233\233\345\271\264\345\205\255\346\234\210\344\272\214\345\215\201\344\270\203\346\227\245/index.html" index ebfa85b1..903b3aaf 100644 --- "a/2024/06/27/\344\272\214\343\200\207\344\272\214\345\233\233\345\271\264\345\205\255\346\234\210\344\272\214\345\215\201\344\270\203\346\227\245/index.html" +++ "b/2024/06/27/\344\272\214\343\200\207\344\272\214\345\233\233\345\271\264\345\205\255\346\234\210\344\272\214\345\215\201\344\270\203\346\227\245/index.html" @@ -184,7 +184,11 @@
    -

    钟摆晃荡 走出时间,
    面包屑洒了一地 变成星空,
    玻璃杯留下的牛奶唇印、折射出极光,
    ​狂犬症发病具有潜伏期,
    宇宙说它也会倦怠、凋落,

    +

    钟摆晃荡 走出时间,
    +面包屑洒了一地 变成星空,
    +玻璃杯留下的牛奶唇印、折射出极光,
    +​狂犬症发病具有潜伏期,
    +宇宙说它也会倦怠、凋落,

    身后伟岸的森林倒下了,我最大的遗失是没有了后悔的权利,我的忽视酿成了天地两别再无可相见的结局。

    我的眼睛面积一定小于湖,我也很少哭,你若坐在我面前,就像站在湖边,细细的雾水就扯着地连着天。

    diff --git a/2024/07/01/OCaml-News-2024-4/index.html b/2024/07/01/OCaml-News-2024-4/index.html index 432ce7e8..bf6aabf1 100644 --- a/2024/07/01/OCaml-News-2024-4/index.html +++ b/2024/07/01/OCaml-News-2024-4/index.html @@ -192,115 +192,167 @@
    -

    那么接下来

      -
    1. 十二对脑神经的顺序(一嗅二视三动眼,四滑五叉六外展,七面八听九舌咽,迷副舌下神经全):嗅神经、视神经、动眼神经、滑车神经、三叉神经、展神经、面神经、位听神经、舌咽神经、迷走神经、副神经和舌下神经。 

      +
    2. +

      十二对脑神经的顺序(一嗅二视三动眼,四滑五叉六外展,七面八听九舌咽,迷副舌下神经全):嗅神经、视神经、动眼神经、滑车神经、三叉神经、展神经、面神经、位听神经、舌咽神经、迷走神经、副神经和舌下神经。

    3. -
    4. 脑干病变的特点:交叉性瘫痪、意识障碍、去大脑僵直、定位体征、脊髓。 

      +
    5. +

      脑干病变的特点:交叉性瘫痪、意识障碍、去大脑僵直、定位体征、脊髓。

    6. -
    7. 瞳孔直径约为3-4mm,一般认为瞳孔直径<2mm为瞳孔缩小,>5mm为瞳孔散大。 

      +
    8. +

      瞳孔直径约为3-4mm,一般认为瞳孔直径<2mm为瞳孔缩小,>5mm为瞳孔散大。

    9. -
    10. 正常脑脊液压力:80-180mmH2O。 

      +
    11. +

      正常脑脊液压力:80-180mmH2O。

    12. -
    13. 意识障碍包括:

      +
    14. +

      意识障碍包括:

        -
      • 嗜睡:是最轻的意识障碍,是一种病理性倦睡,患者陷入持续的睡眠状态,可被唤醒,并能正确回答和做出各种反应,但当刺激去除后很快又再入睡。 
      • -
      • 昏睡:较嗜睡重,持续处于睡眠状态,强刺激方能唤醒,应答不切题。 
      • +
      • 嗜睡:是最轻的意识障碍,是一种病理性倦睡,患者陷入持续的睡眠状态,可被唤醒,并能正确回答和做出各种反应,但当刺激去除后很快又再入睡。
      • +
      • 昏睡:较嗜睡重,持续处于睡眠状态,强刺激方能唤醒,应答不切题。
      • 浅昏迷:对针刺和压眶由痛苦表情及躲避反应,无语言应答,各种反射无明显改变。
      • 深昏迷:意识全部丧失,强刺激也不能引起反应,肢体常呈弛缓状失。偶有深反射亢进与病理反射出现。机体仅能维持呼吸与血循环功能。
      • 意识模糊:较嗜睡为深的一种意识障碍,表现为定向障碍,思维和语言不连贯,可有和幻觉、躁动不安、谵语或精神错乱。
      -

       

    15. -
    16. 运动障碍的护理诊断:有失用综合征的危险的护理措施:

      +
    17. +

      运动障碍的护理诊断:有失用综合征的危险的护理措施:

      • 早期康复干预:重视患侧刺激、保持良好的肢体位臵、体位变换(翻身)、床上运动训练(Bobath握手、桥式运动)
      • 恢复期运动训练
      • @@ -219,25 +224,29 @@
        -
      • 急性炎症性脱髓鞘性多神经根病的临床表现中感觉障碍呈手套袜子样分布。    
      • +
      • 急性炎症性脱髓鞘性多神经根病的临床表现中感觉障碍呈手套袜子样分布。
      • 重要特点是蛋白-细胞分离现象。
    18. -
    19. 脑血管疾病的分类

      +
    20. +

      脑血管疾病的分类

      • 依据症状持续时间:短暂性脑缺血发作、脑卒中。
      • 依据病理性质:缺血性卒中、出血性卒中。
      • 依据发病急缓:急性脑血管疾病、慢性脑血管疾病。
    21. -
    22. 定义: 短暂性脑缺血发作(TIA):局造性脑缺血导致突发短暂的可逆性神经功能障碍。

      +
    23. +

      定义: 短暂性脑缺血发作(TIA):局造性脑缺血导致突发短暂的可逆性神经功能障碍。

    24. -
    25. 脑血栓形成(脑血管病中最常见)

      +
    26. +

      脑血栓形成(脑血管病中最常见)

      +
        +
      • 病因:最常见的病因是脑动脉粥样硬化,其次为脑动脉炎。高血压、高脂血症、糖尿病是加速脑血管硬化进展的重要因素。
      • +
      • 检查:头颅CT:24小时后CT可见相应部位低密度影像。
      • +
      • 急性期治疗:
          -
        • 病因:最常见的病因是脑动脉粥样硬化,其次为脑动脉炎。高血压、高脂血症、糖尿病是加速脑血管硬化进展的重要因素。    
        • -
        • 检查:头颅CT:24小时后CT可见相应部位低密度影像。   
        • -
        • 急性期治疗:
          • 早期溶栓:在发病后6小时以内进行溶栓。
          • 调整血压:急性期应维持病人血压于较平时稍高水平,血压过高(收缩压>220mmHg或舒张压>120mmHg)。
          • 抗血小板聚集。
          • @@ -248,73 +257,80 @@
          • 外科治疗:开颅降压术。
          • 早期康复治疗。
          -

            

        • -
        • 用药护理:
            +
          • 用药护理: +
            • 溶栓和抗凝药物:严格掌握药物剂量,监测BT、PT、 APTT,观察有无黑便、牙龈出血、皮肤瘀点瘀斑等出血表现;观察有无并发颅内出血;观察有无栓子脱落所致其他部位栓塞的表现。
            • 甘露醇:监测尿量及尿液颜色;有无头痛、呕吐、意识障碍等低颅压综合征的表现。
            -

              

          • -
          • 饮食护理:
              +
            • 饮食护理: +
              • 体位选择:能坐者坐位进食,头略前屈,不能坐起者将床头摇起30°,头下垫枕头部前屈。
              • 食物选择:食物柔软、密度与性状均一;不易松散有一定黏度;能够变形;不易粘在黏膜上。
              • 吞咽方法选择:空吞咽和吞咽食物交替进行;侧方吞咽:吞咽时头侧向健侧肩部;点头样吞咽。
              • -
              • 不能吞咽的病人给予鼻饲饮食。 
                +
              • 不能吞咽的病人给予鼻饲饮食。 +

                防止窒息:进食前应注意休息;保持进餐环境的安静、舒适;减少进餐时环境中分散注意力的干扰因素。

            -

             

          • -
          • 脑栓塞的病因:根据栓子来源可分为心源性、非心源性和来源不明性。心源性为最常见的原因,其中一半以上病人有风湿性心脏病二尖瓣狭窄合并心房颤动。

            +
          • +

            脑栓塞的病因:根据栓子来源可分为心源性、非心源性和来源不明性。心源性为最常见的原因,其中一半以上病人有风湿性心脏病二尖瓣狭窄合并心房颤动。

          • -
          • 脑出血临床特点:

            +
          • +

            脑出血临床特点:

            • 多见于50岁以上有高血压病史者,男性较女性多见,冬季发病率较高。
            • 体力活动或情绪激动时发病,多无前驱症状。
            • 起病较急,症状于数分钟至数小时达高峰。
            • 有肢体瘫痪、失语等局灶定位症状和剧烈头痛、喷射性呕吐、意识障碍等全脑症状。
            • -
            • 发病时血压明显升高。(“三偏征”对侧偏瘫、偏身感觉障碍和同向性偏盲)。    
              -

              头颅CT:确诊脑出血的首选检查方法,发病后即刻出现边界清楚的高密度影像。    

              +
            • 发病时血压明显升高。(“三偏征”对侧偏瘫、偏身感觉障碍和同向性偏盲)。
            • +
            +
            +

            头颅CT:确诊脑出血的首选检查方法,发病后即刻出现边界清楚的高密度影像。

            治疗要点:治疗原则是脱水降颅压、调整血压、防止继续出血、减轻血肿所致继发性损害、促进神经功> 能恢复、加强护理防治并发症。

              -
            • 一般治疗:卧床休息,密切观察生命体征,保持呼吸道通畅,吸氧,保持肢体的功能位,鼻饲,预防感染,维持水、电解质平衡等。 
            • +
            • 一般治疗:卧床休息,密切观察生命体征,保持呼吸道通畅,吸氧,保持肢体的功能位,鼻饲,预防感染,维持水、电解质平衡等。
            • 脱水降颅压:目的是控制脑水肿,药物:20%甘露醇。
            • -
            • 调控血压:血压≥200/110mmHg时,可采取降压治疗,给予硫酸镁等。
            • +
            • 调控血压:血压≥200/110mmHg时,可采取降压治疗,给予硫酸镁等。
            • 止血和凝血治疗。
            • -
            • 外科治疗:壳核出血量>30ml,小脑或丘脑出血>10ml(6)康复治疗。 
            • +
            • 外科治疗:壳核出血量>30ml,小脑或丘脑出血>10ml(6)康复治疗。
            • 休息与安全:绝对卧床休息2~4周,抬高床头15~30度,减轻脑水肿。
          • -
          -

           

          -
        • -
        • 帕金森病的临床表现:

          +
        • +

          帕金森病的临床表现:

          • 静止性震颤:多从一侧开始,类似“搓丸”样动作,静止时明显震颤,动作时减轻,精神紧张时加重,入睡后消失。
          • 肌强直:“铅管样强直”、“齿轮样强直”。
          • -
          • 运动迟缓:“小写症”“面具脸”。 
          • +
          • 运动迟缓:“小写症”“面具脸”。
          • 姿势步态异常:“慌张步态”。
        • -
        • 癫痫持续状态在给氧、防护的从速制止发作,首先给地西泮10~20mg静脉注射,注射速度不超过每分钟2mg,以免抑制呼吸,在监测血压同时静脉滴入苯妥英钠以控制发作。 

          +
        • +

          癫痫持续状态在给氧、防护的从速制止发作,首先给地西泮10~20mg静脉注射,注射速度不超过每分钟2mg,以免抑制呼吸,在监测血压同时静脉滴入苯妥英钠以控制发作。

        • -
        • 癫痫的护理诊断:

          +
        • +

          癫痫的护理诊断:

          • 防窒息
          • 防受伤:活动状态时发作,陪伴者应立即将病人缓慢臵于平卧位,防止外伤,抽搐肢体不可用力按压,以免造成骨折或关节脱位。
        • -
        • 重症肌无力的临床特点:

          +
        • +

          重症肌无力的临床特点:

            -
          • 是一种与胸腺异常有关的自身免疫性疾病。 
          • -
          • 最先表现为一侧眼睑下垂。 
          • +
          • 是一种与胸腺异常有关的自身免疫性疾病。
          • +
          • 最先表现为一侧眼睑下垂。
          • 肌无力呈进行性发展逐渐累及其他肌肉。
          • -
          • 症状晨轻暮重、活动后重,休息后轻。 
            +
          • 症状晨轻暮重、活动后重,休息后轻。
          • +
          +

          实验室检查:

          • 疲劳试验(Jolly试验):嘱病人用力眨眼30次后眼裂明显变小或两臂持续平举后出现上臂下垂。
          • @@ -322,50 +338,64 @@
        • -
        -

         

        -
      • -
      • 腰椎穿刺术后护理嘱病人去枕平卧4~6小时,不可抬高头部,观察有无并发症,如头痛、腰背痛、脑疝、感染。 

        +
      • +

        腰椎穿刺术后护理嘱病人去枕平卧4~6小时,不可抬高头部,观察有无并发症,如头痛、腰背痛、脑疝、感染。

      • -
      • 意识障碍按程度可分为嗜睡、昏睡、浅昏迷、中昏迷、深昏迷。  

        +
      • +

        意识障碍按程度可分为嗜睡、昏睡、浅昏迷、中昏迷、深昏迷。

      • -
      • 脑出血病人急性期治疗的主要原则是防止再出血、控制脑水肿、减低颅内压、维持生命功能、防治并发症。  

        +
      • +

        脑出血病人急性期治疗的主要原则是防止再出血、控制脑水肿、减低颅内压、维持生命功能、防治并发症。

      • -
      • 根据癫痫发作的临床表现和脑电图特点,可将癫痫分为部分性发作、全面性发作、不能分类的癫痫发作3大类。  

        +
      • +

        根据癫痫发作的临床表现和脑电图特点,可将癫痫分为部分性发作、全面性发作、不能分类的癫痫发作3大类。

        -

        癫痫全面性强直–阵挛发作过程可分为强直期、阵挛期、痉挛后三期。  

        +

        癫痫全面性强直–阵挛发作过程可分为强直期、阵挛期、痉挛后三期。

      • -
      • 诊断癫痫最有价值的检查是脑电图。  

        +
      • +

        诊断癫痫最有价值的检查是脑电图。

      • -
      • 脑动脉粥样硬化是脑血栓形成最常见的病因。  

        +
      • +

        脑动脉粥样硬化是脑血栓形成最常见的病因。

      • -
      • 蛛网膜下腔出血病人应绝对卧床4-6周周,避免用力排便情绪激动等。 

        +
      • +

        蛛网膜下腔出血病人应绝对卧床4-6周周,避免用力排便情绪激动等。

      • -
      • 三偏症指偏瘫,偏盲,偏麻(偏身感觉障碍)。  

        +
      • +

        三偏症指偏瘫,偏盲,偏麻(偏身感觉障碍)。

      • -
      • 吉兰——巴雷的主要危险是呼吸麻痹。  

        +
      • +

        吉兰——巴雷的主要危险是呼吸麻痹。

      • -
      • 脑血管发病最重要的危险因素是高血压,心脏病,糖尿病,TIA。 

        +
      • +

        脑血管发病最重要的危险因素是高血压,心脏病,糖尿病,TIA。

      • -
      • TIA指脑缺血症状24小时内可以完全恢复。  

        +
      • +

        TIA指脑缺血症状24小时内可以完全恢复。

      • -
      • 早期溶栓指发病6小时内进行溶栓处理。  

        +
      • +

        早期溶栓指发病6小时内进行溶栓处理。

      • -
      • 脑组织中豆纹动脉动脉最容易出血。  

        +
      • +

        脑组织中豆纹动脉动脉最容易出血。

      • -
      • 一般20%甘露醇200ml静脉滴注30分钟分钟内滴完。输入甘露醇后4小时内尿量少于200毫升ml要慎用或停用。  

        +
      • +

        一般20%甘露醇200ml静脉滴注30分钟分钟内滴完。输入甘露醇后4小时内尿量少于200毫升ml要慎用或停用。

      • -
      • 蛛网膜下腔出血的特征性体征是脑膜刺激征,特征性实验室检查是脑脊液检查。  

        +
      • +

        蛛网膜下腔出血的特征性体征是脑膜刺激征,特征性实验室检查是脑脊液检查。

      • -
      • 脑血栓形成常在安静时发病,脑出血常在活动及情绪激动时发病。 

        +
      • +

        脑血栓形成常在安静时发病,脑出血常在活动及情绪激动时发病。

      • -
      • 简述癫痫持续状态的护理要点:(1)迅速控制发作(安定10~20mg静脉慢推)(2)用床挡,专人守护,必要时用约束带(3)移开周围物品(4)立即取下假牙、垫牙,保护皮肤,不用力按压病人(5)解开衣领,头偏一侧,保持气道通畅(6)观察生命体征、神志。 

        +
      • +

        简述癫痫持续状态的护理要点:(1)迅速控制发作(安定10~20mg静脉慢推)(2)用床挡,专人守护,必要时用约束带(3)移开周围物品(4)立即取下假牙、垫牙,保护皮肤,不用力按压病人(5)解开衣领,头偏一侧,保持气道通畅(6)观察生命体征、神志。

      • -
      • 名词解释:短暂性脑缺血发作、癫痫、癫痫持续状态、帕金森病。

        +
      • +

        名词解释:短暂性脑缺血发作、癫痫、癫痫持续状态、帕金森病。

    -

      

    diff --git a/2024/07/23/OCaml-News-2024-6/index.html b/2024/07/23/OCaml-News-2024-6/index.html index 45e38913..8947726f 100644 --- a/2024/07/23/OCaml-News-2024-6/index.html +++ b/2024/07/23/OCaml-News-2024-6/index.html @@ -193,91 +193,133 @@
    1
    2
    3
    4
           ^o3
    ~/\_/\_|)
    |/=_=\|
    " "
    - -

    语言的发展

    -

    阿公烧烟成老瘾,
    阿公饮酒会面红,
    他说日子要开心,
    他说人生要尽兴。

    +

    阿公烧烟成老瘾,
    +阿公饮酒会面红,
    +他说日子要开心,
    +他说人生要尽兴。

    diff --git a/2024/09/12/Functional-Reactive-Programming-in-F/index.html b/2024/09/12/Functional-Reactive-Programming-in-F/index.html index 94586aea..b1bc57f8 100644 --- a/2024/09/12/Functional-Reactive-Programming-in-F/index.html +++ b/2024/09/12/Functional-Reactive-Programming-in-F/index.html @@ -209,7 +209,8 @@

    We saw in the previous post on message queues that one of the advantages of that approach was that the requests were “serialized” making it conceptually easier to deal with.

    There is a similar approach that can be used with events. The idea is to turn a series of events into an “event stream”. Event streams then become quite like IEnumerables, and so the obvious next step is to treat them in much the the same way that LINQ handles collections, so that they can be filtered, mapped, split and combined.

    F# has built in support for this model, as well as for the more traditional approach.

    -

    A simple event stream

    Let’s start with a simple example to compare the two approaches. We’ll implement the classic event handler approach first.

    +

    A simple event stream

    +

    Let’s start with a simple example to compare the two approaches. We’ll implement the classic event handler approach first.

    First, we define a utility function that will:

    Here’s the code:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    open System
    open System.Threading

    /// create a timer and register an event handler,
    /// then run the timer for five seconds
    let createTimer timerInterval eventHandler =
    // setup a timer
    let timer = new System.Timers.Timer(float timerInterval)
    timer.AutoReset <- true

    // add an event handler
    timer.Elapsed.Add eventHandler

    // return an async task
    async {
    // start timer...
    timer.Start()
    // ...run for five seconds...
    do! Async.Sleep 5000
    // ... and stop
    timer.Stop()
    }
    -

    Now test it interactively:

    1
    2
    3
    4
    5
    6
    7
    8
    // create a handler. The event args are ignored
    let basicHandler _ = printfn "tick %A" DateTime.Now

    // register the handler
    let basicTimer1 = createTimer 1000 basicHandler

    // run the task now
    Async.RunSynchronously basicTimer1
    -

    Now let’s create a similar utility method to create a timer, but this time it will return an “observable” as well, which is the stream of events.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    let createTimerAndObservable timerInterval =
    // setup a timer
    let timer = new System.Timers.Timer(float timerInterval)
    timer.AutoReset <- true

    // events are automatically IObservable
    let observable = timer.Elapsed

    // return an async task
    let task = async {
    timer.Start()
    do! Async.Sleep 5000
    timer.Stop()
    }

    // return a async task and the observable
    (task,observable)
    -

    And again test it interactively:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    // create the timer and the corresponding observable
    let basicTimer2 , timerEventStream = createTimerAndObservable 1000

    // register that every time something happens on the
    // event stream, print the time.
    timerEventStream
    |> Observable.subscribe (fun _ -> printfn "tick %A" DateTime.Now)

    // run the task now
    Async.RunSynchronously basicTimer2
    -

    The difference is that instead of registering a handler directly with an event, we are “subscribing” to an event stream. Subtly different, and important.

    -

    Counting events

    In this next example, we’ll have a slightly more complex requirement:

    +

    Counting events

    +

    In this next example, we’ll have a slightly more complex requirement:

    1
    2
    Create a timer that ticks every 500ms.
    At each tick, print the number of ticks so far and the current time.
    -

    To do this in a classic imperative way, we would probably create a class with a mutable counter, as below:

    1
    2
    3
    4
    5
    6
    7
    8
    type ImperativeTimerCount() =

    let mutable count = 0

    // the event handler. The event args are ignored
    member this.handleEvent _ =
    count <- count + 1
    printfn "timer ticked with count %i" count
    -

    We can reuse the utility functions we created earlier to test it:

    1
    2
    3
    4
    5
    6
    7
    8
    // create a handler class
    let handler = new ImperativeTimerCount()

    // register the handler method
    let timerCount1 = createTimer 500 handler.handleEvent

    // run the task now
    Async.RunSynchronously timerCount1
    -

    Let’s see how we would do this same thing in a functional way:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    // create the timer and the corresponding observable
    let timerCount2, timerEventStream = createTimerAndObservable 500

    // set up the transformations on the event stream
    timerEventStream
    |> Observable.scan (fun count _ -> count + 1) 0
    |> Observable.subscribe (fun count -> printfn "timer ticked with count %i" count)

    // run the task now
    Async.RunSynchronously timerCount2
    -

    Here we see how you can build up layers of event transformations, just as you do with list transformations in LINQ.

    The first transformation is scan, which accumulates state for each event. It is roughly equivalent to the List.fold function that we have seen used with lists. In this case, the accumulated state is just a counter.

    And then, for each event, the count is printed out.

    Note that in this functional approach, we didn’t have any mutable state, and we didn’t need to create any special classes.

    -

    Merging multiple event streams

    For a final example, we’ll look at merging multiple event streams.

    +

    Merging multiple event streams

    +

    For a final example, we’ll look at merging multiple event streams.

    Let’s make a requirement based on the well-known “FizzBuzz” problem:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    Create two timers, called '3' and '5'. The '3' timer ticks every 300ms and the '5' timer ticks
    every 500ms.

    Handle the events as follows:
    a) for all events, print the id of the time and the time
    b) when a tick is simultaneous with a previous tick, print 'FizzBuzz'
    otherwise:
    c) when the '3' timer ticks on its own, print 'Fizz'
    d) when the '5' timer ticks on its own, print 'Buzz'
    -

    First let’s create some code that both implementations can use.

    We’ll want a generic event type that captures the timer id and the time of the tick.

    1
    type FizzBuzzEvent = {label:int; time: DateTime}
    -

    And then we need a utility function to see if two events are simultaneous. We’ll be generous and allow a time difference of up to 50ms.

    1
    2
    3
    4
    let areSimultaneous (earlierEvent,laterEvent) =
    let {label=_;time=t1} = earlierEvent
    let {label=_;time=t2} = laterEvent
    t2.Subtract(t1).Milliseconds < 50
    -

    In the imperative design, we’ll need to keep track of the previous event, so we can compare them. And we’ll need special case code for the first time, when the previous event doesn’t exist

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    type ImperativeFizzBuzzHandler() =

    let mutable previousEvent: FizzBuzzEvent option = None

    let printEvent thisEvent =
    let {label=id; time=t} = thisEvent
    printf "[%i] %i.%03i " id t.Second t.Millisecond
    let simultaneous = previousEvent.IsSome && areSimultaneous (previousEvent.Value,thisEvent)
    if simultaneous then printfn "FizzBuzz"
    elif id = 3 then printfn "Fizz"
    elif id = 5 then printfn "Buzz"

    member this.handleEvent3 eventArgs =
    let event = {label=3; time=DateTime.Now}
    printEvent event
    previousEvent <- Some event

    member this.handleEvent5 eventArgs =
    let event = {label=5; time=DateTime.Now}
    printEvent event
    previousEvent <- Some event
    -

    Now the code is beginning to get ugly fast! Already we have mutable state, complex conditional logic, and special cases, just for such a simple requirement.

    Let’s test it:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    // create the class
    let handler = new ImperativeFizzBuzzHandler()

    // create the two timers and register the two handlers
    let timer3 = createTimer 300 handler.handleEvent3
    let timer5 = createTimer 500 handler.handleEvent5

    // run the two timers at the same time
    [timer3;timer5]
    |> Async.Parallel
    |> Async.RunSynchronously
    -

    It does work, but are you sure the code is not buggy? Are you likely to accidentally break something if you change it?

    The problem with this imperative code is that it has a lot of noise that obscures the the requirements.

    Can the functional version do better? Let’s see!

    First, we create two event streams, one for each timer:

    1
    2
    let timer3, timerEventStream3 = createTimerAndObservable 300
    let timer5, timerEventStream5 = createTimerAndObservable 500
    -

    Next, we convert each event on the “raw” event streams into our FizzBuzz event type:

    1
    2
    3
    4
    5
    6
    7
    8
    // convert the time events into FizzBuzz events with the appropriate id
    let eventStream3 =
    timerEventStream3
    |> Observable.map (fun _ -> {label=3; time=DateTime.Now})

    let eventStream5 =
    timerEventStream5
    |> Observable.map (fun _ -> {label=5; time=DateTime.Now})
    -

    Now, to see if two events are simultaneous, we need to compare them from the two different streams somehow.

    It’s actually easier than it sounds, because we can:

    Here’s the actual code to do this:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    // combine the two streams
    let combinedStream =
    Observable.merge eventStream3 eventStream5

    // make pairs of events
    let pairwiseStream =
    combinedStream |> Observable.pairwise

    // split the stream based on whether the pairs are simultaneous
    let simultaneousStream, nonSimultaneousStream =
    pairwiseStream |> Observable.partition areSimultaneous
    -

    Finally, we can split the nonSimultaneousStream again, based on the event id:

    1
    2
    3
    4
    5
    6
    7
    // split the non-simultaneous stream based on the id
    let fizzStream, buzzStream =
    nonSimultaneousStream
    // convert pair of events to the first event
    |> Observable.map (fun (ev1,_) -> ev1)
    // split on whether the event id is three
    |> Observable.partition (fun {label=id} -> id=3)
    -

    Let’s review so far. We have started with the two original event streams and from them created four new ones:

    Now all we need to do is attach behavior to each stream:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    //print events from the combinedStream
    combinedStream
    |> Observable.subscribe (fun {label=id;time=t} ->
    printf "[%i] %i.%03i " id t.Second t.Millisecond)

    //print events from the simultaneous stream
    simultaneousStream
    |> Observable.subscribe (fun _ -> printfn "FizzBuzz")

    //print events from the nonSimultaneous streams
    fizzStream
    |> Observable.subscribe (fun _ -> printfn "Fizz")

    buzzStream
    |> Observable.subscribe (fun _ -> printfn "Buzz")
    -

    Let’s test it:

    1
    2
    3
    4
    // run the two timers at the same time
    [timer3;timer5]
    |> Async.Parallel
    |> Async.RunSynchronously
    -

    Here’s all the code in one complete set:

    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
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    // create the event streams and raw observables
    let timer3, timerEventStream3 = createTimerAndObservable 300
    let timer5, timerEventStream5 = createTimerAndObservable 500

    // convert the time events into FizzBuzz events with the appropriate id
    let eventStream3 = timerEventStream3
    |> Observable.map (fun _ -> {label=3; time=DateTime.Now})
    let eventStream5 = timerEventStream5
    |> Observable.map (fun _ -> {label=5; time=DateTime.Now})

    // combine the two streams
    let combinedStream =
    Observable.merge eventStream3 eventStream5

    // make pairs of events
    let pairwiseStream =
    combinedStream |> Observable.pairwise

    // split the stream based on whether the pairs are simultaneous
    let simultaneousStream, nonSimultaneousStream =
    pairwiseStream |> Observable.partition areSimultaneous

    // split the non-simultaneous stream based on the id
    let fizzStream, buzzStream =
    nonSimultaneousStream
    // convert pair of events to the first event
    |> Observable.map (fun (ev1,_) -> ev1)
    // split on whether the event id is three
    |> Observable.partition (fun {label=id} -> id=3)

    //print events from the combinedStream
    combinedStream
    |> Observable.subscribe (fun {label=id;time=t} ->
    printf "[%i] %i.%03i " id t.Second t.Millisecond)

    //print events from the simultaneous stream
    simultaneousStream
    |> Observable.subscribe (fun _ -> printfn "FizzBuzz")

    //print events from the nonSimultaneous streams
    fizzStream
    |> Observable.subscribe (fun _ -> printfn "Fizz")

    buzzStream
    |> Observable.subscribe (fun _ -> printfn "Buzz")

    // run the two timers at the same time
    [timer3;timer5]
    |> Async.Parallel
    |> Async.RunSynchronously
    -

    The code might seem a bit long winded, but this kind of incremental, step-wise approach is very clear and self-documenting.

    Some of the benefits of this style are:

    1
    2
    3
    // debugging code
    //simultaneousStream |> Observable.subscribe (fun e -> printfn "sim %A" e)
    //nonSimultaneousStream |> Observable.subscribe (fun e -> printfn "non-sim %A" e)
    -

    This would be much harder in the imperative version.

    -

    Summary

    Functional Reactive Programming (known as FRP) is a big topic, and we’ve only just touched on it here. I hope this introduction has given you a glimpse of the usefulness of this way of doing things.

    +

    Summary

    +

    Functional Reactive Programming (known as FRP) is a big topic, and we’ve only just touched on it here. I hope this introduction has given you a glimpse of the usefulness of this way of doing things.

    If you want to learn more, see the documentation for the F# Observable module, which has the basic transformations used above. And there is also the Reactive Extensions (Rx) library which shipped as part of .NET 4. That contains many other additional transformations.

    diff --git a/2024/09/15/Advanced-C-binding-using-ocaml-ctypes-and-dune/index.html b/2024/09/15/Advanced-C-binding-using-ocaml-ctypes-and-dune/index.html index 9f8963b8..a825fda7 100644 --- a/2024/09/15/Advanced-C-binding-using-ocaml-ctypes-and-dune/index.html +++ b/2024/09/15/Advanced-C-binding-using-ocaml-ctypes-and-dune/index.html @@ -200,21 +200,26 @@

    I was working on a OCaml binding for libsrt last summer, to add support for SRT real-time input and output to liquidsoap, and came across the need to access the [sys/socket.h](https://pubs.opengroup.org/onlinepubs/7908799/xns/syssocket.h.html) C API.

    I had already decided to use the very elegant [ocaml-ctypes](https://github.com/ocamllabs/ocaml-ctypes) module for the SRT binding so I went with it and created a [ocaml-sys-socket](https://github.com/toots/ocaml-sys-socket) module using it as well. It was a very interesting experience that I would like to describe here!

    -

    ocaml-ctypes

    The idea behind OCaml ctypes is to create a binding against a C library without having to write C code, or as least as possible. The most straight-forward way of using it is via [libffi](https://github.com/libffi/libffi) , providing access to dynamically-loaded libraries.

    +

    ocaml-ctypes

    +

    The idea behind OCaml ctypes is to create a binding against a C library without having to write C code, or as least as possible. The most straight-forward way of using it is via [libffi](https://github.com/libffi/libffi) , providing access to dynamically-loaded libraries.

    The second way of using it is by letting the module generate the basic C stubs required to build and link against a shared library. This is the mode that we’re going to use here. In this mode, the programmer has to describe the C headers of the library they intent to bind to using dedicated OCaml modules, operators and types. From that description, ocaml-ctypes is able to generate the required glue for the binding.

    One advantage of using ocaml-ctypes is that the created bindings make as few assumptions as possible about the OCaml C interfacing API. This is pretty nice, in particular since the OCaml compiler is moving pretty quickly these days (which is awesome!) and also if, perhaps one day, support for multi-core is added to the compiler, which will undoubtedly change the C interface API quite a bit.

    -

    dune

    [dune](https://github.com/ocaml/dune) (formally jbuilder ) is a build system for OCaml projects that has recently raised to much popularity, particularly due to its tight integration with the rest of the OCaml ecosystem, such as [ocamlfind](http://projects.camlcity.org/projects/findlib.html) and [opam](https://opam.ocaml.org/) .

    +

    dune

    +

    [dune](https://github.com/ocaml/dune) (formally jbuilder ) is a build system for OCaml projects that has recently raised to much popularity, particularly due to its tight integration with the rest of the OCaml ecosystem, such as [ocamlfind](http://projects.camlcity.org/projects/findlib.html) and [opam](https://opam.ocaml.org/) .

    My personal motto in programming in general is that “Simple things should be simple, but complex things should be possible”. dune certainly does not fit into that category but, rather, makes some complex things extremely easy to setup. It’s the kind of tool that will make your life incredibly easier when what you intent to do fits well within their workflow but might not be easy to bend to some very specific niche use. We will see one such case below.

    At any rate, it’s been an amazing experience getting to learn how to use dune and the resulting code and build system is remarkably short and elegant, yet very powerful.

    -

    socket.h

    socket.h is the Unix header that describes the C API to various socket operations, IP version 4 and 6 as well as unix file sockets. There is also a windows API mimicking it, which makes most code using it easily portable to windows.

    +

    socket.h

    +

    socket.h is the Unix header that describes the C API to various socket operations, IP version 4 and 6 as well as unix file sockets. There is also a windows API mimicking it, which makes most code using it easily portable to windows.

    Most network-based C libraries refer to socket.h to describe the type of socket that can be used with their API so it’s an important entry point for a lot of network operations and one that would be nice to support as generically as possible in OCaml.

    The catch, though, is that, most likely for historical reasons¹, the POSIX specifications only partially defines some of the required data structures and types, which makes it possible to write C code using them but does not give enough information to write C bindings without having to use the compiler to parse the actual system-specific headers of the running host.

    For instance, here’s how the sockaddr structure is specified:

    -

    The <sys/socket.h> header defines the sockaddr structure that includes at least the following members:sa_family_t sa_family address family
    char sa_data[] socket address (variable-length data)

    +

    The <sys/socket.h> header defines the sockaddr structure that includes at least the following members:sa_family_t sa_family address family
    +char sa_data[] socket address (variable-length data)

    Likewise, here’s what is specified about the size of the socklen_t data type:

    -

    <sys/socket.h> makes available a type, socklen_t, which is an unsigned opaque integral type of length of at least 32 bits.

    +

    <sys/socket.h> makes available a type, socklen_t, which is an unsigned opaque integral type of length of at least 32 bits.

    Thus, in order to know the exact offset of sa_family inside the sockaddr structure or the actual size of a socklen_t integer, one has to include the OS-specific header, parse its definitions for that specific OS and, only then, is it possible to compute that offset or data size. Let’s see how it’s done in our binding now!

    -

    Putting it together

    The C binding requires 4 separate passes:

    +

    Putting it together

    +

    The C binding requires 4 separate passes:

    dune makes each of these steps fairly easy to integrate into the next one, defining compilation elements and binaries to build before moving to the next pass.

    -

    Constants pass

    During that pass, we compute and export all required C values defined in the headers. We also add our own constants, which give us the sizes that the POSIX specifications leave up to the OS. Here’s the OCaml code for it:

    +

    Constants pass

    +

    During that pass, we compute and export all required C values defined in the headers. We also add our own constants, which give us the sizes that the POSIX specifications leave up to the OS. Here’s the OCaml code for it:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    module Def (S : Cstubs.Types.TYPE) = struct
    let af_inet = S.constant "AF_INET" S.int
    let af_inet6 = S.constant "AF_INET6" S.int
    let af_unix = S.constant "AF_UNIX" S.int
    let af_unspec = S.constant "AF_UNSPEC" S.int
    let sa_data_len = S.constant "SA_DATA_LEN" S.int
    let sa_family_len = S.constant "SA_FAMILY_LEN" S.int
    let sock_dgram = S.constant "SOCK_DGRAM" S.int
    let sock_stream = S.constant "SOCK_STREAM" S.int
    let sock_seqpacket = S.constant "SOCK_STREAM" S.int
    let socklen_t_len = S.constant "SOCKLEN_T_LEN" S.int
    let ni_maxserv = S.constant "NI_MAXSERV" S.int
    let ni_maxhost = S.constant "NI_MAXHOST" S.int
    let ni_numerichost = S.constant "NI_NUMERICHOST" S.int
    let ni_numericserv = S.constant "NI_NUMERICSERV" S.int
    end
    -

    Pretty straightforward! Some of these constants are defined by the POSIX headers and some are custom defined for our needs, for instance SOCKLEN_T_LEN . Here’s how they are extracted, using the dune build configuration for [gen_constants_c](https://github.com/toots/ocaml-sys-socket/blob/master/src/sys-socket/generator/gen_constants_c.ml):

    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
    let c_headers = "
    #ifdef _WIN32
    #include <winsock2.h>
    #include <ws2tcpip.h>
    #else
    #include <sys/socket.h>
    #include <sys/un.h>
    #include <netdb.h>
    #endif
    #define SA_DATA_LEN (sizeof(((struct sockaddr*)0)->sa_data))
    #define SA_FAMILY_LEN (sizeof(((struct sockaddr*)0)->sa_family))
    #define SOCKLEN_T_LEN (sizeof(socklen_t))
    #ifndef NI_MAXHOST
    #define NI_MAXHOST 1025
    #endif
    #ifndef NI_MAXSERV
    #define NI_MAXSERV 32
    #endif
    "
    let () =
    let fname = Sys.argv.(1) in
    let oc = open_out_bin fname in
    let format =
    Format.formatter_of_out_channel oc
    in
    Format.fprintf format "%s@\n" c_headers;
    Cstubs.Types.write_c format (module Sys_socket_constants.Def);
    Format.pp_print_flush format ();
    close_out oc
    -

    This OCaml code makes use of ocaml-ctypes to build a binary that exports the OCaml interface defined by Sys_socket_constants.Def . Once compiled, its output looks like this:

    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
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    include Ctypes
    let lift x = x
    open Ctypes_static

    let rec field : type t a. t typ -> string -> a typ -> (a, t) field =
    fun s fname ftype -> match s, fname with
    | View { ty }, _ ->
    let { ftype; foffset; fname } = field ty fname ftype in
    { ftype; foffset; fname }
    | _ -> failwith ("Unexpected field "^ fname)

    let rec seal : type a. a typ -> unit = function
    | Struct { tag; spec = Complete _ } ->
    raise (ModifyingSealedType tag)
    | Union { utag; uspec = Some _ } ->
    raise (ModifyingSealedType utag)
    | View { ty } -> seal ty
    | _ ->
    raise (Unsupported "Sealing a non-structured type")

    type 'a const = 'a
    let constant (type t) name (t : t typ) : t = match t, name with
    | Ctypes_static.Primitive Cstubs_internals.Int, "NI_NUMERICSERV" ->
    8
    | Ctypes_static.Primitive Cstubs_internals.Int, "NI_NUMERICHOST" ->
    2
    | Ctypes_static.Primitive Cstubs_internals.Int, "NI_MAXHOST" ->
    1025
    | Ctypes_static.Primitive Cstubs_internals.Int, "NI_MAXSERV" ->
    32
    | Ctypes_static.Primitive Cstubs_internals.Int, "SOCKLEN_T_LEN" ->
    4
    | Ctypes_static.Primitive Cstubs_internals.Int, "SOCK_STREAM" ->
    1
    | Ctypes_static.Primitive Cstubs_internals.Int, "SOCK_STREAM" ->
    1
    | Ctypes_static.Primitive Cstubs_internals.Int, "SOCK_DGRAM" ->
    2
    | Ctypes_static.Primitive Cstubs_internals.Int, "SA_FAMILY_LEN" ->
    1
    | Ctypes_static.Primitive Cstubs_internals.Int, "SA_DATA_LEN" ->
    14
    | Ctypes_static.Primitive Cstubs_internals.Int, "AF_UNSPEC" ->
    0
    | Ctypes_static.Primitive Cstubs_internals.Int, "AF_UNIX" ->
    1
    | Ctypes_static.Primitive Cstubs_internals.Int, "AF_INET6" ->
    30
    | Ctypes_static.Primitive Cstubs_internals.Int, "AF_INET" ->
    2
    | _, s -> failwith ("unmatched constant: "^ s)

    let enum (type a) name ?typedef ?unexpected (alist : (a * int64) list) =
    match name with
    | s ->
    failwith ("unmatched enum: "^ s)
    -

    The files used to describe how to build this binary using dune are located in a separate [generator](https://github.com/toots/ocaml-sys-socket/tree/master/src/sys-socket/generator) directory. Here’s the entry to build this one:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    (executable
    (name gen_constants_c)
    (modules gen_constants_c)
    (libraries sys-socket.constants ctypes.stubs))

    (rule
    (targets gen_constants.c)
    (deps (:gen ./gen_constants_c.exe))
    (action (run %{gen} %{targets})))

    (rule
    (targets gen_constants_c)
    (deps (:c_code ./gen_constants.c))
    (action (run %{ocaml-config:c_compiler} -I %{lib:ctypes:} -I %{ocaml-config:standard_library} -o %{targets} %{c_code})))
    -

    This executable is compiled during the next phase. Let’s move into it now!

    -

    Types pass

    During that phase, we use the constants exported during the previous phase to describe the various C structures and types. This is by far the most complex part of the code, making use of first-class modules and several OCaml tricks.

    +

    Types pass

    +

    During that phase, we use the constants exported during the previous phase to describe the various C structures and types. This is by far the most complex part of the code, making use of first-class modules and several OCaml tricks.

    First, let’s look at how we tell dune that we need to generate the .ml file exporting our required constants from the previous pass:

    1
    2
    3
    4
    5
    6
    (rule
    (targets sys_socket_generated_constants.ml)
    (deps (:exec ../generator/exec.sh)
    (:gen ../generator/gen_constants_c))
    (action (with-stdout-to %{targets}
    (system "%{exec} %{ocaml-config:system} %{gen}"))))
    -

    With only this information, if the code refers to a Sys_socket_generated_constants module, dune will know that this module needs to be generated and how to do it. We will explain later the use of the exec.sh wrapper here.

    Now that we can make use of the exported constants in our OCaml code, let’s see how we define the Socklen module, exporting abstract types and interface to use socklen_t integers:

    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
    module Constants = Sys_socket_constants.Def(Sys_socket_generated_constants)

    module type Socklen = functor (S : Cstubs.Types.TYPE) -> sig
    type socklen
    val socklen_t : socklen S.typ
    val int_of_socklen : socklen -> int
    val socklen_of_int : int -> socklen
    end

    let socklen : (module Socklen) =
    match Constants.socklen_t_len with
    | 4 -> (module functor (S : Cstubs.Types.TYPE) -> struct
    type socklen = Unsigned.uint32
    let socklen_t = S.uint32_t
    let int_of_socklen = Unsigned.UInt32.to_int
    let socklen_of_int = Unsigned.UInt32.of_int
    end)
    | 8 -> (module functor (S : Cstubs.Types.TYPE) -> struct
    type socklen = Unsigned.uint64
    let socklen_t = S.uint64_t
    let int_of_socklen = Unsigned.UInt64.to_int
    let socklen_of_int = Unsigned.UInt64.of_int
    end)
    | _ -> assert false

    module Socklen = (val socklen : Socklen)
    -

    As you can see, we make use of first-order modules and the size of the socklen_t integer to define the right API for the compiling host. Now let’s see how we define the sockaddr interface:

    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
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    module type SaFamily = sig
    type sa_family
    val int_of_sa_family : sa_family -> int
    val sa_family_of_int : int -> sa_family

    module T : functor (S : Cstubs.Types.TYPE) -> sig
    val t : sa_family S.typ
    end
    end

    let saFamily : (module SaFamily) =
    match Constants.sa_family_len with
    | 1 -> (module struct
    type sa_family = Unsigned.uint8
    let int_of_sa_family = Unsigned.UInt8.to_int
    let sa_family_of_int = Unsigned.UInt8.of_int
    module T (S : Cstubs.Types.TYPE) = struct
    let t = S.uint8_t
    end
    end)
    ...

    module SaFamily = (val saFamily : SaFamily)

    module Def (S : Cstubs.Types.TYPE) = struct
    include Constants

    include Socklen(S)

    include SaFamily

    module SaFamilyT = SaFamily.T(S)

    let sa_family_t = S.typedef SaFamilyT.t "sa_family_t"

    module Sockaddr = struct
    type t = unit
    let t = S.structure "sockaddr"
    let sa_family = S.field t "sa_family" sa_family_t
    let sa_data = S.field t "sa_data" (S.array sa_data_len S.char)
    let () = S.seal t
    end

    ...
    end
    -

    Here, too, we make use of the size of sa_family as exported previously to define the right structure fields.

    Next step, we need to compile this interface again to export the right offset for the various structures that have been defined. That’s dune’s job again!

    First, the generator code:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    let c_headers = "
    #ifdef _WIN32
    #include <winsock2.h>
    #include <ws2tcpip.h>
    #else
    #include <sys/socket.h>
    #include <sys/un.h>
    #include <netinet/in.h>
    #include <netdb.h>
    #endif
    "

    let () =
    let fname = Sys.argv.(1) in
    let oc = open_out_bin fname in
    let format =
    Format.formatter_of_out_channel oc
    in
    Format.fprintf format "%s@\n" c_headers;
    Cstubs.Types.write_c format (module Sys_socket_types.Def);
    Format.pp_print_flush format ();
    close_out oc
    -

    And the build instructions:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    (executable
    (name gen_types_c)
    (modules gen_types_c)
    (libraries sys-socket.types ctypes.stubs))

    (rule
    (targets gen_types.c)
    (deps (:gen ./gen_types_c.exe))
    (action (run %{gen} %{targets})))

    (rule
    (targets gen_types_c)
    (deps (:c_code ./gen_types.c))
    (action (run %{ocaml-config:c_compiler} -I %{lib:ctypes:} -I %{ocaml-config:standard_library} -o %{targets} %{c_code})))
    -

    Once, compiled, the exported .ml looks like this:

    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
    32
    33
    34
    35
    36
    37
    38
    39
    40
    include Ctypes
    let lift x = x
    open Ctypes_static

    let rec field : type t a. t typ -> string -> a typ -> (a, t) field =
    fun s fname ftype -> match s, fname with
    ...
    | Struct ({ tag = "sockaddr"} as s'), "sa_data" ->
    let f = {ftype; fname; foffset = 2} in
    (s'.fields <- BoxedField f :: s'.fields; f)
    | Struct ({ tag = "sockaddr"} as s'), "sa_family" ->
    let f = {ftype; fname; foffset = 1} in
    (s'.fields <- BoxedField f :: s'.fields; f)
    | View { ty }, _ ->
    let { ftype; foffset; fname } = field ty fname ftype in
    { ftype; foffset; fname }
    | _ -> failwith ("Unexpected field "^ fname)

    let rec seal : type a. a typ -> unit = function
    ...
    | Struct ({ tag = "sockaddr_storage"; spec = Incomplete _ } as s') ->
    s'.spec <- Complete { size = 128; align = 8 }
    | Struct ({ tag = "sockaddr"; spec = Incomplete _ } as s') ->
    s'.spec <- Complete { size = 16; align = 1 }
    | Struct { tag; spec = Complete _ } ->
    raise (ModifyingSealedType tag)
    | Union { utag; uspec = Some _ } ->
    raise (ModifyingSealedType utag)
    | View { ty } -> seal ty
    | _ ->
    raise (Unsupported "Sealing a non-structured type")

    type 'a const = 'a
    let constant (type t) name (t : t typ) : t = match t, name with
    | _, s -> failwith ("unmatched constant: "^ s)

    let enum (type a) name ?typedef ?unexpected (alist : (a * int64) list) =
    match name with
    | s ->
    failwith ("unmatched enum: "^ s)
    -

    As you can see, this exports all the offsets required to access the fields inside a sockaddr_t structure. We’re now ready to move to the final stage, which is the actual binding stubs!

    -

    Binding stubs

    First step in this pass, just like with the previous ones, we need to configure dune to be able to build the exported .ml code from the types pass:

    +

    Binding stubs

    +

    First step in this pass, just like with the previous ones, we need to configure dune to be able to build the exported .ml code from the types pass:

    1
    2
    3
    4
    5
    6
    (rule
    (targets sys_socket_generated_types.ml)
    (deps (:exec ../generator/exec.sh)
    (:gen ../generator/gen_types_c))
    (action (with-stdout-to %{targets}
    (system "%{exec} %{ocaml-config:system} %{gen}"))))
    -

    And we can now define the proper bindings. Here’s how it looks like:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    open Ctypes

    module Def (F : Cstubs.FOREIGN) = struct
    open F

    module Types = Sys_socket_types.Def(Sys_socket_generated_types)

    open Types

    let getnameinfo = foreign "getnameinfo" (ptr sockaddr_t @-> socklen_t @-> ptr char @-> socklen_t @-> ptr char @-> socklen_t @-> int @-> (returning int))

    ...
    end
    - -

    As you can see, we’re exporting the getnameinfo function, taking various arguments, including a pointer to a sockaddr_t structure and a couple of socklen_t integers, making use of all the various data types and structures previously defined. The exact specifications of this function can be found here. We can now define out top-level API..

    -

    Final API

    Building upon the previous modules, we export various OCaml idiomatic APIs that the binding user can now use to build new bindings against the socket.h APIs.

    +

    As you can see, we’re exporting the getnameinfo function, taking various arguments, including a pointer to a sockaddr_t structure and a couple of socklen_t integers, making use of all the various data types and structures previously defined. The exact specifications of this function can be found here. We can now define out top-level API…

    +

    Final API

    +

    Building upon the previous modules, we export various OCaml idiomatic APIs that the binding user can now use to build new bindings against the socket.h APIs.

    Just like with the previous steps, first we need to configure the build system:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    (rule
    (targets sys_socket_generated_stubs.ml)
    (deps (:gen ./generator/gen_stubs.exe))
    (action (run %{gen} ml %{targets})))

    (rule
    (targets sys_socket_generated_stubs.c)
    (deps (:gen ./generator/gen_stubs.exe))
    (action (run %{gen} c %{targets})))
    -

    This time, we need ocaml-ctypes to generate two compilation units: a .ml file describing the API exported during the stubs phase, as well as the C code to glue it with the C APIs. Here’s the code for that generator:

    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
    let c_headers = "
    #ifdef _WIN32
    #include <winsock2.h>
    #include <ws2tcpip.h>
    #else
    #include <sys/socket.h>
    #include <netinet/in.h>
    #include <arpa/inet.h>
    #include <netdb.h>
    #endif
    #include <string.h>
    "

    let () =
    let mode = Sys.argv.(1) in
    let fname = Sys.argv.(2) in
    let oc = open_out_bin fname in
    let format =
    Format.formatter_of_out_channel oc
    in
    let fn =
    match mode with
    | "ml" -> Cstubs.write_ml
    | "c" ->
    Format.fprintf format "%s@\n" c_headers;
    Cstubs.write_c
    | _ -> assert false
    in
    fn ~concurrency:Cstubs.unlocked format ~prefix:"sys_socket" (module Sys_socket_stubs.Def);
    Format.pp_print_flush format ();
    close_out oc
    -

    The exported .ml and .c files are omitted here for simplicity but the reader can generated them themselves from the [ocaml-sys-socket](https://github.com/toots/ocaml-sys-socket) repository if they are curious about their actual content.

    We can now export our top-level API:

    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
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    open Ctypes

    include Sys_socket_types.SaFamily

    include Sys_socket_stubs.Def(Sys_socket_generated_stubs)

    type socklen = Types.socklen
    let socklen_t = Types.socklen_t
    let int_of_socklen = Types.int_of_socklen
    let socklen_of_int = Types.socklen_of_int

    module Sockaddr = struct
    include Types.Sockaddr
    let from_sockaddr_storage = from_sockaddr_storage t
    let sa_data_len = Types.sa_data_len
    end

    let getnameinfo sockaddr_ptr =
    let maxhost = Types.ni_maxhost in
    let s = allocate_n char ~count:maxhost in
    let maxserv = Types.ni_maxserv in
    let p = allocate_n char ~count:maxserv in
    match getnameinfo sockaddr_ptr (socklen_of_int (sizeof sockaddr_t))
    s (socklen_of_int maxhost)
    p (socklen_of_int maxserv)
    (Types.ni_numerichost lor
    Types.ni_numericserv) with
    | 0 ->
    let host =
    let length =
    Unsigned.Size_t.to_int
    (strnlen s (Unsigned.Size_t.of_int maxhost))
    in
    string_from_ptr s ~length
    in
    let port =
    let length =
    Unsigned.Size_t.to_int
    (strnlen p (Unsigned.Size_t.of_int maxserv))
    in
    let port =
    string_from_ptr p ~length
    in
    try
    int_of_string port
    with _ ->
    match getservbyname p null with
    | ptr when is_null ptr -> failwith "getnameinfo"
    | ptr ->
    Unsigned.UInt16.to_int
    (ntohs (!@ (ptr |-> Types.Servent.s_port)))
    in
    host, port
    | _ -> failwith "getnameinfo"

    ...
    -
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    open Ctypes

    (** Ctypes routines for C type socklen_t. *)
    type socklen
    val socklen_t : socklen typ
    val int_of_socklen : socklen -> int
    val socklen_of_int : int -> socklen

    (** Generic sockaddr_t structure. *)
    module Sockaddr : sig
    type t
    val t : t structure typ
    val sa_family : (sa_family, t structure) field
    val sa_data : (char carray, t structure) field
    val sa_data_len : int

    val from_sockaddr_storage : SockaddrStorage.t structure ptr -> t structure ptr
    end

    (** IP address conversion functions. *)
    val getnameinfo : sockaddr ptr -> string * int

    ...
    -

    That’s it! We now have ocaml-ctypes specific data types and structures that can be used to interface with the host’s native socket.h APIs. Note that we also worked on top of the original low-level binding to getnameinfo to export a higher-level function more idiomatic to the OCaml language.

    -

    Lagniappe: cross-compilation to Windows

    On windows platforms, liquidsoap is compiled using [ocaml-cross-windows](https://github.com/ocaml-cross/opam-cross-windows) and, since windows does have compatible socket APIs, we wanted to also look at cross-compiling for the windows target, which is where we hit a snag on the current dune support.

    +

    Lagniappe: cross-compilation to Windows

    +

    On windows platforms, liquidsoap is compiled using [ocaml-cross-windows](https://github.com/ocaml-cross/opam-cross-windows) and, since windows does have compatible socket APIs, we wanted to also look at cross-compiling for the windows target, which is where we hit a snag on the current dune support.

    The problem is that, at each intermediary steps, in the case of a cross-compilation, the compiled binaries need to use the target’s OS headers and not the host’s headers, otherwise we end up using offsets specific to e.g. Debian but for a windows binary.

    In this case, this means that the compiled .exe binaries need to be windows binaries and that we need to execute them as windows native binaries, using [wine](https://www.winehq.org/) .

    dune has a truly amazing support for cross-compiling, which we do not cover here, but, unfortunately, its primitives for building and executing binaries do not yet cover this use case. Thus we had to trick it into compiling things the way we wanted to do, which why we are using the exec.sh wrapper. Here’s its code:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    #!/bin/sh

    SYSTEM=$1
    CMD=$2
    ARG=$3

    if test "${SYSTEM}" = "mingw"; then
    wine $CMD $ARG
    elif test "${SYSTEM}" = "mingw64"; then
    wine64 $CMD $ARG
    else
    $CMD $ARG
    -

    Now, you can go back to the previous dune files and see how this wrapper allows to execute binaries according to the system that the corresponding ocamlopt compiler has been configured to build for.

    -

    Conclusion

    It’s been a fun time working on this binding! It’s amazing to see the level of details that can be built through ocaml-ctypes using their provided primitives. Ultimately, the binding is very clean and elegant, with very few low-level assumptions.

    +

    Conclusion

    +

    It’s been a fun time working on this binding! It’s amazing to see the level of details that can be built through ocaml-ctypes using their provided primitives. Ultimately, the binding is very clean and elegant, with very few low-level assumptions.

    Likewise, the simplicity and power of the dune build system makes this very fluid to build. Without it, each of the described steps above would have been much more painful to execute and compile.

    -

    [1]: My bet is that, at the time the POSIX specifications were being written, there we already several inconsistent socket.h headers out in the wild among the various historical UNIX flavors..

    +

    [1]: My bet is that, at the time the POSIX specifications were being written, there we already several inconsistent socket.h headers out in the wild among the various historical UNIX flavors…

    diff --git "a/2024/09/15/\350\202\251\345\221\250\347\202\216\345\222\214\350\202\251\350\242\226\346\215\237\344\274\244\347\232\204\345\214\272\345\210\253/index.html" "b/2024/09/15/\350\202\251\345\221\250\347\202\216\345\222\214\350\202\251\350\242\226\346\215\237\344\274\244\347\232\204\345\214\272\345\210\253/index.html" index 1c666b0b..16e591a2 100644 --- "a/2024/09/15/\350\202\251\345\221\250\347\202\216\345\222\214\350\202\251\350\242\226\346\215\237\344\274\244\347\232\204\345\214\272\345\210\253/index.html" +++ "b/2024/09/15/\350\202\251\345\221\250\347\202\216\345\222\214\350\202\251\350\242\226\346\215\237\344\274\244\347\232\204\345\214\272\345\210\253/index.html" @@ -204,15 +204,20 @@

    肩袖损伤是由退行性病变或外力等导致肩袖的4块肌肉、肌腱发生病变,进而导致肩关节局部疼痛、活动受限的疾病。

    许多肩袖损伤患者无明确的外伤史,而是由长期做过顶运动、提重物或上肢长期固定于一个姿势引起的。

    diff --git "a/2024/09/27/Turborepo-\347\256\200\350\277\260/index.html" "b/2024/09/27/Turborepo-\347\256\200\350\277\260/index.html" index 91218d7a..505b2d46 100644 --- "a/2024/09/27/Turborepo-\347\256\200\350\277\260/index.html" +++ "b/2024/09/27/Turborepo-\347\256\200\350\277\260/index.html" @@ -192,23 +192,27 @@
    -

    Turborepo 简介

    Monorepos 有很多优势,但它们难以扩展。每个工作区都有自己的测试套件、自己的 linting 和构建过程。单个 monorepo 可能有数千个任务要执行。

    +

    Turborepo 简介

    +

    Monorepos 有很多优势,但它们难以扩展。每个工作区都有自己的测试套件、自己的 linting 和构建过程。单个 monorepo 可能有数千个任务要执行。

    Turborepo 是一个专为 JavaScript 和 TypeScript 代码库设计的构建系统,旨在优化 monorepos 和 single-package workspace 中的任务。它通过远程缓存(remote caching)和高效的任务调度(task scheduling)来解决 monorepos 中的扩展问题。Turborepo 也可以增量部署(adopted incrementally),并与各种包管理器配合使用。

    turbo 基于 workspace 构建,workspaces 是 JavaScript 生态系统中包管理器的一项功能,允许将多个包分组到一个存储库中:

    -

    Workspace

    在 JavaScript 中,Workspace 是指仓库中的特定实体,可以是单个包或包的集合。
    包管理器的 root lock 文件(例如 pnpm-lock.yaml)以及任何其他配置都位于 Wrokspace 的根目录。在 Monorepo 中可以有多个工作区,每个工作区位于存储库的子目录中。

    -

    Single-package workspace

    只有一个独立包的工作区,在工作区根目录下有一个 package.json 文件。

    -

    Multi-package workspace

    包含多个包的工作区,包含多个 package.json 文件,其中一个位于工作区根目录中用于全局配置,其他位于每个包目录中。

    +

    Workspace

    +

    在 JavaScript 中,Workspace 是指仓库中的特定实体,可以是单个包或包的集合。
    +包管理器的 root lock 文件(例如 pnpm-lock.yaml)以及任何其他配置都位于 Wrokspace 的根目录。在 Monorepo 中可以有多个工作区,每个工作区位于存储库的子目录中。

    +

    Single-package workspace

    +

    只有一个独立包的工作区,在工作区根目录下有一个 package.json 文件。

    +

    Multi-package workspace

    +

    包含多个包的工作区,包含多个 package.json 文件,其中一个位于工作区根目录中用于全局配置,其他位于每个包目录中。

    这种类型的工作区通常称为 monorepo


    以 npm 为例,turbo 会初始化一个这样的目录结构使其成为有效的 workspace:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    |- package.json
    |- package-lock.json
    |- turbo.json
    |- apps
    |-- docs
    |--- package.json
    |-- web
    |--- package.json
    |- packages
    |--- ui
    -

    一个 “有效的” turbo 项目至少要有:

    例如,在根目录的 package.json 中配置:

    1
    2
    3
    4
    5
    6
    {
    "workspaces": [
    "apps/*",
    "packages/*"
    ]
    }
    -

    那么 apps 或 packages 目录中有 package.json 的每个目录都将被视为一个包。

    注意:Turborepo 不支持嵌套包,例如 apps/ 或 packages/ 这种,将一个包放在apps/a 并将另一个包放在 apps/a/b 的结构将导致错误。

    @@ -227,10 +230,10 @@

    根目录的 package.json 是 workspace 的基础,常见的配置:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    {
    "private": true,
    "scripts": {
    "build": "turbo run build",
    "dev": "turbo run dev",
    "lint": "turbo run lint"
    },
    "devDependencies": {
    "turbo": "latest"
    },
    "packageManager": "npm@10.0.0"
    }
    -

    而根目录的 turbo.json 用于配置 turbo 的行为。那些 lock 文件是包管理器和 turbo 用于 reproducible 的关键。此外,Turborepo 还利用它们分析工作区中内部包之间的依赖关系。


    -

    包中的 package.json

    name 字段用于标识包。它在 workspace 中应该是唯一的。

    +

    包中的 package.json

    +

    name 字段用于标识包。它在 workspace 中应该是唯一的。

    最佳做法是为内部包使用命名空间前缀,以避免与 npm 注册表上的其他包发生冲突。例如,如果组织名为 clin,则可以将包命名为 @clin/package-name。

    @@ -238,10 +241,8 @@

    exports 字段用于指定要使用该包的其他包的入口点。如果要在另一个包中使用一个包中的代码,将从该入口点导入。

    例如,如果有一个 @repo/math 包,则可以这么写 exports 字段:

    1
    2
    3
    4
    5
    6
    7
    {
    "exports": {
    ".": "./dist/constants.ts",
    "./add": "./dist/add.ts",
    "./subtract": "./dist/subtract.ts"
    }
    }
    -

    然后就可以从 @repo/math 包中导入 add 和 subtract 函数了:

    1
    2
    3
    import { GRAVITATIONAL_CONSTANT, SPEED_OF_LIGHT } from '@repo/math';
    import { add } from '@repo/math/add';
    import { subtract } from '@repo/math/subtract';
    -

    以这种方式使用导出有三个主要好处:

    diff --git "a/2024/10/06/Obsidian-Vault-\347\232\204-obsidian-\347\233\256\345\275\225\344\270\255\347\232\204\345\220\204\346\226\207\344\273\266\344\275\234\347\224\250/index.html" "b/2024/10/06/Obsidian-Vault-\347\232\204-obsidian-\347\233\256\345\275\225\344\270\255\347\232\204\345\220\204\346\226\207\344\273\266\344\275\234\347\224\250/index.html" index 55d63647..0be6fce9 100644 --- "a/2024/10/06/Obsidian-Vault-\347\232\204-obsidian-\347\233\256\345\275\225\344\270\255\347\232\204\345\220\204\346\226\207\344\273\266\344\275\234\347\224\250/index.html" +++ "b/2024/10/06/Obsidian-Vault-\347\232\204-obsidian-\347\233\256\345\275\225\344\270\255\347\232\204\345\220\204\346\226\207\344\273\266\344\275\234\347\224\250/index.html" @@ -194,29 +194,30 @@

    一个简单的例子是:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    .obsidian
    ├── appearance.json
    ├── app.json
    ├── community-plugins.json
    ├── core-plugins.json
    ├── core-plugins-migration.json
    ├── plugins
    │   └── obsidian-git
    │   ├── data.json
    │   ├── main.js
    │   ├── manifest.json
    │   └── styles.css
    └── workspace.json
    -

    其中:

    1
    2
    3
    4
    5
    6
    7
    8
    {
     "interfaceFontFamily": "HarmonyOS Sans SC",
     "textFontFamily": "HarmonyOS Sans SC",
     "monospaceFontFamily": "FrankMono",
     "accentColor": "",
     "cssTheme": "Listive",
     "nativeMenus": false
    }
    - -
  • app.json 包含了编辑器的一些设置,例如 vim mode 和断行设置:

    +
      +
    • app.json 包含了编辑器的一些设置,例如 vim mode 和断行设置:
    • +
    1
    2
    3
    4
    5
    {
     "vimMode": true,
     "strictLineBreaks": false,
     "promptDelete": false
    }
    -
  • -
  • community-plugins.json:包含有关当前 vault 中安装的社区插件的数据,例如:

    +
      +
    • community-plugins.json:包含有关当前 vault 中安装的社区插件的数据,例如:
    • +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    [
     "dataview",
     "novel-word-count",
     "remotely-save",
     "obsidian-tasks-plugin",
     "obsidian-style-settings",
     "obsidian-advanced-slides",
     "calendar",
     "3d-graph-new",
     "webpage-html-export"
    ]
    -
  • -
  • core-plugins.json: 就是当前 valut 中的所有内置插件,例如:

    +
      +
    • core-plugins.json: 就是当前 valut 中的所有内置插件,例如:
    • +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
     "file-explorer",
     "global-search",
     "switcher",
     "graph",
     "backlink",
     "canvas",
     "outgoing-link",
     "tag-pane",
     "page-preview",
     "daily-notes",
     "templates",
     "note-composer",
     "command-palette",
     "editor-status",
     "bookmarks",
     "outline",
     "word-count",
     "file-recovery"
    ]
    - -
      - `core-plugins-migration.json`: 就是 `core-plugins.json` 中的内置插件的开关状态,例如:
    +
    	- `core-plugins-migration.json`: 就是 `core-plugins.json` 中的内置插件的开关状态,例如:
     
    -
  • -
    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
    {
     "file-explorer": true,
     "global-search": true,
     "switcher": true,
     "graph": true,
     "backlink": true,
     "canvas": true,
     "outgoing-link": true,
     "tag-pane": true,
     "properties": false,
     "page-preview": true,
     "daily-notes": true,
     "templates": true,
     "note-composer": true,
     "command-palette": true,
     "slash-command": false,
     "editor-status": true,
     "bookmarks": true,
     "markdown-importer": false,
     "zk-prefixer": false,
     "random-note": false,
     "outline": true,
     "word-count": true,
     "slides": false,
     "audio-recorder": false,
     "workspaces": false,
     "file-recovery": true,
     "publish": false,
     "sync": false
    }
      -
    • workspace.json:包含有关当前 Vault 工作区的数据,包括打开的文件、窗口和布局的设置,例如:
      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
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      49
      {
       "main": {
         "id": "7bc59326f5497d4a",
         "type": "split",
         "children": [
           {
             "id": "06fc674d29eb4c3a",
             "type": "tabs",
             "children": [
               {
                 "id": "97d62f14e2fad2f0",
                 "type": "leaf",
                 "state": {
                   "type": "markdown",
                   "state": {
                     "file": "README.md",
                     "mode": "source",
                     "source": false
                   }
                 }
               },
               {
                 "id": "c94063b5e5f6f995",
                 "type": "leaf",
                 "state": {
                   "type": "markdown",
                   "state": {
                     "file": "README.md",
                     "mode": "source",
                     "source": false
                   }
                 }
               },
               {
                 "id": "143d5ea7a56d7e1d",
                 "type": "leaf",
                 "state": {
                   "type": "markdown",
                   "state": {
                     "file": "测试.md",
                     "mode": "source",
                     "source": false
                   }
                 }
               }
             ],
             "currentTab": 2
           }
      ...
    • +
    • workspace.json:包含有关当前 Vault 工作区的数据,包括打开的文件、窗口和布局的设置,例如:
    +
    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
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    {
     "main": {
       "id": "7bc59326f5497d4a",
       "type": "split",
       "children": [
         {
           "id": "06fc674d29eb4c3a",
           "type": "tabs",
           "children": [
             {
               "id": "97d62f14e2fad2f0",
               "type": "leaf",
               "state": {
                 "type": "markdown",
                 "state": {
                   "file": "README.md",
                   "mode": "source",
                   "source": false
                 }
               }
             },
             {
               "id": "c94063b5e5f6f995",
               "type": "leaf",
               "state": {
                 "type": "markdown",
                 "state": {
                   "file": "README.md",
                   "mode": "source",
                   "source": false
                 }
               }
             },
             {
               "id": "143d5ea7a56d7e1d",
               "type": "leaf",
               "state": {
                 "type": "markdown",
                 "state": {
                   "file": "测试.md",
                   "mode": "source",
                   "source": false
                 }
               }
             }
           ],
           "currentTab": 2
         }
    ...
    diff --git a/2024/10/18/10-Tips-for-Productive-FSharp-Scripting/index.html b/2024/10/18/10-Tips-for-Productive-FSharp-Scripting/index.html index 544f7f2b..791be5f7 100644 --- a/2024/10/18/10-Tips-for-Productive-FSharp-Scripting/index.html +++ b/2024/10/18/10-Tips-for-Productive-FSharp-Scripting/index.html @@ -202,30 +202,29 @@

    Note: these tips are not necessarily ordered by usefulness. For that matter, there might or might not be exactly 10 of them :)

    -

    Tip 1: Use .fsx Files for Interactive Coding

    You can use the F# Interactive 2 ways: you can directly type code into FSI, the F# Interactive window, or you can write code in an .fsx file, and select pieces of the code you want to execute. I recommend the second approach, for at least two reasons. First, FSI is a very primitive environment, .fsx files provide a much richer experience (IntelliSense). Then this encourages writing clean scripts you can reuse later.

    +

    Tip 1: Use .fsx Files for Interactive Coding

    +

    You can use the F# Interactive 2 ways: you can directly type code into FSI, the F# Interactive window, or you can write code in an .fsx file, and select pieces of the code you want to execute. I recommend the second approach, for at least two reasons. First, FSI is a very primitive environment, .fsx files provide a much richer experience (IntelliSense). Then this encourages writing clean scripts you can reuse later.

    This is not specific to scripts, but… if you are on Visual Studio, do yourself a service and install the Visual F# Power Tools - you’ll get nice things such as better code highlighting, refactoring, and more.

    To execute code interactively, simply type code in an .fsx file, select a block of code, and hit Alt + Enter. The selected code will be evaluated, and the result will show up in the FSI window. In Visual Studio, you can also select code and right-click “Execute in Interactive”, but shortcuts are way faster.

    -

    You can also execute a single-line with Alt + ‘. I rarely use this option, but this can save you time because you don’t need to select the entire line of code.

    +

    You can also execute a single-line with Alt + '. I rarely use this option, but this can save you time because you don’t need to select the entire line of code.

    -

    In case the keyboard shortcuts to send code to FSI do not work anymore (ReSharper used to over-write them in the past), you can reset them in Visual Studio, by going to Tools / Options / Environment / Keyboard. The 2 commands you need to map are EditorContextMenus.CodeWindow.ExecuteInInteractive and EditorContextMenus.CodeWindow.ExecuteLineInInteractive.

    +

    In case the keyboard shortcuts to send code to FSI do not work anymore (ReSharper used to over-write them in the past), you can reset them in Visual Studio, by going to Tools / Options / Environment / Keyboard. The 2 commands you need to map are EditorContextMenus.CodeWindow.ExecuteInInteractive and EditorContextMenus.CodeWindow.ExecuteLineInInteractive.

    You can also use these shortcuts from a regular .fs file, which can be handy if you want to validate that a piece of code is behaving the way you want.

    Interactive coding is by far my main usage for scripts - I use it extensively to prototype designs, run dumb tasks, or explore data or libraries. I realized recently that a few of my C# friends use LinqPad for the same purpose.

    -

    Tip 2: What is it?

    While I encourage working primarily from .fsx files, the FSI window is also very helpful. I use it primarily for small verifications. For instance, I might have in my script file code like this:

    +

    Tip 2: What is it?

    +

    While I encourage working primarily from .fsx files, the FSI window is also very helpful. I use it primarily for small verifications. For instance, I might have in my script file code like this:

    1
    2
    let add x y =
    x + y
    -

    Once I send it for evaluation into FSI, I will see the following show up in FSI:

    1
    2
    val add : x:int -> y:int -> int
    >
    -

    My function add is now in memory, in my FSI session; I can start typing in the FSI window and use it:

    1
    2
    3
    > add 1 2;;
    val it : int = 3
    >
    -

    Enter does not trigger execution in FSI. The ;; indicates to FSI “Please execute everything I just typed, up to that point”. This is useful if you want to type multiple lines of code in FSI, and execute them as a block.

    it: in our add 1 2 example, the result showed up as it. We simply ran add, but didn’t assign the result to anything. it now contains the result, until we run another expression. If you want to re-use that value, you can assign it in FSI, by doing for instance let x = it;;.′

    @@ -237,20 +236,20 @@

    FSI often shows an abbreviated version of values for large items. For instance, [1..999] will show up as val it : int list = [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; 32; 33; 34; 35; 36; 37; 38; 39; 40; 41; 42; 43; 44; 45; 46; 47; 48; 49; 50; 51; 52; 53; 54; 55; 56; 57; 58; 59; 60; 61; 62; 63; 64; 65; 66; 67; 68; 69; 70; 71; 72; 73; 74; 75; 76; 77; 78; 79; 80; 81; 82; 83; 84; 85; 86; 87; 88; 89; 90; 91; 92; 93; 94; 95; 96; 97; 98; 99; 100; ...] - note the … at the end, which indicate that there is more.

    What if you inadvertently started a very long computation, or an infinite loop? In Visual Studio, you can either kill the session entirely, by right-clicking over the FSI window and selecting “Reset Interactive Session” or Ctrl + Alt + R, or cancel the latest evaluation you requested (“Cancel Interactive Evaluation”, or Ctrl + Break.).

    -

    Tip 3: Run Scripts from the Command Line

    Besides interactive scripting, you can also run a script from the command line, by using FSI.exe:

    +

    Tip 3: Run Scripts from the Command Line

    +

    Besides interactive scripting, you can also run a script from the command line, by using FSI.exe:

    >fsi.exe "C:\myscript.fsx"

    -

    FSI.exe is typically located at C:\Program Files (x86)\Microsoft SDKs\F#\4.0\Framework\v4.0. You can also install it separately, see fsharp.org/use section for instructions for various platforms.

    +

    FSI.exe is typically located at C:\Program Files (x86)\Microsoft SDKs\F#\4.0\Framework\v4.0. You can also install it separately, see fsharp.org/use section for instructions for various platforms.

    You can define different behaviors in your script, depending on whether it is run interactively or from the command line, like this:

    1
    2
    3
    4
    5
    6
    7
    #if INTERACTIVE
    let msg = "Interactive"
    #else
    let msg = "Not Interactive"
    #endif

    printfn "%s" msg
    -

    Updated, Sep 19: thanks Matt Klein for pointing the issue.

    For more information on FSI from the command line, check the reference page here.

    Updated, Feb 20: Ramon Soto Mathiesen points out that Tip 9 also applies to the command line.

    -

    Tip 4: Use Relative Paths

    Sometimes, your script will reference another resource; for instance, you need to read the contents of a .txt file somewhere. You can use absolute path, as in:

    +

    Tip 4: Use Relative Paths

    +

    Sometimes, your script will reference another resource; for instance, you need to read the contents of a .txt file somewhere. You can use absolute path, as in:

    1
    File.ReadAllLines @"C:/data/myfile.txt"
    -

    Pre-pending a string with @ makes it a verbatim string, and ignore escape sequences, such as \.

    @@ -260,28 +259,22 @@

    However, if that resource lives in a location relative to your script, consider using relative path, so that you can move your script folder around without breaking it.

    Relative paths can be a bit tricky; for instance, running the following code interactively…

    1
    System.Environment.CurrentDirectory
    -

    … produces a potentially unexpected result in FSI:

    1
    2
    val it : string = "C:\Users\Mathias Brandewinder\AppData\Local\Temp"
    >
    -

    You can avoid these issues by using built-in constants, which refer respectively to the directory where the script lives, the script file name, and the current line of the script:

    1
    2
    3
    __SOURCE_DIRECTORY__
    __SOURCE_FILE__
    __LINE__
    -

    So if your folder structure was along these lines…

    1
    2
    3
    root
    /src/script.fsx
    /data/data.txt
    -

    … you could refer to the data file data.txt from your script like this:

    1
    2
    let path = System.IO.Path.Combine(__SOURCE_DIRECTORY__,"..","data/data.txt")
    System.IO.File.ReadAllText path
    - -

    Tip 5: Including Assemblies

    By default, FSI loads FSharp.Core and nothing else. If you want to use System.DateTime, you will need to first open System in your script. If you want to use an assembly that is not part of the standard .NET distribution, you will need to reference it first using #r. Imagine for instance that you installed the Nuget package fsharp.data; to use it in your script, you would do something like:

    +

    Tip 5: Including Assemblies

    +

    By default, FSI loads FSharp.Core and nothing else. If you want to use System.DateTime, you will need to first open System in your script. If you want to use an assembly that is not part of the standard .NET distribution, you will need to reference it first using #r. Imagine for instance that you installed the Nuget package fsharp.data; to use it in your script, you would do something like:

    1
    2
    #r @"../packages/FSharp.Data.2.2.5/lib/net40/FSharp.Data.dll"
    open FSharp.Data
    -

    When you execute open System in interactive, don’t worry if nothing seems to happen: the only result is a new > showing up in FSI.

    For assemblies that are part of .NET but not referenced by default, you can use a shorter version:

    1
    2
    #r @"System.Xaml"
    open System.Xaml
    -

    In Visual Studio, you can right-click a reference from Solution Explorer, and send to F# interactive. You can then directly open it, and start using it in FSI.

    @@ -295,48 +288,47 @@

    @jeroldhaas @sergey_tihon @brandewinder Use #I SOURCE_DIRECTORY, it is wondrous, very satisfying. All relative paths then work

    — Don Syme (@dsyme) February 7, 2016

    -

    Tip 6: Use Paket

    The Nuget package manager is useful to consume existing packages. However, by default, Nuget stores assemblies in a folder that includes the package version number. This is very impractical for a script. In our example above, if fsharp.data gets an update, our script reference will be broken once we update the Nuget package:

    +

    Tip 6: Use Paket

    +

    The Nuget package manager is useful to consume existing packages. However, by default, Nuget stores assemblies in a folder that includes the package version number. This is very impractical for a script. In our example above, if fsharp.data gets an update, our script reference will be broken once we update the Nuget package:

    #r @"../packages/FSharp.Data.2.2.5/lib/net40/FSharp.Data.dll"

    Fixing the script requires manually editing the version number in the path, which quickly becomes a pain. Paket provides a better experience, because it stores packages without the version number, in this case, under:

    #r @"../packages/FSharp.Data/lib/net40/FSharp.Data.dll"

    Your scripts will now gracefully handle version number changes.

    If you end up consuming numerous packages, you can make your life even easier, by referencing paths where assemblies might be searched for, using #I:

    1
    2
    #I @"../packages/
    #r @"FSharp.Data/lib/net40/FSharp.Data.dll"
    -

    If your primary goal is to “just script”, consider using Atom or VSCode, with the Ionide plugin. You can create and run free-standing F# scripts, with beautiful Paket integration.

    -

    Tip 7: Include Files

    You might want to use the code from an existing file in your script. Suppose that we have a code file Code.fs somewhere, looking like this:

    +

    Tip 7: Include Files

    +

    You might want to use the code from an existing file in your script. Suppose that we have a code file Code.fs somewhere, looking like this:

    1
    2
    3
    4
    namespace Mathias

    module Common =
    let hello name = sprintf "Hello, %s" name
    -

    You can use that code from your script, by using the #load directive:

    1
    2
    3
    #load "Code.fs"
    open Mathias.Common
    hello "World"
    -

    You might have to close and re-open the script file if you end up changing the contents of the file.

    If the file you are attempting to load contains references to other assemblies or files, you might get an error on the #load statement: “One or more errors in loaded files. The namespace or module … is not defined”. Simply reference the missing assemblies above the #load statement, so that your script uses the same dependencies as the file it refers to.

    -

    Tip 8: Profile your Code with #time

    Another handy directive, #time, turns on basic profiling. Once it is executed, for every block of code you send for execution you will see timing and garbage collection information. For instance, running this code…

    +

    Tip 8: Profile your Code with #time

    +

    Another handy directive, #time, turns on basic profiling. Once it is executed, for every block of code you send for execution you will see timing and garbage collection information. For instance, running this code…

    1
    2
    #time
    [| 1 .. 10000000 |] |> Array.map (fun x -> x * x)
    -

    … will produce the following in FSI:

    1
    2
    3
    4
    5
    --> Timing now on

    Real: 00:00:00.887, CPU: 00:00:00.828, GC gen0: 2, gen1: 2, gen2: 2
    val it : int [] =
    [|1; 4; 9; 16; 25; 36; 49; // snipped for brevity
    -

    We get the wall time and CPU time it took, as well as some information about garbage collection in generations 0, 1 and 2. This would not replace a full-blown profiler, but this is an awfully convenient tool to figure out quickly if there are obvious ways to improve a piece of code.

    Note that every time you execute #time, the timer will be switched from on to off, or vice-versa. This is not always convenient; you can also explicitly set it to the desired state, like this:

    1
    2
    3
    #time "on"
    // everything now is timed
    #time "off"
    -

    If you are interested in profiling, you should take a look at PrivateEye; check out Greg Young’s talk at NDC Oslo 2015 to get a feel for what it does.

    -

    Tip 9: Turn 64-bits on

    Hat tip to Rick Minerich for that one. I’ll refer you to his blog post to see how to set FSI to 64 bits to handle large datasets.

    -

    Tip 10: Bonus Material

    Did you know that you could…

    +

    Tip 9: Turn 64-bits on

    +

    Hat tip to Rick Minerich for that one. I’ll refer you to his blog post to see how to set FSI to 64 bits to handle large datasets.

    +

    Tip 10: Bonus Material

    +

    Did you know that you could…