F #: Почему я должен явно указывать 'unit' для функций без аргументов? - PullRequest
8 голосов
/ 30 января 2010

Итак, я только что закончил свою первую программу на F #, с моим единственным функциональным фоном: немного знаний о Haskell (читай: на самом деле в нем нет программ). *

После некоторого ошеломляющего поведения я понял, что F # делает различие между:

prepareDeck = allSuits |> List.collect generateCards |> shuffle

и

prepareDeck() = allSuits |> List.collect generateCards |> shuffle

Я заметил, что он «кеширует» первое, никогда не пересчитывая его, если он вызывается снова, тогда как он обрабатывает последнее как обычную функцию. Вы не можете заметить разницу, если у рассматриваемой функции нет побочных эффектов, но мой shuffle сделал!

Предполагалось, что это общеизвестно? Я не видел, чтобы это упоминалось ни в одном учебном материале. Является ли причина только слабостью в синтаксическом анализаторе, вроде того, как у вас есть для объявления функции перед ее использованием?

Ответы [ 2 ]

15 голосов
/ 30 января 2010

Большинство материалов F # действительно объясняют, что все операторы верхнего уровня в модуле выполняются сверху вниз при объявлении. Другими словами, то, что вы объявили, является не функцией, а значением, которое связывается один раз при запуске программы.

Это действительно помогает увидеть отраженный код. У меня есть простой файл:

let juliet = "awesome"
let juliet2() = "awesome"

Скомпилированный код выглядит примерно так:

public static string juliet
{
    [CompilerGenerated, DebuggerNonUserCode]
    get
    {
        return "awesome";
    }
}

//...

public static string juliet2()
{
    return "awesome";
}

То есть одно является статическим свойством, а другое - функцией. Это желательное свойство, потому что представьте, если бы у нас было что-то вроде этого:

let x = someLongRunningDatabaseCall()

Мы хотим, чтобы x был связан только один раз, мы не хотим, чтобы он вызывал функцию базы данных каждый раз, когда мы получаем доступ к x.

Кроме того, мы можем написать интересный код, подобный этому:

> let isInNebraska =
    printfn "Creating cities set"
    let cities = set ["Omaha"; "Bellevue"; "Lincoln"; "Papillion"; "La Vista"; "Ralston"]
    fun n -> cities.Contains(n);;
Creating cities set

val isInNebraska : (string -> bool)

> isInNebraska "Omaha";;
val it : bool = true

> isInNebraska "Okaloosa";;
val it : bool = false

Поскольку isInNebraska является значением, оно оценивается немедленно. Просто так получилось, что его тип данных (string -> bool), поэтому он выглядит как функция. В результате мы заполняем наш cities набор только один раз, даже если мы вызываем функцию 1000 раз.

Давайте сравним этот код с этим:

> let isInNebraska2 n =
    printfn "Creating cities set"
    let cities = set ["Omaha"; "Bellevue"; "Lincoln"; "Papillion"; "La Vista"; "Ralston"]
    cities.Contains(n);;

val isInNebraska2 : string -> bool

> isInNebraska2 "Omaha";;
Creating cities set
val it : bool = true

> isInNebraska2 "Okaloosa";;
Creating cities set
val it : bool = false

Упс, мы создаем новые города, установленные каждый раз, когда мы вызываем функцию.

Таким образом, между ценностями и функциями определенно существует законное и реальное различие.

6 голосов
/ 30 января 2010

Так работает практически на каждом языке с побочными эффектами.

let name = expr

запускает код «сейчас» и может вызывать побочные эффекты, если expr имеет эффекты. Последующие ссылки на name не имеют никакого эффекта. В то время как

let name() = expr

определяет функцию, теперь не имеет эффектов и будет оценивать (и иметь эффекты) каждый раз, когда вызывается name().

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...