Функция арифметики? - PullRequest
       6

Функция арифметики?

6 голосов
/ 04 сентября 2011

Летом я немного изучил PHP и javascript, поэтому я решил, что в этом году я также получу преимущество по математике, что для меня будет исчислением. Я смотрел несколько видео и наткнулся на это:

http://www.youtube.com/watch?v=K6hxKU1kWUs&feature=mfu_in_order&list=UL

Он говорит, что (f+g)(x)=f(x)+g(x). Я никогда не видел функций, написанных таким образом, поэтому я подумал, что спросит, реализовано ли это и в языках программирования.

Предположим, у меня есть, в псевдокоде:

function double(x){
return x*2;
}
function triple(x){
return x*3;
}

Существует ли какой-либо язык программирования, который позволял бы что-то вроде:

(double + triple)(10)

... равно 50?

Кроме того, есть ли место, где я могу изучать исчисление из источника, которому нет 10000 лет?

также я знаю, что ни один язык программирования не будет работать с таким точным синтаксисом, но я имею в виду нечто подобное ...

Ответы [ 4 ]

8 голосов
/ 04 сентября 2011

Как это сделать

В Haskell это на самом деле очень просто:

Prelude Control.Monad> liftM2 (+) (* 2) (* 3) 10
50

Или, альтернативно:

Prelude Control.Applicative> (+) <$> (* 2) <*> (* 3) $ 10
50

Или, более подробно:

import Control.Monad

main :: IO ()
main = do let (+$)   = liftM2 (+) -- Define a new infix operator
          let double = (* 2)
          let triple = (* 3)
          print $ (double +$ triple) 10

Что здесь происходит? Haskell - очень инопланетный язык, если ваш опыт заключается в PHP и JavaScript, поэтому я постараюсь разбить его. Но просто напоследок: соответствующие функции здесь (liftM2 и <$> / <*>) гораздо более общие, чем просто это использование, которое поначалу может затруднить их понимание. Однако полученная мощность того стоит.


Краткий обзор высокого уровня

Вот моя попытка обобщения очень высокого уровня: liftM2 принимает двоичную функцию op и создает двоичную функцию, которая работает с «более сложными» значениями. Если ваши «более сложные» значения являются функциями с одним аргументом f и g, есть только один хороший способ объединить их и создать новую функцию с одним аргументом: создать функцию, которая передает свой аргумент x в f и g, а затем вычисляет f x `op` g x - объединяет их результаты, используя op. В JavaScript это будет что-то вроде

function liftM2_functions(op) {
  return function (f,g) {
    return function (x) { op(f(x), g(x)) }
  }
}

Так как liftM2 работает только с двоичными функциями, есть также liftM3 для троичных функций и т. Д., Но это грязно; таким образом, были введены операторы <$> и <*>, так что liftM<i>n</i> f a1 a2 ... a<i>n</i> в точности эквивалентен f a1 <*> a2 <*> ... <*> a<i>n</i>.


Подробнее

Если вы хотите получить более подробный ответ, читайте дальше. Я предупрежу вас, что я не уверен, насколько это ясно; он опирается на некоторые концепции, которые я имею в виду в полуколке, и я, возможно, не сделал хорошую работу. Но если вы игра, то мы идем.

Во-первых, давайте просто объясним некоторый синтаксис Haskell:

  • (* 2) и (* 3) - это сокращенные способы написания \x -> x*2 и \x -> x*3, которые являются просто Haskell для JavaScript function (x) { return x*2 } и function (x) { return x*3 }.
  • (+) - это просто функция, которая выполняет сложение; это префиксная форма инфиксного оператора +.
  • f x является приложением функции f(x); f x y анализируется как (f x) y, но может рассматриваться как приложение с двумя аргументами f(x,y).
  • Инфиксный оператор $ также является функциональным приложением, но с более низким приоритетом: ... $ x анализируется как (...) x.
  • <$> и <*> являются причудливыми инфиксными операторами.

Типы

Так! После этого давайте сначала рассмотрим первый фрагмент. В liftM2 (+) (* 2) (* 3) 10 требуемая «функция добавления функции» - liftM2 (+); два аргумента: (* 2) и (* 3); и аргумент к результату , что равен 10. В чем дело? В Haskell правильный способ думать об этом с точки зрения типов. Вот соответствующие типы, но будьте осторожны, вы, вероятно, не поймете их сразу.

liftM2 :: Monad m => (a -> b -> r) -> m a -> m b -> m r
(+)    :: Num a => a -> a -> a
(* 2)  :: Num a => a -> a
(* 3)  :: Num a => a -> a
10     :: Num a => a

Здесь f :: t означает «f имеет тип t». Эти типы довольно абстрактны, поэтому давайте упростим некоторые вещи. Когда вы видите Num a, это означает «для некоторого типа a, который является числом»; мы можем думать об этом как, например, Int или Double. Так что это дает нам

(+)    :: Int -> Int -> Int
(* 2)  :: Int -> Int
(* 3)  :: Int -> Int
10     :: Int

Хорошо, 10 - это целое число. А что насчет (* 2) и (* 3)? Это функции, обозначенные ->, которые отображают целое число в целое число. Вы знаете, что (+) - это двоичная функция для целых чисел, поэтому вы можете подумать, что она будет иметь тип (Int,Int) -> Int. Однако в Haskell правильный способ думать об этом - использовать функцию, которая принимает целое число, а возвращает другую функцию ; это называется карри. В JavaScript это будет реализовано как

function add(x) {
  return function (y) {
    return x + y
  }
}
// Usage: add(10)(11) = 21.

Возможно, вы не понимаете, почему это хорошо, но через некоторое время оно станет актуальным.

Теперь давайте займемся liftM2 :: Monad m => (a -> b -> r) -> m a -> m b -> m r. Что на земле происходит здесь? Игнорирование бита Monad m говорит о том, что liftM2 принимает функцию с двумя аргументами, "monadic a" и "monadic b", и выдает "monadic r". Однако - благодаря карри - это то же самое, что сказать, что liftM2 принимает функцию с двумя аргументами, а возвращает функцию с двумя аргументами, аргументы и результаты которой являются монадическими: liftM2 :: Monad m => (a -> b -> r) -> (m a -> m b -> m r). Вот что здесь происходит: liftM2 (+) - монадическая версия сложения.

Монады (не паникуйте!)

Теперь я продолжаю использовать слово monadic, и я не определил его. И я не собираюсь! В Интернете есть множество учебных пособий по монадам, а некоторые даже хороши; если вам интересно, посмотрите «Вы могли бы изобрести монады!» . Вот все, что вам нужно понять на данный момент: монадическое значение - это то, которое каким-то образом «дополняется», имея некоторую дополнительную структуру вокруг него. Поскольку списки являются монадой, [1,2,3] является монадическим int в монаде списка; там структура такова, что это int, который может быть или один, или два, или три. Здесь мы имеем дело с монадой для чтения : в данном случае монадное int - это просто функция, которая принимает некоторый объект типа a и возвращает int. Идея в том, что это целое число, которое может читать и зависеть от некоторой среды.

Итак, мы имеем именно это. (* 2) - это функция, которая принимает и возвращает целое число. Специализируясь на типе liftM2 для использования монады читателя, мы получаем следующее:

liftM2 :: (a -> b -> r) -> ((e -> a) -> (e -> b) -> (e -> r))

Теперь это многообещающе! Функция liftM2 принимает двоичную функцию и создает функцию , которая сама действует на функции . Применяя его к (+) :: Int -> Int -> Int, мы получаем

liftM2 (+) :: (e -> Int) -> (e -> Int) -> (e -> Int)

Если подумать, это тип добавления функций: liftM2 (+) принимает две функции с одним аргументом и создает новую функцию с одним аргументом, и делает это путем добавления их результатов.

Теперь, как реализован liftM2? Вот так (переформатированная версия реальной реализации ):

liftM2 f m1 m2 = do x1 <- m1
                    x2 <- m2
                    return $ f x1 x2

Что здесь происходит? Это говорит: «получить значение типа a из m1, получить другое из m2 и объединить эти значения с заданным f». Внутри нашей монады читателя (функции с одним аргументом) m1 :: e -> Int и x1 <- m1 применяют m1 к "среде", которая в нашем примере - 10. Так что в нашем случае у нас есть

do doubled <- (* 2)
   tripled <- (* 3)
   return $ doubled + tripled

Все это выражение, конечно, должно зависеть от загадочной среды, поскольку я нигде не упоминал об этом. Таким образом, его тип равен Int -> Int: функция, которая принимает целое число, удваивает и утраивает его, а затем объединяет их вместе. Который, конечно, где мы начали.

Аппликативные функторы (не паникуйте!)

А как насчет <$> и <*>? Я объяснил их на очень высоком уровне выше, и я могу рассказать вам основную идею: <$> подобен функции liftM1, а <*> применяет «сложные» функции к «сложным» значениям. Их фактические типы

(<$>) :: Functor f => (a -> b) -> f a -> f b
(<*>) :: Applicative f => f (a -> b) -> f a -> f b

Я не буду здесь подробно объяснять это, потому что не думаю, что смогу сделать очень хорошую работу. В одном предложении объект в функторе является в некотором смысле простым значением с дополнительными усложнениями, а <$> (также называемый fmap) оставляет сложности в одиночестве и применяет функцию к простому старому значению внутри. И <*> является частью дополнительной структуры, которая позволяет применять функции к нескольким простым значениям внутри нескольких структур одновременно. Но если вам нужна дополнительная информация, она там есть (хотя я не знаю хорошего ресурса - люди часто рекомендуют , однако, Learn You A Haskell ).


Заключение

Используя некоторые мощные конструкции (аппликативные функторы и / или монады), мы можем получить это понятие добавления функций в основном бесплатно, а также многое другое.(Умножение функций? liftM2 (*). Уровень функций if-then-else? liftM3 if' where if' c t f = if c then t else f. И т. Д.) И самое замечательное в Haskell заключается в том, что эти расширенные функции запечены в .Функторы и монады определены в Prelude, модуле, который неявно включается в каждую программу на Haskell (например, пространство имен java.lang в Java);модуль Control.Monad (который определяет liftM2) и модуль Control.Applicative (который определяет аппликативные функторы, <$> и <*>) являются частью стандартной библиотеки 1 .Характер высшего порядка и статически типизированного Хаскелла позволяет использовать эти концепции;делать это в PHP или Java было бы в принципе невозможно, и хотя JavaScript мог выражать некоторые концепции, тот факт, что он динамически типизирован, означает, что реализация должна выглядеть совсем иначе.Другие языки, такие как Scala, могут использовать и действительно используют эти концепции, хотя Scala - единственный, о котором я могу подумать (и они представлены в Scalaz , а не в стандартной библиотеке).Главная идея заключается в том, что, имея доступ к этим мощным абстракциям, вы можете писать более простой код и меньше его, чтобы делать менее абстрактные вещи, такие как добавление функций, которые вы хотите делать.


1: Если честно, Control.Applicative не соответствует стандарту Haskell 2010.Тем не менее, он включен по умолчанию в GHC, безусловно, наиболее используемый компилятор Haskell (это стандарт де-факто), а также в Hugs (интерпретатор Haskell, который имел значительное использование, но уступает позиции GHC), и до сих поркак я могу сказать, JHC и YHC (два других компилятора), а также.И ничто не мешает компилятору, не имеющему отношения к Haskell, строить Control.Applicative, поскольку код полностью соответствует Haskell 2010.

4 голосов
/ 04 сентября 2011

Большинство языков позволяют вам делать это так или иначе ...

function add(a, b) {
    return function(x) {
        return a(x) + b(x);
    };
}

add(double, triple)(10);  // Gives 50
2 голосов
/ 04 сентября 2011

Вы можете легко сделать это в Scala:

abstract class F { f =>
  def apply(x: Double): Double
  def +(g: F) = { new F { def apply(x: Double) = f(x) + g(x) } }
}
object F {
  implicit def funcToF(f: Double => Double) = new F { def apply(x: Double) = f(x) }
}

import F.funcToF

val f = (x: Double) => 2*x
val g = (x: Double) => 3*x

val h = f + g
println(f(1))        // 2
println(g(1))        // 3
println(h(1))        // 5
println((f + g)(1))  // 5

Вы можете попробовать это онлайн на http://www.simplyscala.com

1 голос
/ 04 сентября 2011

@ Antal опубликовал отличный ответ на ваш вопрос.Я опубликую Scala-версию его аппликативного решения (используя фантастическую библиотеку Scalaz):

scala> import scalaz._
import scalaz._

scala> import Scalaz._
import Scalaz._

scala> val double = (x: Int) => 2 * x
double: (Int) => Int = <function1>

scala> val triple = (x: Int) => 3 * x
triple: (Int) => Int = <function1>

scala> val h = (double |@| triple) { _ + _ }
h: (Int) => Int = <function1>

scala> h(10)
res13: Int = 50
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...