我正在尝试编写一个f#程序,该程序模仿linux中的touch命令,它会检查文件路径是否存在(如果确实更新了时间戳记,是否不存在创建的文件)。我如何正确实现呢?
我尝试使用.net函数,我认为它基本上可以满足我的需要。我正在使用file.exists,file.create和file.setlastaccesstime
了解有关F#的更多信息open System.IO
type Filepath = string
let touch =
let checkExists filePath =
if File.Exists filePath
then Result.Ok (filePath:Filepath)
else Result.Error "File does not exist"
let path = Console.ReadLine()
let update =
path = Console.ReadLine()
if File.Exists(path)
File.Create(path)
else File.SetLastAccessTime(path, DateTime.Now)
[<EntryPoint>]
let main argv =
printfn "Touch command for windows"
-> touch()
-> update()
0 // return an integer exit code```
答案 0 :(得分:4)
您的代码有几个问题。我会尝试一个接一个地解决它们。
首先,您问题中代码的缩进完全搞砸了。如果这是从您的代码直接复制并粘贴,则我们要做的第一件事就是修复缩进:在F#中,缩进很重要,并确定代码块。 (如果您的问题的缩进被弄乱了,因为您在Stack Overflow问题编辑器中摆弄了缩进,而您的实际代码缩进就可以了,那么考虑一下F#缩进的工作原理。
在F#中,这两个代码块做一些不同的事情:
if value = 5 then
printfn "Five"
printfn "Value: %d" value
这将始终打印“值:3”或“值:27”或其他值,并且如果值正好为5,还将打印单词“五”。但是下一个代码块还会执行其他操作:
if value = 5 then
printfn "Five"
printfn "Value: %d" value
这将完全不打印任何内容,或者将打印单词“ Five”(五),然后在下一行显示“ Value:5”。这是因为第二个printfn
函数调用的缩进程度与第一个相同,这意味着它是if
块的一部分。
此外,在let
语句后必须在同一行上加上一个值或在一个缩进的块之后:
let value = 5
let otherValue =
if value < 5 then
5
else
3
执行此操作后,otherValue
的值为3。此外,let otherValue =
下的代码块将仅一次执行。这是因为otherValue
不是函数,而是一个值。如果您希望otherValue
是一个函数,则需要为其提供参数:
let calculateValue inputValue =
if inputValue < 5 then
5
else
3
现在您可以像下面这样调用该函数了:
let value = 5
let otherValue = calculateValue value
// Now otherValue is equal to 3
这使我们想到了代码的第二个问题,即您显然打算将touch
和update
用作函数,但是没有给它们提供参数。这意味着它们是值,仅执行一次。您需要将let touch =
转换为let touch () =
,以使touch
成为函数而不是值,而对update
进行修改。
您遇到的另一个问题更多是设计问题:您的update
函数正在做两件事。它正在从控制台读取一个值,并根据该值执行操作。更好的设计是让每个函数只做一件事:让您的update
函数以path
作为参数,然后在其他地方您可以拥有从控制台读取path
的代码,呼叫update path
。这使编写测试update
函数的单元测试变得更加容易,因为您不必以某种方式弄清楚如何将单元测试连接到stdin,您只需让您的测试调用update
功能。
古兰在评论中指出的另一个问题(谢谢!)是您的update
函数具有倒退的逻辑。您写了“如果文件存在,创建文件,否则设置上次访问时间”。情况应该相反。我在此答案的第一版中错过了这一点,因此我更新了下面的固定代码,以更正if...then...else
中update
块的顺序。
我看到的另一个问题是:您应该执行的touch
函数是什么(当前是一个值,但您显然打算将其作为一个函数)? let touch =
块中唯一的一件事就是其他函数定义。 touch
代码块从不实际调用其定义的函数。这意味着它没有用:通过在touch
代码块的内部 中使用这些函数定义,您可以将它们隐藏在touch
代码块之外的所有代码中,而在{{1 }}代码块从不调用它们。可以访问该模块的任何代码都可以看到在模块顶层定义的功能。在代码块内部定义的函数(和变量)仅在该代码块的范围内可见。这对于封装外界不应该看到的东西很有用,例如在以下示例中:
touch
在这里,let counter() =
let mutable value = 0
let update() =
value <- value + 1
value
update
内的value
变量对于世界其他地方是不可访问的。另外,请注意我是如何在counter()
代码块的最后一行写上没有括号的update
的:这意味着名为let counter() =
的函数将是{{1 }}。换句话说,当您调用update
时,您将得到一个函数,每次调用它时,将返回一个新值,该值是前一个值加1。但是两个不同的计数器彼此分开:>
counter()
这将打印“ A1:1”,“ A2:2”,“ B1:1”,然后打印“ B2:2”。现在作为练习,如果在上面的代码中将counter()
替换为let a = counter()
let b = counter()
let a1 = a()
let b1 = b()
let a2 = a()
let b2 = b()
printfn "A1: %d" a1
printfn "A2: %d" a2
printfn "B1: %d" b1
printfn "B2: %d" b2
会发生什么? (我还必须将let counter() =
更改为let counter =
,对于let a = counter()
也是一样)。尝试猜测会发生什么,然后尝试一下,看看您是否正确。 (提示:与函数不同,值仅在一次上执行)。
最后一件事:let a = counter
函数中的箭头不正确。这不是let b = ...
运算符的作用。如果需要连续调用多个函数,只需编写一个接一个的函数调用即可。
好的,这就是我现在所能做的,可以帮助您解决代码问题。问题更多(例如,从main
获取路径而不是从->
获取路径),但是这些问题可以等到您掌握了更多F#经验后再进行。这是您的代码,其中只有我到目前为止提到的修复程序。哦,还有一个解决方法:由于我无法告知您打算在argv
函数中输入什么内容,因此我将删除Console.ReadLine()
行。因此,这是您的代码,虽然有些固定,但仍需要更多修正:
let touch =