Fonksiyon, Dance()
veya Sleep()
gibi, bir eylem gerçekleştirilmesine yönelik talimatlar sağlayan ve sağladığın girdiye göre farklı çıktılar üreten yeniden kullanılabilir koddur.
Fonksiyonlar, davranışlar için soyutlama sağlar. Buna göre, bu yeniden kullanılabilir fonksiyonlar, kodunun diğer bölümleriyle ilgili olmayan ve görülmesi gerekmeyen uygulama ayrıntılarını gizler.
Fonksiyonlar ve soyutlama için bir menüden yemek siparişi vermeyi örnek olarak kullanalım. Yemek siparişi vermeye yönelik fonksiyon aşağıdaki gibi görünebilir:
OrderFood(MenuItem : string) : food = {...}
Bir restoranda yemek siparişi verdiğinde garsona menüden hangi yemeği istediğini söylersin: OrderFood("Ramen")
. Restoranın yemeğini nasıl hazırlayacağını bilemezsin, ancak sipariş verdikten sonra food
olarak değerlendirilecek bir şey getirilmesini beklersin. Diğer müşteriler menüden farklı yemekler sipariş edebilir ve onlar da yiyeceklerinin getirilmesini bekler.
Fonksiyonların faydalı olmasının sebebi de budur; bu talimatları tek bir yerde tanımlaman yeterlidir. Bu durumda söz konusu talimatlar, biri yemek sipariş ettiğinde neler olması gerektiğine dairdir. Ardından fonksiyonu farklı bağlamlarda yeniden kullanabilirsin. Örneğin, restorandaki yemek menüsünden sipariş veren her müşteri için.
Aşağıdaki bölümlerde bir fonksiyonun nasıl oluşturulacağı ve tanımlandıktan sonra fonksiyonun nasıl kullanılacağı açıklanmaktadır.
Fonksiyonların Tanımlanması
Fonksiyon imzası, fonksiyon adı (tanımlayıcı) ile fonksiyonun girdi (parametreler) ve çıktısını (sonuç) bildirir.
Verse fonksiyonlarının belirleyicileri de olabilir. Bunlar bir fonksiyonun nasıl kullanılacağını veya uygulanacağını belirler.
Fonksiyon gövdesi, fonksiyonun çağrıldığı zaman ne yaptığını tanımlayan bir kod bloğudur.
Aşağıdaki bölümlerde bu kavramlar daha ayrıntılı olarak açıklanmaktadır.
Fonksiyon sözdizimi aşağıdaki gibi görünür:
Identifier(parameter1 : type, parameter2 : type) <specifier> : type = {}

Parametreler
Parametre, bir fonksiyon imzasında bildirilen ve fonksiyonun gövdesinde kullanılan bir girdi değişkenidir. Bir fonksiyonu çağırdığında, herhangi bir parametre varsa buna değer ataman gerekir. Atanan değerler, fonksiyonun bağımsız değişkenleri olarak adlandırılır.
Bir fonksiyonun hiçbir parametresi (örneğin, ‘Sleep()’) olmayabileceği gibi ihtiyacın olduğu kadar çok parametresi de olabilir. Fonksiyon imzasında bir parametreyi, bir tanımlayıcı ile türü parantez içinde (()
) belirterek bildirirsin. Birden fazla parametren varsa bunlar virgül (,
) ile ayrılmalıdır.
Örneğin:
Example(Parameter1 : int, Parameter2 : string) : string = {}
Aşağıdakilerin tamamı geçerlidir:
Foo():void = {}
Bar(X:int):int = X
Baz(X:int, ?Y:int, ?Z:int = 0) = X + Y + Z
?Y:int
sözdizimi, int
türünde Y
adlı adlandırılmış bir bağımsız değişken tanımlar.
?Z:int = 0
sözdizimi, fonksiyon çağrıldığında sağlanması gerekmeyen, ancak sağlanmazsa değeri olarak 0
ı kullanan int
türünde Z
adlı adlandırılmış bir bağımsız değişken tanımlar.
Sonuç
Sonuç, bir fonksiyon çağrıldığında o fonksiyonun gerçekleşen çıktısıdır. Dönüş türü, fonksiyon başarıyla yürütüldüğünde o fonksiyondan ne tür bir değer bekleyebileceğini belirler.
Fonksiyonunun bir sonucu olmasını istemiyorsan dönüş türünü void
olarak ayarlayabilirsin. Dönüş türü void
olan fonksiyonlar, fonksiyon gövdesinde bir sonuç ifadesi belirtsen dahi her zaman false
değerini döndürür.
Belirleyiciler
Tanımlanan fonksiyonun davranışını açıklayan fonksiyondaki belirleyicilerin yanı sıra fonksiyonun tanımlayıcısında da (adında) belirleyiciler olabilir. Örneğin:
Foo<public>(X:int)<decides>:int = X > 0
Bu örnekte, herkese açık bir erişime ve decides
efektine sahip Foo
adlı bir fonksiyon tanımlanmaktadır. Parametre listesinden sonra, dönüş türünden ise önce gelen belirleyiciler, fonksiyonun semantiğini açıklar ve elde edilen fonksiyonun türüne katkıda bulunur. Fonksiyonun adındaki belirleyiciler, yalnızca tanımlanmış fonksiyonun adıyla ilgili davranışı, örneğin görünürlüğünü ifade eder.
Fonksiyon Gövdesi
Fonksiyon gövdesi, fonksiyonun ne yaptığını tanımlayan kod bloğudur. Fonksiyon, bir sonuç oluşturmak için fonksiyon gövdesinin fonksiyon imzasında tanımladığın tüm parametreleri kullanır.
Bir fonksiyon, otomatik olarak son yürütülen ifade tarafından üretilen değeri döndürür.
Örneğin:
Foo()<decides>:void = {}
Bar():int =
if (Foo[]):
1
else:
2
Bar()
fonksiyonu, Foo[]
’nun başarısız olup olmamasına bağlı olarak 1
veya 2
sonucunu döndürür.
Belirli bir değerin döndürülmesini (ve fonksiyondan hemen çıkılmasını) zorlamak için return
ifadesini kullan.
Örneğin:
Find(X:[]int, F(:int)<decides>:void)<decides>:int =
for (Y:X, F(Y)):
return Y
false?
return Y
ifadesi, Find
fonksiyonundan çıkarak Y
içinde bulunan değeri fonksiyonu çağırana döndürecektir. false?
’un, fonksiyonu başarısız olmaya zorlamanın bir yolu olarak kullanıldığını unutma. Bu durumda, X
te F
koşulu ile eşleşen hiçbir değer bulunamadığından bu mantıklıdır. void
döndürecek şekilde tanımlanan bir fonksiyon söz konusu olduğunda, bir ifadenin return
ifadesiyle beraber belirtilmesine gerek yoktur.
Örneğin:
AnyOf(X:[]int, F(:int)<decides>:void)<decides>:void =
for (Y:X, F(Y)):
return
false?
Efektler
Bir fonksiyondaki efektler, çağrıldığında fonksiyon tarafından gösterilebilecek ilave davranışları tanımlar. Özellikle decides
efekti fonksiyonun, onu çağıranın işlemesi (veya çağırana yine decides
işaretli olarak yayması) gerekebilecek şekilde başarısız olabileceğini belirtir.
Örneğin:
Fail()<decides>:void = false?
Bu, her zaman başarısız olan bir fonksiyonu tanımlar. Herhangi bir çağıranın hatayı işlemesi veya yayması gerekir. Sözdizimine dikkat et: Efekt, fonksiyondaki bir belirleyici olarak tanımlanmıştır. Böyle bir efekte sahip bir fonksiyon türü, tür makrosu aracılığıyla fonksiyonun tanımına çok benzer hale getirilebilir:
type{_()<decides>void}
Fonksiyonları Çağırma
Fonksiyon çağrısı, bir fonksiyonu değerlendiren (çağırmak olarak bilinir) bir ifadedir.
Verse’te fonksiyon çağrıları için iki biçim vardır:
-
FunctionName(Arguments)
: Bu biçim, fonksiyon çağrısının başarılı olmasını ve herhangi bir bağlamda kullanılabilmesini gerektirir. -
FunctionName[Arguments]
: Bu biçim, fonksiyon çağrısının başarısız olabileceği anlamına gelir. Bu biçimin kullanılabilmesi için, fonksiyonun<decides>
belirleyicisiyle tanımlanması ve bir başarısızlık bağlamında çağrılması gerekir.
Fonksiyon decides
efektine sahip değilse çağrı, parantez kullanılarak gerçekleştirilir. Örneğin:
Foo()
Bar(1)
Baz(1, ?Y := 2)
Baz(3, ?Y := 4, ?Z := 5)
Baz(6, ?Z := 7, ?Y := 8)
?Y:int
gibi adlandırılmış bağımsız değişkenlerin nasıl başına ?
eklenmiş ada referans verilerek ve :=
öğesinin sağına bir değer belirtilerek geçildiğine dikkat et. ?Z
adlandırılmış bağımsız değişkeninin isteğe bağlı olduğunu da unutma. Daha da önemlisi, çağrı sitesindeki adlandırılmış bağımsız değişkenlerin sırası, adlandırılmış bağımsız değişken için değer üretilirken meydana gelebilecek herhangi bir yan etki dışında önemsizdir.
decides
efektine sahip bir fonksiyonu çağırmak için köşeli ayraçlar kullanılmalıdır. Köşeli ayraç kullanımı, decides
efektiyle işaretlenmiş olan fonksiyonlara benzer bir sözdizimi izlenmesini sağlayacak şekilde decides
efektini uygulayan dizi dizini oluşturma işlemine imkân tanır. Örneğin:
Foo()<decides>:void = {}
Bar():int =
if (Foo[]):
1
else:
2
Demet Açma
Birden fazla bağımsız değişkeni kabul eden bir fonksiyon, birden fazla bağımsız değişkenle aynı türden öğeler içeren tek bir demet bağımsız değişkenini kabul eden bir fonksiyondan çağrıldığında ayırt edilemez. Çağrıldığında ayrım olmaması, her fonksiyonun türü için de geçerlidir; bunlar aynı türe sahiptirler.
Örneğin:
Second(:any, X:t where t:type):t = X
Bu aşağıdakine eşdeğerdir:
Second(X:tuple(any, t) where t:type):t = X(1)
Her ikisi de şöyle çağrılabilir:
X := 1
Y := 2
Second(X, Y)
veya
X:tuple(int, int) = (1, 2)
Second(X)
Her ikisi de type{_(:any, :t where t:type):t}
türünü karşılar.
Fonksiyon Türü
Bir fonksiyonun türü, parametresinin türünden (potansiyel olarak açılmış bir demet olarak tanımlanır), efektinden ve sonuç türünden oluşur. Örneğin:
type{_(:type1, :type2)<effect1>:type3}
Bu fonksiyon türü, type1
ve type2
olmak üzere iki bağımsız değişken (veya eşdeğer olarak, tuple(type1, type2)
türünde tek bir bağımsız değişken alan), effect1
efektini üreten ve type3
türünde bir değer döndüren bir türdür.
Aşırı Yükleme
Birden fazla fonksiyon, bu fonksiyonlardan bir tanesinden fazlasını karşılayacak bağımsız değişken olmadığı sürece aynı adı paylaşabilir. Bu kavram, aşırı yükleme olarak bilinir.
Örneğin:
Next(X:int):int = X + 1
Next(X:float):float = X + 1
int_list := class:
Head:int
Tail:?int_list = false
Next(X:int_list)<decides>:int_list = X.Tail?
Bu fonksiyonlardan herhangi birinin hangi bağımsız değişkenleri kabul ettiği konusunda bir çakışma yoktur. Çağırılacak doğru fonksiyonun hangisi olduğu, sağlanan türlerle açık bir şekilde çözülebilir. Örneğin:
Next(0)
Next(0.0)
Next(int_list{Head := 0, Tail := int_list{Head := 1}})
Ancak aşağıdakine izin verilmez:
First(X:int, :any):int = X
First(X:[]int)<decides>int = X[0]
Bunun sebebi, demet ile dizinin bir alt tür oluşturma ilişkisine sahip olmasıdır. Dizinin temel türü, demetin tüm öğe türlerinin bir üst türü olduğunda dizi, demetin bir üst türüdür. Örneğin:
X := (1, 2)
First(X)
Bu örnekte First
’e yapılan çağrı, First
’ün iki tanımından herhangi birisiyle karşılanabilir. Sınıf ve arayüzler söz konusu olduğunda ise hiçbir aşırı yükleme oluşamaz çünkü bir sınıf sonradan bir arayüzü uygulamak için değiştirilebilir veya iki sınıf bir devralma ilişkisine sahip olacak şekilde değiştirilebilir. Aşırı yükleme yerine metot geçersiz kılma kullanılmalıdır. Örneğin,
as_int := interface:
AsInt():int
ToInt(X:as_int):int = X.AsInt()
thing1 := class(as_int):
AsInt():int = 1
thing2 := class(as_int):
AsInt():int = 2
Main()<decides>:void =
X := thing1{}
ToInt(X) = 1
Y := thing2{}
ToInt(Y) = 2
Bir sınıf tanımının parçası olan fonksiyonlara metot adı verilir ve metotlar ek işlevlere sahiptir. Verse’teki fonksiyonlarla ilgili bilgi edindikten sonra metotlar hakkında daha fazla bilgi edinmek için Sınıf bölümüne bakabilirsin.