Вы находитесь на странице: 1из 23

Лекция 6.

Более подробно о ML

Введение в Функциональное программирование

Джон Харрисон

Университет Кембриджа

10 сентября 2008 г.

Джон Харрисон Введение в Функциональное программирование


Лекция 6. Более подробно о ML

Темы

Взаимодействие с ML; загрузка файлов


Основные типы данных и операторы
Инфиксные операции и конкретный синтаксис
Дополнительные примеры
Определение новых типов

Джон Харрисон Введение в Функциональное программирование


Лекция 6. Более подробно о ML

Взаимодействие с ML

Пока мы только вводили текст в ML и думали о результатах.


Однако, реальные программы так не пишутся.
Обычно записывают выражения и объявления в файл. Чтобы
попробовать их в работе их можно поместить в ML используя
команды «вырезать и вставить».
Вы можете вырезать и вставить используя X Window System и
схожие системы, или редактор типа Emacs.
Для больших программ удобнее загружать выражения из файла в
сеанс работы ML. Это можно сделать с помощью команды ML
include.

Джон Харрисон Введение в Функциональное программирование


Лекция 6. Более подробно о ML

Загрузка из файлов (1)

Допустим файл myprog.ml содержит следующие строки:


l e t pythag x y z =
x ∗ x + y ∗ y = z ∗ z ;;

pythag 3 4 5 ; ;

p y t h a g 5 12 1 3 ; ;

pythag 1 2 3 ; ;

Джон Харрисон Введение в Функциональное программирование


Лекция 6. Более подробно о ML

Загрузка из файлов (2)

Используя команду include для этого файла, мы получаем


следующее:
#i n c l u d e " myprog . ml " ; ;
pythag : i n t −> i n t −> i n t −> b o o l = <fun>
− : bool = true
− : bool = true
− : bool = false
− : unit = ()

ML отвечает так, как будто содержание файла было введено в сеанс


работы.
Обычно процесс включения файла завершается как только была
обнаружена ошибка. В сообщении об ошибке указывается номер
строки.
Последняя строка это результат работы самой команды include. У
него тип string -> unit.
Допускается использование вложенных включений файлов.

Джон Харрисон Введение в Функциональное программирование


Лекция 6. Более подробно о ML

Комментарии

Комментарии записываются между (* и *), например:


( ∗ −−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−− ∗ )
(∗ I s ( x , y , z ) i s a Pythagorean t r i p l e ? ∗)
( ∗ −−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−− ∗ )

l e t pythag x y z =
x ∗ x + y ∗ y = z ∗ z ;;

( ∗ comments ∗ ) p y t h a g ( ∗ can ∗ ) 3 ( ∗ go ∗ ) 4
(∗ almost ∗) 5 (∗ anywhere ∗)
( ∗ and ( ∗ can ( ∗ be ( ∗ n e s t e d ∗ )
q u i t e ∗) a r b i t r a r i l y ∗) ∗) ; ;

ML игнорирует комментарии, но комментарии полезны для людей


читающих код.

Джон Харрисон Введение в Функциональное программирование


Лекция 6. Более подробно о ML

Основные типы данных

Важнейшие примитивные типы данных:


Тип unit — одноэлементный тип, чей единственный элемент
записывается как ().
Тип bool — двухэлементный тип, чьи элементы записываются
как true и false.
Тип int — подмножество отрицательных и неотрицательных
чисел, записанных, например как 6 и -11.
Тип string, который соответствует последовательности
символов, записанных “например так”.
Типы могут быть соединены с помощью конструкторов типов,
включая конструктор функционального типа -> и конструктор
декартова произведения типов *.
Позже мы увидим как можно определять другие типы и
конструкторы типов.

Джон Харрисон Введение в Функциональное программирование


Лекция 6. Более подробно о ML

Инфиксные операции

У многих встроенных констант, таких как, +, есть статус инфиксной


операции. Есть также понятие старшинства операции, поэтому
выражения связываются более или менее как ожидается
пользователем.
Например x < 2 * y + z разбирается как < x (+ (* 2 y) z).
Вы можете задать своей функции статус инфиксной операции
используя директиву #infix.
#l e t o f g = fun x −> f ( g x ) ; ;
o : ( ’ a −> ’ b ) −> ( ’ c −> ’ a ) −>
’ c −> ’ b = <fun>
##i n f i x " o " ; ;
#l e t add2 = s u c c e s s o r o s u c c e s s o r ; ;
add2 : i n t −> i n t = <fun>
#add2 0 ; ;
− : int = 2

Джон Харрисон Введение в Функциональное программирование


Лекция 6. Более подробно о ML

Основные инфиксные операторы

Оператор Значение
mod Modulus (остаток)
* Умножение
/ Целочисленное деление
+ Сложение
- Вычитание
^ Соединение строк
= Проверка равенства
<> Проверка неравенства
< Строго меньше
<= Меньше или равно
> Строго больше
>= Больше или равно
& Логическое «и»
or Логическое «или»

Джон Харрисон Введение в Функциональное программирование


Лекция 6. Более подробно о ML

Замечания про операторы

Заметим, что у логических операторов & и or стратегия вычислений


не энергичная. Мы можем рассматривать их как синонимы для:

4
p&q = if p then q else false
4
p or q = if p then true else q

Также есть две встроенные символьные операции над основными


типами: - которая выполняет численное отрицание, и not которая
выполняет логическое отрицание.
У логического оператора not необычный префиксный статус, так что
not not p разбирается как not (not p) в противоположность
обычному левоассоциативному разбору для функций.

Джон Харрисон Введение в Функциональное программирование


Лекция 6. Более подробно о ML

Примеры (1)
Мы определим рекурсивную функцию, которая принимает два
аргумента: положительное число n и функцию f , а возвращает f n ,
то есть f ◦ · · · ◦ f (n раз):
#l e t r e c funpow n f x =
i f n = 0 then x
e l s e funpow ( n − 1) f ( f x ) ; ;
funpow : i n t −> ( ’ a −> ’ a ) −>
’ a −> ’ a = <fun>

На самом деле funpow переводит машинное число n в


соответствующий нумерал Чёрча. Мы легко можем определить
обратную функцию следующим образом:
#l e t d e f r o c k n = n ( fun x −> x + 1 ) 0 ; ;
d e f r o c k : ( ( i n t −> i n t ) −> i n t −> ’ a ) −>
’ a = <fun>
#d e f r o c k ( funpow 3 2 ) ; ;
− : i n t = 32

Джон Харрисон Введение в Функциональное программирование


Лекция 6. Более подробно о ML

Примеры (2)

Вспомним арифметические операции над нумералами Чёрча. Мы


можем их проверить:
#l e t add m n f x = m f ( n f x )
and mul m n f x = m ( n f ) x
and exp m n f x = n m f x ; ;
and t e s t bop x y =
d e f r o c k ( bop ( funpow x ) ( funpow y ) ) ; ;
...
#t e s t add 2 1 0 ; ;
− : i n t = 12
#t e s t mul 2 1 0 ; ;
− : i n t = 20
#t e s t exp 2 1 0 ; ;
− : i n t = 1024

Похоже что они работают. Но, конечно, они не очень эффективны!

Джон Харрисон Введение в Функциональное программирование


Лекция 6. Более подробно о ML

Определение новых типов

ML содержит средства определения новых типов и конструкторов


типов.
На деле конструкторы могут быть рекурсивными, то есть можно
использовать как аргументы не только существующие типы, но и сам
новый тип.
Посмотрим сначала на простой пример: несвязную сумму типов.
#type ( ’ a , ’ b ) sum = i n l o f ’ a
| i n r of ’ b ; ;
Type sum d e f i n e d .

Эта запись создаёт новый тип sum и два новых конструктора:


#i n l ;;
− : ’ a −> ( ’ a , ’ b ) sum = <fun>
#i n r ;;
− : ’ a −> ( ’ b , ’ a ) sum = <fun>

Джон Харрисон Введение в Функциональное программирование


Лекция 6. Более подробно о ML

Свойства конструкторов типов

У всех конструкторов типов, возникающих таким образом, есть три


ключевых свойства:
1 Они полны, т.е. любой элемент нового типа доступен либо через
inl x для некоторого x, либо через inr y для некоторого y.
2 Они инъективны, т.е. равенство inl x = inl y верно, если, и
только если x = y, тоже самое и для inr.
3 Они раздельны, т.е. их области не пересекаются. Конкретнее,
это означает, что, в примере выше, для любых x и y сравнение
inl x = inr y всегда ложно.
Благодаря этим свойствам мы можем определять функции
используя сопоставление с образцом.

Джон Харрисон Введение в Функциональное программирование


Лекция 6. Более подробно о ML

Сопоставление с образцом

Мы производим сопоставление с образцом используя в роли


аргументов к λ-функциям более общие выражения, называемые
varstructs. Мы также можем рассмотреть несколько других случаев.
Например:
#fun ( i n l n ) −> n > 6
| ( i n r b ) −> b ; ;
− : ( i n t , b o o l ) sum −> b o o l = <fun>

Как и ожидалось у этой функции есть свойство, такое, что когда она
применяется к inl n, то возвращает n > 6, а когда применяется к
inr b, то возвращает b. Чем это обосновано?
Допускается восстановить n из inl n, потому что конструкторы
инъективны. Экземпляры образца не конфликтуют, потому что
конструкторы раздельны. В заключение, функция определена на
всех допустимых значениях, потому что конструкторы полны.

Джон Харрисон Введение в Функциональное программирование


Лекция 6. Более подробно о ML

Неполное сопоставление

Вообще, мы можем определить частичную функцию, которая


определена не для всех допустимых значений.
#fun ( i n r b ) −> b ; ;
Toplevel input :
>fun ( i n r b ) −> b ; ;
>^^^^^^^^^^^^^^^^
Warning : t h i s m a t c h i n g i s n o t e x h a u s t i v e .
− : ( ’ a , ’ b ) sum −> ’ b = <fun>

Компилятор предупреждает нас об этом. Если мы попробуем


применить функцию над аргументом не в форме inr x, то она не
будет работать:
#(...) ( inl 3);;
Uncaught e x c e p t i o n : M a t c h _ f a i l u r e

Джон Харрисон Введение в Функциональное программирование


Лекция 6. Более подробно о ML

Повсеместное сопоставление
Мы можем производить сопоставление и в других ситуациях, когда
образцы не взаимно исключающие. В этом случае берётся первое
совпадение.
#(fun 0 −> t r u e | n −> f a l s e ) 0 ; ;
− : bool = true
#(fun 0 −> t r u e | n −> f a l s e ) 1 ; ;
− : bool = false

Однако, в основном, необходимо чтобы у констант был статус


конструктора, иначе они будут рассматриваться как обычные
связанные переменные.
#(fun t r u e −> 1 | f a l s e −> 0 ) ( 4 < 3 ) ; ;
− : int = 0
#l e t true_1 = t r u e and f a l s e _ 1 = f a l s e i n
( fun true_1 −> 1 | f a l s e _ 1 −> 0 ) ( 4 < 3 ) ; ;
− : int = 1

Джон Харрисон Введение в Функциональное программирование


Лекция 6. Более подробно о ML

Другие конструкции сопоставления


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

match E with pattern1 ->E1 | · · · | patternn ->En

Простейший выбор из всех вариантов — это использовать

let pattern = expression

но в данном случае разрешён только один образец. Например:


#l e t ( i n l x ) = i n l 3 ; ;
Toplevel input :
>l e t ( i n l x ) = i n l 3 ; ;
>^^^^^^^^^^^^^^^^^^^^^
Warning : t h i s m a t c h i n g i s n o t e x h a u s t i v e .
x : int = 3

Джон Харрисон Введение в Функциональное программирование


Лекция 6. Более подробно о ML

Рекурсивные типы
Как мы уже сказали, типы могут быть заданы рекурсивно, т.е. через
самих себя.
#type ( ’ a ) l i s t = N i l
| Cons o f ’ a ∗ ( ’ a ) l i s t ; ;
Type l i s t d e f i n e d .
#N i l ; ;
− : ’a l i s t = Nil
#Cons ; ;
− : ’ a ∗ ’ a l i s t −> ’ a l i s t = <fun>

Представим, что Nil — это пустой список, а Cons — функция


которая добавляет элемент в начало списка. Списки [], [1], [1; 2] и
[1; 2; 3] записываются как:
Nil ; ;
Cons ( 1 , N i l ) ; ;
Cons ( 1 , Cons ( 2 , N i l ) ) ; ;
Cons ( 1 , Cons ( 2 , Cons ( 3 , N i l ) ) ) ; ;

Джон Харрисон Введение в Функциональное программирование


Лекция 6. Более подробно о ML

Списки
Вообще то, такой тип уже является встроенным. Пустой список
записывается [], и у рекурсивного конструктора :: есть статус
инфиксной операции. Поэтому списки выше, в действительности,
записываются так:
#[];;
− : ’a l i s t = []
#1::[];;
− : int l i s t = [1]
#1::2::[];;
− : int l i s t = [ 1 ; 2]
#1::2::3::[];;
− : int l i s t = [ 1 ; 2; 3]

Вариант который печатается на выводе может быть использован и


для записи в коде:
#[1;2;3;4;5] = 1 : : 2 : : 3 : : 4 : : 5 : : [ ] ; ;
− : bool = true

Джон Харрисон Введение в Функциональное программирование


Лекция 6. Более подробно о ML

Сопоставление с образцом для списков


Теперь мы можем определять функции используя сопоставление с
образцом. Например, мы можем определить функции взятия головы
и хвоста списка:
#l e t hd ( h : : t ) = h ; ;
Toplevel input :
>l e t hd ( h : : t ) = h ; ;
> ^^^^^^^^^^^^^
Warning : t h i s m a t c h i n g i s n o t e x h a u s t i v e .
hd : ’ a l i s t −> ’ a = <fun>
#l e t t l ( h : : t ) = t ; ;
Toplevel input :
>l e t t l ( h : : t ) = t ; ;
> ^^^^^^^^^^^^^
Warning : t h i s m a t c h i n g i s n o t e x h a u s t i v e .
t l : ’ a l i s t −> ’ a l i s t = <fun>

ML предупреждает что возникнет ошибка если эти функции


применить к пустому списку.
Джон Харрисон Введение в Функциональное программирование
Лекция 6. Более подробно о ML

Рекурсивные функции над списками


Допускается совмещать сопоставление с образцом и рекурсию. Это
выглядит естественно, поскольку сами типы были заданы
рекурсивно. Вот например функция, которая возвращает длину
списка:
#l e t r e c l e n g t h =
fun [ ] −> 0
| ( h : : t ) −> 1 + l e n g t h t ; ;
l e n g t h : ’ a l i s t −> i n t = <fun>
#l e n g t h [ ] ; ;
− : int = 0
#l e n g t h [ 5 ; 3 ; 1 ] ; ;
− : int = 3

С другой стороны, эта функция может быть написана используя


ранее определённые «деструкторные» функции hd и tl. Такой стиль
определения функций принят во многих языках, особенно в LISP,
однако прямое использование сопоставления с образцом зачастую
элегантнее.
Джон Харрисон Введение в Функциональное программирование
Лекция 6. Более подробно о ML

Коварство рекурсивных типов


Рассмотрим следующую конструкцию:
#type ( ’ a ) embedding =
K o f ( ’ a ) embedding −>’a ; ;

Она выглядит подозрительно, потому что вставляет функциональное


пространство A → B внутрь A. На самом деле это так только для
вычислимых функций. Мы должны быть внимательны как с
семантикой, так и с типами:
#l e t Y h =
l e t g (K x ) z = h ( x (K x ) ) z i n
g (K g ) ; ;
Y : ( ( ’ a −> ’ b ) −> ’ a −> ’ b ) −>
’ a −> ’ b = <fun>
#l e t f a c t = Y ( fun f n −>
i f n = 0 then 1 e l s e n ∗ f ( n − 1 ) ) ; ;
f a c t : i n t −> i n t = <fun>
#f a c t 6 ; ;
− : i n t = 720
Джон Харрисон Введение в Функциональное программирование

Вам также может понравиться