Что такое "ценность" в чисто функциональном программировании?

Что представляет собой ценность в чисто функциональном программировании?

Я задаю себе эти вопросы, увидев предложение:

Task (или IO) имеет конструктор, который фиксирует побочные эффекты как значения.

  • Является ли функция значением?
    • Если да, то что это означает, приравнивая две функции: assert(f == g). Для двух функций, которые эквивалентны, но определяются отдельно => f != g, почему они не работают как 1 == 1?
  • Является ли объект с методами значением? (например, IO { println("") })
  • Является ли объект с методами setter и изменяемым состоянием значением?
  • Является ли объект с изменчивым состоянием, который работает как конечный автомат?

Как мы проверяем, является ли что-то ценным? Является ли неизменность достаточным условием?

UPDATE: Я использую Scala.

Ответы

Ответ 1

Я попытаюсь объяснить, что такое значение, сравнивая его с вещами, которые не являются значениями.

Грубо говоря, ценности - это структуры, создаваемые процессом оценки, которые соответствуют терминам, которые не могут быть упрощены в дальнейшем.


термины

Во-первых, какие термины? Термины - это синтаксические структуры, которые можно оценить. По общему признанию, это немного круговое, поэтому давайте рассмотрим несколько примеров:

  1. Постоянные литералы - это термины:

    42
    
  2. Функции, применяемые к другим терминам, - это термины:

    atan2(123, 456 + 789)
    
  3. Функциональные литералы - это термины

    (x: Int) => x * x
    
  4. Вызовы конструктора - это термины:

    Option(42)
    

Сравните это с:

  1. Декларации классов/определения не являются терминами:

    case class Foo(bar: Int)
    

    то есть вы не можете писать

    val x = (case class Foo(bar: Int))
    

    это было бы незаконным.

  2. Аналогично, определения признаков и типов не являются терминами:

    type Bar = Int
    sealed trait Baz
    
  3. В отличие от функциональных литералов, определения методов не являются терминами:

    def foo(x: Int) = x * x
    

    например:

    val x = (a: Int) => a * 2 // function literal, ok
    val y = (def foo(a: Int): Int = a * 2) // no, not a term
    
  4. Заявки на упаковку и импортные заявления не являются условиями:

    import foo.bar.baz._ // ok
    List(package foo, import bar) // no
    

Нормальные формы, значения

Теперь, когда, надеюсь, несколько яснее, что такое термин, что подразумевалось под "не может быть упрощено дальше"? В идеализированных языках функционального программирования вы можете определить, что такое нормальная форма или, скорее, слабая нормальная форма головы. термин находится в нормальной форме (wh-), если к термину не могут применяться правила сокращения, чтобы сделать его более простым.Снова несколько примеров:

  1. Это термин, но он не в нормальной форме, потому что его можно уменьшить до 42:

    40 + 2
    
  2. Это не в нормальной нормальной форме головы:

    ((x: Int) => x * 2)(3)
    

    потому что мы можем оценить его до 6.

  3. Эта лямбда находится в слабой головной нормальной форме (она застревает, потому что вычисление не может продолжаться до тех пор, пока не будет поставлено x):

    (x: Int) => x * 42
    
  4. Это не в нормальной форме, потому что его можно упростить дальше:

    42 :: List(10 + 20, 20 + 30)
    
  5. Это в нормальной форме, дальнейшие упрощения не возможны:

    List(42, 30, 50)
    

Таким образом,

  • 42,
  • (x: Int) => x * 42,
  • List(42, 30, 50)

являются значениями, тогда как

  • 40 + 2,
  • ((x: Int) => x * 2)(3),
  • 42 :: List(10 + 20, 20 + 30)

это не значения, а просто ненормированные термины, которые могут быть еще более упрощены.


Примеры и примеры

Я просто перейду к списку подзапросов один за другим:

Является ли функция значением

Да, такие вещи, как (x: T1,..., xn: Tn) => body считаются застрявшими членами в WHNF, на функциональных языках они могут быть фактически представлены, поэтому они являются значениями.

Если да, то что это означает, приравнивая две функции: assert(f == g) для двух функций, которые эквивалентны, но определяются отдельно => f != g, почему они не работают как 1 == 1?

Расширение функции несколько не связано с вопросом, является ли что-то значением или нет. В приведенном выше "определении на примере" я говорил только о форме терминов, а не о существовании/небытии некоторых вычислимых отношений, определенных на этих терминах. Печальным фактом является то, что вы даже не можете определить, действительно ли лямбда-выражение представляет собой функцию (то есть, заканчивается ли она для всех входов), и также известно, что не может быть алгоритма, который мог бы определить, создают ли две функции одинаковый выход для всех входов (т.е.

Является ли объект с методами значением? (например, IO { println("") })

Не совсем понятно, что вы здесь задаете. Объекты не имеют методов. У классов есть методы. Если вы имеете в виду вызовы методов, то нет, они являются терминами, которые могут быть дополнительно упрощены (фактически запуском метода), поэтому они не являются значениями.

Является ли объект с методами setter и изменяемым состоянием значением? Является ли объект с изменчивым состоянием, который работает как конечный автомат?

В чистом функциональном программировании нет такой вещи.

Ответ 2

Что представляет собой ценность в чисто функциональном программировании?

Фон

В чистом функциональном программировании нет мутации. Следовательно, код, такой как

case class C(x: Int)

val a = C(42)
val b = C(42)

станет эквивалентом

case class C(x: Int)

val a = C(42)
val b = a

так как в чистом функциональном программировании, если ax == bx, тогда у нас будет a == b. То есть, будет a == b, сравнивающий значения внутри.

Однако Scala не является чистым, поскольку он позволяет мутировать, например, Java. В этом случае у нас нет эквивалентности между двумя фрагментами выше, когда мы объявляем case class C(var x: Int). Действительно, выполнение ax += 1 послесловия не влияет на bx в первом фрагменте, но во втором, где a и b указывают на один и тот же объект. В этом случае полезно иметь сравнение a == b которое сравнивает ссылки на объекты, а не его внутреннее целочисленное значение.

При использовании case class C(x: Int) сравнения Scala a == b ведут себя ближе к чисто функциональному программированию, сравнивая значения целых чисел. С обычными (не case) классами Scala вместо этого сравнивает ссылки на объекты, нарушающие эквивалентность между двумя фрагментами. Но, опять же, Scala не чиста. Для сравнения, в Haskell

data C = C Int deriving (Eq)
a = C 42
b = C 42

действительно эквивалентно

data C = C Int deriving (Eq)
a = C 42
b = a

поскольку в Haskell нет "ссылок" или "идентификаторов объектов". Обратите внимание, что реализация Haskell, вероятно, выделит два "объекта" в первом фрагменте, и только один объект во втором, но поскольку нет способа рассказать их отдельно внутри Haskell, вывод программы будет таким же.

Ответ

Является ли функция значением? (тогда это означает, что приравнивая две функции: assert (f == g). Для двух функций, эквивалентных, но определенных отдельно => f! = g, почему бы им не работать как 1 == 1)

Да, функции - это значения в чисто функциональном программировании.

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

Чисто функциональное программирование должно сравнивать функции, делающие f == g эквивалентными fx == gx для всех возможных аргументов x. Это возможно, если для x существует только несколько значений, например, если f,g :: Bool → Int нам нужно только проверить x=True, x=False. Для функций, имеющих бесконечные области, это намного сложнее. Например, если f,g :: String → Int мы не можем проверять бесконечное число строк.

Теоретическая информатика (теория вычислимости) также доказала, что нет алгоритма сравнения двух функций String → Int, даже неэффективного алгоритма, даже если у нас есть доступ к исходному коду двух функций. По этой математической причине мы должны признать, что функции - это ценности, которые нельзя сравнивать. В Haskell мы выражаем это через Eq typeclass, заявляя, что почти все стандартные типы сопоставимы, а функции являются исключением.

Является ли объект с методами значением? (например, IO {println ("")})

Да. Грубо говоря, "все это ценность", в том числе ИО.

Является ли объект с методами setter и изменяемыми состояниями значением? Является ли объект с изменяемыми состояниями и работает как конечный автомат?

В чистом функциональном программировании нет изменчивого состояния.

В лучшем случае сеттеры могут создавать "новый" объект с измененными полями.

И да, объект будет стоить.

Как мы проверяем, является ли это значением, является ли неизменяемое условие достаточным условием для значения?

В чисто функциональном программировании мы можем иметь только неизменные данные.

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

case class D(var x: Int)
case class C(c: C)
val a = C(D(42))

то все сложнее. Я думаю, мы могли бы еще назвать "неизменным", так как мы не можем изменить a ac, но мы должны быть осторожны, так как acx может мутировать. В зависимости от намерения, я думаю, что некоторые не назвали a неизменным. Я бы не стал рассматривать быть значение. a

Чтобы сделать вещи более грязными, в нечистом программировании существуют объекты, которые используют мутацию для эффективного "чистого" интерфейса. Например, можно написать чистую функцию, которая перед возвратом сохраняет свой результат в кеше. Когда вызывается снова по тому же аргументу, он вернет ранее вычисленный результат (обычно это называется memoization). Здесь происходит мутация, но она не наблюдается снаружи, где в лучшем случае мы можем наблюдать более быструю реализацию. В этом случае мы можем просто притвориться, что эта функция чиста (даже если она выполняет мутацию) и считает ее "значением".

Ответ 3

Контраст с императивными языками суровый. В непереносимых языках, таких как Python, выводится выход функции. Он может быть назначен переменной, явно возвращенной, напечатанной или записанной в файл.

Когда я сочиняю функцию в Haskell, я никогда не рассматриваю вывод. Я никогда не использую "возврат". Все имеет значение "a". Это называется "символическим" программированием. Под "всем" подразумеваются "символы". Как человеческий язык, существительные и глаголы представляют что-то. Это что-то их ценность. "Ценность" "Пит" - это Пит. Имя "Пит" не Пит, а представляет собой Пит, человека. То же самое относится к функциональному программированию. Лучшая аналогия - математика или логика. Когда вы делаете страницы вычислений, вы направляете вывод каждой функции? Вы даже "присваиваете" переменные заменяемым их "значением" в функциях или выражениях.

Ответ 4

Значения

  1. Неизменный /Timeless
  2. анонимное
  3. Семантически прозрачный

В чем значение 42? 42. Что такое "ценность" new Date()? Date object at 0x3fa89c3. Что такое личность 42? 42. Какова идентичность new Date()? Как мы видели в предыдущем примере, это то, что живет на месте. В разных контекстах он может иметь много разных "ценностей", но он имеет только одну идентичность. OTOH, 42 достаточно для себя. Семантически бессмысленно спрашивать, где живет 42 человека в системе. Что такое смысловое значение 42? Величина 42. Что такое семантический смысл new Foo()? Кто знает.

Я бы добавил четвертый критерий (см. Это в некоторых контекстах в дикой природе, но не в других), а именно: значения являются языковыми агностиками (я не уверен, что первые 3 являются достаточными для обеспечения этого или что такое правило полностью согласуется с большинством интуиция людей о том, что означает значение).

Ответ 5

Ценности - это вещи, которые

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

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

IO monad - отличный пример того, насколько общая концепция. Когда мы говорим, что IO monad моделирует побочные эффекты как значения, мы имеем в виду, что функция может принимать побочный эффект (например, println) в качестве входного и возвращаемого в качестве вывода. IO(println(...)) отделяет идею эффекта действия println от фактического выполнения действия и позволяет рассматривать эти эффекты как значения первого класса, которые могут быть вычислены с использованием тех же языковых средств, что и для любых других значений, таких как числа.