GF


Kaarel Kaljurand
Institute of Computational Linguistics, University of Zurich

Be Informed, Apeldoorn
2012-12-11/12

Presenter Notes

Outline

  • introduction to GF
  • introduction to the Resource Grammar Library
  • tools
  • applications
  • ???

Presenter Notes

About the speaker

  • Kaarel Kaljurand
  • post doc @ Uni Zurich
    • working in MOLTO on multilingual CNL-based semantic wiki
  • GF experience: 1.5 years
  • GF projects
    • multilingual ACE in GF
    • Estonian speech recognition grammars
    • very preliminary Estonian resource grammar
    • Java front-end + various Python scripts for working with the GF webservice
  • language skills
    • good: Estonian, English
    • bad: German, Dutch, Finnish, Hungarian, Russian

Presenter Notes

Grammatical Framework (GF)

  • functional programming language for grammar engineering
  • parsing and generation (linearizing)
  • focus on multilinguality
    • multiple concrete grammars
    • common single abstract grammar (language-neutral)
    • translate = parse string in concrete language A to abstract tree + linearize tree as a string in concrete language B
  • special support for natural language features
    • long-distance dependencies
    • word form generation
    • Resource Grammar Library (RGL)
  • use case: defining multilingual controlled natural languages
  • developed by Aarne Ranta et al at the University of Gothenburg

Presenter Notes

Example (parse + linearize)

String

this delicious cheese is very Italian (English)

Language-neutral tree

Strings

questo formaggio delizioso è molto italiano (Italian, notice word order)
see maitsev juust on väga itaaliapärane (Estonian)
...

Presenter Notes

Usage examples

  • multilingual natural language processing applications
    • map between a natural language and another natural language
    • map between a natural language and a formal language
  • complex NLP applications defined entirely by a grammar, where the complexity is about:
    • multilinguality
    • multimodality (touch, speech, ...)
    • dialogue
  • applications with small (and controlled) vocabulary and grammar
    • software localization
    • although: recent work on parsing large corpora (Penn treebank) with GF (see: Krasimir Angelov)

Presenter Notes

GF as a machine translation tool

  • grammar-based translation (e.g. GF)
    • high precision
    • low coverage
    • suitable for translating human-computer interfaces
  • statistical machine translation (e.g. Google Translate)
    • low precision
    • high coverage
    • suitable for translating arbitrary webpages
  • producer vs consumer
    • when I write (produce) a letter I care about quality (e.g. use gengo.com)
    • when I read (consume) a letter I use Google Translate
  • GF targets producers

Presenter Notes

Abstract syntax as interlingua

Benefit: n translation descriptions, not n*n

Presenter Notes

Topic 1: Getting started

Presenter Notes

GF's "Hello world" abstract + concrete

Abstract syntax

abstract Hello = {


  flags startcat = Greeting ;

  cat Greeting ; Recipient ;


  fun
    Hello : Recipient -> Greeting ;
    World, Mum, Friends : Recipient ;
}

Concrete syntax

concrete HelloEng of Hello = {

  lincat Greeting, Recipient = {s : Str} ;

  lin
    Hello recip = {s = "hello" ++ recip.s} ;
    World = {s = "world"} ;
    Mum = {s = "mum"} ;
    Friends = {s = "friends"} ;
}

Presenter Notes

GF's "Hello world" more concretes

Finnish

-- This is a comment
concrete HelloFin of Hello = {
  lincat Greeting, Recipient = {s : Str} ;
  lin
    Hello recip = {s = "terve" ++ recip.s} ;
    World 
= {s = "maailma"} ;
    Mum = {s = "äiti"} ;
    Friends 
= {s = "ystävät"} ;
}

Italian

{-
   This is a multiline comment
-}
concrete HelloIta of Hello = {
  lincat Greeting, Recipient = {s : Str} ;
  lin
    Hello recip = {s = "ciao" ++ recip.s} ;
    World = {s = "mondo"} ;
    Mum = {s = "mamma"} ;
    Friends = {s = "amici"} ;
}

Presenter Notes

GF's "Hello world" abstract syntax

  • abstract syntax:
    • Greeting, where we greet a Recipient, which can be World or Mum or Friends
  • a comment (optional), saying what the module is doing
  • a module header indicating that it is an abstract syntax module named Hello
  • a module body in braces, consisting of
    • a startcat flag declaration stating that Greeting is the default start category for parsing and generation
    • category declarations introducing two categories, i.e. types of meanings
    • function declarations introducing three meaning-building functions

Presenter Notes

GF's "Hello world" concrete syntax

  • a module header indicating that it is a concrete syntax named HelloEng of the abstract syntax Hello
  • a module body in curly brackets, consisting of
    • linearization type definitions stating that Greeting and Recipient are records holding a single string in the field s
    • linearization definitions telling what records are assigned to each of the meanings defined in the abstract syntax
  • notice:
    • concatenation: ++
    • record: { s : Str }, { s = "world" }
    • record projection: recip.s

Presenter Notes

Using the GF's "Hello world"

Compile info PGF and load it

$ gf --make Hello???.gf
$ gf Hello.pgf

GF commandline examples

> parse "hello world"
Hello World

> parse "hello dad"
Unknown words: dad

> parse "world hello"
no tree found

> linearize Hello World
hello world

> parse -lang=HelloEng "hello friends" | linearize
terve ystävät
ciao amici
hello friends

Look at the internal grammar representation

> print_grammar

Presenter Notes

GF's "Hello world" summary

  • multilinguality: the message is printed in many languages.
  • reversibility: in addition to printing, you can parse the message and translate it to other languages.
  • grammar = abstract syntax + one or more concrete syntaxes

Presenter Notes

Main objects when working with GF

Token sequence

  • sequence of strings
  • obtained by lexing an input string
    • e.g. "Hello, John!" -> ["hello", ",", "John", "!"]
  • input to parser
  • output of linearizer

Tree

  • structure of abstract function names
    • e.g. (f1 (f2 f3))
    • other forms of trees are also possible, e.g. (f1 (f2 ?1))
  • output of parser
  • input to linearizer

Presenter Notes

Main operations

Parse

  • analyze token sequence (in a given language, assuming a category)
  • ... and map it to a set of trees
    • 0 trees: parsing failed
    • 1 tree: token sequence has only one "meaning"
    • 2 or more trees: token sequence is ambiguous

Linearize

  • analyze a tree
  • ... and map it to a token sequence (in some language)
  • variants = sequences that correspond to the same tree in the same language
    • e.g. "do not" and "don't"

Presenter Notes

Ambiguity

Examples from MOLTO Phrasebook

  • are you German?
    • 8 readings in Phrasebook
    • "you": sg/pl, man/woman, polite/informal
    • all 8 are expressed in some Romance languages
  • hello!
    • 2 readings in Phrasebook
    • only revealed in Thai ("hello by man" and "hello by woman")

Example from ACE-in-GF

p -lang=Dut "John ziet precies 2 personen , die slechts reizigers inspecteren ."
| l -lang=Ace,Fin

John sees exactly 2 persons who inspect nothing but travelers .
John näkee tasan 2 henkilö
 , joka tarkastaa vain matkustajia .

John sees exactly 2 persons who nothing but travelers inspect .
John näkee tasan 2 henkilö
 , jonka vain matkustajat tarkastavat .

Presenter Notes

GF example (3 grammar modules)

abstract Unitconv = {
  flags startcat = Unitconv ;
  cat Unit ; Unitconv ;
  fun
    f1 : Unit -> Unit -> Unitconv ;
    f2, f3: Unit ;
}

concrete UnitconvDut of Unitconv = {
  lincat Unit, Unitconv = {s : Str} ;
  lin
    f1 x y = {s = "hoeveel is" ++ x.s ++ "in" ++ y.s ++ "?"} ;
    f2 = {s = "mijl"} ;
    f3 = {s = "nautische mijl" | "mijl"} ;
}

concrete UnitconvWolfram of Unitconv = {
  lincat Unit, Unitconv = {s : Str} ;
  lin
    f1 x y = {s = "convert" ++ x.s ++ "to" ++ y.s} ;
    f2 = {s = "mile"} ;
    f3 = {s = "nmi"} ;
}

Presenter Notes

GF parsing and linearizing

Parsing i.e. converting a string hoeveel is nautische mijl ... to tree(s)

Unitconv> parse -lang=Dut "hoeveel is nautische mijl in mijl ?"

f1 f3 f2
f1 f3 f3

Linearization i.e. converting a tree f1 f3 f2 to string(s)

Unitconv> linearize -treebank -list (f1 f3 f2)

UnitconvDut: hoeveel is nautische mijl in mijl ?, , hoeveel is mijl in mijl ?
UnitconvWolfram: convert nmi to mile

Translation i.e. parse + linearize

Unitconv> parse -lang=Dut "hoeveel is nautische mijl in mijl ?" | l -lang=Wolfram

convert nmi to mile
convert nmi to nmi

Presenter Notes

Ambiguity and synonymy

  • it is up to the application designer what is considered ambiguous and synonymous
  • example: synonymous or not?
    • you should perform the intake in order to publish the invitation.
    • you must perform the intake in order to publish the invitation.
    • in order to publish the invitation, you should perform the intake.
    • you should perform the intake to publish the invitation.
    • perform the intake to publish the invitation!
  • example: "hello"
    • unambiguous, but ...
    • should be made ambiguous when Thai is added to the grammar

Presenter Notes

The grammar also supports ...

  • morphological analysis: find out the possible inflection forms of words
  • morphological synthesis: generate all inflection forms of words
  • random generation: generate random expressions
  • corpus generation: generate all expressions
  • treebank generation: generate a list of trees with their linearizations
  • teaching quizzes: train morphology and translation
  • multilingual authoring: create a document in many languages simultaneously
  • speech input: optimize a speech recognition system for a grammar

Presenter Notes

Topic 2: Designing a grammar for complex phrases

Presenter Notes

Goals

  • build a larger grammar: phrases about food in English and Italian
  • learn to write reusable library functions ("operations")
  • learn the basics of GF's module system

Presenter Notes

The "Food" grammar

abstract Food = {

  flags startcat = Phrase ;


  cat
    Phrase ; Item ; Kind ; Quality ;

  fun
    Is : Item -> Quality -> Phrase ;
    This, That : Kind -> Item ;
    QKind : Quality -> Kind -> Kind ;
    Wine, Cheese, Fish : Kind ;
    Very : Quality -> Quality ;
    Fresh, Warm, Italian, Expensive, Delicious, Boring : Quality ;
}

Presenter Notes

"FoodEng"

concrete FoodEng of Food = {

  lincat
    Phrase, Item, Kind, Quality = {s : Str} ;


  lin
    Is item quality = {s = item.s ++ "is" ++ quality.s} ;

    This kind = {s = "this" ++ kind.s} ;
    That kind = {s = "that" ++ kind.s} ;
    QKind quality kind = {s = quality.s ++ kind.s} ;
    Wine = {s = "wine"} ;
    Cheese = {s = "cheese"} ;
    Fish = {s = "fish"} ;
    Very quality = {s = "very" ++ quality.s} ;
    Fresh = {s = "fresh"} ;
    Warm = {s = "warm"} ;
    Italian = {s = "Italian"} ;
    Expensive = {s = "expensive"} ;
    Delicious = {s = "delicious" | "exquisit" | "tasty"} ; -- NB: variants
    Boring = {s = "boring"} ;
}

Presenter Notes

"FoodIta"

concrete FoodIta of Food = {

  lincat
    Phrase, Item, Kind, Quality = {s : Str} ;

  lin
    Is item quality = {s = item.s ++ "
è" ++ quality.s} ;
    This kind = {s = "questo" ++ kind.s} ;
    That kind = {s = "quel" ++ kind.s} ;
    QKind quality kind = {s = kind.s ++ quality.s} ; -- NB: word order
    Wine = {s = "vino"} ;
    Cheese = {s = "formaggio"} ;
    Fish = {s = "pesce"} ;

    Very quality = {s = "molto" ++ quality.s} ;
    Fresh = {s 
= "fresco"} ;
    Warm = {s = "caldo"} ;
    Italian = {s = "italiano"} ;
    Expensive = {s = "caro"} ;
    Delicious = {s = "delizioso"} ;
    Boring = {s = "noioso"} ;
}

Presenter Notes

Operation definitions

  • problem in the previous examples: too much repetition
    • ++
    • {s = .. }
    • lots of similar lin structures
  • solution
    • write an operator

Example

Instead:

This kind = {s = "questo" ++ kind.s} ;
That kind = {s = "quel" ++ kind.s} ;

we want to write:

This = prefix "questo" ;
That = prefix "quel" ;

Presenter Notes

Example: resource module

resource StringOper = {
  oper
    SS : Type = {s : Str} ;
    ss : Str -> SS = \x -> {s 
= x} ;
    cc : SS -> SS -> SS = \x,y -> ss (x.s ++ y.s) ;
    prefix : Str -> SS -> SS = \p,x -> ss (p ++ x.s) ;
}

Explanation

  • SS becomes a shorthand for {s : Str} (record with a single string)
  • ss constructs a SS from a given string Str
  • cc concatenates 2 SS args and returns SS
  • prefix is the same as cc, but the 1st arg is a simple string

Presenter Notes

Example: opening a resource

concrete FoodEng of Food = open StringOper in {
  lincat S, Item, Kind, Quality = SS ;
  lin
    Is item quality = cc item (prefix "is" quality) ;
    This = prefix "this" ; -- same as: This k = prefix "this" k ;
    That = prefix "that" ;
    QKind k q = cc k q ;
    Wine = ss "wine" ;
    Cheese = ss "cheese" ;
    Fish = ss "fish" ;
    Very = prefix "very" ;
    Fresh = ss "fresh" ;
    Warm = ss "warm" ;
    Italian = ss "Italian" ;
    Expensive = ss "expensive" ;
    Delicious = ss "delicious" ;

    Boring = ss "boring" ;
}

Notice:

  • Wine takes no arguments and has lincat SS, ss "wine" produces SS
  • This takes 1 argument (SS) and has lincat SS, prefix "this" also takes one argument (SS) and produces SS

Presenter Notes

Overloading

Idea: for readability reasons reuse the same name for different functions.

This cannot be done directly but can be done via the overload statement.

oper mkN : overload {
  mkN : (dog : Str) -> Noun ;         -- regular nouns
  mkN : (mouse,mice : Str) -> Noun ;  -- irregular nouns
}

The definition can be also given at the same time:

oper mkN = overload {
  mkN : (dog : Str) -> Noun = regNoun ;
  mkN : (mouse,mice : Str) -> Noun = mkNoun ;
}

where regNoun is an operator that takes a string and produces Noun, and mkNoun is one that takes two strings as arguments.

Presenter Notes

Extending a grammar

abstract Morefood = Food, Fruit, Mushroom ** {
  cat
    Question ;
  fun
    QIs : Item -> Quality -> Question ;
    Pizza : Kind ;
}

concrete MorefoodIta of Morefood = FoodIta, FruitIta ** open StringOper in {
  lincat

    Question = SS ;
  lin
    QIs item quality = ss (item.s ++ "
è" ++ quality.s) ;
    Pizza = ss "pizza" ;
}

Presenter Notes

Extending a grammar

  • extending applies to all names (funs, lins, opers)
  • by default everything is copied over
    • potential for naming conflicts!
  • restricted inheritance
    • Fruit [Peach,Apple]: include only Peach and Apple from Fruit
    • Fruit - [Peach,Apple]: include everything but exclude Peach and Apple

Presenter Notes

Topic 3: Parameters and tables

Presenter Notes

Goals

  • implement sophisticated linguistic structures:
    • morphology: the inflection of words
    • agreement: rules for selecting word forms in syntactic combinations
  • cover all GF constructs for concrete syntax
  • NB: the following only applies to the concrete syntax

Presenter Notes

Parameters and tables

Parameter type Number enumerating its constructors.

param Number = Sg | Pl ;

Table that depends on Number used in a linearization type.

lincat Kind = {s : Number => Str} ;

The linearization of Cheese is now a record that holds a table whose leaves are strings. This models the fact that the form of Cheese depends on Number (parametric feature).

lin Cheese = {
  s = table {
    Sg => "cheese" ;
    Pl => "cheeses"
  }
} ;

To be able to extract the string (which we need for the surface form) we use the selection operator !, e.g.

table {Sg => "cheese" ; Pl => "cheeses"} ! Pl

which returns "cheeses".

Presenter Notes

Complex parameters

Constructors can take arguments from other parameter types.

param Number = Sg | Pl ;
param VerbForm = VPresent Number | VPast | VPastPart | VPresPart ;

A table VerbForm => Str:

table {
  VPresent Sg => "drinks" ;
  VPresent Pl => "drink" ;
  VPast       => "drank" ;
  VPastPart   => "drunk" ;
  VPresPart   => "drinking"
  }

Presenter Notes

Operators returning tables

A word is really a more complex structure than a string.

oper regNoun : Str -> {s : Number => Str} = \dog -> {
  s = table {
    Sg => dog ;
    Pl => dog + "s"
    }
  } ;

oper regVerb : Str -> {s : VerbForm => Str} = \talk -> {
  s = table {
    VPresent Sg => talk + "s" ;
    VPresent Pl => talk ;
    VPresPart   => talk + "ing" ;
    _           => talk + "ed" -- i.e. VPast (I asked), VPastPart (asked by)
    }
  } ;

Notice

  • glue operator + for concatenating strings into a single token (can be applied only to strings known at compile time)
  • "catch all" case _ applies to everything which the pattern matcher did not yet match
  • linguistic remark: exceptions like "mice" and "seen" are not handled

Presenter Notes

Using parameters in concrete syntax

Abstract definition of Is does not specify agreement restrictions:

-- this/these pizza/pizzas is/are warm
fun Is : Item -> Quality -> Phrase ;

Copula (koppelwerkwoord) depends on the number (in English):

-- copula Sg ==> "is"
oper copula : Number -> Str = \n ->
  case n of {
    Sg => "is" ;
    Pl => "are"
    } ;

Item (e.g. "this wine") should contain information about its number to be able to pass it along.

lincat Item = {s : Str ; n : Number} ;

lin This kind = {
  s = "this" ++ kind.s ! Sg ;
  n = Sg
} ;

We can now form a correct sentence

lin Is item qual = {s = item.s ++ copula item.n ++ qual.s} ;

Presenter Notes

The case-statement

Case expressions are syntactic sugar for tables:

case e of {...} ===  table {...} ! e

i.e. these definitions are equivalent:

oper copula : Number -> Str = \n ->
  case n of {
    Sg => "is" ;
    Pl => "are"
    } ;

oper copula : Number -> Str = \n ->
  table {
    Sg => "is" ;
    Pl => "are"
    } ! n ;

Presenter Notes

Parametric vs. inherent features

Kinds have number as a parametric feature: both singular and plural can be formed,

lincat Kind = {s : Number => Str} ;

Items have number as an inherent feature: they are inherently either singular or plural,

lincat Item 
= {s : Str ; n : Number} ;

Italian Kind will have parametric number and inherent gender:

lincat Kind = {s : Number => Str ; g : Gender} ;

Questions to ask when designing parameters:

  • existence: what forms are possible to build by morphological and other means?
  • need: what features are expected via agreement or government?

Presenter Notes

Tables of tables

English has also cases:

param Case = Nom | Gen ;

and noun forms depend on both number and case:

oper Noun : Type = {s : Number => Case => Str} ;

We can define the worst-case function that builds the internal English noun structure like this (where x = singular form, y = plural form):

oper mkNoun : Str -> Str -> Noun 
= \x,y -> {
  s = table {
    Sg => table {
      Nom => x ;
      Gen => x + "'s"
      } ;
    Pl => table {

      Nom => y ;
      Gen => y + case last y of {
        "s" => "'" ;
        _   => "'s"
      }
    }
  } ;

Presenter Notes

Smart paradigms

For convenience reasons and thanks to the fact that English plural nouns usually end with "s", we can define a 1-argument version:

oper regNoun : Str -> Noun = \x -> mkNoun x (x + "s")

Note that it hides the internal noun structure, i.e. only mkNoun operates with that.

We can make the operator even smarter (more accurate):

oper regNoun : Str -> Noun = \w ->
  let
    ws : Str = case w of {
      _ + ("a" | "e" | "i" | "o") + "o" => w + "s" ;  -- bamboo
      
_ + ("s" | "x" | "sh" | "o")      => w + "es" ; -- bus, hero
      _ + "z"                           => w + "zes" ;-- quiz
      _ + ("a" | "e" | "o" | "u") + "y" => w + "s" ;  -- boy
      x + "y"                           => x + "ies" ;-- fly
      _                                 => w + "s"    -- car
      }
  in
  mkNoun w ws

Presenter Notes

Regular expressions

GF supports regular expression patterns:

_ + ("a" | "e" | "i" | "o") + "o" => w + "s" ;  -- bamboo
_ + ("s" | "x" | "sh" | "o")      => w + "es" ; -- bus, hero
x + "y"                           => x + "ies" ;-- fly
_                                 => w + "s"    -- car

Note:

  • disjunctive patterns P | Q
  • concatenation patterns P + Q
  • variable (x) matches anything and gets bound to it
  • ordering matters, e.g. the suffix "oo" prevents "bamboo" from matching the suffix "o".

Presenter Notes

Smart paradigms

Idea: operator calculates the word full paradigm from as little input information as possible. As a result the application lexicon will look simple and easy to modify.

English

Dog = mkN "dog"
Mouse = mkN "mouse" "mice"
...

German

Country = mkN "Land" neutr
...

Presenter Notes

The \\ notation

Concise notation for tables

\\x1,...,xn => t === table { x1 => ... table { xn => t } ... }

Example

param Number = Sg | Pl ;
      Gender = Masc | Fem ;

lincat
  Quality = {s : Gender => Number => Str} ;
  Kind = {s : Number => Str ; g : Gender} ;

lin
  QKind quality kind = {
    s = table {
      Sg => kind.s ! Sg ++ quality.s ! kind.g ! Sg ;
      Pl => kind.s ! Pl ++ quality.s ! kind.g ! Pl ;
    };
    g = kind.g
  } ;


  -- NB: shorter
  QKind quality kind = {
    s = \\n => kind.s ! n ++ quality.s ! kind.g ! n ;
    g = kind.g
  } ;

Presenter Notes

The \\ notation. Example 2

param
  Number = Sg | Pl ;
  Gender = Masc | Fem ;

lincat
  Quality = {s : Gender => Number 
=> Str} ;
  Kind = {s : Number => Str ; g : Gender} ;

lin
  Very qual = {s = table {
        Masc => table {
            Sg => "molto" + qual.s ! Masc ! Sg ;
            Pl => "molto" + qual.s ! Masc ! Pl
        } ;
        Fem => table {
            Sg => "molto" + qual.s ! Fem ! Sg ;
            Pl => "molto" + qual.s ! Fem ! Pl
        }
    }
  }

  -- NB: shorter

  Very qual = {s = \\g,n => "molto" ++ qual.s ! g ! n} ;

Presenter Notes

Extending records

Example: German transitive verbs (i.e. verbs that syntactically require an object) determine the case of their object (i.e. this is an inherent feature).

Clean solution: extend the regular verb type and definitions:

lincat TV = Verb ** {c : Case} ;

lin Follow = regVerb "folgen" ** {c = Dative} ;

Benefits

  • TV can be used in contexts where Verb is required

Presenter Notes

Prelude. Strings

-- Optional string with preference on the string vs. empty.
optStr : Str -> Str = \s -> variants {s ; []} ;
strOpt : Str -> Str = \s -> variants {[] ; s} ;

-- Infix
infixSS   : Str  -> SS -> SS -> SS = \f,x,y -> ss (x.s ++ f ++ y.s) ;
prefixSS  : Str        -> SS -> SS = \f,x   -> ss (f ++ x.s) ;
...

-- Bind together two tokens in some lexers, either obligatorily or optionally
glue : Str -> Str -> Str = \x,y -> x ++ BIND ++ y ;
glueOpt : Str -> Str -> Str = \x,y -> variants {glue x y ; x ++ y} ;
noglueOpt : Str -> Str -> Str = \x,y -> variants {x ++ y ; glue x y} ;

-- Force capitalization of next word in some unlexers
capitalize : Str -> Str = \s -> CAPIT ++ s ;

-- These should be hidden, and never changed since they are hardcoded
-- in (un)lexers
BIND : Str = "&+" ;
PARA : Str = "&-" ;
CAPIT : Str = "&|" ;

-- Parentheses
paren : Str -> Str = \s -> "(" ++ s ++ ")" ;
parenss : SS -> SS = \s -> ss (paren s.s) ;

Presenter Notes

Prelude. Other

-- Missing form.
nonExist : Str = variants {} ;

-- Identity function
id : (A : Type) -> A -> A = \_,a -> a ;

-- Zero, one, two, or more (elements in a list etc)
param ENumber = E0 | E1 | E2 | Emore ;

eNext : ENumber -> ENumber = \e -> case e of {
  E0 => E1 ; E1 => E2 ; _ => Emore
} ;

-- Use the glue operator with arbitrary number of arguments
-- (where arbitrary =< 5 ;))
BIND : Str = "&+" ;
glue = overload {
  glue : (x1,x2 : Str) -> Str = \x1,x2 -> x1 ++ BIND ++ x2 ;
  glue : (x1,x2,x3 : Str) -> Str = \x1,x2,x3 -> x1 ++ BIND ++ x2 ++ BIND ++ x3;
  glue : (x1,x2,x3,x4 : Str) -> Str = \x1,x2,x3,x4 -> x1 ++ BIND ++ x2 ++ ... 
  glue : (x1,x2,x3,x4,x5 : Str) -> Str = \x1,x2,x3,x4,x5 -> x1 ++ BIND ++ ... 
};

Presenter Notes

Prelude. Booleans

param Bool = True | False ;

oper

if_then_else : (A : Type) -> Bool -> A -> A -> A = \_,c,d,e ->
  case c of {
    True => d ;

    False => e
  } ;

andB : (_,_ : Bool) -> Bool = \a,b -> if_then_else Bool a b False ;
orB  : (_,_ : Bool) -> Bool = \a,b -> if_then_else Bool a True b ;
notB : Bool         -> Bool = \a   -> if_then_else Bool a False True ;

if_then_Str : Bool -> Str -> Str -> Str = if_then_else Str ;


onlyIf : Bool -> Str -> Str = \b,s -> case b of {
  True => s ;
  _ => nonExist
} ;

Presenter Notes

Topic 4: Resource Grammar Library

Presenter Notes

Resource Grammar Library (motivation)

  • contain the linguistic knowledge
  • provide a language-neutral API over many languages
  • currently 26 languages
    • mostly European languages

Presenter Notes

Presenter Notes

Lexical vs. phrasal rules

A resource grammar has two kinds of categories and two kinds of rules:

  • lexical:
    • lexical categories, to classify words
    • lexical rules, to define words and their properties
  • phrasal (combinatorial, syntactic):
    • phrasal categories, to classify phrases of arbitrary size
    • phrasal rules, to combine phrases into larger phrases

GF makes no formal distinction between these two kinds. But it is a good discipline to follow.

Presenter Notes

Lexical categories

Two kinds of lexical categories:

  • closed:
    • a finite number of words
    • seldom extended in the history of language
    • structural words / function words

Example:

  Conj ;     -- conjunction           e.g. "and"
  Det ;      -- determiner            e.g. "this"
  • open:
    • new words are added all the time
    • content words

Example:

  N ;        -- noun         e.g. "pizza"
  A ;        -- adjective    e.g. "good"
  V ;        -- verb         e.g. "sleep"

Presenter Notes

Lexical rules

this_Det, that_Det, these_Det, those_Det : Det ;
very_AdA  : AdA ;

Presenter Notes

Phrasal categories

Cl ;   -- clause             e.g. "this pizza is good"
NP ;   -- noun phrase        e.g. "this pizza"
CN ;   -- common noun        e.g. "warm pizza"
AP ;   -- adjectival phrase  e.g. "very warm"
...

Presenter Notes

Syntactic combinations

We need the following combinations:

mkCl : NP -> AP -> Cl ;      -- e.g. "this pizza is very warm"
mkNP : Det -> CN -> NP ;     -- e.g. "this pizza"
mkCN : AP -> CN -> CN ;      -- e.g. "warm pizza"
mkAP : AdA -> AP -> AP ;     -- e.g. "very warm"

We also need lexical insertion, to form phrases from single words:

mkCN : N -> NP ;
mkAP : A -> AP ;

Naming convention: to construct a C, use a function mkC.

Heavy overloading: e.g. ~20 operations named mkNP!

Presenter Notes

RGL API

Language-specific and language-independent parts:

  • the syntax API Syntax??? has the same types and functions for all languages
  • the morphology API Paradigms??? has partly different types and functions for different languages

Presenter Notes

Paradigms (examples)

Dutch

> i -path=alltenses -retain alltenses/ParadigmsDut.gfo
> cc -table mkN "auto"
s . ResDut.NF ParamX.Sg ResDut.Nom => auto
s . ResDut.NF ParamX.Sg ResDut.Gen => autos
s . ResDut.NF ParamX.Pl ResDut.Nom => auto's
s . ResDut.NF ParamX.Pl ResDut.Gen => auto's
g . ResDut.Utr

Finnish

> i -path=alltenses -retain alltenses/ParadigmsFin.gfo
> cc -table mkN "talo"
s . ResFin.NCase ParamX.Sg ResFin.Nom => talo
s . ResFin.NCase ParamX.Sg ResFin.Gen => talon
s . ResFin.NCase ParamX.Sg ResFin.Part => taloa
s . ResFin.NCase ParamX.Sg ResFin.Transl => taloksi

...
...
...

Presenter Notes

"Foods" concrete using RGL

lincat
  Phrase = Cl ;
  Item = NP ;
  Kind = CN ;
  Quality = AP ;

lin
  Is item quality = mkCl item quality ;
  This kind = mkNP this_Det kind ;
  That kind = mkNP that_Det kind ;
  These kind = mkNP these_Det kind ;
  Those kind = mkNP those_Det kind ;
  QKind quality kind = mkCN quality kind ;
  Very quality = mkAP very_AdA quality ;

Lexical rules (English-specific):

  Wine = mkCN (mkN "wine") ;
  Pizza = mkCN (mkN "pizza") ;
  Cheese = mkCN (mkN "cheese") ;
  -- NB: 'fish' is irregular word, 1-arg smart paradigm would fail
  Fish = mkCN (mkN "fish" "fish") ;
  Fresh = mkAP (mkA "fresh") ;
  Warm = mkAP (mkA "warm") ;
  Italian 
= mkAP (mkA "Italian") ;
  Expensive = mkAP (mkA "expensive") ;

Presenter Notes

Functors (parametrized modules)

  • functor is a module that opens one or more interfaces
  • interface is a module similar to resource, but it only contains the types of opers, not (necessarily) their definitions

Presenter Notes

Syntax for functors

Add the keyword incomplete. We will use the header

incomplete concrete FoodsI of Foods = open Syntax, LexFoods in

where

interface Syntax    -- the resource grammar interface
interface LexFoods  -- the domain lexicon interface

When we moreover have

instance SyntaxEng of Syntax     -- the English resource grammar
instance LexFoodsEng of LexFoods -- the English domain lexicon

we can write a functor instantiation

concrete FoodsGer of Foods = FoodsI with
  (Syntax = SyntaxGer),
  (LexFoods = LexFoodsGer) ;

Presenter Notes

"Foods" functor

incomplete concrete FoodsI of Foods = open Syntax, LexFoods in {
lincat
  Phrase = Cl ;
  Item = NP ;
  Kind 
= CN ;
  Quality = AP ;
lin
  Is item quality = mkCl item quality ;
  This kind = mkNP this_Det kind ;
  That kind = mkNP that_Det kind ;
  These kind = mkNP these_Det kind ;
  Those kind = mkNP those_Det kind ;
  QKind quality kind = mkCN quality kind ;
  Very quality = mkAP very_AdA quality ;

  Wine = mkCN wine_N ;

  Pizza = mkCN pizza_N ;
  Cheese = mkCN cheese_N ;
  Fish 
= mkCN fish_N ;
  Fresh = mkAP fresh_A ;
  Warm = mkAP warm_A ;
  Italian = mkAP italian_A ;
  ...
}

Presenter Notes

"Foods" domain lexicon

interface LexFoods = open Syntax in {
oper
  wine_N : N ;
  pizza_N : N ;
  cheese_N : N ;
  fish_N : N ;
  fresh_A : A ;
  warm_A : A ;
  italian_A : A ;
  expensive_A : A ;
  delicious_A : A ;
  boring_A : A ;
}

Presenter Notes

"Foods" German lexicon

Definitions for the opers in the instance.

instance LexFoodsGer of LexFoods = open SyntaxGer, ParadigmsGer in {
oper
  wine_N = mkN "Wein" ;
  pizza_N = mkN "Pizza" "Pizzen" feminine ;
  cheese_N = mkN "Käse" "Käsen" masculine ;
  fish_N = mkN "Fisch" ;
  fresh_A = mkA "frisch" ;
  warm_A = mkA "warm" "wärmer" "wärmste" ;
  italian_A = mkA "italienisch" ;
  expensive_A = mkA "teuer" ;
  delicious_A = mkA "k
stlich" ;
  boring_A = mkA "langweilig" ;
}

Presenter Notes

"Foods" German grammar

Super simple! Just parametrizes the functor (FoodsI) with the German grammar and the domain lexicon.

concrete FoodsGer of Foods = FoodsI with
  (Syntax = SyntaxGer),
  (LexFoods = LexFoodsGer) ;

Presenter Notes

Adding languages to "Foods"

Just two modules are needed:

  • a domain lexicon instance
  • a functor instantiation

The functor instantiation is completely mechanical to write.

The domain lexicon instance requires some knowledge of the words of the language:

  • what words are used for which concepts
  • how the words are constructed
  • features such as gender

Presenter Notes

Module structure

Module types:

  • abstract: Foods
  • concrete: FoodsFin, FoodsI (incomplete)
  • interface: Syntax, LexFoods
  • instance: SyntaxFin, LexFoodsFin

Presenter Notes

Listing of lib/src/dutch/

AdjectiveDut.gf
AdverbDut.gf
AllDutAbs.gf
AllDut.gf
CatDut.gf
ConjunctionDut.gf
ExtDut.gf
ExtraDutAbs.gf
ExtraDut.gf
GrammarDut.gf
IdiomDut.gf
IrregDutAbs.gf
IrregDut.gf
LangDut.gf
LexiconDut.gf
MakeStructuralDut.gf: resource
MorphoDut.gf: resource
NounDut.gf
NumeralDut.gf
ParadigmsDut.gf: resource
PhraseDut.gf
QuestionDut.gf
RelativeDut.gf
ResDut.gf
SentenceDut.gf
StructuralDut.gf
SymbolDut.gf
VerbDut.gf

Presenter Notes

Listing of lib/src/abstract/

Adjective.gf
Adverb.gf
Backward.gf
Cat.gf
Common.gf
Compatibility.gf
Conjunction.gf
Extra.gf
Grammar.gf
Idiom.gf
Lang.gf
Lexicon.gf
Noun.gf
Numeral.gf
NumeralTransfer.gf
Phrase.gf
Question.gf
Relative.gf
Sentence.gf
Structural.gf
Symbol.gf
Tense.gf
Text.gf
Transfer.gf
Verb.gf

Presenter Notes

Listing of lib/src/api/

Combinators.gf: incomplete resource
CombinatorsDut.gf: resource
...
Constructors.gf: incomplete resource
ConstructorsDut.gf: resource
...
Syntax.gf: interface

SyntaxDut.gf: instance
...
Symbolic.gf: incomplete resource
SymbolicDut.gf: resource
...
TryDut.gf
...

Presenter Notes

Recap: GF module types

Presenter Notes

abstract

  • language-neutral
  • categories (cat) and functions (fun)
  • at most one startcat (can be also given at runtime)
  • each function has exactly one type
    • type is a category
    • or a structure of categories
  • dependent types (see Topic 5)

Presenter Notes

abstract (example)

abstract Test = {
    flags startcat = Greeting ;

    cat Greeting ;
    cat Name ;

    fun hi : Greeting ;
    fun personal_hi : Name -> Greeting ;
    fun two_person_hi : Name -> Name -> Greeting ;

    -- Note: you cannot use the same function name
    -- with a different type
    -- fun hi : Name -> Greeting ;

    -- this is OK because -> is right assoc.
    fun two_person_hi : Name -> (Name -> Greeting) ;

    fun weird_hi : (Name -> Name) -> Greeting ;
}

Presenter Notes

concrete

  • concrete language (e.g. Dutch)
  • of some abstract module (e.g. some application)
  • category linearization type (lincat)
  • function linearization (lin)
  • possibly complex linearization types
    • string
    • record
    • table
    • record of tables of tables of strings
    • ...
  • advice: use records (e.g. {s : Str }) instead of strings (Str), because records can be often extended without breaking code (e.g {s : Str, g : Gender})

Presenter Notes

resource

  • can be opened
  • oper definitions

Presenter Notes

interface

  • interface is a module similar to resource
  • only contains the types of opers, not (necessarily) their definitions

Presenter Notes

instance

  • implements interface
  • fills in the operator definitions

Presenter Notes

incomplete

  • anything can be declared incomplete, e.g.
    • concrete is complete with respect to an abstract
    • resource is complete if all opers and params have a definition part
  • incomplete concrete can be parametrized in a concrete

Presenter Notes

Topic 5: Refining semantics in abstract syntax

Presenter Notes

Motivation

  • in the concrete syntax it is possible to restrict which phrases go together e.g. a noun phrase and a verb phrase must agree in number
    • 2 men run
    • 1 man runs
    • * 2 men runs
    • * 1 man run
  • sometimes restrictions are language-independent, e.g.
    • convert 3 kg to grams
    • * convert 3 kg to EUR
    • dim the light
    • * dim the fan
  • language-independent restrictions should be defined in the abstract syntax
  • solution: dependent types

Presenter Notes

Example: Smart house

  • there are commands and device kinds
  • for each kind of device, there are devices and actions
  • a command concerns an action of some kind on a device of the same kind

Modeling:

  • Command and Kind are GF categories
  • Device and Action are dependent types (they depend on Kind)
  • Command does not have a Kind

Presenter Notes

Example: Smart house

Abstract

cat
  Command ;
  Kind ;
  Device Kind ; -- argument type Kind
  Action Kind ;


fun
  -- the Kinds of Action and Device must be the same,
  -- to be able to form a Command
  CAction : (k : Kind) -> Action k -> Device k -> Command ;
  DKindOne : (k : Kind) -> Device k ;
  light, fan : Kind ;
  dim : Action light ;

Concrete

The concrete syntax does not know anything about dependent types but must suppress the extra argument.

lincat Action = {s : Str} ;
-- the Kind argument is suppressed in linearization
lin CAction _ act dev = {s = act.s ++ dev.s} ;

Presenter Notes

Parsing and dependent types

Syntax and semantics are OK:

> parse "dim the light"
CAction light dim (DKindOne light)

Syntax OK, semantics not:

> parse "dim the fan"
The parsing is successful but the type checking failed with error(s):
  Couldn't match expected type Device light
         against inferred type Device fan
  In the expression: DKindOne fan

Token look-ahead (unfortunately) ignores dependent types:

> parse "dim the
fan    light

Presenter Notes

Polymorphism

Sometimes an action can be performed on all kinds of devices.

This is represented as a function that takes a Kind as an argument and produces an Action for that Kind:

fun switchOn, switchOff : (k : Kind) -> Action k ;

Functions of this kind are called polymorphic.

Compare:

light : Kind ;
dim : Action light ;

Parsing examples:

Test> parse "switch on the fan "
CAction fan (switchOn fan) (DKindOne fan)

Test> parse "switch on the light "
CAction light (switchOn light) (DKindOne light)

Presenter Notes

Dependent types (full code)

Abstract

flags startcat = Command ;

cat Command ; Kind ; Device Kind ; Action Kind ;

fun
    CAction : (k : Kind) -> Action k -> Device k -> Command ;
    light, fan : Kind ;
    dim : Action light ;
    DKindOne : (k : Kind) -> Device k ;
    switchOn, switchOff : (k : Kind) -> Action k ;

Concrete

lincat Command, Kind, Device, Action = { s : Str };

lin CAction _ act dev = {s = act.s ++ dev.s} ;

    -- Kinds
    light = { s = "light" } ;
    fan = { s = "fan" } ;

    DKindOne k = { s = "the" ++ k.s } ;

    -- Actions
    dim = { s = "dim" } ;
    switchOn _ = { s = "switch on" } ;
    switchOff _ = { s = "switch off" } ;

Presenter Notes

Dependent types (problems)

  • concrete syntax is unaware of dependent types
  • GF's built-in token look-ahead ignores dependent types
  • various other GF tools do not support dep. types

Presenter Notes

Topic 6: GF tools

Presenter Notes

Lexing and unlexing

  • the parser and linearizer work with tokens
  • lexing (tokenization) = mapping a string to tokens
    • e.g. split punctuation from the words
  • unlexing = mapping tokens to strings
    • e.g. glue punctuation to preceding words
  • lexing/unlexing and application/language dependent (i.e. not general solution)
  • lexing/unlexing can be done by an external application as long as some GF conventions are observed
    • space marks token border
    • &+ glues tokens

Example (token list that as a string should look like "12 is a number."):

1 &+ 2 is a number &+ .

Presenter Notes

GF commandline tool

  • (see the commandline examples on the previous slides)
  • can be also run in "server mode"

PGF server usage example

$ GF_RESTRICTED=yes gf --server=1234 --document-root /path/docroot/

$ (create /path/docroot/grammars/Test.pgf)

$ curl "http://localhost:1234/grammars/Test.pgf?

    command=parse&cat=Utt&from=TestEng&input=hello"


$ (process the JSON output)

Presenter Notes

PGF Services

  • parse
    • in: string, language
    • out: tree set
  • complete
    • in: string, language
    • out: token set
  • linearize
    • in: tree
    • out: language -> string (+ variants?)
  • translate (= parse + linearize)
  • info
    • out: lists of functions, categories, startcat, ...

Presenter Notes

Minibar and Syntax editor

  • Syntax editor (really a tree editor)
    • edit a tree: add/remove node, replace node with a random structure of the same category, wrap node
    • view linearizations (of a possibly partial tree)
    • tree cannot be ambiguous but linearizations can be
  • Minibar
    • look-ahead edit a sentence
    • view its trees and translations
  • the editors are online and integrated
    • switch to Syntax editor to edit a small bit in the beginning of an existing sentence
    • switch to Minibar to view linearization ambiguity
  • links

Presenter Notes

GF Eclipse plugin (grammar editor)

  • features
    • syntax highlighting and error detection
    • code folding, quick block-commenting, automatic code formatting
    • definition outlining, jump to declaration, find usage
    • warnings for problems in module dependency hierarchy
    • launch configurations, i.e. compilation directly from IDE
    • use GF Shell from within Eclipse
    • auto-completion for declared identifiers
    • background compilation (shallow) using project builder
    • support for Open Declaration (F3), including qualified names
    • code generation for new languages in application grammars
    • inline documentation for function calls, overloads
    • proper cross-reference handling with qualified names
    • test management and testing tool
    • external library browser
  • http://www.grammaticalframework.org/eclipse/index.html

Presenter Notes

GF online editor for simple grammars

Presenter Notes

APIs

  • access to PGF services
    • Haskell API
    • GF Webservice (REST)
    • JPGF (Java)
    • Python API (work in progress ?)
    • C API (work in progress ?)
    • Javascript API (?)
    • Java API to GF Webservice
  • GF Cloud Service beta

Presenter Notes

Topic 7: Applications

Presenter Notes

Application examples

  • multilingual applications (with high quality language)
    • tourist phrasebook
    • museum knowledge base
  • multimodal interfaces (with different modalities handled in the same framework)
  • dialog systems
  • software localization (?)
  • speech recognition grammars
  • multilingual wiki

Presenter Notes

Tourist phrasebook

Presenter Notes

Multimodal/multilingual/dialog systems

  • multimodal
    • speech input
    • touch/click input
    • ...
  • multilingual
    • English
    • Spanish
    • ...
  • dialog-based
    • speech output
    • meaning emerges from a sequence of smaller conversation turns
  • demo video: http://www.youtube.com/watch?v=1bfaYHWS6zU
  • see also GF Book 7.15

Presenter Notes

Voice actions

  • interpret an audio signal as string in language A and translate it to language B for evaluation
    • lang A: natural language, audio signal interpreted using acoustic models and a speech recognition grammar (e.g. JSGF, FSA)
    • lang B: formal language (calculator, directions query, ...)
  • GF provides
    • translation (approximation): GF -> JSGF/SLF/...
    • translation: lang A -> lang B
    • modular grammar, multiple languages, smart paradigms, ...
  • a smartphone app
    • records audio
    • sends it for transcription+translation
    • sends the result for evaluation/execution

Presenter Notes

Voice actions

Example

audio -> natural language string -> tree -> formal expression -> evaluation result

/one plus two/ -> "one plus two" -> (plus n1 n2) -> eval(1+2) -> 3

Links

Presenter Notes

Multilingual CNL-based semantic wiki

Presenter Notes

Existing wiki systems

  • wiki
    • user-friendly collaborative environment for knowledge management
    • content typically unconstrained natural language (NL)
    • powered by software, e.g. MediaWiki
    • e.g. Wikipedia
  • semantic wiki (= wiki + formal semantics)
    • provides: richer query language, consistency checking (via automatic reasoning)
    • content typically NL + typed links (i.e. RDF triples)
    • software: Semantic Mediawiki, ...
  • CNL-based semantic wiki (= semantic wiki using CNL)
    • formal languages hidden (=> can use more expressive formal languages)
    • software: AceWiki
  • multilingual wiki
    • authoring in multiple (natural) languages
    • current systems: only document-level interlinking

Presenter Notes

Multilingual CNL-based Semantic Wiki

  • multiple languages
    • natural: English, German, ACE, ...
    • formal: ACE, FOL, ...
    • languages for content, UI, meta information
  • content
    • viewable/editable/queryable in multiple languages
    • automatically kept in sync
  • CNL-based
    • backed by formal grammar(s)
    • formal languages are hidden
  • semantic
    • consistency checking, question answering, ...
    • precise translation

Presenter Notes

Use cases

  • multilingual ACE wiki
    • authoring in multiple ACE-based CNLs
  • tourist phrasebook
    • book structure (ToC, chapters, index)
    • multiple languages
    • grammar editing
  • catalog of museum objects (paintings, painters)
    • each object on a separate wiki page
    • multiple languages
    • rich queries (e.g. "which Dutch painter painted which French painter?")
  • logic/math puzzles
    • multiple user solutions
    • automatically checked

Presenter Notes

Technologies

  • AceWiki
    • collaborative environment
    • GUI (e.g. look-ahead editor)
    • storage
    • connection to ACE parser
    • connection to OWL reasoners
  • Grammatical Framework (GF)
    • multilingual grammars
    • parser (translation, completion, ...)
    • grammar editor

Presenter Notes

AceWiki

  • goal: user-friendly yet expressive semantic wiki system
  • wiki features: collaborative editing, multiple interlinked articles
  • background reasoning language: OWL
    • expressive fragment of first-order logic
    • decidable reasoning tasks: consistency checking, question answering, ...
    • complex syntax
  • front-end language: ACE
    • subset of natural English
    • well-defined translation into first-order logic / OWL
    • end-user documentation: construction and interpretation rules
  • developed by Tobias Kuhn
  • see more: http://attempto.ifi.uzh.ch/acewiki/

Presenter Notes

AceWiki article (screenshot)

Screenshot: article

Presenter Notes

Look-ahead editor (screenshot)

Screenshot: look-ahead editor

Presenter Notes

AceWiki integration with GF

  • multilingual viewing and editing of wiki content
    • grammar-based editing (show next possible tokens)
  • wiki entry is GF abstract tree set
    • viewed via linearization(s)
    • can represent ambiguity
  • access to multiple online GF grammars
    • provided by GF Webservice
    • single grammar per wiki
  • grammar integrated into the wiki
    • wiki-linking of grammar and content
    • grammar can be updated while building the wiki
  • multilingual ACE grammar implemented in GF
    • other GF grammars can be used instead (no ACE-based reasoning in this case)

Presenter Notes

Article with Editor (screenshot)

Presenter Notes

Grammar module page (screenshot)

Presenter Notes

ACE

  • goal: user-friendly language for formal knowledge engineering
  • subset of natural English
  • translatable into Discourse Representation Structures (DRS)
    • and further into standard first-order logic, OWL, various rule languages
    • enables automatic reasoning, e.g. consistency checking, question answering, ...
  • verbalization of formal languages
    • DRS, OWL
  • end-user documentation: construction and interpretation rules
  • editing environments: AceWiki, ACE Editor, ACE View, ...

Presenter Notes

Multilingual ACE

An ACE grammar in GF/RGL adds multiple natural languages as front-ends to ACE.

Multilinguality

Presenter Notes

ACE in GF

  • implementation of the ACE syntax (i.e. no DRS generation)
    • extension of Angelov and Ranta (CNL 2009)
  • available in 15 natural languages via the RGL
    • Catalan, Danish, Dutch, English, Finnish, French, German, Italian, Latvian, Norwegian, Polish, Romanian, Russian, Spanish, Swedish
    • design allows for easy extendability
  • status
    • focus on the subset of ACE that is used in AceWiki (almost 100% coverage at almost 0% ambiguity)
    • some precision problems, e.g. anaphoric references do not obey DRS accessibility constraints
    • ambiguity and coverage problems in some languages

Presenter Notes

Translation example

p -lang=Ace "if a person admires no golfer then the person buys
    at least 2 aquariums that nothing but travelers inspect ." | l

si una persona no admira cap golfista llavors la persona compra
    almenys 2 aquarins que nomÈs viatgers inspeccionen .

als een persoon geen golfer bewondert , dan koopt de persoon
    ten minste 2 aquaria die slechts reizigers inspecteren .

jos henkilö ei ihaile mitään golfaajaa niin henkilö ostaa
    vähintä
än 2 akvaariota jonka vain matkustajat tarkastavat .

si une personne n' admire aucun golfeur alors la personne achète
    au moins 2 aquariums que seulement des voyageurs inspectent .

wenn eine Person keinen Golfer bewundert , dann kauft die Person
    wenigstens 2 Aquariume die nur Reisenden inspizieren .

si una persona non ammira nessuno giocatore di golf allora la persona compra
    almeno 2 acquari che soltanto viaggiatori ispezionano .

si una persona no admira hacia golfista entonces la persona compra
    al menos 2 acuarios que solamente viajeros inspeccionan .

om en person beundrar inget golfspelare så personen k
öper
    minst 2 akvariumar som bara resenärar avsynar .

Presenter Notes

Links

Presenter Notes

...

Presenter Notes